Stand with Ukraine flag
Try it now Pricing
Cloud
North America
How to connect AM308 Lorawan 9-IN-1 IAQ Sensor to ThingsBoard?
Getting Started Documentation Devices Library Guides API FAQ
On this page

How to connect AM308 Lorawan 9-IN-1 IAQ Sensor to ThingsBoard?

AM308 Lorawan 9-IN-1 IAQ Sensor AM300 series is a compact indoor ambiance monitoring sensor for measurement of temperature, humidity, light, CO2 concentration, barometric pressure, PM2.5, PM10 and motion. The data will be shown on the E-ink screen in real-time, which helps to measure the indoor environment and comfort. AM300 series is widely used for offices, stores, classrooms, hospitals, etc. Sensor data is transmitted using LoRaWAN ® technology. Using Milesight LoRaWAN® gateway and ThingsBoard, users can manage all sensor data remotely and visually.




Features of the AM300 series device:

  • Integrated with multiple sensors like humidity, temperature, CO2, level light, barometric pressure, PM2.5, PM10, etc. Multiple display modes and clear emoticons to easily understand the comfort levels via screen;
  • Support batteries or DC power supply;
  • Equipped with traffic light indicator and buzzer to indicate device status and threshold alarms;
  • Able to store locally more than 18, 000 records of 512 KB in total;
  • Compliant with standard LoRaWAN® gateways and network servers.
Doc info icon
ThingsBoard PE Feature

Only Professional Edition supports Platform Integrations feature.
Use ThingsBoard Cloud or install your own platform instance.

Prerequisites

To continue with this guide we will need the following:

Device connection

According to the official user manual, we need a smartphone with NFC enabled and the ToolBox application to connect the sensor.
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.
Afterward, it can be provisioned to ThingsBoard.

Device configuration

To connect and send data we should configure the device and network server.
At first 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!

The parameters above are required for connection.

Depending on the network server, you may also need to provide join type (OTAA), LoRaWAN version.

To configure device via NFC, you will need to hold your smartphone like on the picture below:

NFC zone

To read and write configuration on the device you may follow next steps on your smartphone:

  • Open ToolBox application.

  • Click on NFC Read button and hold your smartphone near the device.

  • Go to tab Setting, set and save required fields and any other configuration that you need.

  • Press Write button and hold your smartphone near the device.

The developer also provides the possibility of connecting through a computer if necessary:

Device connect

See the manual for details.

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.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

Or you can copy the whole code of the converter and paste it to your converter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

    // --- Decoding code --- //
    return output;
}

// --- attributes and telemetry objects ---
var telemetry = {};
var attributes = {};
// --- attributes and telemetry objects ---

// --- Timestamp parsing
var 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 timestamp
if (timestamp == -1) {
    timestamp = Date.now();
}
// --- Timestamp parsing

// You can add some keys manually to attributes or telemetry
attributes.deduplicationId = data.deduplicationId;

// You can exclude some keys from the result
var 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 decoding
var customDecoding = decodePayload(base64ToBytes(data.data));

// Collecting data to result
if (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.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

Or you can copy the whole code of the converter and paste it to your converter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

    // --- Decoding code --- //
    return output;
}

// --- attributes and telemetry objects ---
var telemetry = {};
var attributes = {};
// --- attributes and telemetry objects ---

// --- Timestamp parsing
var 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 server
if ((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 timestamp
if (timestamp == -1) {
    timestamp = Date.now();
}
// --- Timestamp parsing

// You can add some keys manually to attributes or telemetry
attributes.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 manually
attributes.correlation_ids = data.correlation_ids;

// You can exclude some keys from the result
var 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 decoding
var customDecoding = {};
if (data.uplink_message.get("frm_payload") != null) {
    customDecoding = decodeFrmPayload(base64ToBytes(data.uplink_message.frm_payload));
}

// Collecting data to result
if (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.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

Or you can copy the whole code of the converter and paste it to your converter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

    // --- Decoding code --- //
    return output;
}

// --- attributes and telemetry objects ---
var telemetry = {};
var attributes = {};
// --- attributes and telemetry objects ---

// --- Timestamp parsing
var 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 server
if ((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 timestamp
if (timestamp == -1) {
    timestamp = Date.now();
}
// --- Timestamp parsing

// You can add some keys manually to attributes or telemetry
attributes.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 manually
attributes.correlation_ids = data.correlation_ids;

// You can exclude some keys from the result
var 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 decoding
var customDecoding = {};
if (data.uplink_message.get("frm_payload") != null) {
    customDecoding = decodeFrmPayload(base64ToBytes(data.uplink_message.frm_payload));
}

// Collecting data to result
if (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.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

Or you can copy the whole code of the converter and paste it to your converter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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++];
        // BATTERY
        if (channel_id === 0x01 && channel_type === 0x75) {
            decoded.battery = input[i];
            i += 1;
        }
        // TEMPERATURE
        if (channel_id === 0x03 && channel_type === 0x67) {
            // ℃
            decoded.temperature = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
            // ℉
            // decoded.temperature = parseBytesToInt(input, i, 2, false) / 10 * 1.8 + 32;
            // i +=2;
        }
        // HUMIDITY
        if (channel_id === 0x04 && channel_type === 0x68) {
            decoded.humidity = input[i] / 2;
            i += 1;
        }
        // PIR
        if (channel_id === 0x05 && channel_type === 0x00) {
            decoded.pir = input[i] === 1 ? "trigger" : "idle";
            i += 1;
        }
        // LIGHT
        if (channel_id == 0x06 && channel_type == -53) {
            decoded.light_level = input[i];
            i += 1;
        }
        // CO2
        if (channel_id === 0x07 && channel_type === 0x7d) {
            decoded.co2 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // TVOC
        if (channel_id === 0x08 && channel_type === 0x7d) {
            decoded.tvoc = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PRESSURE
        if (channel_id === 0x09 && channel_type === 0x73) {
            decoded.pressure = parseBytesToInt(input, i, 2, false) / 10;
            i += 2;
        }
        // HCHO
        if (channel_id === 0x0a && channel_type === 0x7d) {
            decoded.hcho = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // PM2.5
        if (channel_id === 0x0b && channel_type === 0x7d) {
            decoded.pm2_5 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // PM10
        if (channel_id === 0x0c && channel_type === 0x7d) {
            decoded.pm10 = parseBytesToInt(input, i, 2, false);
            i += 2;
        }
        // O3
        if (channel_id === 0x0d && channel_type === 0x7d) {
            decoded.o3 = parseBytesToInt(input, i, 2, false) / 100;
            i += 2;
        }
        // BEEP
        if (channel_id === 0x0e && channel_type === 0x01) {
            decoded.beep = input[i] === 1 ? "yes" : "no";
            i += 1;

        }
        // HISTORY DATA (AM307)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            i += 16;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM308)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            i += 20;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY DATA (AM319 CH2O)
        if (channel_id === 32 && channel_type === 206) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.hcho = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
        // HISTORY historyData (AM319 O3)
        if (channel_id === 0x20 && channel_type === 0xce) {
            historyData = {};
            historyData.timestamp = parseBytesToInt(input, i, 4, false);
            historyData.temperature = parseBytesToInt(input, i + 4, 2, false) / 10;
            historyData.humidity = parseBytesToInt(input, i + 6, 2, false) / 2;
            historyData.pir = input[i + 8] === 1 ? "trigger" : "idle";
            historyData.light_level = input[i + 9] === 1;
            historyData.co2 = parseBytesToInt(input, i + 10, 2, false);
            historyData.tvoc = parseBytesToInt(input, i + 12, 2, false);
            historyData.pressure = parseBytesToInt(input, i + 14, 2, false) / 10;
            historyData.pm2_5 = parseBytesToInt(input, i + 16, 2, false);
            historyData.pm10 = parseBytesToInt(input, i + 18, 2, false);
            historyData.o3 = parseBytesToInt(input, i + 20, 2, false) / 100;
            i += 22;
            if (decoded.history == null) {
                decoded.history = [];
            }
            decoded.history.push(historyData);
        }
    }

    output.telemetry = decoded;

    // --- Decoding code --- //
    return output;
}

// --- attributes and telemetry objects ---
var telemetry = {};
var attributes = {};
// --- attributes and telemetry objects ---

// --- Timestamp parsing
var timestamp = data.ts;
// If we cannot parse timestamp - we will use the current timestamp
if (timestamp == -1) {
    timestamp = Date.now();
}
// --- Timestamp parsing

// You can add some keys manually to attributes or telemetry
attributes.fPort = data.port;
attributes.dataRange = data.dr;

// You can exclude some keys from the result
var 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 decoding
var customDecoding = decodePayload(hexToBytes(data.data));

// Collecting data to result
if (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 click on it and open tab Attributes or Latest telemetry.


In order to get more user-friendly view - you can use dashboards.
You can download a simple dashboard for this device, it is configured to display a data from “pir”, “light level”, “beep”, “battery level”, “Temperature”, “Humidity”, “CO2”, “Pressure”, “TVOC”, “PM2.5”, “PM10” and “Signal strength” telemetry keys for 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:

  • 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

With the knowledge outlined in this guide, you can easily connect your AM308 Lorawan 9-IN-1 IAQ Sensor and send data to ThingsBoard.

Explore the platform documentation to learn more about key concepts and features. For example, configure alarm rules or dashboards.