ChirpStack v4 / TTN v3 ↔ ThinkLink 数据接入桥(BizCode 90004)
通用 MQTT 转发桥,将 ChirpStack v4 或 TTN v3 的 LoRaWAN 设备数据双向接入 ThinkLink 平台,无需改动设备侧配置。
1. 概述
本桥是一个通用 MQTT 转发桥,面向已在 ChirpStack v4 或 TTN v3(The Things Stack)上运行 LoRaWAN 设备、希望将设备数据同步接入 ThinkLink 物联网平台的用户。桥由 4 个 ThinkLink 转发器组成,分别处理两种 NS 各自的上行(设备 → 平台)和下行(平台 → 设备)方向。上行报文在转发层统一归一为 ThinkLink AS 标准格式后,由平台侧物模型解码并落库;下行 RPC 则由平台下发至对应 NS 的下行入队接口。新增设备无需修改转发脚本,只需在平台预建设备实体并绑定物模型即可。
2. 架构与数据流
2.1 上行链路
┌─────────────────────────────────────────────────────────┐
│ 外部 NS MQTT Broker │
│ ChirpStack: application/{appId}/device/{devEui}/event/up │
│ TTN: v3/{appId}@{tenantId}/devices/{devId}/up │
└────────────────────┬────────────────────────────────────┘
│ 订阅(source broker)
▼
┌───────────────────────┐
│ ThinkLink 转发器 │
│ forwardScript() │
│ (chirpstack-up 或 │
│ ttn-up) │
│ 从 org_params 读租户码 │
│ 归一成 AS 上行格式 │
└───────────┬───────────┘
│ 发布(target broker)
▼
┌────────────────────────────────┐
│ ThinkLink AS MQTT Broker │
│ /v32/{tenant}/as/up/data/{eui} │
└────────────────────────────────┘
│
▼
平台 payload_parser_type=CHIRPSTACK 物模型解析
→ 遥测落库 / 触发器 / 告警各跳说明:
- 外部 NS Broker → 转发器:转发器订阅 NS 的上行 topic(通配符
+匹配所有应用和设备),收到原始上行 JSON。 - 转发器处理:从原始报文提取 EUI、fPort、base64 payload、网关 RSSI/SNR/时间戳,归一成 ThinkLink AS 上行格式,并从
org_params['lns.tenant']读取租户码拼出目标 topic。 - ThinkLink AS Broker → 平台:平台消费 AS MQTT 消息,按 EUI 匹配设备,调用物模型 codec(
payload_parser_type=CHIRPSTACK)解码 payload,写入遥测、触发告警/触发器。
2.2 下行链路
平台下发 RPC / 定时任务 / 手动下行
│
▼
┌────────────────────────────────┐
│ ThinkLink AS MQTT Broker │
│ /v32/{tenant}/as/dn/data/{eui} │
└────────────────────────────────┘
│ 订阅(source broker = AS)
▼
┌───────────────────────┐
│ ThinkLink 转发器 │
│ forwardScript() │
│ (chirpstack-down 或 │
│ ttn-down) │
│ 从 topic 末段取 eui │
│ 组装 NS 下行入队 body │
└───────────┬───────────┘
│ 发布(target = NS broker)
▼
┌──────────────────────────────────────────────────────────┐
│ 外部 NS MQTT Broker │
│ ChirpStack: application/{appId}/device/{devEui}/command/down │
│ TTN: v3/{appId}@{tenantId}/devices/{devId}/down/push │
└──────────────────────────────────────────────────────────┘各跳说明:
- 平台 → AS Broker:平台对设备下发 RPC 时,将下行报文发布到 AS 下行 topic,topic 末段为设备 EUI。
- 转发器处理:从 topic 末段提取 EUI,从
msg.userdata取 fPort、payload、confirmed 标志,组装目标 NS 的下行入队消息体,并从org_params读取appId/tenantId拼出目标 topic。 - AS Broker → 外部 NS:NS 收到下行入队消息后,在下一个可用下行窗口(Class A)或立即(Class C)将报文发送给设备。
单 NS 约定(本期):
chirpstack-down与ttn-down均订阅同一 AS 下行 topic/v32/{tenant}/as/dn/data/+。本期约定一个 ThinkLink 租户只接入一个 NS(只启用对应的*-down转发器,另一个保持禁用),避免同一条下行指令被两个 NS 同时下发。多 NS 混用场景留待后续版本支持。
3. 前置条件
在部署本转发桥之前,请确认以下事项:
- 设备在 ThinkLink 平台已预建:每台 LoRa 设备须在 ThinkLink 平台按 EUI 创建设备实体,EUI 与 NS 侧保持一致(大小写不敏感,桥接脚本统一转小写)。
- 物模型类型为
CHIRPSTACK:设备绑定的遥测物模型须将payload_parser_type设为CHIRPSTACK,且 codec 实现标准函数签名Decode(fPort, bytes)——ThinkLink 将以此解析归一后的上行 payload。 - NS MQTT Broker 可达:ChirpStack 或 TTN 的 MQTT broker 地址、端口、认证凭据须已知,且部署 ThinkLink 的服务器网络能够访问该 broker。
- ThinkLink AS Broker 账密:需从平台系统配置中取得内部 AS MQTT broker 的连接凭据(地址通常为平台内部域名或 IP)。
4. 上行接入
4.1 ChirpStack v4 上行
转发器订阅 topic(订阅 ChirpStack broker):
application/{appId}/device/{devEui}/event/up实际部署时以通配符 application/+/device/+/event/up 订阅,匹配所有设备。
字段来源映射:
| ThinkLink AS 归一字段 | ChirpStack v4 原始字段 | 说明 |
|---|---|---|
eui | msg.deviceInfo.devEui | 转小写 |
userdata.port | msg.fPort | 必需,缺失则丢弃 |
userdata.seqno | msg.fCnt | 帧计数 |
userdata.payload | msg.data | base64,必需,缺失则丢弃 |
gwrx[0].eui | msg.rxInfo[0].gatewayId | 转小写 |
gwrx[0].rssi | msg.rxInfo[0].rssi | dBm |
gwrx[0].lsnr | msg.rxInfo[0].snr | dB |
gwrx[0].time | msg.rxInfo[0].time | ISO 8601 |
4.2 TTN v3 上行
转发器订阅 topic(订阅 TTN broker):
v3/{appId}@{tenantId}/devices/{devId}/up实际部署时以通配符 v3/+/devices/+/up 订阅。
字段来源映射:
| ThinkLink AS 归一字段 | TTN v3 原始字段 | 说明 |
|---|---|---|
eui | msg.end_device_ids.dev_eui | 转小写 |
userdata.port | msg.uplink_message.f_port | 必需 |
userdata.seqno | msg.uplink_message.f_cnt | 帧计数 |
userdata.payload | msg.uplink_message.frm_payload | base64,必需 |
gwrx[0].eui | msg.uplink_message.rx_metadata[0].gateway_ids.eui | 转小写 |
gwrx[0].rssi | msg.uplink_message.rx_metadata[0].rssi | dBm |
gwrx[0].lsnr | msg.uplink_message.rx_metadata[0].snr | dB |
gwrx[0].time | msg.uplink_message.rx_metadata[0].time | ISO 8601 |
如果 NS 不携带网关接收信息(
rxInfo/rx_metadata为空),gwrx字段将为空数组[],不影响遥测落库。
4.3 归一后 ThinkLink AS 上行格式
两种 NS 的上行报文经转发器处理后,均以以下格式发布到:
发布 topic:/v32/{tenant}/as/up/data/{eui}({eui} 为小写)
{
"if": "loraWAN",
"type": "data",
"version": "3.0",
"moteeui": "6353012b00021678",
"gwrx": [
{
"eui": "a840411e5f8e7200",
"rssi": -85,
"lsnr": 7.5,
"time": "2026-06-14T08:00:00Z"
}
],
"userdata": {
"adr": false,
"port": 1,
"class": "ClassA",
"seqno": 123,
"payload": "AQIDBA==",
"confirmed": false
}
}必需字段:userdata.payload(base64)和 userdata.port 均不可为空;任一缺失时转发器返回 null,该帧被静默丢弃(不转发、不报错)。
5. 下行接入
平台对设备下发 RPC 或手动下行时,在 ThinkLink AS broker 发布如下格式:
AS 下行入口 topic:/v32/{tenant}/as/dn/data/{eui}
转发器从 topic 末段取 eui,从 msg.userdata 取以下字段:
| 字段 | 含义 |
|---|---|
msg.userdata.port | LoRa fPort,必需 |
msg.userdata.payload | base64 下行 payload,必需 |
msg.userdata.confirmed | 是否需要确认帧(可选,默认 false) |
5.1 转发至 ChirpStack v4
发布 topic:application/{appId}/device/{eui}/command/down
appId 来自 org_params['lns.chirpstack.appId']。
{
"devEui": "6353012b00021678",
"confirmed": false,
"fPort": 1,
"data": "AQIDBA=="
}5.2 转发至 TTN v3
发布 topic:v3/{appId}@{tenantId}/devices/{devId}/down/push
appId 来自 org_params['lns.ttn.appId'],tenantId 来自 org_params['lns.ttn.tenantId']。
{
"downlinks": [
{
"f_port": 1,
"frm_payload": "AQIDBA==",
"priority": "NORMAL",
"confirmed": false
}
]
}TTN 下行 device_id 映射:TTN 下行 topic 使用字符串型 devId(非裸 EUI)。转发器按以下顺序确定 devId:
- 优先查
org_params['lns.ttn.deviceMap'][eui](运营人员在组织参数中配置的精确映射); - 若映射表为空或该 EUI 不在表中,则回退使用
eui本身作为devId(适用于 TTN 侧 device id 与 EUI 一致的常见部署)。
6. 配置项(org_params)
租户管理员在平台 系统管理 → 服务器配置 → 组织参数 中维护以下 5 个键值:
| key | 用途 | 示例 |
|---|---|---|
lns.tenant | ThinkLink 租户码,用于拼接 AS topic /v32/{tenant}/as/... | demo |
lns.chirpstack.appId | ChirpStack application id(整数字符串) | 1 |
lns.ttn.appId | TTN application id | my-app |
lns.ttn.tenantId | TTN tenant id(The Things Stack 多租户标识) | ttn |
lns.ttn.deviceMap | JSON 对象 {"eui": "ttn-device-id"};若 TTN device id 与 EUI 相同可填空对象 {} | {"a1b2c3d4e5f60708":"my-sensor-01"} |
安全要求:以上均为运营配置,不得写入代码仓库。Broker 账密和 API Key 在平台 Broker 连接配置中单独维护,不存入 org_params。
7. 部署步骤
步骤一:建立 3 个 MQTT Broker 连接
平台路径:系统配置 → 服务器配置 → Broker 连接管理
1a. ThinkLink AS Broker(内部)
| 参数 | 说明 |
|---|---|
| 连接类型 | AS(平台内置 AS broker) |
| 地址 / 端口 | 在系统配置 → 服务器配置 → 内部 MQTT 查看实际地址和端口 |
| 账号 / 密码 | 同上,取平台内部 MQTT 账密 |
| 用途 | 上行转发器的 target;下行转发器的 source |
1b. ChirpStack Broker
| 参数 | 说明 |
|---|---|
| 连接类型 | Customize(自定义外部 broker) |
| 地址 / 端口 | 从 ChirpStack 集成页 → MQTT integration → Server 取地址(如 mqtt://chirpstack.example.com:1883) |
| 账号 / 密码 | ChirpStack MQTT integration 中配置的凭据 |
| TLS | 按 ChirpStack 实际配置决定是否启用,生产环境建议开启 |
| 用途 | chirpstack-up 的 source;chirpstack-down 的 target |
1c. TTN Broker
| 参数 | 说明 |
|---|---|
| 连接类型 | Customize(自定义外部 broker) |
| 地址 / 端口 | 从 TTN Console → 应用 → Integrations → MQTT 取地址(如 mqtts://eu1.cloud.thethings.network:8883) |
| 账号 / 密码 | TTN MQTT 账密(username 格式 {appId}@{tenantId},password 为 API Key) |
| TLS | TTN 云服务默认启用 TLS |
| 用途 | ttn-up 的 source;ttn-down 的 target |
步骤二:创建 4 个转发器
平台路径:高级功能 → 转发器 → 新增
逐个按下表填写,脚本内容粘贴对应转发脚本的完整内容。
2a. chirpstack-up(ChirpStack 上行)
| 参数 | 值 |
|---|---|
| 名称 | chirpstack-up |
| Source Broker | ChirpStack broker(步骤 1b) |
| 订阅 Topic | application/+/device/+/event/up |
| Target Broker | ThinkLink AS broker(步骤 1a) |
2b. ttn-up(TTN 上行)
| 参数 | 值 |
|---|---|
| 名称 | ttn-up |
| Source Broker | TTN broker(步骤 1c) |
| 订阅 Topic | v3/+/devices/+/up |
| Target Broker | ThinkLink AS broker(步骤 1a) |
2c. chirpstack-down(ChirpStack 下行)
| 参数 | 值 |
|---|---|
| 名称 | chirpstack-down |
| Source Broker | ThinkLink AS broker(步骤 1a) |
| 订阅 Topic | /v32/{tenant}/as/dn/data/+(将 {tenant} 替换为实际租户码,与 lns.tenant 一致) |
| Target Broker | ChirpStack broker(步骤 1b) |
2d. ttn-down(TTN 下行)
| 参数 | 值 |
|---|---|
| 名称 | ttn-down |
| Source Broker | ThinkLink AS broker(步骤 1a) |
| 订阅 Topic | /v32/{tenant}/as/dn/data/+(同上,替换为实际租户码) |
| Target Broker | TTN broker(步骤 1c) |
单 NS 约定:
chirpstack-down与ttn-down订阅同一 AS 下行 topic。本期一个租户只接一个 NS——只启用对应的*-down转发器,另一个保持禁用,避免同一条下行同时被两个 NS 下发。
步骤三:填写 org_params
平台路径:系统管理 → 服务器配置 → 组织参数
在目标租户的组织参数中维护以下键值(仅填写实际使用的 NS 对应项,不用的可留空):
| key | 说明 | 示例(请替换为实际值) |
|---|---|---|
lns.tenant | ThinkLink 租户码 | demo |
lns.chirpstack.appId | ChirpStack application id | 1 |
lns.ttn.appId | TTN application id | my-app |
lns.ttn.tenantId | TTN tenant id | ttn |
lns.ttn.deviceMap | {"eui":"ttn-device-id"} 映射,device id 与 EUI 相同时填 {} | {"a1b2c3d4e5f60708":"my-sensor-01"} |
步骤四:在 ThinkLink 平台预建设备
对每台需要接入的 LoRa 设备:
- 在平台按设备 EUI 创建设备实体(EUI 与 NS 侧保持一致)。
- 为设备绑定遥测物模型,确认物模型
payload_parser_type为CHIRPSTACK,codec 实现Decode(fPort, bytes)函数。 - 确认设备 EUI 在平台侧存储为小写(或平台查询时不区分大小写)。
步骤五:端到端联调
上行验证:
- 从 ChirpStack / TTN 控制台或模拟器发送一帧上行数据(有效 fPort + payload)。
- 在 ThinkLink 平台 → 设备详情 → 遥测 中确认数据已落库。
- 若遥测为空,检查平台转发器运行日志(高级功能 → 转发器 → 运行日志),确认
lns.tenant、设备预建状态、物模型类型。
下行验证:
- 在 ThinkLink 平台对目标设备手动下发 RPC 或调用下行接口。
- 在 ChirpStack → 设备 → LoRaWAN Frames 或 TTN Console → Live data 中确认下行已入队。
- 若下行未到 NS,检查下行转发器订阅 topic 中
{tenant}是否已替换为实际值,以及相关 org_params 是否填写。
8. 故障排查
| 现象 | 可能原因 | 排查路径 |
|---|---|---|
| 上行遥测不落库 | lns.tenant 与实际租户码不符 | 检查 org_params + 转发器运行日志 |
| 上行遥测不落库 | 设备未预建或 EUI 不一致 | 平台设备列表核查 |
| 上行遥测不落库 | 物模型未设为 CHIRPSTACK 类型 | 查看设备绑定的物模型详情 |
| 下行 NS 未收到 | 下行转发器订阅 topic 中 {tenant} 未替换为实际值 | 转发器配置详情 |
| 下行 NS 未收到 | appId / tenantId 等 org_params 未填写 | org_params 配置页 |
| TTN 下行 topic 报错 | lns.ttn.deviceMap 缺失且 TTN device id 不等于 EUI | 补填 lns.ttn.deviceMap |
| 两个 NS 同时收到下行 | chirpstack-down 和 ttn-down 同时处于启用状态 | 禁用未使用的 *-down 转发器(单 NS 约定) |