Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

When the tag is configured in the MikroTik format, the payload's 14th and 15th octets indicate the temperature (ambient temperature in Celsius in signed 16-bit integer [twos complement] 8.8 fixed point format) that was "sensed" at the moment of the broadcast.

...

Please note that all multi-byte values in the payload are in little-endian. So, if #14 and #15 octets indicate the temperature as "50 19" → the decimal temperature value is going to be (0x1950)/256 = 25.31 C.

With this knowledge, we can then utilize RouterOS scripting to structure a message out of the received payloads and send it to the MQTT/HTTP server. The server will be responsible for the data conversion from hexadecimal format to decimal format. In this guide, we will achieve the results using MQTT post to ThingsBoard.

ThingsBoard server and MQTT configuration

You will require to have a ThingsBoard server up and running. You can find useful information on how to set up your server by following the link. You can install the server locally onto a machine or use their cloud solution.

After you have the server ready, you will need to configure it to be able to receive MQTT messages. Please follow the "ThingsBoard configuration" link to set up your "device".

RouterOS configuration

Please check the link over here on how you can set up your MQTT broker depending on the device credential used by the ThingsBoard server.

After setting up your broker, confirm that the Bluetooth tag is broadcasting information as shown in the picture above under the IoT>Bluetooth>Advertising reports tab.

System>Script

Paste in the script below (under System>Scripts>Add):

you will need to switch octets places, to 0x1950.

(plus) temperature:

We've captured the temperature as 0x1950 (hex.) → 6480 (dec.). To get the actual temperature value, divide the result by 256 → 6480/256=25.31 C.

(minus) temperature:

We've captured the temperature as 0xFE80 (hex.). Keep in mind that the twos-complement format is used -384 (decimal from signed 2's complement). To get the actual temperature value, divide the result by 256 → -384/256=-1.5 C.

With this knowledge, we can then utilize RouterOS scripting to structure a message out of the received payloads and send it to the MQTT/HTTP server. It is suggested for the server to be responsible for the data conversion from hexadecimal format to decimal format. But! if you are willing to load the RouterOS device, you can perform hexadecimal value conversion to decimal format using RouterOS scripting (it will load the device and can affect RouterOS performance → we advise converting values on the server and not on the RouterOS). In this guide, we will achieve the results using MQTT post to ThingsBoard.

ThingsBoard server and MQTT configuration

You will require to have a ThingsBoard server up and running. You can find useful information on how to set up your server by following the link. You can install the server locally onto a machine or use their cloud solution.

After you have the server ready, you will need to configure it to be able to receive MQTT messages. Please follow the "ThingsBoard configuration" link to set up your "device".

RouterOS configuration

Please check the link over here on how you can set up your MQTT broker depending on the device credential used by the ThingsBoard server.

After setting up your broker, confirm that the Bluetooth tag is broadcasting information as shown in the picture above under the IoT>Bluetooth>Advertising reports tab.

System>Script

Paste in the script below (under System>Scripts>Add):

# Required packages: iot

################################ Configuration ################################
# Name of an existing MQTT broker that should be used for publishing
 :local broker "tb"

# MQTT topic where the message should be published
 :local topic "v1/devices/me/telemetry"

 # Interface whose MAC should be used as 'Locator ID'
:local locatorIface "ether1"

# POSIX regex for filtering advertisement Bluetooth addresses. E.g. "^BC:33:AC"
# would only include addresses which start with those 3 octets

# Required packages: iot

################################ Configuration ################################
# Name of an existing MQTT broker that should be used for publishing
 :local broker "tb"

# MQTT topic where the message should be published
 :local topic "v1/devices/me/telemetry"

 # Interface whose MAC should be used as 'Locator ID'
:local locatorIface "ether1"

# POSIX regex for filtering advertisement Bluetooth addresses. E.g. "^BC:33:AC"
# would only include addresses which start with those 3 octets.
# To disable this filter, set it to ""
:local addressRegex "2C:C8:1B:4B:BB:0A"

# POSIX regex for filtering Bluetooth advertisements based on their data. Same
# usage as with 'addressRegex'.
:local advertisingDataRegex ""

# Signal strength filter. E.g. -40 would only include Bluetooth advertisements
# whose signal strength is stronger than -40dBm.
# To disable this filter, set it to ""
:local rssiThreshold ""

#################################### System ###################################
:put ("[*] Gathering system info...")
:local ifaceMac [/interface get [/interface find name=$locatorIface] mac-address]
:local cpuLoad [/system resource get cpu-load]
:local freeMemory [/system resource get free-memory]
:local usedMemory ([/system resource get total-memory] - $freeMemory)
:local rosVersion [/system package get value-name=version \
    [/system package find where name ~ "^routeros"]]
:local model [/system routerboard get value-name=model]
:local serialNumber [/system routerboard get value-name=serial-number]
# Health is a bit iffy since '/system health' does not have 'find' in ROS6
:local health [/system health print as-value]
:local supplyVoltage 0
:local boardTemp 0
:foreach entry in=$health do={
    :if ($entry->"name" = "voltage") do={:set $supplyVoltage ($entry->"value")}
    :if ($entry->"name" = "board-temperature1") do={:set $boardTemp ($entry->"value")}
}

################################## Bluetooth ##################################
:put ("[*] Gathering Bluetooth info...")
:global btOldestAdvertisementTimestamp
:if ([:typeof $btOldestAdvertisementTimestamp] = "nothing") do={
    # First time this script has been run since booting, need to initialize
    # persistent variables
    :set $btOldestAdvertisementTimestamp 0
}
:local btProcessingStart [/system clock get time]
:local advertisements [/iot bluetooth scanners advertisements print detail \
    as-value where \
        epoch > $btOldestAdvertisementTimestamp and \
        address ~ $addressRegex and \
        data ~ $advertisingDataRegex and \
        rssi > $rssiThreshold
]
:local advJson ""
:local advCount 0
:local advSeparator ""
:local lastAdvTimestamp 0
# Remove semicolons from MAC/Bluetooth addresses
:local minimizeMac do={
    :local minimized
    :local lastIdx ([:len $address] - 1)
    :for idx from=0 to=$lastIdx step=1 do={
        :local char [:pick $address $idx]
        :if ($char != ":") do={
            :set $minimized "$minimized$char"
        }
    }
    :return $minimized
}

:foreach adv in=$advertisements do={
    :local address ($adv->"address")
    :local ts ($adv->"epoch")
    :local rssi ($adv->"rssi")
    :local ad ($adv->"data")
    :local tempf [:pick ($adv->"data") 28 32]
    :local temp ("0x".[:pick $tempf 2 4].[:pick $tempf 0 2])
    :local obj "{\
        \
           \"ts\":$ts,\
           \"values\":{\
            \"id\":\"$[$minimizeMac address=$address]\",\
            \"rssi\":\"$rssi\",\
            \"temp\":\"$temp\",\
            \"ad\":\"$ad\"\
            }\
        }"
    :set $advCount ($advCount + 1)
    :set $lastAdvTimestamp $ts
    # Ensure that the last object is not terminated by a comma
    :set $advJson "$advJson$advSeparator$obj"
    :if ($advSeparator = "") do={
        :set $advSeparator ","}
}

:if ($advCount > 0) do={

    :set $btOldestAdvertisementTimestamp $lastAdvTimestamp

}

:put ("[*] Found $advCount new advertisements \
    (processing time: $[([/system clock get time] - $btProcessingStart)])")

#################################### MQTT #####################################
:local message \
    "[$advJson]"
:log info "$message";
:put ("[*] Total message size: $[:len $message] bytes")
:put ("[*] Sending message to MQTT broker...")
/iot mqtt publish broker=$broker topic=$topic message=$message
:put ("[*] Done")

Change the broker's name that you have chosen for the MQTT broker within the quotation marks (in our example, we called it tb):

 :local broker "tb"

The default topic used by the ThingsBoard is (you do not have to change it):

 :local topic "v1/devices/me/telemetry"

Change the MAC address of the tag that you wish to monitor (in our example, we have the MAC address 2C:C8:1B:4B:BB:0A):

:local addressRegex "2C:C8:1B:4B:BB:0A"

The lines below will take the payload's 14th and 15th octets and "re-structure" the values from "ABCD" to "0xCDAB" (per our example from the Introduction section, from "5019" to "0x1950"):

    :local tempf [:pick ($adv->"data") 28 32]

    :local temp ("0x".[:pick $tempf 2 4].[:pick $tempf 0 2])

The MQTT message that we are posting is structured in the following section (you do not have to change it):

    :local obj "{\

        \

           \"ts\":$ts,\

           \"values\":{\

            \"id\":\"$[$minimizeMac address=$address]\",\

            \"rssi\":\"$rssi\",\

            \"temp\":\"$temp\",\

            \"ad\":\"$ad\"\

            }\

        }"

The message that is structured per the example above will be displayed in JSON as (the only real value we are going to focus on are "timestamp"→ "ts" and "temperature"→ "temp" in hexadecimal format, so feel free to remove additional parameters):

...

[

  {

    "ts": 1661333913375,

    "values": {

      "id": "2CC81B4BBB0A",

      "rssi": "-43",

      "temp": "0x1939",

      "ad": "15ff4f0901002de0ffff010000003919877c54000061"

    }

  }

]

addressRegex "2C:C8:1B:4B:BB:0A"

# POSIX regex for filtering Bluetooth advertisements based on their data. Same
# usage as with 'addressRegex'.
:local advertisingDataRegex ""

# Signal strength filter. E.g. -40 would only include Bluetooth advertisements
# whose signal strength is stronger than -40dBm.
# To disable this filter, set it to ""
:local rssiThreshold ""

####The lines below are used for the data conversion from hex to dec###

    :global invertU16 do={
        :local inverted 0
        :for idx from=0 to=15 step=1 do={
            :local mask (1 << $idx)
            :if ($1 & $mask = 0) do={
                :set $inverted ($inverted | $mask)
            }
        }
        return $inverted
    }

    :global le16ToHost do={
        :local lsb [:pick $1 0 2]
        :local msb [:pick $1 2 4]

        :return [:tonum "0x$msb$lsb"]
    }
    :local from88 do={
        :global invertU16
        :global le16ToHost
        :local num [$le16ToHost $1]

        # Handle negative numbers
        :if ($num & 0x8000) do={
            :set num (-1 * ([$invertU16 $num] + 1))
        }

        # Convert from 8.8. Scale by 1000 since floating point is not supported
        :return (($num * 125) / 32)
    }


################################## Bluetooth ##################################
:put ("[*] Gathering Bluetooth info...")
:global btOldestAdvertisementTimestamp
:if ([:typeof $btOldestAdvertisementTimestamp] = "nothing") do={
    # First time this script has been run since booting, need to initialize
    # persistent variables
    :set $btOldestAdvertisementTimestamp 0
}
:local btProcessingStart [/system clock get time]
:local advertisements [/iot bluetooth scanners advertisements print detail \
    as-value where \
        epoch > $btOldestAdvertisementTimestamp and \
        address ~ $addressRegex and \
        data ~ $advertisingDataRegex and \
        rssi > $rssiThreshold
]
:local advJson ""
:local advCount 0
:local advSeparator ""
:local lastAdvTimestamp 0
# Remove semicolons from MAC/Bluetooth addresses
:local minimizeMac do={
    :local minimized
    :local lastIdx ([:len $address] - 1)
    :for idx from=0 to=$lastIdx step=1 do={
        :local char [:pick $address $idx]
        :if ($char != ":") do={
            :set $minimized "$minimized$char"
        }
    }
    :return $minimized
}

:foreach adv in=$advertisements do={
    :local address ($adv->"address")
    :local ts ($adv->"epoch")
    :local rssi ($adv->"rssi")
    :local ad ($adv->"data")
    :local tempf [:pick ($adv->"data") 28 32]

#The line below will set temperature in hexadecimal format, like "0x1950"
    :local temp ([:pick $tempf 2 4].[:pick $tempf 0 2])

#The line below will set temperature in decimal format, like "25313". It is commented with "#", so if you wish to use it instead of the hexadecimal format value, comment the line above and uncomment the line below > remove "#" from the line below and add "#" to the line above.

#    :local temp [$from88 [:pick $ad 28 32]]


    :local obj "{\
        \
           \"ts\":$ts,\
           \"values\":{\
            \"id\":\"$[$minimizeMac address=$address]\",\
            \"rssi\":\"$rssi\",\
            \"temp\":\"$temp\",\
            \"ad\":\"$ad\"\
            }\
        }"
    :set $advCount ($advCount + 1)
    :set $lastAdvTimestamp $ts
    # Ensure that the last object is not terminated by a comma
    :set $advJson "$advJson$advSeparator$obj"
    :if ($advSeparator = "") do={
        :set $advSeparator ","}
}

:if ($advCount > 0) do={

    :set $btOldestAdvertisementTimestamp $lastAdvTimestamp

}

:put ("[*] Found $advCount new advertisements \
    (processing time: $[([/system clock get time] - $btProcessingStart)])")

#################################### MQTT #####################################
:local message \
    "[$advJson]"
:log info "$message";
:put ("[*] Total message size: $[:len $message] bytes")
:put ("[*] Sending message to MQTT broker...")
/iot mqtt publish broker=$broker topic=$topic message=$message
:put ("[*] Done")

Change the broker's name that you have chosen for the MQTT broker within the quotation marks (in our example, we called it tb):

 :local broker "tb"

The default topic used by the ThingsBoard is (you do not have to change it):

 :local topic "v1/devices/me/telemetry"

Change the MAC address of the tag that you wish to monitor (in our example, we have the MAC address 2C:C8:1B:4B:BB:0A):

:local addressRegex "2C:C8:1B:4B:BB:0A"

The lines below will take the payload's 14th and 15th octets and "re-structure" the values from "ABCD" to "CDAB" (per our example from the Introduction section, from "5019" to "1950") → basically structure the message with temperature in hexadecimal format:

    :local tempf [:pick ($adv->"data") 28 32]

    :local temp ([:pick $tempf 2 4].[:pick $tempf 0 2])

Your second option is to convert the temperature from hexadecimal to decimal format using RouterOS (if you want to keep hexadecimal format - ignore this step). This can be achieved by "commenting" (adding "#") to the previous line (or removing it altogether) and "uncommenting" (removing the "#") from the upcoming line, like so:

#  :local temp ([:pick $tempf 2 4].[:pick $tempf 0 2])

    :local temp [$from88 [:pick $ad 28 32]]

Because of the fact that floating point is not supported → every calculation behind a decimal point will be "rounded up" to a whole number. This is why the script will calculate the temperature scaled by 1000 (multiplied by 1000) and as a result, the temperature will look like "25546" (that would mean 25.546 C). You can add additional scripting to set up a decimal point (but remember that additional manipulations will load the device) and you can also do it on the server itself.

The MQTT message that we are posting is structured in the following section (you do not have to change it):

    :local obj "{\

        \

           \"ts\":$ts,\

           \"values\":{\

            \"id\":\"$[$minimizeMac address=$address]\",\

            \"rssi\":\"$rssi\",\

            \"temp\":\"$temp\",\

            \"ad\":\"$ad\"\

            }\

        }"

The message that is structured per the example above will be displayed in JSON (the only real value we are going to focus on are "timestamp"→ "ts" and "temperature"→ "temp", so feel free to remove additional parameters). If the temperature is calculated in hexadecimal format, the message would look like this:

[

  {

    "ts": 1661333913375,

    "values": {

      "id": "2CC81B4BBB0A",

      "rssi": "-43",

      "temp": "1939",

      "ad": "15ff4f0901002de0ffff010000003919877c54000061"

    }

  }

]

If the temperature is calculated in decimal format, it would look like this:

[

  {

    "ts": 1661333913375,

    "values": {

      "id": "2CC81B4BBB0A",

      "rssi": "-43",

      "temp": "25222",

      "ad": "15ff4f0901002de0ffff010000003919877c54000061"

    }

  }

]

Save the script and run it:

Code Block
languageros
[admin@MikroTik] > system script run name_of_the_script
[*] Gathering system info...
[*] Gathering Bluetooth info...
[*] Found 1 new advertisements (processing time: 00:00:00)
[*] Total message size: 134 bytes
[*] Sending message to MQTT broker...

[*] Done
[admin@MikroTik] > 

System>Scheduler

Apply a scheduler to the script to run it with a required interval. Please note! If you have set up a scheduler to run the script, for example, every 2 minutes, it will mean that every received payload (broadcasted by the tag) during the 2-minute interval will be "structured" into the MQTT message. You will, most likely, not need to know the temperature of the tag every 2-5 seconds, so you can either change the MQTT message that is structured to only post the latest temperature reading or just change the IoT>Bluetooth>Scanners setting Filter duplicates to "keep-newest" (that way, the Bluetooth scanner will overwrite every new report from each individual tag's MAC address) and the script will only post 1 payload that was "scanned" at the moment when the script ran.

Results

You will be able to verify the results under the Latest telemetry section under the device you have created for ThingsBoard (in our example, we called the "device" KNOT):

Image Added

The result is a successful MQTT post! From the screenshot above, we can see that the temp parameter is in hexadecimal format.

But! we would like to display the temperature (temp parameter) in decimal format and we would also like to display the data easier to perceive.

And in case, we have posted the temperature in the decimal format, we would like to set up a decimal point.

This is where "Widgets" come into play.

ThingsBoard widgets

After you receive the value under the Latest telemetry section, you can show it on the "Widget".

The Javascripts used in the widgets should be able to convert the data from the hexadecimal format to the decimal format.

Select the value you wish to display in the widget (in our example, temp) and click on the "Show on widget" button:

Image Added

You will be able to browse the widgets in the window that pops up:

Image Added

Click on the "Add to dashboard" button:

Image Added

Select a new dashboard to be created or choose an existing dashboard that you already have pre-installed/pre-made. Click on "Add".

By applying the checkbox "Open dashboard" you will be redirected to the dashboard menu or you can go there manually.

We have also added a second table and, as a result, our test dashboard looks like this:

Image Added

In case we post the date in hexadecimal format

Both tables will display the value in the hexadecimal format as "17f9" (raw value as we've sent it from the KNOT → the javascript for the table did not convert it to a decimal value yet).

Because of the fact that the values in our tag's payload are in signed 8.8 fixed point format (keeping in mind the twos-complement format), we will need to add additional javascript code to "convert" the data to the decimal value.

Enter the "Edit" mode and click on the "Edit widget" button:

Image Added

Click on the "edit" button to edit the parameter temp:

Image Added

In the popup window "Data key configuration" enable the checkbox "Use data post-processing function" and input the code shown below:

Image Added

The code:

function f(p){

this.p = p;

}

f.prototype.calculate = function(mes){

let intValue = parseInt(mes, 16);

let sign = 1;

if(intValue & 0x8000) {

intValue = (~intValue + 1) & 0xFFFF;

sign = -1;

}

intValue = sign * intValue / Math.pow(2, this.p);

return intValue;

}

let res = new f(8);

return (res.calculate(value));

Save and apply the changes.

Do the same steps for the second/first table.

In case we post the date in decimal format

You will see the results as:

Image Added

Do the exact same steps from the example above, but for this scenario, divide the result by "1000":

Image Added

Save and apply the changes.

Verification

The end

Save the script and run it:

Code Block
languageros
[admin@MikroTik] > system script run name_of_the_script
[*] Gathering system info...
[*] Gathering Bluetooth info...
[*] Found 1 new advertisements (processing time: 00:00:00)
[*] Total message size: 134 bytes
[*] Sending message to MQTT broker...

[*] Done
[admin@MikroTik] > 

System>Scheduler

Apply a scheduler to the script to run it with a required interval. Please note! If you have set up a scheduler to run the script, for example, every 2 minutes, it will mean that every received payload (broadcasted by the tag) during the 2-minute interval will be "structured" into the MQTT message. You will, most likely, not need to know the temperature of the tag every 2-5 seconds, so you can either change the MQTT message that is structured to only post the latest temperature reading or just change the IoT>Bluetooth>Scanners setting Filter duplicates to "keep-newest" (that way, the Bluetooth scanner will overwrite every new report from each individual tag's MAC address) and the script will only post 1 payload that was "scanned" at the moment when the script ran.

Results

You will be able to verify the results under the Latest telemetry section under the device you have created for ThingsBoard (in our example, we called the "device" KNOT):

Image Removed

The result is a successful MQTT post!

But! we would like to display the temperature (temp parameter) in decimal format and we would also like to display the data easier to perceive. This is where "Widgets" come into play.

ThingsBoard widgets

After you receive the value under the Latest telemetry section, you can show it on the "Widget".

The Javascripts used in the widgets should be able to convert the data from the hexadecimal format to the decimal format.

Select the value you wish to display in the widget (in our example, tempt) and click on the "Show on widget" button:

Image Removed

You will be able to browse the widgets in the window that pops up:

Image Removed

Click on the "Add to dashboard" button:

Image Removed

Select a new dashboard to be created or choose an existing dashboard that you already have pre-installed/pre-made. Click on "Add".

By applying the checkbox "Open dashboard" you will be redirected to the dashboard menu or you can go there manually.

We have also added a second table and, as a result, our test dashboard looks like this:

Image Removed

Our first table displays the value in the hexadecimal format as "0x192a" (raw value as we've sent it from the KNOT → the javascript for the table did not convert it to a decimal value yet) and our second table displays the value in a decimal format as "6442" (the javascript used for the table did convert the value from "0x192a" to "6442").

Because of the fact that the values in our tag's payload are in signed 8.8 fixed point format, we will need to divide the result by "256".

Enter the "Edit" mode and click on the "Edit widget" button:

Image Removed

Click on the "edit" button to edit the parameter temp:

Image Removed

In the popup window "Data key configuration" enable the checkbox "Use data post-processing function" and divide the returned value by 256 (return value/256;):

Image Removed

Save and apply the changes.

Do the same steps for the second/first table and, after that, the result should look like this:

Now both tables show converted decimal temperature.