Image

Sensor Telemetry w/Canvas

Canvas™ Device Manager (DM) gateway firmware includes support for forwarding Bluetooth Low Energy (BLE) sensor telemetry data to an MQTT broker. This guide describes how to setup the MQTT telemetry feature of Canvas DM firmware to send and visualize sensor data with ThingsBoard.

Image Expand

Create an account on ThingsBoard

To get started, you will need an account with Thingsboard Cloud. The steps in this guide can be used even with the free trial account. To learn more about the ThingsBoard Cloud platform, check out their Documentation page.

Required Hardware

The following hardware will be required to demonstrate the MQTT connectivity to ThingsBoard Cloud for BLE sensor telemetry data:

Preparing ThingsBoard for Canvas Devices

1. Log in at https://thingsboard.cloud

2. Setup a Rule Chain to handle data ingest of telemetry data from the MG100 gateway

A new rule chain needs to be added with a small script that interprets the messages published by the MG100 gateway over MQTT and generates JSON messages to the ThingsBoard platform that can be saved to the timeseries database and used for dashboard visualization. Use the sidebar menu and navigate to Rule chains.

Start by making a copy of the “Root Rule Chain” and naming it “MG100 Telemetry”. This can easily be done by downloading the Root Rule Chain, opening in a text editor and modifying the “name” property to “MG100 telemetry” and saving the file, then clicking the + to import a new rule chain from your modified file.

From the existing Post telemetry output edge from the Message Type Switch node, introduce a new script node named “MG100 Telemetry Parser”. Paste the script code below into the transform.

BT510 Sensor Data Telemetry Script **NOTE:** This script assumes the sensor **Device Profile** is named "Sentrius BT510". Devices will be created automatically when telemetry data is received._

javascript
const LAIRD_CONNECTIVITY_MANUFACTURER_SPECIFIC_COMPANY_ID1 = 0x0077;
const BTXXX_1M_PHY_AD_PROTOCOL_ID = 0x0001;
const RECORD_TYPE_1M_PHY_RESERVED = 0;
const RECORD_TYPE_1M_PHY_TEMPERATURE = 1;           // Hundredths of degree C, signed 16 bit value
const RECORD_TYPE_1M_PHY_MAGNET = 2;                // (PROXIMITY) MAGNET STATE See 4.1.5 Magnet States
const RECORD_TYPE_1M_PHY_MOVEMENT = 3;              // MOVEMENT
const RECORD_TYPE_1M_PHY_ALARM_HIGH_TEMP_1 = 4;     // ALARM HIGH TEMP 1 TEMPERATURE Hundredths of degree C
const RECORD_TYPE_1M_PHY_ALARM_HIGH_TEMP_2 = 5;     // ALARM HIGH TEMP 2 TEMPERATURE Hundredths of degree C
const RECORD_TYPE_1M_PHY_ALARM_HIGH_TEMP_CLEAR = 6; // ALARM HIGH TEMP CLEAR TEMPERATURE Hundredths of degree C
const RECORD_TYPE_1M_PHY_ALARM_LOW_TEMP_1 = 7;      // ALARM LOW TEMP 1 TEMPERATURE Hundredths of degree C
const RECORD_TYPE_1M_PHY_ALARM_LOW_TEMP_2 = 8;      // ALARM LOW TEMP 2 TEMPERATURE Hundredths of degree C
const RECORD_TYPE_1M_PHY_ALARM_LOW_TEMP_CLEAR = 9;  // ALARM LOW TEMP CLEAR TEMPERATURE Hundredths of degree C
const RECORD_TYPE_1M_PHY_ALARM_DELTA_TEMP = 10;     // ALARM DELTA TEMP TEMPERATURE Hundredths of degree C
const RECORD_TYPE_1M_PHY_BATTRY_GOOD = 12;          // BATTERY GOOD BATTERY VOLTAGE Millivolts (unsigned 16-bit number)
const RECORD_TYPE_1M_PHY_BUTTON_PRESS = 13;         // ADVERTISE ON BUTTON BATTERY VOLTAGE Millivolts
const RECORD_TYPE_1M_PHY_BATTERY_BAD = 16;          // BATTERY BAD BATTERY VOLTAGE Millivolts

function hexStringToUint8Array(hexString) {
    var buf = new Uint8Array(Math.ceil(hexString.length / 2));
    for (var i = 0; i < buf.length; i++) buf[i] = parseInt(hexString.substr(i * 2, 2), 16);
    return buf;
}

function ParseBtxxx1mPhyAd(entry, buf) {
    entry.values.networkId = (buf[5] << 8) | buf[4];
    entry.values.flags = (buf[7] << 8) | buf[6];
    entry.values.addrString = "";
    for(var i = 0; i < 6; i++) {
        if(buf[13-i] < 16) entry.values.addrString += '0';
        entry.values.addrString += buf[13-i].toString(16);
    }
    entry.values.recordType = buf[14];
    entry.values.recordNumber = (buf[16] << 8) | buf[15];
    entry.ts = (buf[20] << 24) | (buf[19] << 16) | (buf[18] << 8) | (buf[17]);
    entry.values.button_press = 0;

    /* parse the data based on the record Type */
    switch(entry.values.recordType) {
        case RECORD_TYPE_1M_PHY_RESERVED:
            break;
        case RECORD_TYPE_1M_PHY_TEMPERATURE: // Hundredths of degree C, signed 16 bit value
        entry.values.temperature = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
        break; 
        case RECORD_TYPE_1M_PHY_MAGNET: // (PROXIMITY) MAGNET STATE See 4.1.5 Magnet States
            entry.values.magnet = buf[22];
            break;
        case RECORD_TYPE_1M_PHY_MOVEMENT: // MOVEMENT
            entry.values.movement = buf[22];
            break;
        case RECORD_TYPE_1M_PHY_ALARM_HIGH_TEMP_1: // ALARM HIGH TEMP 1 TEMPERATURE Hundredths of degree C
            entry.values.alarm_high_temperature_1 = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
            break;
        case RECORD_TYPE_1M_PHY_ALARM_HIGH_TEMP_2: // ALARM HIGH TEMP 2 TEMPERATURE Hundredths of degree C
            entry.values.alarm_high_temperature_2 = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
            break;
        case RECORD_TYPE_1M_PHY_ALARM_HIGH_TEMP_CLEAR: // ALARM HIGH TEMP CLEAR TEMPERATURE Hundredths of degree C
            entry.values.alarm_high_temperature_clear = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
            break;
        case RECORD_TYPE_1M_PHY_ALARM_LOW_TEMP_1: // ALARM LOW TEMP 1 TEMPERATURE Hundredths of degree C
            entry.values.alarm_low_temp_1 = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
            break;
        case RECORD_TYPE_1M_PHY_ALARM_LOW_TEMP_2: // ALARM LOW TEMP 2 TEMPERATURE Hundredths of degree C
            entry.values.alarm_low_temperature_2 = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
            break;
        case RECORD_TYPE_1M_PHY_ALARM_LOW_TEMP_CLEAR: // ALARM LOW TEMP CLEAR TEMPERATURE Hundredths of degree C
            entry.values.alarm_low_temperature_clear = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
            break;
        case RECORD_TYPE_1M_PHY_ALARM_DELTA_TEMP: // ALARM DELTA TEMP TEMPERATURE Hundredths of degree C
            entry.values.alarm_delta_temperature = (((buf[22] << 8) | buf[21]) << 16 >> 16) / 100.0;
            break;
        case RECORD_TYPE_1M_PHY_BATTRY_GOOD: // BATTERY GOOD BATTERY VOLTAGE Millivolts (unsigned 16-bit number)
            entry.values.battery = ((buf[22] << 8) | buf[21]) / 1000.0;
            break;
        case RECORD_TYPE_1M_PHY_BUTTON_PRESS: // ADVERTISE ON BUTTON BATTERY VOLTAGE Millivolts
            entry.values.battery = ((buf[22] << 8) | buf[21]) / 1000.0;
            entry.values.button_press = 1;
            break;
        case RECORD_TYPE_1M_PHY_BATTERY_BAD: // BATTERY BAD BATTERY VOLTAGE Millivolts
            entry.values.battery = ((buf[22] << 8) | buf[21]) / 1000.0;
            break;
        default:
            // record type not found
            break;
    }
    return entry;
}

function msg_handler(msg) {
    var ads = msg.ads.split(',');
    var entries = [];
    var result = [];

    for(var i = 0; i < ads.length; i++) {
        var ad = ads[i];
        var adbuf = hexStringToUint8Array(ad);

        /* check for company ID */
        if((adbuf[0] != (LAIRD_CONNECTIVITY_MANUFACTURER_SPECIFIC_COMPANY_ID1 & 0xFF)) || adbuf[1] != (LAIRD_CONNECTIVITY_MANUFACTURER_SPECIFIC_COMPANY_ID1 >> 8)) {
            continue;
        }
        
        /* check protocol ID */
        var protocolId = adbuf[2] | (adbuf[3] << 8);
        var entry = {
            ts: 0,
            values: {
                companyId: (adbuf[1] << 8) | adbuf[0],
                protocolId: (adbuf[3] << 8) | adbuf[2],
            }
        };

        /* call appropriate protocol parser */
        switch(protocolId) {
            case BTXXX_1M_PHY_AD_PROTOCOL_ID:
                entry = ParseBtxxx1mPhyAd(entry, adbuf);
                break;
            default:
                break;
        }

        entries.push(entry);

        var newMsg = entry.values;
        var newMeta = {};
        newMeta.deviceName = "sensor-" + entry.values.addrString;
        newMeta.deviceType = "Sentrius BT510";
        
        newMeta.ts = (entry.ts) * 1000; // convert from seconds to milliseconds (ThingsBoard uses millisecond timestamps)
        result.push({
            "msg": newMsg,
            "metadata": newMeta,
            "msgType": "POST_TELEMETRY_REQUEST"
        });

     }
     return result;
}

return msg_handler(msg);

Here is an example of a properly configured rule chain:

Image Expand

Details on the create relation node:

Image Expand

3. Setup a Device Profile for each device type

Canvas MG100 MQTT

Setup a device profile named “Canvas MG100 MQTT”. Select the MG100 Telemetry rule chain. Under Transport configuration select MQTT and leave the defaults for topic filters.

Sentrius BT510 Sensor

Setup a device profile named “Sentrius BT510”. Select the Root Rule Chain rule chain. Under Transport configuration select Default.

4. Add the MG100 gateway device

Click Device groups from the side bar and then All to show the device list. Click the + button to add a new device. In the Name field, enter the name you’d like to associate with your gateway (e.g., demo_mg100). Check the option for Select existing device profile, delete the “default” text that is in the field and select Canvas MG100 MQTT (i.e., the profile added in previous steps).

Click the Next button and check the box labeled Add credentials. Under Credentials type, select MQTT Basic.

For Client ID, specify a lowercase client identifier for your gateway without any spaces such as “demo_mg100”. This value must match the mqtt_id attribute as set on the MG100 device.

For User Name, use the same value entered for Client ID (e.g., “demo_mg100”). This value must match the mqtt_user_name attribute as set on the MG100 device.

For Password, specify the password you’d like to set for your device (e.g., a 6 to 8 character alphanumeric password that is easy to type when later configuring the device itself). This value must match the mqtt_password attribute as set on the MG100 device.

5. Setup the attributes on the Canvas MG100

If the MG100 gateway is running the Canvas DM + MQTT firmware, it will have assignable attributes for MQTT telemetry accessible via both the UART shell (via USB connection to a workstation) or remotely via LwM2M. For simplicity, this guide will step through the process using the locally connected USB cable to the MG100 shell console.

Note: If you are running your own MQTT endpoint, such as on AWS, you’ll need to set these attributes according to your endpoint’s requirements. For more details, see the MG100 firmware user guide.

The dropdown below covers the required attributes for configuring ThingsBoard as an endpoint.

Details for setting required MG100 attributes

Attach a micro-USB cable from the MG100 gateway to your workstation and open a serial terminal to the associated serial port (115200 baud,8N1). Press ENTER and a login: prompt should appear. You may need to type the password for your device if a shell password is configured. Once logged in, a uart:~$ prompt should appear, ready for commands.

The Canvas DM firmware provides configurable attributes allowing the operator to customize the behavior of the gateway. To list the configurable attributes, type attr show ENTER. To set an attribute value, type attr set followed by a space and then the identifier for the attribute as it is shown when listing the attributes (e.g., mqtt_id for the MQTT id used by the device).

The following steps require use of the attr set command to set the value for the attributes used by the gateway in order to configure it to communicate with ThingsBoard via MQTT.

  • Set attribute mqtt_user_name to match the User Name value entered when adding the device in the ThingsBoard Web UI
  • Set attribute mqtt_password to match the Password value entered when adding the device in the ThingsBoard Web UI
  • Set attribute mqtt_endpoint to mqtt.thingsboard.cloud
  • Set attribute mqtt_port to 1883
  • Set attribute mqtt_id to match the Client ID value entered when adding the device in the ThingsBoard Web UI
  • Set attribute mqtt_watchdog to 1 (true)
  • Set attribute mqtt_publish_qos to 1 (At Least Once)
  • Set attribute mqtt_peer_verify to 0 (None)
  • Set attribute mqtt_subscribe_qos to 1
  • Set attribute mqtt_connect_on_request to 1 (true)
  • Set attribute mqtt_transport_secure to 0 (false)
  • Set attribute mqtt_root_only to 1 (true)
  • Set attribute mqtt_clean_session to 1 (true)
  • Set attribute mqtt_ble_enable to 1 (true)
  • Set attribute mqtt_ble_topic to 'v1/devices/me/telemetry' (include the single quotes)
  • Set attribute mqtt_ble_prefix to '{"ads":"' (include the single quotes)
  • Set attribute mqtt_ble_delimiter to ',' (comma, include the single quotes)
  • Set attribute mqtt_ble_postfix to '"}' (include the single quotes)
  • Set attribute mqtt_ble_quote to 0 (false)
  • Set attribute mqtt_memfault_enable to 0 (false)
  • Set attribute mqtt_memfault_topic to '' (empty string)
  • Finally, issue the kernel reboot cold command to force the gateway to reboot and start using the new configuration
  • 6. Verifying MG100 + BT510 telemetry

    At this point, the MG100 gateway should be properly configured to communicate with your ThingsBoard cloud account. If you now power on up to 4 Sentrius BT510 sensors nearby your connected MG100, the MG100 should receive data from the sensor(s) and periodically forward the BLE advertisement data over MQTT. By default, the MG100 will only forward MQTT data once it has enough advertisement data buffered to trigger an MQTT publish. This will directly depend on the configuration of the reporting interval for your BT510 sensors.

    To check whether your MG100 has reported telemetry data, click Device groups from the sidebar in the ThingsBoard Web UI, then click All. In the device list on the right, verify you see an entry for the gateway you have configured. If the MG100 has reported ad data and you have properly configured the MQTT Telemetry rule chain from the above steps, you will see Sentrius BT510 sensors now in the device list.

    To verify the sensor data is being reported, click the row for the sensor device, then click the tab labeled Latest telemetry. This displays the most recent telemetry values reported by the sensor. You can verify the timestamps in the Last update time column to see when the last values from your sensor have been received by the ThingsBoard cloud platform.

    Note that the MG100 cellular gateway uses LTE-M and NB-IoT networks for connectivity. This network technology inherently limits the total bandwidth available for telemetry data. Please carefully select reporting intervals appropriate to the amount of cellular data you have available with your carrier’s data plan.

    A good rule of thumb is to consider setting update intervals for sensors on the order of 5+ minutes to ensure you do not eat through your cellular data plan too quickly. For values that don’t change often such as battery voltage, consider setting the upadte interval to 30 minutes or more to minimize reporting unnecessary data. You can always check the data usage of your MG100 gateway using the attr show command to display attributes and read the values for lte_udp_tx, lte_udp_rx, lte_tcp_tx and lte_tcp_rx or lte_data_total for a total of data used since the last reboot.

    7. Setting up a Dashboard

    Now that data is being reported to ThingsBoard, processed by the MQTT Telemetry rule chain and saved to the timeseries database you can setup a dashboard to visualize the sensor telemetry data.

    Click the Dashboard groups sidebar menu and click All. To add a dashboard, click the + button, give your dashboard a title and click Add.

    From here, a detailed description of how to use ThingsBoard dashboards is out of scope of this guide, please review the extensive documentation and rich tutorial videos available on the ThingsBoard Documentation website.

    As a quick example, to add a Line Chart displaying the temperature reported by a set of BT510 sensors:

    • Open your newly created dashboard
    • Dashboard widgets require an Entity alias to be defined as a data source. Click the Entity aliases button Image to create an alias. Click Add alias and name it something related to the sensors you want to use in your dashboard. For Filter type, select Device type and for Device type, select Sentrius BT510. This Entity alias can now be used as a data source by dashboard widgets.
    • Click + Add new widget and click the Charts section. Click Timeseries Line Chart to add a line chart to your dashboard. For Data, click + Add and specify the Entity alias you created in the prior step.
    • Click in the field labeled Timeseries data keys and scroll down to the property labeled temperature, then click it to add it to the list of values to plot. Click the pencil icon next to temperature to edit the timeseries data key. Set the Label field to ${deviceName} temperature to show the device name in the chart legend.
    • Optionally specify a Filter if desired
    • Expand the Data settings section in the lower part of the dialog and type “°C” in the Special symbol to show next to value field.
    • Click Settings and enter a title for your chart in the Title field.
    • Click the Add button to close the dialog and add the line chart to your dashboard.