[HOWTO] Presence detection using Mikrotik, CapsMan and MQTT

Hi!

A while ago I had some issues with the Mikrotik binding so I created a Wi-Fi-based (guest) presence solution with MikroTik (CAPsMAN and the IoT package) and MQTT.
Since RouterOS 7.22, it is possible to run a script on log events. I use CAPsMAN, but it might also work with regular Wi-Fi.

(disclaimer: I wrote this howto, AI spellchecked it :))

Requirements

  • MikroTik router/access point with RouterOS 7.22+
  • IoT package installed on MikroTik for MQTT functionality
  • MQTT broker (I use Mosquitto)
  • OpenHAB with MQTT and JavaScript rules configured
  • Scripts

On MikroTik

  1. Install the IoT package and reboot. (Packages | RouterOS Manual)
  2. Configure the MQTT broker ( MQTT | RouterOS Manual )
/iot mqtt brokers
add address=1.2.3.4 keep-alive=30 name=Mosquitto password="SuperSecret" username=someIotUser
  1. Add a script that publishes all connected Wi-Fi clients to MQTT as a JSON string (the broker name should match the broker from step 2).
/system script
add dont-require-permissions=no name=PublishWifiMQTT owner=admin policy=\
    ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":local broker \"Mosquitto\"\r\
    \n\r\
    \n:local topic \"capsman/registrationtable\"\r\
    \n\r\
    \n/iot mqtt publish broker=\$broker topic=\$topic message=[/interface/wifi/registration-table/print proplist=mac-address,ssid as-\
    value]\r\
    \n"
  1. Add a logging action. This tells RouterOS what to do when a configured log event occurs (the name should match the script name from step 3) ( logging | RouterOS Manual ).
/system logging action
add name=WiFiEvent script=PublishWifiMQTT target=script
  1. Add a log rule (the action name should match the action name from step 4).

    It matches all log entries with the topic wireless,info and content containing connected or disconnected.

/system logging
add action=WiFiEvent regex="(dis)\?connected" topics=wireless,info

On OpenHAB

  1. Create a Generic MQTT Thing.

    Under Things, click +, select the MQTT binding, and choose Generic MQTT Thing.
    Select your MQTT Bridge and click Create Thing.

    In the newly created Thing, under the Channels tab, click Add Channel. Pick an ID, label, and description. Select the channel type String. The MQTT state topic should match the topic set in the RouterOS script. Click Create.

    Click on the newly created channel and select Add Link to Item. Choose Create a New Item and click Link.

version: 1
things:
  mqtt:topic:8fc118bcb1:fe62d94935:
    bridge: mqtt:broker:8fc118bcb1
    label: CapsMan
    channels:
      RegistrationTable:
        type: string
        label: RegistrationTable
        description: ""
        config:
          stateTopic: capsman/registrationtable
  1. Create items for every device you want to monitor/track.

    Under Items, click +, and choose an identifier, name, and label.

    Select the type Switch.

    Under Non-Semantic Tags, add WirelessClient.

    Click Create.

    In the newly created item, click Add Metadata. Select Enter Custom Namespace and enter MacAddress. On the next screen, enter the MAC address in the value field:

value: AA:BB:CC:DD:EE:FF
config: {}
version: 1
items:
  TomsS22Ultra_Online:
    type: Switch
    label: Toms S22 Ultra Online
    icon: man_1
    groups:
      - Tom_present
    tags:
      - Point
      - WirelessClient
    metadata:
      MacAddress: AA:BB:CC:DD:EE:FF
  1. Create a group for every person you want to track.

    In case a person owns multiple devices, a single connected device means that person is present.

    Under Items, click +, and choose an identifier, name, and label.

    Select the type Group.

    Set Members Base Type to Switch.

    Set Aggregation Function to One ON then ON else OFF.

    In the newly created group, add all devices owned by that person from step 2.

version: 1
items:
  Tom_present:
    type: Group
    group:
      type: Switch
      function: Or
      parameters:
        - "ON"
        - "OFF"
    label: Tom present
    icon: man_1
  1. Create a rule.

    Under Rules, click + and enter a label such as CapsManRegistrationTable.

    Under When, click Add Trigger, select Show All, and choose An item state changes. Select the item created in step 1.

    Under Then, click Add Action, choose Inline Script, and select ECMAScript 262 Edition 11.

    On the next screen, add the following script:

//console.log(event);
//console.log(event.eventName);
//console.log(event.payload);

switch(event.eventName){
  case 'ExecutionEvent':
    break;
  case 'TimerEvent':
    break;
  case 'ItemCommandEvent':
    /*
    switch(event.itemName.toString()){
      case '':
        break;
    }
    */
    break;
  case 'ItemStateChangedEvent':
    switch(event.itemName.toString()){
      case 'CapsMan_RegistrationTable':
        var newValue = event.newState.toLocaleUpperCase();
        var oldValue = event.oldState.toLocaleUpperCase();
        
        /*
        var guestCount = newValue.match(/GUEST/g)?.length;
        if(guestCount > 0){
          //items.getItem('GuestMode').sendCommandIfDifferent('ON');
          items.getItem('Guest_count').sendCommandIfDifferent(guestCount);
          items.getItem('Guest_present').sendCommandIfDifferent('ON');
        }else{
          items.getItem('Guest_count').sendCommandIfDifferent(0);
          items.getItem('Guest_present').sendCommandIfDifferent('OFF');
        }
        */
        
        items.getItemsByTag('WirelessClient').forEach(function(i){
          var metaData = i.getMetadata('MacAddress');
          if(metaData){
            if(newValue.match(metaData.value)){
              i.sendCommandIfDifferent("ON");
            }else{
              i.sendCommandIfDifferent("OFF");
            }
          }else{
            logger.warn('[CAPSMAN] Client ' + i.name + ' is missing mac address!');
          }
        });

        var oldArray = Array.from(oldValue.matchAll("([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})"), (m) => m[0]);
        var newArray = Array.from(newValue.matchAll("([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})"), (m) => m[0]);
        
        var added = newArray.filter(x => !oldArray.includes(x));
        var deleted = oldArray.filter(x => !newArray.includes(x));

        if(added.length){
          console.log("[CAPSMAN] Added: " + added)
        }
        if(deleted.length){
          console.log("[CAPSMAN] Deleted: " + deleted)
        }
        
        break;
    }
    break;
  case 'ItemStateUpdatedEvent':
    
    break;
  case 'ThingStatusInfoChangedEvent':
    /*
    switch(event.thingUID.toString()){
      case '':
        break;
    }
    */
    break;
}

For guests, I have a separate SSID named GUEST. In the commented-out section of the script, I check for clients connected to this SSID and update items accordingly.

Hopefully this is useful for someone looking to implement Wi-Fi-based presence detection with Mikrotik.

I am using the binding to do this directly with out of the box settings of a mikrotik router. The binding got updated to support the newer routerOS versions around 6 months ago and a second lot of changes 2 months ago. If you were having issues you should find them resolved now. I find the presence to work quite well.