产品定价 立即试用
目录
如何将 AM308 Lorawan 9-IN-1 IAQ Sensor 连接至 ThingsBoard?

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.
文档信息图标
ThingsBoard PE 功能

专业版支持Platform Integrations功能。
请使用ThingsBoard Cloud自行安装平台实例。

前置条件

继续本指南前,需准备以下内容:

设备连接

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 EUI - 设备标识符
  • Application EUI - 应用标识符
  • Application Key - 用于识别设备的应用密钥。建议使用生成的密钥,而非示例中的密钥!

以上参数为连接所必需。

根据网络服务器要求,可能还需提供接入类型(OTAA)、LoRaWAN版本。

通过NFC配置设备时,请如右图所示将智能手机靠近设备:

NFC zone

在智能手机上读写设备配置,可按以下步骤操作:

  • 打开 ToolBox 应用。

  • 点击 NFC Read 按钮,将智能手机靠近设备。

  • 进入 Setting 选项卡,设置并保存所需字段及其他配置。

  • 点击 Write 按钮,将智能手机靠近设备。

如有需要,开发者也提供了通过电脑连接的方式:

Device connect

详情请参阅用户手册

To configure the device we also need to add it to a network server, so select the network server your gateway is connected to:

在ChirpStack上添加设备

我们需要在 ChirpStack 上添加设备。

要添加设备,请按以下步骤操作:

  • 登录ChirpStack服务器。

  • 进入设备配置页面,点击添加设备配置按钮。

  • 填写字段并点击提交按钮。

  • 进入应用页面,点击您的应用后按添加设备按钮。

  • 使用设备配置中的值填写参数。然后选择之前创建设备配置并点击提交按钮。

  • 应用密钥填入相应字段并点击提交按钮保存设备。

ThingsBoard集成中的上行数据转换器

由于我们已连接网关并配置了集成,需要修改转换器并添加解析设备上行数据负载的能力。

为此,您可以将代码添加到”解码块“中,该块位于转换器内”// — Decoding code — //“注释之间(若使用ThingsBoard v3.5.2及以上版本的默认转换器)。

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;

或者,您也可以复制完整的转换器代码并粘贴到您的转换器中:

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;

在The Things Stack社区版上添加设备

我们需要在 The Things Stack社区版 上添加设备。

要添加设备,请按以下步骤操作:

  • 登录云端并打开控制台。

  • 进入 应用 页面。然后选择您的应用并点击应用名称。

  • 点击 注册终端设备 按钮。

  • APP EUI 值填入 JoinEUI 字段。点击 确认 按钮。

  • 填写其余参数并点击 注册终端设备 按钮。

ThingsBoard集成中的上行数据转换器

由于我们已连接网关并配置了集成,需要修改转换器并添加解析设备上行数据负载的能力。

为此,您可以将代码添加到”解码块“中,该块位于转换器内”// — Decoding code — //“注释之间(若使用ThingsBoard v3.5.2及以上版本的默认转换器)。

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;

或者,您也可以复制完整的转换器代码并粘贴到您的转换器中:

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;

在The Things Industries上添加设备

我们需要在 The Things Industries云 上添加设备。

要添加设备,请按以下步骤操作:

  • 登录云端并打开控制台。

  • 进入 应用 页面。然后选择您的应用并点击应用名称。

  • 点击 注册终端设备 按钮。

  • APP EUI 值填入 JoinEUI 字段。点击 确认 按钮。

  • 填写其余参数并点击 注册终端设备 按钮。

ThingsBoard集成中的上行数据转换器

由于我们已连接网关并配置了集成,需要修改转换器并添加解析设备上行数据负载的能力。

为此,您可以将代码添加到”解码块“中,该块位于转换器内”// — Decoding code — //“注释之间(若使用ThingsBoard v3.5.2及以上版本的默认转换器)。

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;

或者,您也可以复制完整的转换器代码并粘贴到您的转换器中:

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;

在Loriot上添加设备

我们需要在 Loriot 上添加设备。

要添加设备,请按以下步骤操作:

  • 登录Loriot服务器。我们使用 eu2.loriot.io,具体取决于注册时选择的区域。

  • 在左侧菜单中进入“应用”页面。

  • 打开您的应用,我们的示例为“SampleApp”。

  • 进入“注册设备”页面。使用设备配置中的值填写字段,然后点击“注册”按钮。

ThingsBoard集成中的上行数据转换器

由于我们已连接网关并配置了集成,需要修改转换器并添加解析设备上行数据负载的能力。

为此,您可以将代码添加到”解码块“中,该块位于转换器内”// — Decoding code — //“注释之间(若使用ThingsBoard v3.5.2及以上版本的默认转换器)。

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;

或者,您也可以复制完整的转换器代码并粘贴到您的转换器中:

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;

在ThingsBoard上查看数据

设备已添加,若其发送了数据,将显示在 设备 中。
可在 实体 部分打开 设备 页面查看。 设备将出现在设备列表中,点击设备并打开 属性最新遥测 选项卡即可查看数据。


为获得更直观的视图,可使用仪表板
可下载适用于本设备的简易仪表板,该仪表板配置为显示设备名称为 “eui-24e124538b223213” 的设备的 “pir”、”light level”、”beep”、”battery level”、”Temperature”、”Humidity”、”CO2”、”Pressure”、”TVOC”、”PM2.5”、”PM10” 和 “Signal strength” 遥测数据。

ThingsBoard支持创建和自定义用于监控与管理数据、设备的交互式可视化(仪表板)。
通过ThingsBoard仪表板,您可以高效管理与监控IoT设备及数据。下面为我们的设备创建仪表板。

将仪表板添加到ThingsBoard需要导入。请按以下步骤操作:

  • 进入 仪表板 页面。默认进入仪表板组 “All”。点击右上角 + 图标,选择 导入仪表板

  • 在仪表板导入窗口中上传JSON文件并点击 导入 按钮。

  • 仪表板已导入。

打开导入的仪表板,点击进入。然后需在仪表板的实体别名中指定您的设备。

请按以下步骤操作:

  • 打开仪表板并进入编辑模式。点击“实体别名”图标,在弹窗中点击别名旁的“编辑别名”图标。

  • 在编辑别名窗口的下拉列表中选择您的设备并保存实体别名。

  • 应用所有更改。

此时应能看到设备数据。

带数据的仪表板示例:

总结

现在您可以轻松将AM308 Lorawan 9-IN-1 IAQ Sensor连接到ThingsBoard并开始发送数据。

进一步了解可查阅ThingsBoard文档, 学习创建仪表板可视化遥测、 配置告警规则实时监控设备行为等核心功能。

发现即插即用硬件,助力您的解决方案
合作伙伴图标