VS121 AI workplace sensor
VS121 AI workplace sensor
Milesight
- Platform
- ThingsBoard
- Hardware Type
- Other devices
- Connectivity
- LoRaWAN
- Industry
- Healthcare, Smart Buildings
- Use Case
- Smart Office
Introduction
Milesight VS121, is an AI workplace sensor designed to monitor occupancy and utilization in modern workspaces, which can reach up to 98% recognition rate based on its AI algorithm. Milesight VS121 is available in dual versions that transmit data through LoRaWAN® or Ethernet for different applications. Based on standard Lorawan protocol, VS121 can work with the Milesight gateway. It is equipped with WI-FI for easy configuration without the need for any additional configuration tools. By connecting this device to the ThingsBoard, you can get improved visualization and data management capabilities.
Prerequisites
To continue with this guide we will need the following:
- VS121 AI workplace sensor
- VS121 Device user manual
-
Computer with Wi-Fi
-
LoRaWAN® gateway (in our case UG56 LoRaWAN® Gateway)
-
Configured integration on networks server and ThingsBoard
- ThingsBoard account
Device connection
According to the official user manual and this guide, you can connect the device to the network and get access to the Web UI via wireless connection. Since this device can only be operated using a LoRaWAN® gateway, we must first connect it to a network server that has an integration configured with ThingsBoard.
Device configuration
To connect and send data we should configure the device and network server. Firstly, we are going to configure the device, and save required information for network server configuration. To add a device to network server and get information from it, we will need the following device parameters:
- Device EUI - device identifier
- Application EUI - Application identifier
- Application Key - Application key to identify device. We recommend to use a generated key, not from the example!
Depending on the network server, you may also need to provide join type (OTAA), LoRaWAN version.
VS121 sensor provides user-friendly web GUI for configuration and users can access it via Wi-Fi connection. Default SSID device: Workplace Sensor.
Follow the steps below:
- Power on the device over Type-C Power Port;
- Enable the Wireless Network Connection on your computer and the corresponding access point, then connect computer to this access point;
- Open the Browser and type 192.168.1.1 to access the web GUI (you must be in the one subnetwork);
- Users need to set the password when using the device for the first time. Additionally three security questions can also be set optionally;
- After configuration, use username (admin) and custom password to log in to the sensor.
To obtain the necessary parameters, follow these steps:
- Go to the IoT page in the left panel and navigate to the LoRa tab;
- Make a note of Device EUI and App EUI;
- Generate and enter a new password in hexadecimal format in the Application Key field and note it;
- Scroll down and click on the Save button.
The parameters above are required for connection.
To configure the device we also need to add it to a network server, so select the network server your gateway is connected to:
Add a device on the Chirpstack
We need to add a device on the Chirpstack.
To add a device, you can follow next steps:
- Login to Chirpstack server.
- Go to the Device profiles page and click on Add device profile button.
- Fill the fields and click on Submit button.
- Go to the Applications page, click on your application and press Add device button.
- Fill parameters with values from the device configuration. Then choose previously created device profile and click on Submit button.
- Put your Application key to the field and click on Submit button to save the device.
Uplink converter in ThingsBoard integration
Such as we have already connected gateway and configured integration - we will need to modify the converter and add an ability to parse incoming payload from the device.
To do this you can add code to “Decoding block”, it locates between comments ”// --- Decoding code --- //” in your converter, if you are using the default converters (for ThingsBoard v3.5.2 or above).
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;Or you can copy the whole code of the converter and paste it to your converter:
var data = decodeToJson(payload);var deviceName = data.deviceInfo.deviceName;var deviceType = data.deviceInfo.deviceProfileName;// var groupName = 'IAQ devices';// var customerName = 'Customer A';// use assetName and assetType instead of deviceName and deviceType// to automatically create assets instead of devices.// var assetName = 'Asset A';// var assetType = 'building';
// If you want to parse incoming data somehow, you can add your code to this function.// input: bytes// expected output:// {// "attributes": {"attributeKey": "attributeValue"},// "telemetry": {"telemetryKey": "telemetryValue"}// }//// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.//
function decodePayload(input) { var output = { attributes:{}, telemetry: {} }; // --- Decoding code --- //
output.telemetry.HEX_bytes = bytesToHex(input);
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;
// --- Decoding code --- // return output;}
// --- attributes and telemetry objects ---var telemetry = {};var attributes = {};// --- attributes and telemetry objects ---
// --- Timestamp parsingvar dateString = data.time;var timestamp = -1;if (dateString != null) { timestamp = new Date(dateString).getTime(); if (timestamp == -1) { var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1; var millisecondsEndIndex = dateString.lastIndexOf('+'); if (millisecondsEndIndex == -1) { millisecondsEndIndex = dateString.lastIndexOf('Z'); } if (millisecondsEndIndex == -1) { millisecondsEndIndex = dateString.lastIndexOf('-'); } if (millisecondsEndIndex == -1) { if (dateString.length >= secondsSeparatorIndex + 3) { dateString = dateString.substring(0, secondsSeparatorIndex + 3); } } else { dateString = dateString.substring(0, secondsSeparatorIndex + 3) + dateString.substring(millisecondsEndIndex, dateString.length); } timestamp = new Date(dateString).getTime(); }}// If we cannot parse timestamp - we will use the current timestampif (timestamp == -1) { timestamp = Date.now();}// --- Timestamp parsing
// You can add some keys manually to attributes or telemetryattributes.deduplicationId = data.deduplicationId;
// You can exclude some keys from the resultvar excludeFromAttributesList = ["deviceName", "rxInfo", "confirmed", "data", "deduplicationId","time", "adr", "dr", "fCnt"];var excludeFromTelemetryList = ["data", "deviceInfo", "txInfo", "devAddr", "adr", "time", "fPort", "region_common_name", "region_config_id", "deduplicationId"];
// Message parsing// To avoid paths in the decoded objects we passing false value to function as "pathInKey" argument.// Warning: pathInKey can cause already found fields to be overwritten with the last value found.
var telemetryData = toFlatMap(data, excludeFromTelemetryList, false);var attributesData = toFlatMap(data, excludeFromAttributesList, false);
var uplinkDataList = [];
// Passing incoming bytes to decodePayload function, to get custom decodingvar customDecoding = decodePayload(base64ToBytes(data.data));
// Collecting data to resultif (customDecoding.?telemetry.size() > 0) { telemetry.putAll(customDecoding.telemetry);}
if (customDecoding.?attributes.size() > 0) { attributes.putAll(customDecoding.attributes);}
telemetry.putAll(telemetryData);attributes.putAll(attributesData);
var result = { deviceName: deviceName, deviceType: deviceType,// assetName: assetName,// assetType: assetType,// customerName: customerName,// groupName: groupName, attributes: attributes, telemetry: { ts: timestamp, values: telemetry }};
return result;Add a device on The Things Stack Community Edition
We need to add a device on The Things Stack Community Edition.
To add a device, you can follow next steps:
- Login to the cloud and open your console.
- Go to the Applications page. Then select your application and click on its name.
- Click on the Register end device button.
- Put the APP EUI value to the JoinEUI field. Press the Confirm button.
- Fill the rest parameters and press Register end device button.
Uplink converter in ThingsBoard integration
Such as we have already connected gateway and configured integration - we will need to modify the converter and add an ability to parse incoming payload from the device.
To do this you can add code to “Decoding block”, it locates between comments ”// --- Decoding code --- //” in your converter, if you are using the default converters (for ThingsBoard v3.5.2 or above).
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;Or you can copy the whole code of the converter and paste it to your converter:
var data = decodeToJson(payload);
var deviceName = data.end_device_ids.device_id;var deviceType = data.end_device_ids.application_ids.application_id;// var groupName = 'IAQ devices';// var customerName = 'Customer A';// use assetName and assetType instead of deviceName and deviceType// to automatically create assets instead of devices.// var assetName = 'Asset A';// var assetType = 'building';
// If you want to parse incoming data somehow, you can add your code to this function.// input: bytes// expected output:// {// "attributes": {"attributeKey": "attributeValue"},// "telemetry": {"telemetryKey": "telemetryValue"}// }//// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.//
function decodeFrmPayload(input) { var output = { attributes: {}, telemetry: {} }; // --- Decoding code --- //
output.telemetry.HEX_bytes = bytesToHex(input);
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;
// --- Decoding code --- // return output;}
// --- attributes and telemetry objects ---var telemetry = {};var attributes = {};// --- attributes and telemetry objects ---
// --- Timestamp parsingvar dateString = data.uplink_message.received_at;// If data is simulated or device doesn't send his own date string - we will use date from upcoming message, set by network serverif ((data.simulated != null && data.simulated) || dateString == null) { dateString = data.received_at;}var timestamp = new Date(dateString).getTime();var timestamp = -1;if (dateString != null) { timestamp = new Date(dateString).getTime(); if (timestamp == -1) { var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1; var millisecondsEndIndex = dateString.lastIndexOf('+'); if (millisecondsEndIndex == -1) { millisecondsEndIndex = dateString.lastIndexOf('Z'); } if (millisecondsEndIndex == -1) { millisecondsEndIndex = dateString.lastIndexOf('-'); } if (millisecondsEndIndex == -1) { if (dateString.length >= secondsSeparatorIndex + 3) { dateString = dateString.substring(0, secondsSeparatorIndex + 3); } } else { dateString = dateString.substring(0, secondsSeparatorIndex + 3) + dateString.substring(millisecondsEndIndex, dateString.length); } timestamp = new Date(dateString).getTime(); }}// If we cannot parse timestamp - we will use the current timestampif (timestamp == -1) { timestamp = Date.now();}// --- Timestamp parsing
// You can add some keys manually to attributes or telemetryattributes.devEui = data.end_device_ids.dev_eui;attributes.fPort = data.uplink_message.f_port;// We want to save correlation ids as single object, so we are excluding them from attributes parse and add manuallyattributes.correlation_ids = data.correlation_ids;
// You can exclude some keys from the resultvar excludeFromTelemetryList = ["uplink_token", "gateway_id", "settings", "f_port", "time", "timestamp", "received_at", "network_ids"];var excludeFromAttributesList = ["uplink_token", "gateway_id", "f_port", "time", "timestamp", "received_at", "session_key_id", "dev_eui"];
// Message parsing// To avoid paths in the decoded objects we passing false value to function as "pathInKey" argument.// Warning: pathInKey can cause already found fields to be overwritten with the last value found, e.g. receive_at from uplink_message will be written receive_at in the root.var telemetryData = toFlatMap(data.uplink_message, excludeFromTelemetryList, false);var attributesData = {};attributesData.putAll(toFlatMap(data.uplink_message.settings, excludeFromAttributesList, false));attributesData.putAll(toFlatMap(data.uplink_message.network_ids, excludeFromAttributesList, false));attributesData.putAll(toFlatMap(data.end_device_ids, excludeFromAttributesList, false));
// Passing incoming bytes to decodeFrmPayload function, to get custom decodingvar customDecoding = {};if (data.uplink_message.get("frm_payload") != null) { customDecoding = decodeFrmPayload(base64ToBytes(data.uplink_message.frm_payload));}
// Collecting data to resultif (customDecoding.?telemetry.size() > 0) { telemetry.putAll(customDecoding.telemetry);}
if (customDecoding.?attributes.size() > 0) { attributes.putAll(customDecoding.attributes);}
telemetry.putAll(telemetryData);attributes.putAll(attributesData);
var result = { deviceName: deviceName, deviceType: deviceType,// assetName: assetName,// assetType: assetType,// customerName: customerName// groupName: groupName, attributes: attributes, telemetry: { ts: timestamp, values: telemetry }};
return result;Add a device on The Things Industries
We need to add a device on The Things Industries cloud.
To add a device, you can follow next steps:
- Login to the cloud and open your console.
- Go to the Applications page. Then select your application and click on its name.
- Click on the Register end device button.
- Put the APP EUI value to the JoinEUI field. Press the Confirm button.
- Fill the rest parameters and press Register end device button.
Uplink converter in ThingsBoard integration
Such as we have already connected gateway and configured integration - we will need to modify the converter and add an ability to parse incoming payload from the device.
To do this you can add code to “Decoding block”, it locates between comments ”// --- Decoding code --- //” in your converter, if you are using the default converters (for ThingsBoard v3.5.2 or above).
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;Or you can copy the whole code of the converter and paste it to your converter:
var data = decodeToJson(payload);
var deviceName = data.end_device_ids.device_id;var deviceType = data.end_device_ids.application_ids.application_id;// var groupName = 'IAQ devices';// var customerName = 'Customer A';// use assetName and assetType instead of deviceName and deviceType// to automatically create assets instead of devices.// var assetName = 'Asset A';// var assetType = 'building';
// If you want to parse incoming data somehow, you can add your code to this function.// input: bytes// expected output:// {// "attributes": {"attributeKey": "attributeValue"},// "telemetry": {"telemetryKey": "telemetryValue"}// }//// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.//
function decodeFrmPayload(input) { var output = { attributes:{}, telemetry: {}}; // --- Decoding code --- //
output.telemetry.HEX_bytes = bytesToHex(input);
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;
// --- Decoding code --- // return output;}
// --- attributes and telemetry objects ---var telemetry = {};var attributes = {};// --- attributes and telemetry objects ---
// --- Timestamp parsingvar dateString = data.uplink_message.received_at;// If data is simulated or device doesn't send his own date string - we will use date from upcoming message, set by network serverif ((data.simulated != null && data.simulated) || dateString == null) { dateString = data.received_at;}var timestamp = -1;if (dateString != null) { timestamp = new Date(dateString).getTime(); if (timestamp == -1) { var secondsSeparatorIndex = dateString.lastIndexOf('.') + 1; var millisecondsEndIndex = dateString.lastIndexOf('+'); if (millisecondsEndIndex == -1) { millisecondsEndIndex = dateString.lastIndexOf('Z'); } if (millisecondsEndIndex == -1) { millisecondsEndIndex = dateString.lastIndexOf('-'); } if (millisecondsEndIndex == -1) { if (dateString.length >= secondsSeparatorIndex + 3) { dateString = dateString.substring(0, secondsSeparatorIndex + 3); } } else { dateString = dateString.substring(0, secondsSeparatorIndex + 3) + dateString.substring(millisecondsEndIndex, dateString.length); } timestamp = new Date(dateString).getTime(); }}// If we cannot parse timestamp - we will use the current timestampif (timestamp == -1) { timestamp = Date.now();}// --- Timestamp parsing
// You can add some keys manually to attributes or telemetryattributes.devEui = data.end_device_ids.dev_eui;attributes.fPort = data.uplink_message.f_port;// We want to save correlation ids as single object, so we are excluding them from attributes parse and add manuallyattributes.correlation_ids = data.correlation_ids;
// You can exclude some keys from the resultvar excludeFromTelemetryList = ["uplink_token", "gateway_id", "settings", "f_port", "time", "timestamp", "received_at", "network_ids"];var excludeFromAttributesList = ["uplink_token", "gateway_id", "f_port", "time", "timestamp", "received_at", "session_key_id", "dev_eui"];
// Message parsing// To avoid paths in the decoded objects we passing false value to function as "pathInKey" argument.// Warning: pathInKey can cause already found fields to be overwritten with the last value found, e.g. receive_at from uplink_message will be written receive_at in the root.var telemetryData = toFlatMap(data.uplink_message, excludeFromTelemetryList, false);var attributesData = {};attributesData.putAll(toFlatMap(data.uplink_message.settings, excludeFromAttributesList, false));attributesData.putAll(toFlatMap(data.uplink_message.network_ids, excludeFromAttributesList, false));attributesData.putAll(toFlatMap(data.end_device_ids, excludeFromAttributesList, false));
// Passing incoming bytes to decodeFrmPayload function, to get custom decodingvar customDecoding = {};if (data.uplink_message.get("frm_payload") != null) { customDecoding = decodeFrmPayload(base64ToBytes(data.uplink_message.frm_payload));}
// Collecting data to resultif (customDecoding.?telemetry.size() > 0) { telemetry.putAll(customDecoding.telemetry);}
if (customDecoding.?attributes.size() > 0) { attributes.putAll(customDecoding.attributes);}
telemetry.putAll(telemetryData);attributes.putAll(attributesData);
var result = { deviceName: deviceName, deviceType: deviceType,// assetName: assetName,// assetType: assetType,// customerName: customerName,// groupName: groupName, attributes: attributes, telemetry: { ts: timestamp, values: telemetry }};
return result;Add a device on the Loriot
We need to add a device on the Loriot.
To add a device, you can follow next steps:
- Login to Loriot server. We use eu2.loriot.io, but it depends on chosen region during registration.
- Go to the “Applications” page in left menu.
- Open your application, in our case it is “SampleApp”.
- Go to the “Enroll Device” page. Fill in the fields, with a configuration from your device. Then click the “Enroll” button.
Uplink converter in ThingsBoard integration
Such as we have already connected gateway and configured integration - we will need to modify the converter and add an ability to parse incoming payload from the device.
To do this you can add code to “Decoding block”, it locates between comments ”// --- Decoding code --- //” in your converter, if you are using the default converters (for ThingsBoard v3.5.2 or above).
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;Or you can copy the whole code of the converter and paste it to your converter:
var data = decodeToJson(payload);var deviceName = data.EUI;var deviceType = "LoraDevices";// groupName = 'IAQ devices';// var customerName = 'Customer A';// use assetName and assetType instead of deviceName and deviceType// to automatically create assets instead of devices.// var assetName = 'Asset A';// var assetType = 'building';var gatewayDeviceType = "LoraGateway";
// If you want to parse incoming data somehow, you can add your code to this function.// input: bytes// expected output:// {// "attributes": {"attributeKey": "attributeValue"},// "telemetry": {"telemetryKey": "telemetryValue"}// }//// In the example - bytes will be saved as HEX string and also parsed as light level, battery level and PIR sensor value.//
function decodePayload(input) { var output = { attributes:{}, telemetry: {} }; // --- Decoding code --- //
output.telemetry.HEX_bytes = bytesToHex(input);
var historyData = {}; var decoded = {}; decoded.hexString = bytesToHex(input); for (var i = 0; i < input.length; ) { var channel_id = input[i]; var channel_type = input[i+1];
i += 2;
// PROTOCOL VERSION if (channel_id == -1 && channel_type == 1) { decoded.protocol_version = input[i]; i += 1; } // SERIAL NUMBER else if (channel_id == -1 && channel_type == 8) { var temp = []; var last_index_sn = i + 6; for (var idxsn = i; idxsn < last_index_sn; idxsn++) { temp.push(bytesToHex([input[idxsn] & 0xff])); } decoded.serialNumber = temp.join(""); i += 6; } // HARDWARE VERSION else if (channel_id == -1 && channel_type == 9) { var temphv = []; var last_index_hv = i + 2; for (var idxhv = i; idxhv < last_index_hv; idxhv++) { temphv.push((input[idxhv] & 0xff).toString()); } decoded.hardwareVersion = temphv.join("."); i += 2; } // FIRMWARE VERSION else if (channel_id == -1 && channel_type == 31) { var tempfv = []; var last_index_fv = i + 4; for (var idxfv = i; idxfv < last_index_fv; idxfv++) { tempfv.push((input[idxfv] & 0xff).toString()); } decoded.firmwareVersion = tempfv.join("."); i += 4; } // PEOPLE COUNTER else if (channel_id == 4 && channel_type == -55) { decoded.peopleCountAll = input[i]; decoded.regionCount = input[i + 1]; var region = parseBytesToInt(input, i + 2, 2, false); for (var idxpc = 0; idxpc < decoded.regionCount; idxpc++) { var tmp = "region" + (idxpc + 1); decoded[tmp] = (region >> idxpc) & 1; } i += 4; } // PEOPLE IN/OUT else if (channel_id == 5 && channel_type == -52) { decoded.peopleIn = parseBytesToInt(input, i, 2, false); decoded.peopleOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // PEOPLE MAX else if (channel_id == 6 && channel_type == -51) { decoded.peopleCountMax = input[i]; i += 1; } // REGION COUNTER else if (channel_id == 7 && channel_type == -43) { decoded.region1Count = input[i]; decoded.region2Count = input[i + 1]; decoded.region3Count = input[i + 2]; decoded.region4Count = input[i + 3]; decoded.region5Count = input[i + 4]; decoded.region6Count = input[i + 5]; decoded.region7Count = input[i + 6]; decoded.region8Count = input[i + 7]; i += 8; } // REGION COUNTER else if (channel_id == 8 && channel_type == -43) { decoded.region9Count = input[i]; decoded.region10Count = input[i + 1]; decoded.region11Count = input[i + 2]; decoded.region12Count = input[i + 3]; decoded.region13Count = input[i + 4]; decoded.region14Count = input[i + 5]; decoded.region15Count = input[i + 6]; decoded.region16Count = input[i + 7]; i += 8; } // A FLOW else if (channel_id == 9 && channel_type == -38) { decoded.aToA = parseBytesToInt(input, i, 2, false); decoded.aToB = parseBytesToInt(input, i + 2, 2, false); decoded.aToC = parseBytesToInt(input, i + 4, 2, false); decoded.aToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // B FLOW else if (channel_id == 10 && channel_type == -38) { decoded.bToA = parseBytesToInt(input, i, 2, false); decoded.bToB = parseBytesToInt(input, i + 2, 2, false); decoded.bToC = parseBytesToInt(input, i + 4, 2, false); decoded.bToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // C FLOW else if (channel_id == 11 && channel_type == -38) { decoded.cToA = parseBytesToInt(input, i, 2, false); decoded.cToB = parseBytesToInt(input, i + 2, 2, false); decoded.cToC = parseBytesToInt(input, i + 4, 2, false); decoded.cToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // D FLOW else if (channel_id == 12 && channel_type == -38) { decoded.dToA = parseBytesToInt(input, i, 2, false); decoded.dToB = parseBytesToInt(input, i + 2, 2, false); decoded.dToC = parseBytesToInt(input, i + 4, 2, false); decoded.dToD = parseBytesToInt(input, i + 6, 2, false); i += 8; } // TOTAL IN/OUT else if (channel_id == 13 && channel_type == -52) { decoded.peopleTotalIn = parseBytesToInt(input, i, 2, false); decoded.peopleTotalOut = parseBytesToInt(input, i + 2, 2, false); i += 4; } // DWELL TIME else if (channel_id == 14 && channel_type == -28) { var region1 = input[i]; decoded.region = region1; decoded.dwellTimeAvg = parseBytesToInt(input, i + 1, 2, false); decoded.dwellTimeMax = parseBytesToInt(input, i + 3, 2, false); i += 5; } // TIMESTAMP else if (channel_id == 15 && channel_type == -123) { decoded.timestamp = parseBytesToInt(input, i, 4, false); i += 4; } else { break; } }
output.telemetry = decoded;
// --- Decoding code --- // return output;}
// --- attributes and telemetry objects ---var telemetry = {};var attributes = {};// --- attributes and telemetry objects ---
// --- Timestamp parsingvar timestamp = data.ts;// If we cannot parse timestamp - we will use the current timestampif (timestamp == -1) { timestamp = Date.now();}// --- Timestamp parsing
// You can add some keys manually to attributes or telemetryattributes.fPort = data.port;attributes.dataRange = data.dr;
// You can exclude some keys from the resultvar excludeFromAttributesList = ["data", "gws", "EUI", "ts", "cmd", "port", "seqno", "fcnt", "toa", "dr", "ack", "bat", "snr", "rssi"];var excludeFromTelemetryList = ["gws", "EUI", "ts", "freq", "port", "data", "cmd", "dr", "offline"];
// Message parsing// To avoid paths in the decoded objects we passing false value to function as "pathInKey" argument.// Warning: pathInKey can cause already found fields to be overwritten with the last value found.
var telemetryData = toFlatMap(data, excludeFromTelemetryList, false);var attributesData = toFlatMap(data, excludeFromAttributesList, false);
var uplinkDataList = [];
// Passing incoming bytes to decodePayload function, to get custom decodingvar customDecoding = decodePayload(hexToBytes(data.data));
// Collecting data to resultif (customDecoding.?telemetry.size() > 0) { telemetry.putAll(customDecoding.telemetry);}
if (customDecoding.?attributes.size() > 0) { attributes.putAll(customDecoding.attributes);}
telemetry.putAll(telemetryData);attributes.putAll(attributesData);
var deviceInfo = { deviceName: deviceName, deviceType: deviceType,// assetName: assetName,// assetType: assetType,// customerName: customerName,// groupName: groupName, attributes: attributes, telemetry: { ts: timestamp, values: telemetry }};
uplinkDataList.add(deviceInfo);
if (data.cmd == "gw") { foreach( gatewayInfo : data.gws ) { var gatewayInfoMsg = { deviceName: gatewayInfo.gweui, deviceType: gatewayDeviceType, attributes: {}, telemetry: { "ts": gatewayInfo.ts, "values": toFlatMap(gatewayInfo, ["ts", "time", "gweui"], false) } }; uplinkDataList.add(gatewayInfoMsg); }}
return uplinkDataList;Check data on ThingsBoard
So, the device was added and if it sends any data - it should appear in “Devices”. To check it you may open “Devices” page in “Entities” section. The device should be in devices list. You can check the data by clicking the device and navigate to the “Attributes” or “Latest telemetry” tab.
In order to get more user-friendly view - you can use dashboard. Download a simple dashboard for this device. It is configured to display a data from “Count people all”, “Max count people”, “Signal strength”, “Region count” and “Occupancy” timeseries keys of device with name “eui-24e124538b223213”.
ThingsBoard provides the ability to create and customize interactive visualizations (dashboards) for monitoring and managing data and devices. Through ThingsBoard dashboards, you can efficiently manage and monitor your IoT devices and data. So, we will create the dashboard, for our device.
To add the dashboard to ThingsBoard, we need to import it. To import a dashboard, follow these steps:
- First download the Check and control device data dashboard file.
- Navigate to the “Dashboards” page. By default, you navigate to the dashboard group “All”. Click on the ”+” icon in the top right corner. Select “Import dashboard”.
- In the dashboard import window, upload the JSON file and click “Import” button.
- Dashboard has been imported.
To open the imported dashboard, click on it. Then you should specify your device in entity alias of the dashboard.
To do this, follow these steps:
- Open the dashboard and enter edit mode. Click the “Entity aliases” icon, then in the pop-up window click the “Edit alias” icon next to the alias.
- In edit alias window select your device from dropdown list and save entity alias.
- Apply all changes.
Now you should be able to see the data from the device.
Example of the dashboard with data:
Conclusion
Now you can easily connect your VS121 AI workplace sensor and start sending data to ThingsBoard.
To go further, explore the ThingsBoard documentation to learn more about key features, such as creating dashboards to visualize your telemetry, or setting up alarm rules to monitor device behavior in real time.