SWMx DIDO 控制器接入 ThinkLink 对接文档
1. 传感器简介
SWMx 是竞速电子 JSDZ 的 DIDO 控制器,主要用于数字量输入状态采集和继电器输出控制。
本方案中,SWMx 通过 RS-485 接入 KC11 采集器,由 KC11 内置 EdgeBus 执行 Modbus RTU 采集逻辑,并通过 LoRaWAN 将数据上传到 ThinkLink 平台。
设备基本信息如下:
| 项目 | 内容 |
|---|---|
| 设备型号 | SWMx |
| 厂商 | 竞速电子 |
| 英文名 | JSDZ |
| 设备类型 | DIDO 控制器 |
| 业务代码 | 22108 |
| ThinkLink 模板 | SWMx-22108 |
2. 产品特点
SWMx DIDO 控制器具备以下特点:
- 支持数字量输入状态采集
- 支持继电器输出控制
- 支持 RS-485 / Modbus RTU 通讯
- 可通过 KC11 + EdgeBus 接入 LoRaWAN 网络
- 支持通过 ThinkLink 进行状态解析、数据展示和远程控制
- 支持上报 RY / DI 的完整状态字节
- 支持将 RY、DI 前 5 路状态解析为独立字段
- 支持通过 RPC 修改采集周期、上传周期和 Modbus 地址
- 支持通过 RPC 控制指定继电器开关状态
3. 适用范围
本方案适用于以下场景:
- 远程继电器控制
- 数字量输入状态监测
- 设备运行状态采集
- 门禁、门磁、开关量状态监测
- 工业现场 DIDO 控制器无线化改造
- 传统 RS-485 控制器接入 LoRaWAN / ThinkLink 平台
- 需要通过 MQTT / RPC 实现第三方平台远程控制的场景
4. 采集器信息
4.1 硬件信息
本方案采用 KC11 作为采集器。
| 项目 | 内容 |
|---|---|
| 采集器型号 | KC11 |
| 采集接口 | RS-485 |
| 通讯协议 | Modbus RTU |
| 上行方式 | LoRaWAN |
| 供电方式 | 220V / 12V 供电 |
| 边缘计算能力 | 支持 EdgeBus |
| 平台接入 | ThinkLink |
KC11 负责通过 RS-485 读取 SWMx 的 Modbus 数据,并将采集结果通过 LoRaWAN 上传到网关,最终进入 ThinkLink 平台。
4.2 接线信息
电源与通讯接口
| 接线项 | 说明 |
|---|---|
| KC11 电源 | 220V / 12V 供电 |
| SWMx 电源 | 按 SWMx 控制器实际供电要求接入 |
| RS-485 A | KC11 RS-485 A 接 SWMx A |
| RS-485 B | KC11 RS-485 B 接 SWMx B |
| GND | 如现场需要,可连接通讯参考地 |
传感器接口
SWMx 本身为 DIDO 控制器,传感器或现场设备接入 SWMx 的 DI 输入端,控制对象接入 SWMx 的 RY / 继电器输出端。
本方案中,KC11 不直接接入 DI 或继电器负载,而是通过 RS-485 读取和控制 SWMx。
5. 数据采集
本方案中,通过 Modbus 读取以下数据:
| 数据类型 | Modbus 功能码 | 说明 |
|---|---|---|
| RY 状态 | 0x01 | 读取线圈状态 |
| DI 状态 | 0x02 | 读取离散输入状态 |
本次采集使用的是 Modbus 01 和 02 功能码:
0x01:读取 RY / Coil 状态0x02:读取 DI / Discrete Input 状态
需要注意的是,01 和 02 功能码本身是按 bit 读取,而不是像 03 / 04 那样直接按寄存器读取。但是在本方案中,EBHelper 已经在底层完成了处理,会自动将 bit 数据组合为字节。
因此,EB 代码中配置:
{ start: "0", end: "15", covType: "HEX" }表示读取 0 到 15 共 16 个 bit,底层会自动组合为 2 字节数据上传。
5.1 寄存器定义
RY 状态采集
| 项目 | 内容 |
|---|---|
| 协议 | Modbus RTU |
| 功能码 | 0x01 |
| 读取对象 | Coil / 继电器状态 |
| 起始地址 | 0 |
| 结束地址 | 15 |
| 数据长度 | 16 bit,底层组合为 2 字节 |
| 上传格式 | HEX |
| 变化触发 | 支持,covType: "HEX" |
对应 EB 配置:
{
protocol: "modbus",
code: "0x01",
periodIndex: 74,
indexAPP: 150,
indexCMD: 0,
copySize: 1,
isLast: false,
listVal: [
{ start: "0", end: "15", covType: "HEX" }
]
}DI 状态采集
| 项目 | 内容 |
|---|---|
| 协议 | Modbus RTU |
| 功能码 | 0x02 |
| 读取对象 | Discrete Input / 数字量输入状态 |
| 起始地址 | 0 |
| 结束地址 | 15 |
| 数据长度 | 16 bit,底层组合为 2 字节 |
| 上传格式 | HEX |
| 变化触发 | 支持,covType: "HEX" |
对应 EB 配置:
{
protocol: "modbus",
code: "0x02",
periodIndex: 74,
indexAPP: 150,
indexCMD: 0,
copySize: 1,
isLast: false,
listVal: [
{ start: "0", end: "15", covType: "HEX" }
]
}5.2 状态位定义
本方案中,物模型同时解析完整状态字和前 5 路通道状态。
RY 状态位定义
| 字段名称 | field_name | 数据位置 | 类型 | 说明 |
|---|---|---|---|---|
| RY | ry_status_hex | index 6 | hexbe2 | RY 2 字节状态值,HEX 字符串 |
| RY1 | ry1 | index 6 bit0 | bitLE0-0 | 第 1 路继电器状态 |
| RY2 | ry2 | index 6 bit1 | bitLE1-1 | 第 2 路继电器状态 |
| RY3 | ry3 | index 6 bit2 | bitLE2-2 | 第 3 路继电器状态 |
| RY4 | ry4 | index 6 bit3 | bitLE3-3 | 第 4 路继电器状态 |
| RY5 | ry5 | index 6 bit4 | bitLE4-4 | 第 5 路继电器状态 |
DI 状态位定义
| 字段名称 | field_name | 数据位置 | 类型 | 说明 |
|---|---|---|---|---|
| DI | di_status_hex | index 8 | hexbe2 | DI 2 字节状态值,HEX 字符串 |
| DI1 | di1 | index 8 bit0 | bitLE0-0 | 第 1 路 DI 状态 |
| DI2 | di2 | index 8 bit1 | bitLE1-1 | 第 2 路 DI 状态 |
| DI3 | di3 | index 8 bit2 | bitLE2-2 | 第 3 路 DI 状态 |
| DI4 | di4 | index 8 bit3 | bitLE3-3 | 第 4 路 DI 状态 |
| DI5 | di5 | index 8 bit4 | bitLE4-4 | 第 5 路 DI 状态 |
6. EdgeBus 模型
本方案中,SWMx 是 RS-485 / Modbus RTU 设备,不是原生 LoRaWAN 设备,因此需要通过 KC11 内置 EdgeBus 执行采集逻辑。
EdgeBus 负责:
- 定时读取 SWMx 的 RY 状态
- 定时读取 SWMx 的 DI 状态
- 使用 Modbus 01 / 02 功能码完成 bit 状态读取
- 将读取结果封装为 LoRaWAN 上行数据
- 支持变化上传逻辑
- 支持通过平台 RPC 修改参数
- 支持通过平台 RPC 控制继电器输出
6.1 EB 配置参数
| 参数 | 说明 |
|---|---|
| port | 22 |
| dataType | 0x86 |
| version | 0x08 |
| upPeriodIndex | 70 |
| readPeriodIndex | 74 |
| Modbus 地址参数 index | 150 |
| Modbus 波特率 | 9600 |
| 数据位 | 8 |
| 停止位 | 1 |
| 校验位 | NONE |
| 业务类型 | 22108 |
| 业务版本 | 13 |
| EB 软件版本 | 31 |
| 是否电池设备 | false |
参数说明:
| 参数 index | 字段 | 说明 |
|---|---|---|
| 70 | period_up | 上传周期,单位秒 |
| 74 | period_read | 采集周期,单位秒 |
| 150 | addr_modbus | SWMx 的 Modbus 地址 |
6.2 EB 代码
import { Buffer } from "buffer";
import { buildOtaFile } from "@EBSDK/run";
import {
ActionAfertExpr, CalcData,
CrcMode,
CvtRule,
EBBuffer,
EBModel,
ExprCondition,
LoraUpEvent,
QueryEvent, SetUpCovDataType, QuItemBase,
UserConfUPItem, EventInfoItem, QuItemModBus, UserConfQueryItem, EventConfig, Utils
} from "@EBSDK/EBCompiler/all_variable";
import { CheckbitEnum, getOtaConfig, HwTypeEnum, UpgrdTypeEnum } from "@EBSDK/otaConfig";
////////////////////////////////////////////////////////////////////////////////////////
// 01、02 是按 bit 读取的,EBHelper 会自动组合成字节。
// start: "0", end: "15" 实际读取 16 bit,上传时为 2 字节。
// covType 如果是 HEX,会将采集到的数据与上次数据做对比,如果有差异则触发上传。
const eventInfo: UserConfUPItem[] = [
{
name: "swm",
dataType: "0x86",
port: 22,
version: "0x08",
upPeriodIndex: 70,
quInfo: [
{
protocol: "modbus",
code: "0x01",
periodIndex: 74,
indexAPP: 150,
indexCMD: 0,
copySize: 1,
isLast: false,
listVal: [
{ start: "0", end: "15", covType: "HEX" }
]
},
{
protocol: "modbus",
code: "0x02",
periodIndex: 74,
indexAPP: 150,
indexCMD: 0,
copySize: 1,
isLast: false,
listVal: [
{ start: "0", end: "15", covType: "HEX" }
]
}
]
}
];
let otaConfig = getOtaConfig({
SwVersion: 31,
BaudRate: 9600,
StopBits: 1,
DataBits: 8,
Checkbit: CheckbitEnum.NONE,
Battery: false,
ConfirmDuty: 60,
BzType: 22108,
BzVersion: 13
});
const MODBUS_TT = (ebModel: EBModel) => {
for (let i = 0; i < eventInfo.length; i++) {
let event = new EventInfoItem(eventInfo[i]);
event.upEventSetup();
event.eventInstall();
}
return JSON.stringify(ebModel, null, 2);
};6.3 说明
当前 EB 逻辑说明:
- KC11 通过 RS-485 连接 SWMx。
- EdgeBus 按照配置周期读取 SWMx 数据。
- 读取 RY 状态时使用 Modbus
0x01功能码。 - 读取 DI 状态时使用 Modbus
0x02功能码。 start: "0", end: "15"表示读取 16 个 bit。- EBHelper 底层会自动将 16 bit 结果组合为 2 字节。
- 物模型中将 RY 状态作为
ry_status_hex上传。 - 物模型中将 DI 状态作为
di_status_hex上传。 - 同时解析 RY1~RY5、DI1~DI5 作为独立状态字段。
- 当
covType设置为HEX时,采集结果会与上一次数据对比,数据发生变化时触发上传。 - RPC 可用于配置采集周期、上传周期和 Modbus 地址。
- RPC 可用于控制指定继电器开关。
7. 物模型
7.1 物模型基本信息
| 项目 | 内容 |
|---|---|
| 物模型名称 | SWMx-22108 |
| idName | swm_22108 |
| 业务代码 | 22108 |
| 数据上报端口 | 22 |
| 参数端口 | 214 |
| 透传端口 | 51 |
| 数据长度 | 9 |
| 是否解析 RSSI | 是 |
| 电池字段 index | 4 |
| 数据标识 | 0x86 |
| 版本标识 | 0x08 |
7.2 上行帧结构
本方案中,SWMx 数据通过 LoRaWAN port 22 上报。
let frameInfo = {
port: 22,
dataLen: 9,
rssi: true,
battery: 4,
tagList: [
{ index: 0, tag: 0x86 },
{ index: 1, tag: 0x08 }
]
};上行数据结构说明:
| index | 内容 | 说明 |
|---|---|---|
| 0 | 0x86 | 数据类型 |
| 1 | 0x08 | 协议版本 |
| 4 | battery | 电池或供电状态字段 |
| 6~7 | RY 状态 | 2 字节 HEX |
| 8~9 | DI 状态 | 2 字节 HEX |
注:根据提供的物模型脚本,
dataLen配置为 9,RY 从 index 6 解析 2 字节,DI 从 index 8 解析 2 字节。实际部署时建议结合真实上行 payload 再确认帧长度是否需要调整。
7.3 物模型脚本
let port = msg?.userdata?.port || null;
if (port === 51) {
let retval = Utils.paraCheck(Utils.msgType.transParent, device.server_attrs, { device: device, msg: msg });
return {
telemetry_data: retval.tdata,
server_attrs: retval.sdata,
shared_attrs: retval.pdata,
actions: retval.actions
};
}
if (port !== 22) return null;
let frameInfo = {
port: 22,
dataLen: 9,
rssi: true,
battery: 4,
tagList: [
{ index: 0, tag: 0x86 },
{ index: 1, tag: 0x08 }
]
};
let appInfo = [
{ name: "RY", field_name: "ry_status_hex", unit: "", index: 6, type: "hexbe2" },
{ name: "DI", field_name: "di_status_hex", unit: "", index: 8, type: "hexbe2" },
{ name: "RY1", field_name: "ry1", unit: "", index: 6, type: "bitLE0-0" },
{ name: "RY2", field_name: "ry2", unit: "", index: 6, type: "bitLE1-1" },
{ name: "RY3", field_name: "ry3", unit: "", index: 6, type: "bitLE2-2" },
{ name: "RY4", field_name: "ry4", unit: "", index: 6, type: "bitLE3-3" },
{ name: "RY5", field_name: "ry5", unit: "", index: 6, type: "bitLE4-4" },
{ name: "DI1", field_name: "di1", unit: "", index: 8, type: "bitLE0-0" },
{ name: "DI2", field_name: "di2", unit: "", index: 8, type: "bitLE1-1" },
{ name: "DI3", field_name: "di3", unit: "", index: 8, type: "bitLE2-2" },
{ name: "DI4", field_name: "di4", unit: "", index: 8, type: "bitLE3-3" },
{ name: "DI5", field_name: "di5", unit: "", index: 8, type: "bitLE4-4" }
];
let payParser = new PayloadParser({
device: device,
msg: msg,
frameInfo: frameInfo,
appInfo: appInfo,
});
let tdata = payParser.telemetry();
if ((tdata?.status & 0x02) === 0x02) {
// time out,只更新 status
const status = tdata.status;
tdata = { ...(device.telemetry_data?.[thingModelId] ?? {}) };
tdata.status = status;
}
return {
telemetry_data: tdata,
server_attrs: null,
shared_attrs: null
};8. 第三方平台数据订阅
8.1 MQTT Topic
第三方平台可通过以下 Topic 订阅 ThinkLink 上报数据:
/v32/{Organization Account}/tkl/up/telemetry/{eui}其中:
| 参数 | 说明 |
|---|---|
| Organization Account | ThinkLink 组织账号 |
| eui | LoRaWAN 设备 EUI |
8.2 上报示例数据
{
"eui": "6353012af10a9331",
"active_time": "2026-02-05T08:35:48.000Z",
"thingModelId": "88304368717139973",
"thingModelIdName": "swm_22108",
"telemetry_data": {
"snr": 13.5,
"rssi": -51,
"battery": 3.37,
"ry_status_hex": "001F",
"di_status_hex": "000B",
"ry1": 1,
"ry2": 1,
"ry3": 1,
"ry4": 1,
"ry5": 1,
"di1": 1,
"di2": 1,
"di3": 0,
"di4": 1,
"di5": 0,
"status": 0
}
}字段说明:
| 字段 | 说明 |
|---|---|
| ry_status_hex | RY 状态 2 字节 HEX 字符串 |
| di_status_hex | DI 状态 2 字节 HEX 字符串 |
| ry1~ry5 | 第 1~5 路继电器状态 |
| di1~di5 | 第 1~5 路 DI 输入状态 |
| battery | 电池或供电状态字段 |
| rssi | LoRaWAN RSSI |
| snr | LoRaWAN SNR |
| status | 数据状态 |
9. RPC
本方案支持以下 RPC:
- 配置参数
- 读取参数
- 继电器控制
9.1 RPC 名称
| RPC 功能 | RPC 名称 |
|---|---|
| 配置参数 | swm_set_22108 |
| 读取参数 | swm_get_22108 |
| 继电器控制 | swm_action_22108 |
9.2 参数定义
配置参数定义
let paraDef = {
app_70: {
name: "period_up",
field_name: "period_up",
unit: "s",
type: "uint32LE"
},
app_74: {
name: "period_read",
field_name: "period_read",
unit: "s",
type: "uint32LE"
},
app_150: {
name: "addr_modbus",
field_name: "addr_modbus",
unit: "",
type: "uint8"
}
};参数表:
| 参数 | field_name | 类型 | 单位 | 说明 |
|---|---|---|---|---|
| app_70 | period_up | uint32LE | s | 上传周期 |
| app_74 | period_read | uint32LE | s | 采集周期 |
| app_150 | addr_modbus | uint8 | - | SWMx Modbus 地址 |
继电器控制参数
每一路继电器对应一个独立的布尔参数,可在一次调用中控制任意一路或多路。表单打开时各字段会从设备共享属性(ry1~ry5)自动回填上次的设定状态。
| 参数 | 类型 | 说明 |
|---|---|---|
| switch_ry1 | boolean | 第 1 路继电器:true 闭合 / 打开,false 断开 / 关闭 |
| switch_ry2 | boolean | 第 2 路继电器 |
| switch_ry3 | boolean | 第 3 路继电器 |
| switch_ry4 | boolean | 第 4 路继电器 |
| switch_ry5 | boolean | 第 5 路继电器 |
仅传入的字段会下发控制(对应一帧 Modbus FC05,线圈地址 = 路号 − 1);未传入的路不动作。设置成功后,对应路状态会同步写回遥测
ryN与共享属性ryN。
示例(控制第 1 路闭合、第 3 路断开):
{
"switch_ry1": true,
"switch_ry3": false
}9.3 RPC 代码
9.3.1 配置参数 RPC
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let intervalms = classMode === "ClassA" ? 0 : 2000;
const rpcName = "swm_set_22108";
let paraDef = {
app_70: { name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE" },
app_74: { name: "period_read", field_name: "period_read", unit: "s", type: "uint32LE" },
app_150: { name: "addr_modbus", field_name: "addr_modbus", unit: "", type: "uint8" }
};
let frames = RPCHelper.buildFrame({
paraDef: paraDef,
params: params
});
let redoBuffer = RPCHelper.redo();
let dnBuffer = Buffer.alloc(frames.writeBuffer.length + frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer, 0);
frames.readBuffer.copy(dnBuffer, frames.writeBuffer.length);
logger.info("set para");
let msgQue = Utils.makeParaSetMSG({
device: device,
classMode: classMode,
rpcName: rpcName,
params: params,
paraDownBuffer: dnBuffer,
extraAppBuffer: redoBuffer
});
if (msgQue.length == 0) return null;
return msgQue;9.3.2 读取参数 RPC
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let sleepMs = classMode === "ClassA" ? 500 : 5000;
let paraDef = {
app_70: { name: "period_up", field_name: "period_up", unit: "s", type: "uint32LE" },
app_74: { name: "period_read", field_name: "period_read", unit: "s", type: "uint32LE" },
app_150: { name: "addr_modbus", field_name: "addr_modbus", unit: "", type: "uint8" }
};
let frames = RPCHelper.buildFrame({
paraDef: paraDef,
params: params
});
let msg = RPCHelper.makeMSG({
msgType: Utils.msgType.paras,
device: device,
dnBuffer: frames.readBuffer,
sleepTime: sleepMs,
});
return [msg];9.3.3 继电器控制 RPC
表单提供 switch_ry1~switch_ry5 五个布尔参数,每个对应一路继电器。脚本遍历本次传入的字段,每路下发一帧 Modbus 05 功能码(线圈地址 = 路号 − 1),多帧拼接后一次性下行;通过设备回显逐帧校验线圈值,成功后把每路状态同步写回遥测 ryN 与共享属性 ryN(用于下次打开表单自动回填)。
let classMode = (device?.shared_attrs?.class_mode) || "ClassA";
let addr_modbus = device.shared_attrs?.addr_modbus ?? 1;
let rpcName = "swm_action_22108";
// 5 路继电器:表单字段 switch_ry1..switch_ry5,各自从 shared_attrs ry1..ry5 回填。
// 一次提交可含任意子集;每个出现的字段发一帧 Modbus FC05(线圈地址 = 路号-1)。
let frames = [];
let appInfo = [];
let telemetry_data = {};
let shared_attrs = {};
for (let n = 1; n <= 5; n++) {
let key = "switch_ry" + n;
if (params[key] === undefined) continue;
let on = params[key] ? 1 : 0;
let checkVal = params[key] ? 0xFF00 : 0x0000;
frames.push(RPCHelper.modbusAction(addr_modbus, 5, n - 1, checkVal));
let tdataKey = "ry" + n;
// 每帧回显 8 字节 FC05: [addr][05][coilH][coilL][valH][valL][crcL][crcH]
// 回显线圈值 = uint16BE @ (帧首+4);帧首+6 是 CRC,切勿用作校验。
appInfo.push({field_name: tdataKey, index: appInfo.length * 8 + 4, type: "uint16BE", val: checkVal});
telemetry_data[tdataKey] = on;
shared_attrs[tdataKey] = on;
}
if (frames.length === 0) return null;
let dnBuffer = Buffer.alloc(frames.length * 8);
frames.forEach((f, i) => f.copy(dnBuffer, i * 8));
let checkInfo = {
frameInfo: {port: 51, dataLen: dnBuffer.length},
appInfo: appInfo,
telemetry_data: telemetry_data,
server_attrs: null,
shared_attrs: shared_attrs
};
let msgQue = Utils.makeParaSetMSG({
msgType: Utils.msgType.transParent,
checkInfo: checkInfo,
device: device,
classMode: classMode,
rpcName: rpcName,
params: params,
paraDownBuffer: dnBuffer,
timeout: 3000,
maxRetries: 3
});
if (msgQue.length === 0) return null;
return msgQue;控制逻辑说明:
| 项目 | 说明 |
|---|---|
| Modbus 功能码 | 05(每路一帧) |
| 控制对象 | 任意一路或多路继电器(一次调用) |
| 打开值 | 0xFF00 |
| 关闭值 | 0x0000 |
| 表单字段 | switch_ry1 ~ switch_ry5(布尔) |
| 线圈地址 | 路号 − 1(switch_ry1→0 … switch_ry5→4) |
| 成功回写 | 遥测 ryN + 共享属性 ryN(用于表单回填) |
10. 模板选择
在 ThinkLink 平台中搜索模板:
SWMx-22108或按业务类型查找:
22108模板建议信息:
| 项目 | 内容 |
|---|---|
| 模板名称 | SWMx-22108 |
| 设备类型 | DIDO 控制器 |
| 厂商 | 竞速电子 / JSDZ |
| 业务代码 | 22108 |
| 接入方式 | KC11 + RS-485 + EdgeBus + LoRaWAN |
| 数据上报 | RY 状态、DI 状态 |
| 控制能力 | 支持继电器 RPC 控制 |
| 参数配置 | 支持上传周期、采集周期、Modbus 地址配置 |
11. 对接总结
本方案通过 KC11 + EdgeBus + LoRaWAN + ThinkLink 将 SWMx DIDO 控制器接入物联网平台。
核心特点是:
- 使用 KC11 通过 RS-485 连接 SWMx。
- EdgeBus 使用 Modbus
01读取 RY 状态。 - EdgeBus 使用 Modbus
02读取 DI 状态。 - 01/02 功能码按 bit 读取,EBHelper 已在底层完成与 03/04 的差异处理。
- RY、DI 状态分别以 2 字节 HEX 字符串上传。
- 平台侧同时解析 RY1~RY5、DI1~DI5 状态。
- 支持 RPC 修改上传周期、采集周期和 Modbus 地址。
- 支持 RPC 通过 Modbus 05 功能码控制任意一路或多路继电器(switch_ry1~switch_ry5),并将状态同步回写共享属性供表单回填。
该方案适合将传统 RS-485 DIDO 控制器快速改造成 LoRaWAN 无线设备,并接入 ThinkLink 实现远程监测、远程控制和第三方平台数据订阅。