- 物联网
- 后端
- 数据可视化
- 消息队列
【免费下载链接】thingsboard
All-in-one IoT Platform - Device management, data collection, processing and visualization.
导读
本文围绕 ThingsBoard 数据转换器(Uplink Data Converter)中 TBEL 解码函数的典型场景——解析二进制设备上行报文展开,以仓库内置帮助文档simple-binary示例为骨架,完整讲解报文逐字节拆解、parseBytesToInt的用法与字节序语义、解码函数返回值结构,以及平台对解码输出的全部约束。读完本文,你将能独立编写一个把 8 字节二进制帧解析为设备名、电量、温度、血氧饱和度的可运行 TBEL 解码函数,并理解其在 HTTP、MQTT、LoRaWAN 等集成中的实际应用方式。
一、示例场景:一条 8 字节的二进制上行帧
在 ThingsBoard 的集成(Integration)体系中,Uplink Data Converter 负责把来自设备或第三方网络(如 SigFox、LORIOT、ChirpStack、The Things Stack)的上行消息解析并转换为平台通用格式。当设备以二进制协议上报数据时,载荷就是一串原始字节,需要用 TBEL 内置的字节解析函数把它们切分、解释成有业务含义的字段。
本文的主角simple-binary示例描述了这样一个设备:体温计/血氧仪,每次上报 8 个字节,依次包含:
| 字节偏移 | 长度 | 含义 | 本示例取值 |
|---|---|---|---|
| 0 | 4 字节 | 设备序列号(big-endian 整数) | 00BC614E→ 十进制12345678 |
| 4 | 1 字节 | 电池电量 | 5F→ 十进制95 |
| 5 | 2 字节 | 温度值(×100 后的整数) | 0E4C→ 十进制3660→ 实际温度36.6℃ |
| 7 | 1 字节 | 血氧饱和度 | 63→ 十进制99 |
对应的 HEX 报文为:
00BC614E5F0E4C63对应的 Base64 表示为:
ALxhTl8OTGM=Base64 形式在转换器或集成开启 debug 时会出现在调试事件(Debug Events)中,方便排查报文内容。
上述报文拆解、HEX/Base64 两种表达以及字段对照表,均出自仓库帮助文档 simple-binary 示例的 payload 说明。
二、解码函数源码逐行剖析
完整示例代码位于 simple-binary 示例的 decoder_fn.md,核心逻辑如下:
// Use first 4 bytes as device name var deviceName = "SN-" + parseBytesToInt(payload, 0, 4); var result = { deviceName: deviceName, deviceType: "Thermometer", telemetry: { // Use 5th byte as a battery level battery: parseBytesToInt(payload, 4, 1), // Use bytes 6 and 7 as a temperature temperature: parseBytesToInt(payload, 5, 2) / 100.0, // Use 8th byte as a saturation level saturation: parseBytesToInt(payload, 7, 1) } }; return result;逐行解读:
parseBytesToInt(payload, 0, 4)—— 从字节数组payload的第 0 个字节起,连续读取 4 个字节,按大端序(big-endian)解释为整数,得到设备序列号12345678。前缀"SN-"使设备名变为SN-12345678,既直观又可读。parseBytesToInt(payload, 4, 1)—— 读取第 5 个字节(偏移 4),得到电量95。parseBytesToInt(payload, 5, 2) / 100.0—— 读取第 6、7 两个字节(偏移 5,长度 2),得到3660,再除以 100 还原出真实温度36.6。这是典型的“定点数”编码:设备端把浮点温度放大 100 倍后以整数传输,解码端再缩放回来,避免浮点字节序问题。parseBytesToInt(payload, 7, 1)—— 读取第 8 个字节(偏移 7),得到饱和度99。- 函数返回包含
deviceName、deviceType与telemetry的 JSON 对象,作为转换器输出。
提示:文档代码中出现的
{:code-style="max-height: 500px;"}与{:copy-code}是 UI 帮助弹窗的渲染标记,并非 TBEL 语法,实际编写转换器时无需保留。
三、预期输出:平台统一 JSON 格式
解码函数运行后得到的结果与文档 output.md 完全一致:
{ "deviceName": "SN-12345678", "deviceType": "Thermometer", "telemetry": { "battery": 95, "temperature": 36.6, "saturation": 99 } }平台收到该结果后,会按deviceName(租户范围内唯一)查找设备SN-12345678;若不存在且集成开启了“允许创建设备/资产”选项,则自动创建设备,类型为Thermometer。telemetry中的三个键值对将作为时序数据(time-series data)写入,默认使用服务器时间为时间戳(详见下文第五节)。
四、解码函数签名与输出格式的完整约束
在动手写自己的解码函数前,需要了解平台对函数签名与返回值的硬性要求。这些约束在通用帮助文档 TBEL 解码函数说明(decoder_fn.md) 中有系统化描述。
函数签名
function Decoder(payload, metadata): object | object[]payload(any):包含集成上报原始消息的字节数组。集成产生的 payload 内容类型可能是 JSON、TEXT 或 BINARY(Base64),但内容类型只是调试事件存储的提示,不影响解码函数的行为——解码函数收到的始终是字节数组,可用decodeToString、decodeToJson把字节数组转为字符串或 JSON 对象后再处理。metadata({[key: string]: string}):集成消息携带的键值元数据,可在每个集成的详情中配置额外的 metadata 字段,供解码函数读取使用。
返回值的必须与可选字段
返回值必须是合法 JSON,且满足:
- 必须包含
deviceName+deviceType,或assetName+assetType成对属性,用于标识设备/资产(名称在租户范围内唯一)。平台用它们查找已有实体;找不到且集成允许创建时,自动新建。实践中常用 DevEUI、MAC 地址等唯一标识作为设备名。 - 可选
attributes对象:为设备/资产设置的服务端属性集合。 - 可选
telemetry对象/数组:设备/资产的时序数据。 - 可选
customerName:自动把设备归属到指定客户(客户不存在时自动创建);仅在该设备/资产由当前集成创建时生效,已存在的实体忽略此参数。 - 可选
groupName:自动把设备加入实体组,组默认创建在租户范围,若同时提供了customerName则创建在客户范围;同样仅在实体由当前集成首次创建时生效。 - 可选
deviceLabel/assetLabel:非唯一的用户友好标签,可在仪表盘上替代设备名展示。 - 若需自定义时间戳,可在 telemetry 数据中加入平台约定的时间戳字段,格式为Unix epoch 毫秒;否则使用服务器时间。
另外,解码函数可以返回对象数组(每个元素描述一台设备/资产),且每台设备可携带多条不同时间戳的时序数据点,适用于一个上行消息包含多设备数据的中继/网关场景。
五、decoder_v2 变体:attributes/telemetry 结构与显式时间戳
除上述 v1 扁平结构外,ThingsBoard 还提供了 decoder_v2 风格的同主题示例,同样解析“序列号 + 电量 + 温度 + 饱和度”的二进制帧,但返回值采用更结构化的格式,并支持显式时间戳。见 decoder_v2/simple-binary 的 decoder_fn.md:
function decodePayload(input) { var result = { attributes: {}, telemetry: {}}; result.attributes.sn = parseBytesToInt(input, 0, 4); var timestamp = metadata.ts; var values = {}; values.battery = parseBytesToInt(input, 4, 1); values.temperature = parseBytesToInt(input, 5, 2) / 100.0; values.saturation = parseBytesToInt(input, 7, 1); result.telemetry = { ts: timestamp, values: values }; return result; } var result = decodePayload(payload); return result;该变体对应的报文为01ed03335f0e4c63(Base64:Ae0DM18OTGM=),其中01ED0333对应序列号32310067,其余字段含义与 v1 相同。解码输出见 decoder_output.md:
{ "attributes": { "sn": 32310067 }, "telemetry": { "ts": 1684478801936, "values": { "battery": 95, "temperature": 36.6, "saturation": 99 } } }可见 decoder_v2 的约束与 v1 不同,主要体现在:
attributes为必填,且至少包含一个键值对;telemetry为必填(对象或数组),至少包含一条数据;telemetry.ts取自metadata.ts(集成注入的消息时间戳),即显式时间戳,为 Unix epoch 毫秒;- 实体名、类型、设备档案(profile)、客户、组、标签等可通过转换器的预配置设定,也可在解码函数中覆写。
最终 Converter 输出会把预配置信息与解码结果合并,见 converter_output.md,其形态包含entityType: "DEVICE"、name、profile、合并后的telemetry与attributes,其中 LoRaWAN 网关元数据(rssi、snr、fCnt、dr、frequency、eui等)也被一并合入——这正是前面所说的“LoRaWAN 网络服务器常把二进制设备载荷连同 RSSI/SNR 等元数据一起包装成 JSON”的真实场景。
六、parseBytesToInt 等 TBEL 内置函数的源码级定义
parseBytesToInt并非 JavaScript 原生函数,而是 ThingsBoard 向 TBEL 运行时注入的内置工具函数。其精确定义在 UI 前端源码 tbel-utils.models.ts 中:
parseBytesToInt(data, offset, length, bigEndian)参数说明(来自源码定义):
data(list | array):待解析的字节列表/数组,即传入的payload。offset(number,可选):起始字节索引,默认 0。length(number,可选):要解析的字节数,最大 4(即最大 32 位整数)。bigEndian(boolean,可选):是否按大端序解释,默认 true。
该函数返回解析后的整数(number)。与之配套的字节解析函数还有:
parseBytesToLong(data, offset, length, bigEndian):解析长整数,length最大 8(64 位),适用于需要 8 字节整数的设备字段(如计数器、时间戳),定义见 tbel-utils.models.ts。parseBytesToFloat(data, offset, length, bigEndian):按 IEEE 754 格式解析为浮点数,定义见 tbel-utils.models.ts。parseHexToLong(hex, bigEndian)/parseBigEndianHexToLong(hex)/parseLittleEndianHexToLong(hex):从十六进制字符串直接解析长整数,可指定字节序,适用于 payload 中内嵌 HEX 字符串字段的协议。decodeToString(data):把字节列表转换为字符串,见 tbel-utils.models.ts。decodeToJson(data):把 JSON 字符串或字节列表解析为 JSON 对象,见 tbel-utils.models.ts。stringToBytes、bytesToBase64等用于编码方向的工具函数(如 encoder 场景)。
从源码结构看,这些内置函数统一定义于TBEL_UTILS等模型常量中,在加载 TBEL 脚本时注入执行环境,因此它们与 ECMAScript 标准函数一样可直接在 Decoder/Encoder 中调用。
七、实战要点与调试建议
- 先定协议后写代码:二进制解码的第一步永远是逐字节定义字段表(偏移、长度、缩放系数、字节序),就像示例文档在 payload 说明里对
00BC614E、5F、0E4C、63逐段标注的做法。 - 字节序保持一致:
parseBytesToInt默认大端序。若设备端按小端序发送(如常见的01ED0333类帧),需显式传入false:parseBytesToInt(payload, 0, 4, false)。 - 定点数缩放:遇到“温度 ×100”“湿度 ×10”这类协议,解码时记得除回缩放因子,避免把整数值误当真实测量值。
- 善用元数据:LoRaWAN 网络服务器的 JSON 包装中通常带有
RSSI、SNR、frequency等元数据,可像 decoder_v2 示例那样通过metadata读取并合并进最终输出。 - 开启 Debug 验证:在转换器或集成上启用 debug 后,调试事件中会记录 Base64 形式的原始载荷与解码输出,可对照 payload/expected output 逐步验证字节解析是否正确。
- 设备唯一性:设备名在租户范围内唯一,示例用序列号加
SN-前缀生成稳定名称,是避免重复创建设备的良好实践;需要仪表盘友好展示时再使用deviceLabel。
八、本文引用的仓库文档与源码清单
- 示例主文档:simple-binary/decoder_fn.md
- 报文拆解说明:simple-binary/payload.md
- 预期输出:simple-binary/output.md
- 解码函数通用约束:converter/tbel/decoder_fn.md
- decoder_v2 规范:converter/tbel/decoder_fn_v2.md
- decoder_v2 二进制示例:decoder_v2/simple-binary/decoder_fn.md
- TBEL 内置函数定义:tbel-utils.models.ts
仓库中同目录还提供了 Simple JSON、Simple CSV、JSON with multiple hex encoded values、Use metadata fields 等更多解码示例(见 examples/decoder 目录),可作为编写不同内容类型转换器时的参考模板。
- 物联网
- 后端
- 数据可视化
- 消息队列
【免费下载链接】thingsboard
All-in-one IoT Platform - Device management, data collection, processing and visualization.
相关推荐
ThingsBoard 上行数据解码实战:simple-binary 二进制报文解码与输出示例深度解析
ThingsBoard 上行数据解码实战:simple binary 二进制报文解码与输出示例深度解析 本文基于 ThingsBoard 官方帮助文档中 sim
物联网后端数据可视化消息队列Open edX AuthZ 集成指南:`openedx.core.djangoapps.authz` 应用与 `authz_permission_required` 装饰器实战
Open edX AuthZ 集成指南: openedx.core.djangoapps.authz 应用与 authz_permission_required
物联网后端数据可视化消息队列使用 @visx/wordcloud 构建 React 词云图:API 全解析与实战指南
使用 @visx/wordcloud 构建 React 词云图:API 全解析与实战指南 词云(Word Cloud)是一种以文字大小、颜色直观反映文本数据权重
物联网后端数据可视化消息队列
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考