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)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.

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):

(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 ""

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

:local from88 do={
    :global invertU16
    :global le16ToHost
    :local num [$le16ToHost $1]

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     # Handle negative numbers
    :if ($num & 0x8000) do={
        :set num (-1 * ([$invertU16 $num] + 1)local inverted 0
        :for idx from=0 to=15 step=1 do={
            :local mask (1 << $idx)
    }     # Convert from 8.8. Scale by 1000 since floating point is not supported
    :return (($num * 125) / 32)
}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)
    #################################### 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]

#The line below will set temperature in hexadecimal format, like "0x1950"
    :local temp ("0x".[: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")

...

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 "0x19501950") → basically structure the message with temperature in hexadecimal format:

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

    :local temp ("0x".[: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 ("0x".[:pick $tempf 2 4].[:pick $tempf 0 2])

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

...

[

  {

    "ts": 1661333913375,

    "values": {

      "id": "2CC81B4BBB0A",

      "rssi": "-43",

      "temp": "0x19391939",

      "ad": "15ff4f0901002de0ffff010000003919877c54000061"

    }

  }

]

...

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 RemovedImage Added

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

...

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

Image RemovedImage Added

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

Image RemovedImage Added

Click on the "Add to dashboard" button:

Image RemovedImage Added

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

...

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

Image RemovedImage Added

In case we post the date in hexadecimal format

Our first table Both tables will display the value in the hexadecimal format as "0x192a17f9" (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 will display 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 (keeping in mind the twos-complement format), we will need to divide the result by "256"add additional javascript code to "convert" the data to the decimal value.

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

Image RemovedImage Added

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;):

...

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.

...