产品定价 立即试用
目录
如何将 Helmet Sensor 连接至 ThingsBoard?
  • Hardware type: Sensors
  • Connectivity: Bluetooth, LoRaWAN
  • Industry: Industrial Manufacturing, Smart Cities, Security, Healthcare
  • Use cases: Health Care
  • Platforms: Community Edition, Professional Edition, Cloud

头盔传感器(Helmet Sensor)基于GNSS、蓝牙5.0和LoRaWAN技术设计,支持室内和室外追踪。
具备多种功能,便于工业场景管理。内置3轴加速度计可判断终端运动状态,有助于节省电池并提升使用体验。适用于访客管理、工厂工人追踪、建筑工人追踪、车辆追踪等场景。

前置条件

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

配置

仅当该设备通过MQTT直接与ThingsBoard通信时,需使用ThingsBoard Cloud。

使用ThingsBoard集成时,可使用本地部署的ThingsBoard PE或ThingsBoard Cloud。

若要与网络服务器创建集成,请先选择所支持的网络服务器之一:

在ChirpStack上添加网关

我们需要在 ChirpStack 上添加网关。

要添加网关,请按以下步骤操作:

  • 登录ChirpStack服务器。进入“网关”页面,点击“添加网关”按钮。

  • 填写 名称网关ID(根据您的网关而定,可在网关控制面板上查看),向下滚动并点击“提交”按钮。

  • 网关已添加。在网关选项卡中可查看其状态。

在ChirpStack上添加设备配置

  • 登录ChirpStack服务器。进入“设备配置”页面,点击 添加设备配置 按钮。

  • 填写必填字段。

  • 进入 Codec 选项卡,在 Payload codec 下拉菜单中选择 JavaScript functions,粘贴解码器函数。然后点击 提交 按钮。

解码函数:

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
// Decode uplink function.
//
// Input is an object with the following fields:
// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
// - fPort = Uplink fPort.
// - variables = Object containing the configured device variables.
//
// Output must be an object with the following fields:
// - data = Object representing the decoded payload.
// Helmet Sensor decoder
function decodeUplink(input) {
// bytes
    var bytes = input.bytes;
// type
    var uplinkType = (bytes[0] >> 4) & 0x0f;
    switch (uplinkType) {
        case 0x01:
            return {data: decodeRegistration(bytes)};
        case 0x02:
            return {data: decodeHeartbeat(bytes)};
        case 0x03:
            return {data: decodeGNSSPosition(bytes)};
        case 0x05:
            return {data: decodeUUIDReport(bytes)};
        case 0x07:
            return {data: decodeBeacon(bytes)};
        case 0x08:
            return {data: decodeAlarm(bytes)};
        case 0x0c:
            return {data: decodeBracelet(bytes)};
        default:
            return null;
    }
}

// type: 0x1 Registration
function decodeRegistration(bytes) {
    var data = {};
    data.type = "Registration";
// adr
    data.adr = ((bytes[0] >> 3) & 0x1) == 0 ? "OFF" : "ON";
// power
    data.power = ((bytes[2] >> 3) & 0x1f) + "dBm";
// dr
    data.dr = (bytes[3] >> 4) & 0x0f;
// gnssEnable
    data.gnssEnable = ((bytes[3] >> 3) & 0x01) == 0 ? "Disable" : "Enable";
// positionReportMode
    var positionReportMode = (bytes[3] >> 1) & 0x03;
    if (positionReportMode == 0) {
        data.positionReportMode = "Period";
    } else if (positionReportMode == 0) {
        data.positionReportMode = "Autonomous";
    } else if (positionReportMode == 0) {
        data.positionReportMode = "On-Demand";
    }
// bleEnable
    data.bleEnable = (bytes[3] & 0x01) == 0 ? "Disable" : "Enable";
// blePositionReportInterval
    data.blePositionReportInterval =
        (((bytes[4] << 8) & 0xff00) | (bytes[5] & 0xff)) * 5 + "s";
// gnssPositionReportInterval
    data.gnssPositionReportInterval =
        (((bytes[6] << 8) & 0xff00) | (bytes[7] & 0xff)) * 5 + "s";
// heartbeatPeriod
    data.heartbeatPeriod = (bytes[8] & 0xff) * 30 + "s";
// versiondata.version =
    (bytes[9] & 0xff).toString(16).toUpperCase() +
    "." +
    (bytes[10] & 0xff).toString(16).toUpperCase();
// cfmmsg
    data.cfmmsg = "1 Confirmed every " + (bytes[11] & 0xff) + " Heartbeat";
// hbCount
    data.hbCount = "Disconnect Judgement " + (bytes[12] & 0xff);
// fallDetectFeatureThreshold
    data.fallDetectFeatureThreshold = (bytes[13] & 0xff) * 0.5 + " meters";
    return data;
}

// type: 0x2 Heartbeat
function decodeHeartbeat(bytes) {
    var data = {};
// type
    data.type = "Heartbeat";
// battery
    data.battery = bytes[1] + "%";
// rssi
    data.rssi = bytes[2] * -1 + "dBm";
// snr
    data.snr = (((bytes[3] << 8) & 0xff00) | (bytes[4] & 0xff)) / 100 + "dB";
// bleReceivingNumber
    data.bleReceivingNumber = bytes[5];
// gnssSearchingNumber
    data.gnssSearchingNumber = bytes[6];
// chargeTime
    data.chargeTime = bytes[7] * 30 + "s";
// wearTime
    data.wearTime = bytes[8] * 30 + "s";
// moveState
    data.moveState = (bytes[9] >> 4) & 0x0f;
// temperature
    data.temperature = 0;
    return data;
}

// type: 0x3 GNSSPosition
function decodeGNSSPosition(bytes) {
    var data = {};
// type
    data.type = "GNSSPosition";
// gnssState
    data.gnssState = ((bytes[0] >> 3) & 0x01) == 0 ? "Success" : "Fail";
// wearState
    data.wearState = (bytes[0] & 0x01) == 0 ? "Do not wear" : "Wear";
// pressure
    let pressure =
        (bytes[1] << 24) | (bytes[2] << 16) | (bytes[3] << 8) | bytes[4];
    data.pressure = pressure / 10 + "pa";
// longitude
    let longitude = (bytes[5] << 24) | (bytes[6] << 16) | (bytes[7] << 8) | bytes[8];
    data.longitude = hex2float(longitude);
// latitude
    let latitude =
        (bytes[9] << 24) | (bytes[10] << 16) | (bytes[11] << 8) | bytes[12];
    data.latitude = hex2float(latitude);
// time
    let time =
        (bytes[13] << 24) | (bytes[14] << 16) | (bytes[15] << 8) | bytes[16];
    data.time = timestampToTime((time + 8 * 60 * 60) * 1000);
    return data;
}

// type: 0x5 UUIDReport
function decodeUUIDReport(bytes) {
    var data = {};
// type
    data.type = "UUIDReport";
// number
    data.number = Math.floor((bytes.length - 1) / 17);
    var beaconUUIDList = [];
    for (let i = 0; i < data.number; i++) {
        var beaconTypeId = bytes[1 + 17 * i] & 0x03;
        if (beaconTypeId == 0x00) {
            beaconTypeId = "PositionBeaconUUID";
        } else if (beaconTypeId == 0x01) {
            beaconTypeId = "AssetBeaconUUID";
        } else if (beaconTypeId == 0x02) {
            beaconTypeId = "SpecialBeaconUUID";
        } else if (beaconTypeId == 0x03) {
            beaconTypeId = "SearchBeaconUUID";
        }
        var beaconUUID = "";
        for (let j = 0; j < 16; j++) {
            beaconUUID += (bytes[2 + 17 * i + j] & 0xff)
                .toString(16)
                .toUpperCase()
                .padStart(2, "0");
        }
        beaconUUIDList.push({beaconTypeId, beaconUUID});
    }
    data.beaconUUIDList = beaconUUIDList;
    return data;
}

// type: 0x7 Beacon
function decodeBeacon(bytes) {
    var data = {};
    data.type = "Beacon";
// wearState
    data.wearState = (bytes[0] & 0x01) == 0 ? "Not wearing" : "Wearing";
// pressure
    let pressure = (bytes[1] << 24) | (bytes[2] << 16) | (bytes[3] << 8) | bytes[4];
    data.pressure = pressure / 10 + "pa";
// numner
    data.number = bytes[5] & 0x0f;
    for (let i = 0; i < data.number; i++) {
        var index = 7 + 5 * i;
        var major = ((bytes[index] << 8) | bytes[index + 1])
            .toString(16)
            .toUpperCase()
            .padStart(4, "0");
        var minor = ((bytes[index + 2] << 8) | bytes[index + 3])
            .toString(16)
            .toUpperCase()
            .padStart(4, "0");
        var rssi = bytes[index + 4] - 256 + "dBm";
        data["beacon" + (i + 1)] = major + minor;
        data["rssi" + (i + 1)] = rssi;
    }
    return data;
}

// type: 0x8 Alarm
function decodeAlarm(bytes) {
    var data = {};
    data.type = "Alarm";
    var alarmValue = bytes[1] & 0xff;
    if (alarmValue == 1) {
        data.alarm = "SOS";
    } else if (alarmValue == 2) {
        data.alarm = "Fall";
    } else if (alarmValue == 3) {
        data.alarm = "Special area";
    } else if (alarmValue == 4) {
        data.alarm = "Search";
    }
    return data;
}

// type: 0x0c Bracelet
function decodeBracelet(bytes) {
    var data = {};
    data.type = "Bracelet";
    var number =
        bytes[1];
    data.number = number;
    var braceletList = [];
    for (let i = 0; i < number; i++) {
        var bracelet = {};
        var mac = ""
        for (let j = 0; j < 6; j++) {
            mac += bytes[j + 2 + i * 21]
                .toString(16)
                .toUpperCase()
                .padStart(2, "0");
        }
        bracelet.mac = mac;
        bracelet.batteryLevel = bytes[8 + i * 21]; // 电量等级
        bracelet.heartrate = bytes[9 + i * 21]; // 心率
        bracelet.systolicPressure = bytes[10 + i * 21]; // 收缩压
        bracelet.diastolicPressure = bytes[11 + i * 21]; // 舒张压
        bracelet.wristTemperature = (((bytes[12 + i * 21] << 8) & 0xff00) |
            (bytes[13 + i * 21] & 0xff)) / 10 + ""; // 腕温
        bracelet.bodyTemperature = (((bytes[14 + i * 21] << 8) & 0xff00) |
            (bytes[15 + i * 21] & 0xff)) / 10 + ""; // 体温
        bracelet.stepNumber = ((bytes[16 + i * 21] << 16) & 0xff0000) |
            ((bytes[17 + i * 21] << 8) & 0xff00) | (bytes[18 + i * 21] & 0xff); // 步数
        bracelet.wearStatus = ((bytes[19 + i * 21]) & 0x01) == 0 ? "Not
            wearing
        " : "
        Wearing
        ";
        bracelet.bluetoothBroadcastInterval = (bytes[20 + i * 21] & 0xff) +
            "s";
        bracelet.samplingInterval = (bytes[21 + i * 21] & 0x7f) + "s";
        bracelet.rssi = (bytes[22 + i * 21] - 256) + "dBm";
        braceletList.push(bracelet);
    }
    data.braceletList = braceletList;
    return data;
}

function hex2float(num) {
    var sign = num & 0x80000000 ? -1 : 1;
    var exponent = ((num >> 23) & 0xff) - 127;
    var mantissa = 1 + (num & 0x7fffff) / 0x7fffff;
    return sign * mantissa * Math.pow(2, exponent);
}

function timestampToTime(timestamp) {
    const date = new Date(timestamp);
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, "0");
    const day = date.getDate().toString().padStart(2, "0");
    const hour = date.getHours().toString().padStart(2, "0");
    const minute = date.getMinutes().toString().padStart(2, "0");
    const second = date.getSeconds().toString().padStart(2, "0");
    return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}

在ChirpStack上添加设备

  • 进入 应用 页面,点击 添加应用 按钮。

  • 输入名称并点击 提交 按钮。

  • 点击 添加设备

  • 使用设备信息填写必填字段,并指定之前创建设备配置。

  • 进入 Variables 选项卡,输入 ThingsBoardAccessToken 的值,点击 提交 按钮。

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

配置应用与ThingsBoard的集成

  • 进入 集成 页面,找到并选择 ThingsBoard

  • 输入 ThingsBoard服务器URL,点击 提交 按钮。

在ThingsBoard上创建设备

  • 进入 设备 页面。

  • 点击 添加设备 按钮。

  • 填写设备名称并点击 下一步:凭证 按钮。

  • 输入设备 访问令牌(先前复制的 Device EUI 值)并点击 添加 按钮。

  • 点击设备打开设备信息窗口。

  • 进入 最新遥测数据 选项卡查看设备上报的数据。

在The Things Stack社区版上添加网关

我们需要在 The Things Stack社区版 上添加网关。

要添加网关,请按以下步骤操作:

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

  • 进入 主页,点击“注册网关”按钮。

  • 填写网关信息(网关EUI)并点击“注册网关”按钮。

  • 网关已添加。您可看到其状态为已断开连接。

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

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

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

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

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

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

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

配置Payload Formatter

进入 Payload formatters 页面,为 Formatter type 选择 Custom Javascript formatter。粘贴解码函数并点击 Save changes 按钮。

解码函数:

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
// Decode uplink function.
//
// Input is an object with the following fields:
// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
// - fPort = Uplink fPort.
// - variables = Object containing the configured device variables.
//
// Output must be an object with the following fields:
// - data = Object representing the decoded payload.
// Helmet Sensor decoder
function decodeUplink(input) {
// bytes
    var bytes = input.bytes;
// type
    var uplinkType = (bytes[0] >> 4) & 0x0f;
    switch (uplinkType) {
        case 0x01:
            return {data: decodeRegistration(bytes)};
        case 0x02:
            return {data: decodeHeartbeat(bytes)};
        case 0x03:
            return {data: decodeGNSSPosition(bytes)};
        case 0x05:
            return {data: decodeUUIDReport(bytes)};
        case 0x07:
            return {data: decodeBeacon(bytes)};
        case 0x08:
            return {data: decodeAlarm(bytes)};
        case 0x0c:
            return {data: decodeBracelet(bytes)};
        default:
            return null;
    }
}

// type: 0x1 Registration
function decodeRegistration(bytes) {
    var data = {};
    data.type = "Registration";
// adr
    data.adr = ((bytes[0] >> 3) & 0x1) == 0 ? "OFF" : "ON";
// power
    data.power = ((bytes[2] >> 3) & 0x1f) + "dBm";
// dr
    data.dr = (bytes[3] >> 4) & 0x0f;
// gnssEnable
    data.gnssEnable = ((bytes[3] >> 3) & 0x01) == 0 ? "Disable" : "Enable";
// positionReportMode
    var positionReportMode = (bytes[3] >> 1) & 0x03;
    if (positionReportMode == 0) {
        data.positionReportMode = "Period";
    } else if (positionReportMode == 0) {
        data.positionReportMode = "Autonomous";
    } else if (positionReportMode == 0) {
        data.positionReportMode = "On-Demand";
    }
// bleEnable
    data.bleEnable = (bytes[3] & 0x01) == 0 ? "Disable" : "Enable";
// blePositionReportInterval
    data.blePositionReportInterval =
        (((bytes[4] << 8) & 0xff00) | (bytes[5] & 0xff)) * 5 + "s";
// gnssPositionReportInterval
    data.gnssPositionReportInterval =
        (((bytes[6] << 8) & 0xff00) | (bytes[7] & 0xff)) * 5 + "s";
// heartbeatPeriod
    data.heartbeatPeriod = (bytes[8] & 0xff) * 30 + "s";
// version
    data.version =
        (bytes[9] & 0xff).toString(16).toUpperCase() +
        "." +
        (bytes[10] & 0xff).toString(16).toUpperCase();
// cfmmsg
    data.cfmmsg = "1 Confirmed every " + (bytes[11] & 0xff) + " Heartbeat";
// hbCount
    data.hbCount = "Disconnect Judgement " + (bytes[12] & 0xff);
// fallDetectFeatureThreshold
    data.fallDetectFeatureThreshold = (bytes[13] & 0xff) * 0.5 + " meters";
    return data;
}// type: 0x2 Heartbeat
function decodeHeartbeat(bytes) {
    var data = {};
// type
    data.type = "Heartbeat";
// battery
    data.battery = bytes[1] + "%";
// rssi
    data.rssi = bytes[2] * -1 + "dBm";
// snr
    data.snr = (((bytes[3] << 8) & 0xff00) | (bytes[4] & 0xff)) / 100 + "dB";
// bleReceivingNumber
    data.bleReceivingNumber = bytes[5];
// gnssSearchingNumber
    data.gnssSearchingNumber = bytes[6];
// chargeTime
    data.chargeTime = bytes[7] * 30 + "s";
// wearTime
    data.wearTime = bytes[8] * 30 + "s";
// moveState
    data.moveState = (bytes[9] >> 4) & 0x0f;
// temperature
    data.temperature = 0;
    return data;
}

// type: 0x3 GNSSPosition
function decodeGNSSPosition(bytes) {
    var data = {};
// type
    data.type = "GNSSPosition";
// gnssState
    data.gnssState = ((bytes[0] >> 3) & 0x01) == 0 ? "Success" : "Fail";
// wearState
    data.wearState = (bytes[0] & 0x01) == 0 ? "Do not wear" : "Wear";
// pressure
    let pressure =
        (bytes[1] << 24) | (bytes[2] << 16) | (bytes[3] << 8) | bytes[4];
    data.pressure = pressure / 10 + "pa";
// longitude
    let longitude =
        (bytes[5] << 24) | (bytes[6] << 16) | (bytes[7] << 8) | bytes[8];
    data.longitude = hex2float(longitude);
// latitude
    let latitude =
        (bytes[9] << 24) | (bytes[10] << 16) | (bytes[11] << 8) | bytes[12];
    data.latitude = hex2float(latitude);
// time
    let time =
        (bytes[13] << 24) | (bytes[14] << 16) | (bytes[15] << 8) | bytes[16];
    data.time = timestampToTime((time + 8 * 60 * 60) * 1000);
    return data;
}// type: 0x5 UUIDReport
function decodeUUIDReport(bytes) {
    var data = {};
// type
    data.type = "UUIDReport";
// number
    data.number = Math.floor((bytes.length - 1) / 17);
    var beaconUUIDList = [];
    for (let i = 0; i < data.number; i++) {
        var beaconTypeId = bytes[1 + 17 * i] & 0x03;
        if (beaconTypeId == 0x00) {
            beaconTypeId = "PositionBeaconUUID";
        } else if (beaconTypeId == 0x01) {
            beaconTypeId = "AssetBeaconUUID";
        } else if (beaconTypeId == 0x02) {
            beaconTypeId = "SpecialBeaconUUID";
        } else if (beaconTypeId == 0x03) {
            beaconTypeId = "SearchBeaconUUID";
        }
        var beaconUUID = "";
        for (let j = 0; j < 16; j++) {
            beaconUUID += (bytes[2 + 17 * i + j] & 0xff)
                .toString(16)
                .toUpperCase()
                .padStart(2, "0");
        }
        beaconUUIDList.push({beaconTypeId, beaconUUID});
    }
    data.beaconUUIDList = beaconUUIDList;
    return data;
}

// type: 0x7 Beacon
function decodeBeacon(bytes) {
    var data = {};
    data.type = "Beacon";
// wearState
    data.wearState = (bytes[0] & 0x01) == 0 ? "Not wearing" : "Wearing";
// pressure
    let pressure =
        (bytes[1] << 24) | (bytes[2] << 16) | (bytes[3] << 8) | bytes[4];
    data.pressure = pressure / 10 + "pa";
// numner
    data.number = bytes[5] & 0x0f;
    for (let i = 0; i < data.number; i++) {
        var index = 7 + 5 * i;
        var major = ((bytes[index] << 8) | bytes[index + 1])
            .toString(16)
            .toUpperCase()
            .padStart(4, "0");
        var minor = ((bytes[index + 2] << 8) | bytes[index + 3])
            .toString(16)
            .toUpperCase()
            .padStart(4, "0");
        var rssi = bytes[index + 4] - 256 + "dBm";
        data["beacon" + (i + 1)] = major + minor;
        data["rssi" + (i + 1)] = rssi;
    }
    return data;
}

// type: 0x8 Alarm
function decodeAlarm(bytes) {
    var data = {};
    data.type = "Alarm";
    var alarmValue = bytes[1] & 0xff;
    if (alarmValue == 1) {
        data.alarm = "SOS";
    } else if (alarmValue == 2) {
        data.alarm = "Fall";
    } else if (alarmValue == 3) {
        data.alarm = "Special area";
    } else if (alarmValue == 4) {
        data.alarm = "Search";
    }
    return data;
}

// type: 0x0c Bracelet
function decodeBracelet(bytes) {
    var data = {};
    data.type = "Bracelet";
    var number =
        bytes[1];
    data.number = number;
    var braceletList = [];
    for (let i = 0; i < number; i++) {
        var bracelet = {};
        var mac = ""
        for (let j = 0; j < 6; j++) {
            mac += bytes[j + 2 + i * 21]
                .toString(16)
                .toUpperCase()
                .padStart(2, "0");
        }
        bracelet.mac = mac;
        bracelet.batteryLevel = bytes[8 + i * 21]; // 电量等级
        bracelet.heartrate = bytes[9 + i * 21]; // 心率
        bracelet.systolicPressure = bytes[10 + i * 21]; // 收缩压
        bracelet.diastolicPressure = bytes[11 + i * 21]; // 舒张压
        bracelet.wristTemperature = (((bytes[12 + i * 21] << 8) & 0xff00) |
            (bytes[13 + i * 21] & 0xff)) / 10 + ""; // 腕温
        bracelet.bodyTemperature = (((bytes[14 + i * 21] << 8) & 0xff00) |
            (bytes[15 + i * 21] & 0xff)) / 10 + ""; // 体温
        bracelet.stepNumber = ((bytes[16 + i * 21] << 16) & 0xff0000) | ((bytes[17
        + i * 21] << 8) & 0xff00) | (bytes[18 + i * 21] & 0xff); // 步数
        bracelet.wearStatus = ((bytes[19 + i * 21]) & 0x01) == 0 ? "Not wearing" :
            "Wearing";
        bracelet.bluetoothBroadcastInterval = (bytes[20 + i * 21] & 0xff) + "s";
        bracelet.samplingInterval = (bytes[21 + i * 21] & 0x7f) + "s";
        bracelet.rssi = (bytes[22 + i * 21] - 256) + "dBm";
        braceletList.push(bracelet);
    }
    data.braceletList = braceletList;
    return data;
}

function hex2float(num) {
    var sign = num & 0x80000000 ? -1 : 1;
    var exponent = ((num >> 23) & 0xff) - 127;
    var mantissa = 1 + (num & 0x7fffff) / 0x7fffff;
    return sign * mantissa * Math.pow(2, exponent);
}

function timestampToTime(timestamp) {
    const date = new Date(timestamp);
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, "0");
    const day = date.getDate().toString().padStart(2, "0");
    const hour = date.getHours().toString().padStart(2, "0");
    const minute = date.getMinutes().toString().padStart(2, "0");
    const second = date.getSeconds().toString().padStart(2, "0");
    return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}

在ThingsBoard中创建集成

接下来,我们将在ThingsBoard中创建“The Things Stack”(TTS)集成。

首先,复制以下代码,创建上行数据转换器时需要用到:

1
2
3
4
5
6
7
8
9
10
11
var data = decodeToJson(payload);
var deviceName = data.end_device_ids.device_id;
var deviceType = data.end_device_ids.application_ids.application_id;

var result = {
  deviceName: deviceName,
  deviceType: deviceType,
  telemetry: data.uplink_message.decoded_payload
};

return result;

在“连接”步骤中,您需要以下参数:

  • Region(区域)eu1(在The Things Stack社区中注册应用所在区域)
  • Username(用户名)thingsboard-application-2025-06@ttn(使用TTS集成中的 用户名
  • Password(密码):使用The Things Stack社区集成中的 密码

现在,进入“集成中心”部分下的“集成”页面,按以下步骤操作:

  • 点击右上角的“加号”图标添加新集成。选择类型“The Things Stack社区”。然后点击“下一步”按钮。

  • 将先前复制的脚本粘贴到解码器函数区域。点击“下一步”按钮。

  • 将“下行数据转换器”字段留空。点击“跳过”按钮。

  • 填写您的参数,然后点击“添加”按钮。

在The Things Industries上添加网关

我们需要在 The Things Industries云 上添加网关。

要添加网关,请按以下步骤操作:

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

  • 进入 主页,点击“注册网关”按钮。

  • 填写网关信息(网关EUI)并点击“注册网关”按钮。

  • 网关已添加。您可看到其状态为已断开连接。

在The Things Industries上添加设备

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

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

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

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

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

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

配置Payload Formatter

进入 Payload formatters 页面,将 Formatter type 选择为 Custom JavaScript formatter。将解码函数粘贴到编辑器中,点击 Save changes 按钮。

解码函数:

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
// Decode uplink function.
//
// Input is an object with the following fields:
// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
// - fPort = Uplink fPort.
// - variables = Object containing the configured device variables.
//
// Output must be an object with the following fields:
// - data = Object representing the decoded payload.
// Helmet Sensor decoder
function decodeUplink(input) {
// bytes
    var bytes = input.bytes;
// type
    var uplinkType = (bytes[0] >> 4) & 0x0f;
    switch (uplinkType) {
        case 0x01:
            return {data: decodeRegistration(bytes)};
        case 0x02:
            return {data: decodeHeartbeat(bytes)};
        case 0x03:
            return {data: decodeGNSSPosition(bytes)};
        case 0x05:
            return {data: decodeUUIDReport(bytes)};
        case 0x07:
            return {data: decodeBeacon(bytes)};
        case 0x08:
            return {data: decodeAlarm(bytes)};
        case 0x0c:
            return {data: decodeBracelet(bytes)};
        default:
            return null;
    }
}

// type: 0x1 Registration
function decodeRegistration(bytes) {
    var data = {};
    data.type = "Registration";
// adr
    data.adr = ((bytes[0] >> 3) & 0x1) == 0 ? "OFF" : "ON";
// power
    data.power = ((bytes[2] >> 3) & 0x1f) + "dBm";
// dr
    data.dr = (bytes[3] >> 4) & 0x0f;
// gnssEnable
    data.gnssEnable = ((bytes[3] >> 3) & 0x01) == 0 ? "Disable" : "Enable";
// positionReportMode
    var positionReportMode = (bytes[3] >> 1) & 0x03;
    if (positionReportMode == 0) {
        data.positionReportMode = "Period";
    } else if (positionReportMode == 0) {
        data.positionReportMode = "Autonomous";
    } else if (positionReportMode == 0) {
        data.positionReportMode = "On-Demand";
    }
// bleEnable
    data.bleEnable = (bytes[3] & 0x01) == 0 ? "Disable" : "Enable";
// blePositionReportInterval
    data.blePositionReportInterval =
        (((bytes[4] << 8) & 0xff00) | (bytes[5] & 0xff)) * 5 + "s";
// gnssPositionReportInterval
    data.gnssPositionReportInterval =
        (((bytes[6] << 8) & 0xff00) | (bytes[7] & 0xff)) * 5 + "s";
// heartbeatPeriod
    data.heartbeatPeriod = (bytes[8] & 0xff) * 30 + "s";
// version
    data.version =
        (bytes[9] & 0xff).toString(16).toUpperCase() +
        "." +
        (bytes[10] & 0xff).toString(16).toUpperCase();
// cfmmsg
    data.cfmmsg = "1 Confirmed every " + (bytes[11] & 0xff) + " Heartbeat";
// hbCount
    data.hbCount = "Disconnect Judgement " + (bytes[12] & 0xff);
// fallDetectFeatureThreshold
    data.fallDetectFeatureThreshold = (bytes[13] & 0xff) * 0.5 + " meters";
    return data;
}

// type: 0x2 Heartbeatfunction decodeHeartbeat(bytes) {
var data = {};
// type
data.type = "Heartbeat";
// battery
data.battery = bytes[1] + "%";
// rssi
data.rssi = bytes[2] * -1 + "dBm";
// snr
data.snr = (((bytes[3] << 8) & 0xff00) | (bytes[4] & 0xff)) / 100 + "dB";
// bleReceivingNumber
data.bleReceivingNumber = bytes[5];
// gnssSearchingNumber
data.gnssSearchingNumber = bytes[6];
// chargeTime
data.chargeTime = bytes[7] * 30 + "s";
// wearTime
data.wearTime = bytes[8] * 30 + "s";
// moveState
data.moveState = (bytes[9] >> 4) & 0x0f;
// temperature
data.temperature = 0;
return data;
}

// type: 0x3 GNSSPosition
function decodeGNSSPosition(bytes) {
    var data = {};
// type
    data.type = "GNSSPosition";
// gnssState
    data.gnssState = ((bytes[0] >> 3) & 0x01) == 0 ? "Success" : "Fail";
// wearState
    data.wearState = (bytes[0] & 0x01) == 0 ? "Do not wear" : "Wear";
// pressure
    let pressure =
        (bytes[1] << 24) | (bytes[2] << 16) | (bytes[3] << 8) | bytes[4];
    data.pressure = pressure / 10 + "pa";
// longitude
    let longitude =
        (bytes[5] << 24) | (bytes[6] << 16) | (bytes[7] << 8) | bytes[8];
    data.longitude = hex2float(longitude);
// latitude
    let latitude =
        (bytes[9] << 24) | (bytes[10] << 16) | (bytes[11] << 8) | bytes[12];
    data.latitude = hex2float(latitude);
// time
    let time =
        (bytes[13] << 24) | (bytes[14] << 16) | (bytes[15] << 8) | bytes[16];
    data.time = timestampToTime((time + 8 * 60 * 60) * 1000);
    return data;
}

// type: 0x5 UUIDReportfunction decodeUUIDReport(bytes) {
var data = {};
// type
data.type = "UUIDReport";
// number
data.number = Math.floor((bytes.length - 1) / 17);
var beaconUUIDList = [];
for (let i = 0; i < data.number; i++) {
    var beaconTypeId = bytes[1 + 17 * i] & 0x03;
    if (beaconTypeId == 0x00) {
        beaconTypeId = "PositionBeaconUUID";
    } else if (beaconTypeId == 0x01) {
        beaconTypeId = "AssetBeaconUUID";
    } else if (beaconTypeId == 0x02) {
        beaconTypeId = "SpecialBeaconUUID";
    } else if (beaconTypeId == 0x03) {
        beaconTypeId = "SearchBeaconUUID";
    }
    var beaconUUID = "";
    for (let j = 0; j < 16; j++) {
        beaconUUID += (bytes[2 + 17 * i + j] & 0xff)
            .toString(16)
            .toUpperCase()
            .padStart(2, "0");
    }
    beaconUUIDList.push({beaconTypeId, beaconUUID});
}
data.beaconUUIDList = beaconUUIDList;
return data;
}

// type: 0x7 Beacon
function decodeBeacon(bytes) {
    var data = {};
    data.type = "Beacon";
// wearState
    data.wearState = (bytes[0] & 0x01) == 0 ? "Not wearing" : "Wearing";
// pressure
    let pressure =
        (bytes[1] << 24) | (bytes[2] << 16) | (bytes[3] << 8) | bytes[4];
    data.pressure = pressure / 10 + "pa";
// numner
    data.number = bytes[5] & 0x0f;
    for (let i = 0; i < data.number; i++) {
        var index = 7 + 5 * i;
        var major = ((bytes[index] << 8) | bytes[index + 1])
            .toString(16)
            .toUpperCase()
            .padStart(4, "0");
        var minor = ((bytes[index + 2] << 8) | bytes[index + 3])
            .toString(16)
            .toUpperCase()
            .padStart(4, "0");
        var rssi = bytes[index + 4] - 256 + "dBm";
        data["beacon" + (i + 1)] = major + minor;
        data["rssi" + (i + 1)] = rssi;
    }
    return data;
}

// type: 0x8 Alarm
function decodeAlarm(bytes) {
    var data = {};
    data.type = "Alarm";
    var alarmValue = bytes[1] & 0xff;
    if (alarmValue == 1) {
        data.alarm = "SOS";
    } else if (alarmValue == 2) {
        data.alarm = "Fall";
    } else if (alarmValue == 3) {
        data.alarm = "Special area";
    } else if (alarmValue == 4) {
        data.alarm = "Search";
    }
    return data;
}

// type: 0x0c Bracelet
function decodeBracelet(bytes) {
    var data = {};
    data.type = "Bracelet";
    var number =
        bytes[1];
    data.number = number;
    var braceletList = [];
    for (let i = 0; i < number; i++) {
        var bracelet = {};
        var mac = ""
        for (let j = 0; j < 6; j++) {
            mac += bytes[j + 2 + i * 21]
                .toString(16)
                .toUpperCase()
                .padStart(2, "0");
        }
        bracelet.mac = mac;
        bracelet.batteryLevel = bytes[8 + i * 21]; // 电量等级
        bracelet.heartrate = bytes[9 + i * 21]; // 心率
        bracelet.systolicPressure = bytes[10 + i * 21]; // 收缩压
        bracelet.diastolicPressure = bytes[11 + i * 21]; // 舒张压
        bracelet.wristTemperature = (((bytes[12 + i * 21] << 8) & 0xff00) |
            (bytes[13 + i * 21] & 0xff)) / 10 + ""; // 腕温
        bracelet.bodyTemperature = (((bytes[14 + i * 21] << 8) & 0xff00) |
            (bytes[15 + i * 21] & 0xff)) / 10 + ""; // 体温
        bracelet.stepNumber = ((bytes[16 + i * 21] << 16) & 0xff0000) | ((bytes[17
        + i * 21] << 8) & 0xff00) | (bytes[18 + i * 21] & 0xff); // 步数
        bracelet.wearStatus = ((bytes[19 + i * 21]) & 0x01) == 0 ? "Not wearing" :
            "Wearing";
        bracelet.bluetoothBroadcastInterval = (bytes[20 + i * 21] & 0xff) + "s";
        bracelet.samplingInterval = (bytes[21 + i * 21] & 0x7f) + "s";
        bracelet.rssi = (bytes[22 + i * 21] - 256) + "dBm";
        braceletList.push(bracelet);
    }
    data.braceletList = braceletList;
    return data;
}

function hex2float(num) {
    var sign = num & 0x80000000 ? -1 : 1;
    var exponent = ((num >> 23) & 0xff) - 127;
    var mantissa = 1 + (num & 0x7fffff) / 0x7fffff;
    return sign * mantissa * Math.pow(2, exponent);
}

function timestampToTime(timestamp) {
    const date = new Date(timestamp);
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, "0");
    const day = date.getDate().toString().padStart(2, "0");
    const hour = date.getHours().toString().padStart(2, "0");
    const minute = date.getMinutes().toString().padStart(2, "0");
    const second = date.getSeconds().toString().padStart(2, "0");
    return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}

在ThingsBoard中创建集成

接下来,我们将在 ThingsBoard 中创建“The Things Industries”集成。

首先,复制以下代码,创建上行数据转换器时需要用到:

1
2
3
4
5
6
7
8
9
10
11
var data = decodeToJson(payload);
var deviceName = data.end_device_ids.device_id;
var deviceType = data.end_device_ids.application_ids.application_id;

var result = {
  deviceName: deviceName,
  deviceType: deviceType,
  telemetry: data.uplink_message.decoded_payload
};

return result;

在“连接”步骤中,您需要以下参数:

  • Region(区域)eu1(在The Things Industries控制台中注册应用所在区域);
  • Username(用户名)thingsboard-application-2025-05@lansitec-testplan(使用The Things Stack Industries集成中的 用户名);
  • Password(密码):使用The Things Industries集成中的 密码

现在,进入“集成中心”部分下的“集成”页面,按以下步骤操作:

  • 点击右上角的“加号”图标添加新集成。选择类型“The Things Industries集成”。然后点击“下一步”按钮。

  • 将先前复制的脚本粘贴到解码器函数区域。点击“下一步”按钮。

  • 将“下行数据转换器”字段留空。点击“跳过”按钮。

  • 填写您的参数,然后点击“添加”按钮。

在Loriot上添加网关

我们需要在 Loriot 上添加网关。

要添加网关,请按以下步骤操作:

  • 登录Loriot服务器。在“网络”部分打开“示例网络”或创建新网络。

  • 点击“添加网关”按钮。

  • 向下滚动并选择“MultiTech Conduit AEP”。

  • 向上滚动,将网关的 MAC地址(从 网关EUI 中去掉中间的 FFFFFFFE 后得到)填入 eth0 MAC address 字段,将网关EUI填入 Custom EUI 字段。

  • 网关已添加。您可看到其状态为已断开连接。

在Loriot上添加设备

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

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

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

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

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

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

在ThingsBoard中创建集成

接下来,我们将在ThingsBoard中创建与Loriot的集成。

首先,复制以下代码,创建上行数据转换器时需要用到:

1
2
3
4
5
6
7
8
9
10
11
var data = decodeToJson(payload);
var deviceName = data.end_device_ids.device_id;
var deviceType = data.end_device_ids.application_ids.application_id;

var result = {
  deviceName: deviceName,
  deviceType: deviceType,
  telemetry: data.uplink_message.decoded_payload
};

return result;

现在,进入“集成中心”部分下的“集成”页面,按以下步骤操作:

  • 点击右上角的“加号”图标按钮添加新集成。选择类型“Loriot”。然后点击“下一步”按钮。

  • 将先前复制的脚本粘贴到解码器函数区域。点击“下一步”按钮。

  • 将“下行数据转换器”字段留空。点击“跳过”按钮。

  • 填写您的参数,然后点击“添加”按钮。

在ThingsBoard上检查数据

设备已添加后,若其发送了数据,应会显示在设备中。 要检查数据,您可以在 实体 部分打开 设备 页面。设备应出现在设备列表中。您可以通过点击设备,然后打开 属性最新遥测数据 选项卡来查看数据。

为了获得更友好的视图,您可以使用仪表板。 您可以为此设备下载简单仪表板,其已配置为显示名称为“Devices”的设备的“latitude”和“longitude”遥测键数据。

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

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

  • 进入“仪表板”页面。默认会进入仪表板组“全部”。点击右上角的“加号”图标。选择“导入仪表板”。

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

  • 仪表板已导入。

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

请按以下步骤操作:

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

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

  • 应用所有更改。

此时您应能看到来自设备的数据。

带数据的仪表板示例:

结论

借助本指南所述内容,您可以轻松连接资产管理追踪器并向ThingsBoard发送数据。

浏览平台文档以了解更多核心概念和功能。例如,配置告警规则仪表板

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