news 2026/10/2 11:51:54

ThingsBoard 数据转换器(Converter)解码器 JSON 输出格式详解:从基础结构到多设备批量上报

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ThingsBoard 数据转换器(Converter)解码器 JSON 输出格式详解:从基础结构到多设备批量上报
  • 物联网
  • 后端
  • 数据可视化
  • 消息队列

【免费下载链接】thingsboard

All-in-one IoT Platform - Device management, data collection, processing and visualization.

项目地址:https://gitcode.com/GitHub_Trending/th/thingsboard
点击查看免费下载

导读

本文围绕 ThingsBoard 集成(Integration)数据转换器中解码器(Decoder)函数的 JSON 输出格式展开,核心示例取自仓库中的 simple_json_output.md。你将掌握解码器返回对象的完整字段契约(deviceName/deviceType/attributes/telemetry)、带时间戳(ts)的时序数据写法、deviceLabel/customerName/groupName等扩展字段的用法,以及如何返回 JSON 数组实现一条消息批量创建设备或资产;同时结合后端解析源码,理解这些字段在被 AbstractUplinkDataConverter 解析时的校验规则与默认值行为,从而写出可稳定落库、可调试排错的解码器脚本。

一、解码器输出格式在数据转换链路中的位置

在 ThingsBoard 的 Integration 模块中,上行数据(Uplink)从外部设备进入平台后,需要经过数据转换器(Converter)中的解码器(Decoder)脚本,把原始 payload(可能是二进制、CSV、HEX 或 JSON)翻译成平台统一的数据模型。解码器脚本的执行入口位于 ScriptUplinkDataConverter:

String decoderField = ScriptLanguage.JS.equals(scriptInvokeService.getLanguage()) ? "decoder" : "tbelDecoder"; String decoder = configuration.getConfiguration().get(decoderField).asText();

即解码器支持 JavaScript 与 TBEL 两种语言,分别读取配置中的decoder与tbelDecoder字段。脚本执行的原始结果是一段字符串,随后在 AbstractUplinkDataConverter#convert 中被JsonParser.parseString(rawResult)解析为JsonElement:

JsonElement element = JsonParser.parseString(rawResult); List<UplinkData> resultList = new ArrayList<>(); if (element.isJsonArray()) { for (JsonElement uplinkJson : element.getAsJsonArray()) { resultList.add(parseUplinkData(uplinkJson.getAsJsonObject(), finalMetadata)); } } else if (element.isJsonObject()) { resultList.add(parseUplinkData(element.getAsJsonObject(), finalMetadata)); }

这段源码揭示了两条关键事实:

  1. 解码器返回的字符串必须是合法的 JSON,且必须是一个 JSON 对象或 JSON 数组;
  2. 对象会被逐条解析,数组中的每个元素视为一条独立的 UplinkData,因此数组可以用于一次转换多条设备/资产数据。

提示:以上调试与解析逻辑还支持 Debug 模式下将转换前后的原始数据与结果持久化(见同文件中的persistUplinkDebug调用),便于排查解码器输出是否正确。

二、基础输出结构:最简单的 JSON 对象

仓库中的 simple_json_output.md 给出了解码器输出对象的最小骨架:

{ "deviceName": "001B638446E7", "deviceType": "thermostat", "attributes": { "serialNumber": "SN-111" }, "telemetry": { "temperature": 42, "humidity": 80 } }

这个对象由四个顶层字段组成:

字段是否必填说明
deviceName必填(设备场景)平台中设备的名称,若不存在会被自动创建
deviceType可选设备类型;缺省时后端使用默认类型DEFAULT_DEVICE_TYPE
attributes可选客户端属性(Client-Side Attributes)键值对,会写入平台属性存储
telemetry可选时序遥测数据键值对,作为当前时刻的遥测写入

上述解析逻辑与 AbstractUplinkDataConverter#parseUplinkData 完全对应。例如设备名与类型的读取:

entityName = src.get("deviceName").getAsString(); builder.deviceName(entityName); if (src.has("deviceType")) { builder.deviceType(src.get("deviceType").getAsString()); } else { builder.deviceType(DEFAULT_DEVICE_TYPE); }

而telemetry与attributes则分别经由parseTelemetry、parseAttributesUpdate转换为平台内部的PostTelemetryMsg与PostAttributeMsg(底层基于 Transport 层 Protobuf 消息)。也就是说,解码器返回 JSON 后,平台会自动完成从 JSON 到内部传输协议的转换,开发者无需关心序列化细节。

2.1 关键校验规则

从 getIsAssetAndVerify 的实现可以看到两条硬性校验,解码器脚本必须遵守:

  • deviceName与assetName至少出现其一,否则抛出JsonParseException("Either 'deviceName' or 'assetName' should be present in the converter output!");
  • 二者不能同时出现,否则抛出JsonParseException("Both 'deviceName' and 'assetName' can't be present in the converter output!");
  • 若选择资产场景(只提供assetName),则必须同时提供assetType,否则抛出JsonParseException("Asset type is not set!")。

因此在编写解码器时,务必根据原始数据只填充设备字段或资产字段之一,避免输出对象同时携带两个名称字段。

三、带时间戳的遥测输出:ts + values 结构

基础示例中的telemetry是一个扁平键值对象,表示"当前时刻"的遥测。若原始数据携带了时间信息,则需要使用 simple_json_output_with_ts.md 展示的ts + values结构:

{ "deviceName": "001B638446E7", "deviceType": "thermostat", "attributes": { "serialNumber": "SN-111" }, "telemetry": { "ts": 1527863043000, "values": { "temperature": 42, "humidity": 80 } } }

此时telemetry对象包含两个字段:

  • ts:Unix 毫秒时间戳(epoch milliseconds),指示这条遥测数据对应的时间点;
  • values:实际的遥测键值对。

在 simple-json 完整示例 中可以看到典型的解码器写法——把人类可读的时间字符串换算成 epoch 毫秒:

// decode payload to JSON. See helper function below var json = decodeToJson(payload); // convert date to epoch in milliseconds var timestamp = Date.parse(json.ts); // Construct result object with time-series data var result = { deviceName: json.serialNumber, deviceType: "Thermostat", deviceLabel: "Kitchen Thermostat", telemetry: { ts: timestamp, values: { temperature: json.t, humidity: json.h } } };

配合仓库中的 payload.md(输入{"serialNumber": "SN-111", "ts": "2021-11-21 14:27:39 UTC", "t": 36.6, "h": 70})与 output.md(输出ts: 1637504859000),可以看到一条完整的"原始 payload → 解码函数 → 标准输出"链路。需要说明的是:这里Date.parse是否支持非标准日期格式取决于运行环境(JavaScript 执行器),生产环境中建议先对时间字符串做规范化解析后再转换。

四、扩展字段:deviceLabel、customerName、groupName、integrationName

除了最小骨架,解码器输出还支持若干可选扩展字段,用于在转换阶段直接完成实体的业务归属配置。见 label_json_output.md 与 json_output.md:

{ "deviceName": "001B638446E7", "deviceType": "thermostat", "deviceLabel": "Room A thermostat", "customerName": "Company Name", "groupName": "Thermostats", "attributes": { "model": "Model A", "serialNumber": "SN-111", "integrationName": "Test integration" }, "telemetry": { "temperature": 42, "humidity": 80 } }

各扩展字段的语义如下:

字段说明
deviceLabel设备的显示标签,与deviceName分离,更友好地展示设备
customerName该设备所属客户(Customer)的名称,平台会按名称查找或创建客户并完成归属
groupName设备分组名称,转换后设备会被加入指定分组
attributes.integrationName属性中的integrationName仅是一个普通属性键示例,并非特殊字段;integrationName这类业务属性同样通过attributes写入平台

对应后端解析(见上文parseUplinkData):

if (src.has("deviceLabel")) { builder.deviceLabel(src.get("deviceLabel").getAsString()); } if (src.has("customerName")) { builder.customerName(src.get("customerName").getAsString()); } if (src.has("groupName")) { builder.groupName(src.get("groupName").getAsString()); }

从源码可见,deviceLabel仅在设备场景生效(位于isAsset为 false 的分支中),而customerName与groupName对设备和资产均适用。这些字段的完整语义(如 customerName 是按名称自动查找/创建还是必须已存在)取决于转换器的后续 Uplink 处理逻辑,编写脚本时建议在测试环境中先验证实际行为。

五、JSON 数组输出:一次上报多设备/资产

当一条外部消息包含多条设备数据(例如网关批量转发、多传感器聚合报文)时,解码器可以返回 JSON 数组。仓库中的 json_array_output.md 给出了同时包含设备与资产两种实体的完整示例:

[ { "deviceName": "001B638446E7", "deviceType": "thermostat", "deviceLabel": "Room A thermostat", "attributes": { "model": "Model A" }, "telemetry": [ { "ts": 1527863043000, "values": { "battery": 3.99, "temperature": 27.05 } }, { "ts": 1527863044000, "values": { "battery": 3.98, "temperature": 27.06 } } ] }, { "assetName": "OF-123", "assetType": "office", "attributes": { "model": "Model A" }, "telemetry": { "ts": 1527863041000, "values": { "battery": 3.99, "temperature": 27.05 } } } ]

这个示例同时展示了三种进阶能力:

  1. 数组逐条解析:数组中的每个对象被独立解析成一条UplinkData(对应前文element.isJsonArray()分支);
  2. 单实体多时间点遥测:设备001B638446E7的telemetry本身是数组,携带了两个不同ts的时间点,实现一条转换结果回填多段历史时序数据;
  3. 资产混合输出:第二个对象改用assetName+assetType(必填),平台据此创建/更新名为OF-123、类型为office的资产。混合输出时要注意同一对象内deviceName与assetName不能并存(见 2.1 节校验规则)。

类似的数组输出同样出现在 complex-json-hex/output.md 中,其values内还嵌入了rawData对象,说明遥测值除了标量(数字、字符串、布尔)外,也支持嵌套 JSON 对象——平台会把嵌套对象按 JSON 类型键值(JSON_V)写入时序数据。这与 filterKeyValueAndUpdateMap 中对JSON_V类型的显式支持相印证。

六、编写解码器输出时的实践建议

综合仓库中的示例文档与后端解析源码,可以总结出以下可直接落地的经验:

  1. 统一使用标准 JSON 返回:解码器return的对象会被JSON.stringify成字符串后再被后端JsonParser解析,务必保证输出是合法的 JSON 对象或数组(例如使用JSON.parse而非手工拼串,参考 decoder_fn.md 中decodeToJson的写法)。
  2. 名称字段的完备性:每个输出对象要么包含deviceName(可选deviceType、deviceLabel),要么包含assetName+assetType;两者互斥。
  3. 时间字段统一毫秒:需要回填历史遥测时使用telemetry.ts(epoch 毫秒)+telemetry.values;不带ts则数据落为当前时刻。
  4. 合理利用数组:网关批量上报场景返回数组,每个元素对应一个实体;单实体多时间点可把telemetry写成ts/values对象数组。
  5. 利用 Debug 模式验证:转换器支持 Debug 输出,可将转换前后的原始 payload 与 JSON 结果持久化查看,用于核对字段是否被正确解析。

七、相关资源索引

  • 输出格式官方示例(本文主文档):simple_json_output.md
  • 带时间戳输出:simple_json_output_with_ts.md
  • 扩展字段(label/customer/group):label_json_output.md、json_output.md
  • 数组输出:json_array_output.md
  • 完整链路示例(payload + 解码函数 + 输出):simple-json 目录、complex-json-hex 目录
  • 后端解析实现:AbstractUplinkDataConverter、ScriptUplinkDataConverter

掌握了解码器 JSON 输出契约,你就能在 ThingsBoard 的各类集成(MQTT、HTTP、CoAP、TCP 等)中编写出结构正确、语义清晰的数据转换脚本,让任意原始格式的报文都能稳定地映射为平台统一的设备、属性与时序数据模型。

  • 物联网
  • 后端
  • 数据可视化
  • 消息队列

【免费下载链接】thingsboard

All-in-one IoT Platform - Device management, data collection, processing and visualization.

项目地址:https://gitcode.com/GitHub_Trending/th/thingsboard
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 11:51:18

GitHub Copilot 实战:前端开发效率提升 30% 的配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 11:50:11

Proteus 9.0安装配置全攻略:从下载到单片机仿真跑通

电子设计自动化这条路上&#xff0c;几乎每个搞单片机的人都绕不开一个名字——Proteus。不管你是电子专业的学生&#xff0c;还是做嵌入式开发的工程师&#xff0c;手头没有几块开发板的时候&#xff0c;想在电脑上先把电路跑通、把代码验证一遍&#xff0c;Proteus 就是那个最…

作者头像 李华
网站建设 2026/10/2 11:48:37

PLC与运动控制器:不是取代而是分工,轨迹规划与实时性才是分水岭

直接抛出我的结论&#xff1a;PLC和运动控制器不是一个“谁取代谁”的问题&#xff0c;而是一个“谁更适合干什么活”的问题。这两年总有人拿“高端PLC已经能做运动控制”说事&#xff0c;但真到现场调试的时候&#xff0c;你会发现两者之间的差距依然刺眼——不是功能列表上的…

作者头像 李华
网站建设 2026/10/2 11:48:23

好客搜GEO实践:从关键词到语义理解的企业落地路径

一、搜索引擎的技术演进的四个常见问题传统搜索引擎依赖关键词匹配&#xff0c;用户搜“苏州短视频运营系统”&#xff0c;结果页会按词频和链接权重排列网页。但如今用户习惯变了&#xff0c;直接问AI“苏州哪家短视频系统能对接多平台”&#xff0c;期望得到整合性的答案而非…

作者头像 李华
网站建设 2026/10/2 11:47:57

GitHub Copilot SDK 初体验:用 C# 把 CLI 能力接进自己的工具链

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华