tklHelper
tklHelper 是 ThinkLink 平台为简化物模型(Thing Model)和 RPC 配置而提供的工具集。通过 JSON 风格的声明式配置,配合 PayloadParser / MSparser / RPCHelper / Utils / TriggerHelper 等类,可以快速实现上行数据解析、参数下发和告警触发,无需手写底层 Buffer 逻辑。
本说明书及完整代码已同步至
tklHelper 1.01.043(来源:tkl-opt/tooling/integration-tools/tklHelper.js,2026-10-12)。在物模型脚本、RPC 脚本和触发模型脚本中,tklHelper暴露的所有类(BufferTKL/PayloadParser/MSparser/Utils/TriggerHelper/RPCHelper)均作为 sandbox 全局变量直接使用,无需 import。
V3 使用前提
V3 是显式启用的参数事务版本,默认仍是 V2。本次文档同步不代表后端 helper 已同步或平台已发布;使用前确认目标运行环境具备 1.01.043。发送 RPC 与接收物模型必须配套迁移,完整示例见第 12 节。
1. frameInfo — 数据帧合法性校验
frameInfo 用于在解析前对上行帧做合法性校验,不符合规则的帧直接返回 null。
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
port | Number | -1 | LoRaWAN 上行端口号。正数时端口不匹配的帧会被丢弃;-1 表示不校验。 |
dataLen | Number | -1 | 预期帧长(字节)。正数时长度不一致的帧会被丢弃;-1 表示不校验。 |
status | Number | -1 | 帧内设备状态字节偏移。>= 0 时自动把该字节读为 uint8 写入 tdata.status。 |
battery | Number | -1 | 帧内电池电压字节偏移。>= 0 时取 uint8 后按 (vbat * 1.6) / 254 + 2.0 换算为电压(V),保留 2 位小数写入 tdata.battery。 |
rssi | Boolean | false | true 时从 msg.gwrx[0] 读取网关侧 RSSI 与 SNR,写入 tdata.rssi / tdata.snr。 |
tagList | Array | [] | 帧头校验数组,每项 { index, tag }:要求 payload[index] === tag,否则丢弃整帧。 |
subDevice | Object | { index: -1, type: "uint8" } | 多子设备场景下标识地址在帧中的位置与类型。index >= 0 时按 type 读取地址写入 tdata.addr;读出 0 则丢弃整帧。 |
示例:
let frameInfo = {
port: 22,
dataLen: 24,
rssi: true,
status: 5,
battery: 6,
tagList: [
{ index: 0, tag: 0x83 },
{ index: 1, tag: 0x23 }
],
subDevice: { index: 7, type: "uint8" }
};2. appInfo — 变量信息标识
appInfo 是数组,每项描述一个要从帧中解析出的应用层变量。
| 字段 | 类型 | 描述 |
|---|---|---|
name | String | 前端展示用名称。 |
field_name | String | 程序与接口调用使用的字段名;也是 tdata 中的键。 |
unit | String | 单位。 |
index | Number | 变量在帧中的起始字节偏移。 |
type | String | 数据类型字符串(见 §3)。 |
coefficient | Number | String | 比例系数。数值:解析值乘以该数。字符串:当作另一变量的 field_name,在第一轮解析完成后再做二次乘法(适用于系数本身编码在帧中的场景)。默认 1。 |
decimal | Number | 保留的小数位数,默认 0。 |
illegal | String | 数据合法性检查,格式 "<操作符><十六进制值>",例如 ">0x80"、"<0x10"、"=0xFF"。操作符为 > / < / =,命中时该字段被静默丢弃。检查值会按当前字段的 type 重新读取,十六进制位宽要与 type 对应。 |
options | Object | 值映射表,例如 { 0: "normal", 1: "fault" }。命中时直接替换解析值;在 coefficient / decimal 之后、postProcess 跳过之前应用。 |
postProcess | String | JS 表达式字符串,输入变量名 value。可写多语句,若不含 return 则自动补 return value;。例:"if (value > 8) value = 8; return value"。 |
示例:
let appInfo = [
{
name: "重量1",
field_name: "weight1",
unit: "kg",
index: 6,
type: "int32be",
coefficient: "precision1", // 乘以字段 "precision1" 的解析值
decimal: 2,
illegal: ">0x7FFFFFFE"
},
{
name: "状态",
field_name: "status",
index: 10,
type: "uint8",
options: { 0: "normal", 1: "fault" }
}
];3. 数据类型详解
类型字符串大小写不敏感(内部统一 toLowerCase())。
3.1 整数类型
| 类型 | 字节数 | 说明 |
|---|---|---|
int8 / uint8 | 1 | |
int16 / int16be / uint16 / uint16be | 2 | 大端(默认) |
int16le / uint16le | 2 | 小端 |
int24 / int24be / uint24 / uint24be | 3 | 大端 |
int24le / uint24le | 3 | 小端 |
int32 / int32be / uint32 / uint32be | 4 | 大端 |
int32le / uint32le | 4 | 小端 |
int40 / uint40 / int48 / uint48 / int56 / uint56(含 BE / LE 变体) | 5 / 6 / 7 | |
int64 / uint64(含 BE / LE 变体) | 8 | 内部使用 BigInt64* 读取后 Number() 转换,超过 2⁵³ 会损失精度。 |
3.2 浮点类型
| 类型 | 字节数 | 说明 |
|---|---|---|
floatbe / floatle | 4 | IEEE 754 单精度浮点,大/小端。 |
floatcdab | 4 | 字节序 2-3-0-1,常见于部分 Modbus 设备。 |
intcdab | 4 | 有符号 32 位整数,字节序 2-3-0-1。 |
3.3 编码数值类型
| 类型 | 说明 |
|---|---|
bcdbe<N> | N 字节大端 BCD。每个 nibble 必须为 0–9,否则抛错。例:bcdbe4。 |
bcdle<N> | N 字节小端 BCD。 |
hexbe<N> | N 字节大端 → 小写十六进制字符串(无 0x 前缀)。 |
hexle<N> | N 字节小端 → 小写十六进制字符串。 |
3.4 位字段类型
格式:bitbe<S>-<E> 或 bitle<S>-<E>。S / E 为起止位索引(含,从 0 起)。
bitbe:字节按大端拼成大整数后再 shift/mask。bitle:字节按小端拼成大整数后再 shift/mask。
例:bitle3-9 按小端字节序提取第 3 到 9 位。
3.5 布尔类型
| 类型 | 返回值 |
|---|---|
bool<N> | N 字节无符号整数 → 非零返回 1,零返回 0。 |
boolean<N> | N 字节无符号整数 → 非零返回 true,零返回 false。 |
3.6 字符串类型
| 类型 | 说明 |
|---|---|
string<N> | N 字节 ASCII 字符串。 |
4. 物模型展示字段配置
控制平台前端如何渲染设备数据:
{
"fields": {
"<field_name>": {
"icon": null,
"name": "<展示名称>",
"type": "<number|string|boolean|object>",
"unit": "<单位>",
"order": 1,
"field_name": "<必须与 appInfo.field_name 一致>"
}
}
}| 字段 | 类型 | 描述 |
|---|---|---|
icon | Null | 当前保持为 null。 |
name | String | 前端显示名。 |
type | String | number / string / boolean / object 之一。 |
order | Number | 展示顺序(升序)。 |
field_name | String | 必须与对应 appInfo.field_name 一致。 |
unit | String | 单位,可为空串。 |
5. BufferTKL — 底层 Buffer 读写
BufferTKL 封装 Node.js Buffer,提供基于 §3 类型字符串的有类型读写。
// 读:传入已有 Buffer 构造,然后调用 read(offset, type)
let buf = new BufferTKL(Buffer.from("0102030405", "hex"));
let val = buf.read(0, "uint16be"); // → 258
// 写:静态方法,返回编码后的 Buffer
let outBuf = BufferTKL.write(258, "uint16be"); // → <Buffer 01 02>| 方法 | 描述 |
|---|---|
new BufferTKL(buffer) | 包装已有 Buffer。若 buffer 为 undefined 或非 Buffer,则包装空 Buffer。 |
buf.read(offset, type) | 从 offset 起按指定类型读取值。越界或类型无效时返回 false。 |
BufferTKL.write(value, type) | 将 value 按指定类型编码为新 Buffer。类型无效时返回空 Buffer。 |
BufferTKL.getTypeInfo(type) | 返回 { type, dataLen, bitStart?, bitEnd? },用于计算字节长度等。 |
6. 解析器类
6.1 PayloadParser
负责 LoRaWAN 上行数据的标准解析。构造参数:
new PayloadParser({
bufferType, // 可选,默认 BufferTKL
device, // 当前设备对象
msg, // 上行 msg(提供 msg.userdata.payload / port、msg.gwrx 等)
frameInfo, // 见 §1
appInfo, // 见 §2
paraInfo, // 见 §6.3,仅 paras() 使用
extraData // 透传字段,子类(如 MSparser)使用
})| 方法 | 说明 |
|---|---|
telemetry() | 解析为遥测数据 tdata。按顺序校验 port / dataLen / tagList / subDevice,然后提取 status / battery / rssi 及各 appInfo 字段,依次应用 illegal / coefficient / decimal / postProcess / options。第二轮处理 coefficient 为字段名字符串的条目。返回对象或 null。 |
paras() | 解析端口 214 的参数回包。按段标识拆分——0x2F(app) / 0x21(fw) / 0x29(cf) / 0x22(radio) / 0x24(ds) / 0x23(status)——再按 paraInfo 键名 <段名>_<地址>[_<k>] 匹配解析。端口不为 214 时直接返回 null。 |
子类可重写 _preParseTelemetry / _postParseTelemetry / _preParseParas / _postParseParas 注入自定义逻辑。
6.2 MSparser — 多传感器 / 多子设备
PayloadParser 的子类,专用于"一帧含多个子设备数据"的场景。构造时必须传 extraData:
new MSparser({ device, msg, frameInfo, extraData })帧格式:重复的 [ID (idType 字节) | 字段数据...] 块。每读到一个 ID,MSparser 按 extraData 中键名 user_0x<ID>_<j>(j = 0..3,每个 ID 最多 4 个字段)找到对应 appInfo 项并自动累加偏移。frameInfo.idType 指定 ID 的读取类型(通常 "uint16be")。
6.3 paraInfo 键命名规范
paraInfo 由 paras() 和 RPCHelper.buildFrame() 使用,将寄存器地址映射到具名字段。
| 键格式 | 含义 |
|---|---|
app_<addr> | APP 段,寄存器地址 <addr>。 |
app_<addr>_<k> | 同地址多个字段,k = 1..15。 |
cf_<addr> / fw_<addr> / radio_<addr> / ds_<addr> / status_<addr> | 配置 / 固件 / 射频 / 设备 / 状态段。 |
值对象结构与 appInfo 项一致(field_name / type / coefficient / decimal / illegal / options / postProcess)。
7. RPCHelper — 参数下发与告警
7.1 设备控制指令
| 方法 | 返回 | 描述 |
|---|---|---|
RPCHelper.reset() | Buffer | 固定字节序列:设备复位。 |
RPCHelper.redo() | Buffer | 固定字节序列:重做。 |
RPCHelper.join() | Buffer | 固定字节序列:重入网。 |
7.2 TKL 协议参数读写
生成 TKL 协议设备的原始参数帧 Buffer。
| 方法 | 描述 |
|---|---|
RPCHelper.paraRead(name, type, addr) | 单寄存器读指令。name 为段名(app / cf / fw / radio / ds / status)。 |
RPCHelper.paraWrite(name, type, addr, value) | 单寄存器写指令。 |
RPCHelper.buildFrame({ serverParaDef, paraDef, params }) | 按 paraInfo 将多字段打包为一个复合帧;自动合并相邻寄存器,最大写 180 字节 / 最大读 50 字节。返回 { writeBuffer, readBuffer, serverPara, log }。 |
RPCHelper.arrangePara(paraInfo) | 将 paraInfo 键按段(app / fw / cf / radio / ds)分组并按寄存器地址排序。buildFrame 内部调用。 |
7.3 Modbus 帧构造
| 方法 | 描述 |
|---|---|
RPCHelper.buildmodbusFrame06({ serverParaDef, paraDef, params }) | 构造 Modbus FC06(写单个寄存器)帧,含 CRC16 Modbus。 |
RPCHelper.buildmodbusFrame10({ serverParaDef, paraDef, params }) | 构造 Modbus FC10(写多个寄存器)帧。 |
RPCHelper.buildmodbusFrameRead(addr, code, regStart, count) | 构造 Modbus 读帧(如 FC03 / FC04)。addr 和寄存器值支持 "0x..." 字符串或十进制数。 |
RPCHelper.modbusAction(addr, code, regAddr, regVal, valBuffer?) | 构造通用 Modbus 单写帧;如提供 valBuffer 则追加到帧尾。 |
7.4 其他协议帧
| 方法 | 描述 |
|---|---|
RPCHelper.buildSensorFrame({ serverParaDef, paraDef, params }) | 通用传感器协议帧,帧头须以 paraDef.cmd_header(十六进制字符串)给出。 |
RPCHelper.cj188reader(addr, model) | 构造 CJ/T 188 读表请求。addr 解码后必须恰好 7 字节,model 解码后必须恰好 5 字节,否则返回空 Buffer。 |
7.5 下行消息构造
| 方法 | 描述 |
|---|---|
RPCHelper.makeMSG({ msgType, device, dnBuffer, type?, port?, confirmed?, sleepTime?, dnWaitms?, intervalms? }) | 构造下行消息体。msgType 取 Utils.msgType.*:user(自定义端口)/ paras(port 214)/ transParent(port 51)/ swDown(直发 LoRa SW 帧)。 |
RPCHelper.makeDnDataClear(eui) | 构造下行 dataClear 消息(port 214)。 |
RPCHelper.makeAlarm({ alarms, eui, name, action, group, title, desc, level }) | 构造告警消息;action = "new" 且现存告警的 title / desc / level 完全一致时返回 null(去重)。 |
8. Utils — 工具函数
8.1 参数下发状态机
库版本 1.01.043 与事务版本不同。所有版本参数仅接受数字 2 / 3,省略或 undefined 等同于 2;字符串 "3"、null 等会抛错。V3 不回退读取 V2,也不自动迁移旧事务。
| 方法 | 描述 |
|---|---|
Utils.makeParaSetMSG({ version, msgType, checkInfo, device, classMode, rpcName, params, paraDownBuffer, extraAppBuffer, timeout, retryIntervalMs, echoTimeoutMs, maxRetries, telemetryFields }) | 返回属性保存动作、下行参数包及可选重发包的队列。V2 状态使用 server_attrs;V3 完整事务保存在 process_data.__para_set__<槽位>。 |
Utils.paraCheck(rpcName, device, data, { version: 3 }) | V3 检查入口,第二参为完整设备。返回标准物模型字段 process_data / telemetry_data / shared_attrs / server_attrs / actions,只输出本次需要的字段;无事务或已终态返回 {}。 |
Utils.paraCheck(rpcName, device.server_attrs, data) | V2 兼容入口,返回 { sdata, tdata, pdata, actions },需转换为标准物模型字段。 |
Utils.getParaSetInfo({ device, rpcName, version, msgType? }) | 返回 { version, rpcInfo, checkState } 的独立副本,无记录为 null;不写回,也不自动判定过期。 |
Utils.updateParaSetInfo({ device, rpcName, version, msgType?, updates }) | 校验后生成待保存补丁;不修改设备、不直接保存、不重新下发。用法和允许修改的字段见第 12.6 节。 |
makeParaSetMSG 参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
version | 2 | 显式传数字 3 启用 V3;每次 RPC 调用(含自动重试)都需固定传入。 |
msgType | Utils.msgType.paras | paras 为 214 端口;transParent 为 51 端口,沿用设备实际通道。 |
timeout | Class A:864000000;Class C:5000 | 毫秒。Class C 的单次响应预算,并作为默认重发间隔;Class A 作为默认总确认窗口。工程接入应按实际链路显式选择,不直接照搬库默认值。 |
retryIntervalMs | timeout | Class C 预排重发间隔,单位毫秒。 |
echoTimeoutMs | Class C:retryIntervalMs * maxRetries + timeout;Class A:timeout | 总确认窗口,单位毫秒;保存到 rpcInfo.timeout。Class C 至少覆盖最后一包计划时间,工程上还应留足最后一包响应与排队裕量。 |
maxRetries | 3 | 首次发送以外的重试次数。Class C 在首次调用时预排;Class A 在检查未匹配时按状态生成重试动作。 |
classMode | device.shared_attrs.class_mode,缺省 "ClassA" | "ClassA" / "ClassC";Class C 确认成功后返回清队列动作。 |
rpcName | — | 实际可调用的 RPC 标识;V3 必填。普通 RPC 使用独立槽位,透传共用 transParent 槽位。 |
device | — | 当前设备完整对象。 |
params | — | 参与设备反馈比较的参数,保留真实字段名和类型。 |
paraDownBuffer | — | 沿用协议编码器生成的原始下行 Buffer。 |
extraAppBuffer | — | 可选,主包之后追加的额外 APP 段 Buffer。 |
checkInfo | — | 省略时直接比较已解析参数;提供对象时选择报文检查,V3 必须有非空 appInfo 及期望值 val。 |
telemetryFields | — | V3 确认后回填的遥测字段白名单。显式 checkInfo.telemetry_data 优先,null 关闭遥测回填;无映射或白名单则不自动回填。 |
V3 事务及新增读写接口要求数据可无损表示为 JSON;可选字段请省略,不能含 undefined 值、非有限数字、函数、BigInt、循环引用、稀疏数组或非普通对象。下行 Buffer 仍按原协议传入。不要把传输封装中的 dnMsg.version: "3.0" / "3.1" 当成事务版本。
8.2 Buffer / 值解析工具
| 方法 | 描述 |
|---|---|
Utils.parseToBuffer(inputStr) | 将十六进制字符串(支持 0x 前缀和空格)解析为 Buffer。undefined 返回空 Buffer。 |
Utils.parseVal(valstr) | 将 "0x..." 十六进制字符串或十进制字符串解析为数值。若已是数值则原样返回。 |
8.3 CRC 计算
| 方法 | 描述 |
|---|---|
Utils.crcSum(buf, len, poly?) | 简单累加校验:buf[0..len-1] 求和,16 位掩码。poly 默认 0。 |
Utils.crc16CCIT(data, len, poly?) | CRC-16/CCITT。poly 默认 0x1021。 |
Utils.crc16Modbus(buf, len, poly?) | CRC-16/Modbus。poly 默认 0xA001。 |
8.4 枚举常量
| 常量 | 值 | 说明 |
|---|---|---|
Utils.ACTION | { no, new, clear } | 告警动作枚举。 |
Utils.LEVEL | { low, mid, high, urgent } | 告警等级枚举。 |
Utils.msgType | { paras, transParent, swDown, user } | 下行通道类型枚举。 |
Utils.paraName | { app, cf, fw, radio, user } | 参数段名称枚举。 |
9. TriggerHelper — 告警类触发模型基类
继承 TriggerHelper 并重写 _alarmProcess(info),可避免手写完整的触发返回对象。详见 TriggerModel。
构造参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
device | — | 当前设备对象。 |
thingModelId | — | 物模型 ID;构造时自动取 device.telemetry_data[thingModelId] 存入 this.tdata。 |
alarmEvent | device.name | 告警事件名。 |
title | "" | 告警标题,自动加 [device.name] : 前缀。 |
desc | "" | 告警描述。 |
level | Utils.LEVEL.low | low / mid / high / urgent。 |
action | Utils.ACTION.new | no / new / clear。 |
alarm(info) 行为:
- 调用
_alarmProcess(info)。 - 若返回
false或action为no,返回null。 - 否则返回标准触发返回对象:
{ delayms: 0, abort_previous_timer: true, actions: [{ method: "alarm", params: {...} }] }。
10. BufferTKL 常量
| 常量 | 值 | 说明 |
|---|---|---|
BufferTKL.PERIOD_DEFAULT | 900 | 默认上报周期 15 分钟(秒)。 |
BufferTKL.PERIOD_DUMB | 86400 × 3650 ≈ 10 年 | "事实上禁用周期"的占位值。 |
BufferTKL.PERIOD_MAX | 86400 × 365 | 周期上限判定值(1 年,秒)。 |
BufferTKL.PERIOD_TRIGGER | "TRIGGER" | 表示"由触发驱动,无固定周期"。 |
BufferTKL.INVALID_NUM | -1000000000 | 数值类型的 sentinel(下发时跳过)。 |
BufferTKL.INVALID_STR | "INVALID" | 字符串类型的 sentinel(下发时跳过)。 |
11. 完整示例
function payload_parser({ device, msg, thingModelId, noticeAttrs }) {
let port = msg?.userdata?.port || null;
let frameInfo = {
port: 11, dataLen: 15, rssi: true,
tagList: [
{ index: 0, tag: 0x21 },
{ index: 1, tag: 0x07 },
{ index: 2, tag: 0x03 }
]
};
let appInfo = [
{ name: "status", field_name: "status", unit: "", index: 7, type: "bitle0-0" },
{ name: "leakStatus", field_name: "leakStatus", unit: "", index: 7, type: "bitle4-4" },
{ name: "temperature", field_name: "temperature", unit: "℃", index: 8, type: "uint16le" },
{ name: "humidity", field_name: "humidity", unit: "%", index: 10, type: "uint16le", coefficient: 0.1, decimal: 1 },
{ name: "vbat", field_name: "vbat", unit: "v", index: 12, type: "uint8" }
];
let paraInfo = {
app_40: { name: "upPeriod", field_name: "upPeriod", unit: "s", type: "uint32le" },
app_142: { name: "measurePeriod", field_name: "measurePeriod", unit: "s", type: "uint16le" },
app_144: { name: "covTemperature", field_name: "covTemperature", unit: "℃", type: "uint8", coefficient: 0.1, decimal: 1 },
app_145: { name: "covHumidity", field_name: "covHumidity", unit: "%", type: "uint8", coefficient: 0.1, decimal: 1 }
};
let payParser = new PayloadParser({ device, msg, frameInfo, appInfo, paraInfo });
let tdata = null, pdata = null;
if (port === 214) {
pdata = payParser.paras();
} else {
tdata = payParser.telemetry();
}
if (tdata) {
tdata.status = tdata.status !== 0 ? "fault" : "normal";
tdata.temperature = Number(((tdata.temperature - 1000) * 0.1).toFixed(1));
tdata.vbat = Number(((tdata.vbat * 1.6) / 254 + 2.0).toFixed(2));
}
return {
telemetry_data: tdata,
server_attrs: null,
shared_attrs: pdata
};
}12. V3:RPC 与物模型接入
适用版本:tklHelper 1.01.043(tkl-opt 中也记为 1.01.43),依据 2026-10-13 的接入合同及实现核对。使用前需确认目标脚本运行环境已加载所需 API,不能由文档版本推断所有环境均已升级。本文基础片段说明函数用法;生产子设备链路还需落实第 12.9 节的回读、事务身份及业务反馈要求。
库版本 1.01.043 与参数事务 version: 2 / 3 是两种编号。省略事务版本仍走 V2;只有显式传数字 3 才启用新方式。RPC 与接收物模型必须配套选择版本。
12.1 哪些地方需要改
| 位置 | V2 旧模式 | V3 新模式 |
|---|---|---|
| RPC 下发 | 原 makeParaSetMSG() 调用 | 增加 version: 3,按需配置回填 |
| 物模型回显检查 | paraCheck(rpcName, device.server_attrs, data) | paraCheck(rpcName, device, data, { version: 3 }) |
| 物模型返回 | 将 sdata/tdata/pdata 转换为标准字段 | 直接返回 helper 的标准结果,或与原解析结果按字段合并 |
| 过程记录 | server_attrs 中的原槽位 | process_data.__para_set__<槽位> |
| 页面业务反馈 | 原逻辑 | 读取确认后产生的遥测;过程数据本身不推送 MQTT |
RPC 中的 rpcName 是实际可调用的 RPC 标识,必须沿用目标设备已有标识。不要把 UI 显示名当成标识。物模型负责检查本设备、本协议的有效反馈;helper 没有新增跨设备路由或通用 CRC 校验。
以下代码均为脚本函数体或嵌入片段。平台上应保存实际执行的函数体,不要只保存一个未被调用的函数声明。既有参数校验、协议编码、设备路由和持久化执行日志保留。
12.2 RPC:发送参数并记录 V3 事务
此例展示已解析参数的直接比较方式。接入前已有:
device:本次目标设备。rpcName:本次实际 RPC 标识。params:当前 RPC 已校验的业务参数。paraDownBuffer:沿用原协议编码器得到的下行 Buffer。
target_value 是教学字段,替换成设备实际字段;不要把用户原始输入、密码或其他不参与反馈的字段整体传给比较参数。
// 示例:只把需要设备反馈确认的参数交给 helper。
return Utils.makeParaSetMSG({
version: 3,
device,
rpcName,
msgType: Utils.msgType.paras,
params: { target_value: params.target_value },
paraDownBuffer,
telemetryFields: ['target_value']
});msgType 必须沿用实际通道:本地 helper 的 paras 使用 214 端口,transParent 使用 51 端口。这里不能为了示例改变原设备通道;透传报文检查见第 12.4 节。
telemetryFields 是允许确认后回填的字段白名单,字段需要在接收物模型中声明,名称、类型和单位与解析值一致。没有白名单或显式映射时,不自动回填所有参数。
RPC 每次进入,包括 Class A 的自动重试入口,都必须固定传 version: 3。不能只在首次页面提交时传版本,因为 helper 的重试动作不会把版本加入业务参数。生成队列只代表待执行动作,不能在这里报告设备已生效。
12.3 物模型:使用已解析的新反馈确认
parsedParams 必须来自当前有效报文的解析结果,不能使用缓存的遥测或 RPC 的目标值代替设备反馈。协议层先检查端口、长度、地址、功能码及适用的校验码,再执行下面的片段。
// rpcName 与第 12.2 节的发送 RPC 一致。
// parsedParams 示例:{ target_value: 30 },数值/字符串类型必须一致。
return Utils.paraCheck(rpcName, device, parsedParams, { version: 3 });对于仅处理参数回显的分支,可以直接返回上述结果。输出可能包括:
| 输出字段 | 用途 |
|---|---|
process_data | 保存本次事务的完整新状态 |
telemetry_data | 确认成功后的业务值回填 |
shared_attrs / server_attrs | 按显式配置回填业务属性 |
actions | 原重试动作或 Class C 的清队列动作 |
没有对应事务或记录已终态时返回 {}。不要只取 telemetry_data 而丢掉其他字段,否则会丢失事务状态或取消动作。
如果同一物模型还返回正常采样数据,保留原解析结果,只在没有同名字段冲突时合并 helper 返回的各组字段,并保留双方 actions 的顺序。不要用 { ...baseResult, ...checkResult } 覆盖整组遥测/属性;也不要重新发布缓存中的旧采样。已有方案中的 Utils.mergeParaResult() 尚未实现,不能调用。
12.4 透传回显:使用 checkInfo 检查报文
真实设备应沿用已验证的 frameInfo、appInfo 和协议编码。下面使用合成的 51 端口、两字节大端数值说明接口配合,不是 Modbus 帧,也不能直接用于现场设备。
RPC 示例:
// 教学协议:下行和回显都只有一个 uint16be 数值。
const value = params.target_value;
if (!Number.isInteger(value) || value < 0 || value > 65535) {
throw new TypeError('target_value 必须是 0 到 65535 的整数');
}
const paraDownBuffer = Buffer.alloc(2);
paraDownBuffer.writeUInt16BE(value, 0);
return Utils.makeParaSetMSG({
version: 3,
device,
rpcName,
msgType: Utils.msgType.transParent,
params: { target_value: value },
paraDownBuffer,
checkInfo: {
frameInfo: { port: 51, dataLen: 2 },
appInfo: [
{ field_name: 'target_value', index: 0, type: 'uint16be', val: value }
]
},
telemetryFields: ['target_value']
});对应物模型的回显分支:
return Utils.paraCheck(
Utils.msgType.transParent,
device,
{ device, msg },
{ version: 3 }
);这里 msg 是平台本次收到的消息,载荷位于 msg.userdata.payload(Base64),端口位于 msg.userdata.port。实际下挂地址、设备身份及帧有效性按设备协议过滤后再进入此分支。
透传继续共用一个 transParent 事务槽位,新命令会替换该槽位。不要据此并行发出多个独立透传事务。内部状态不能严格区分两次相同命令的迟到同值回显。
同时回填遥测和 shared/server 属性
在第 12.4 节有效的 checkInfo 中增加显式映射,例如:
const checkInfo = {
frameInfo: { port: 51, dataLen: 2 },
appInfo: [
{ field_name: 'target_value', index: 0, type: 'uint16be', val: value }
],
telemetry_data: { target_value: value },
shared_attrs: { target_value: value }
};将该对象传给 makeParaSetMSG(),只有反馈匹配成功后才回填。需要服务端业务属性时同样配置 server_attrs,不要写入 helper 的保留状态键。
checkInfo.telemetry_data 优先于 telemetryFields;设为 null 明确关闭遥测回填。不能只传 { telemetry_data: null } 作为完整 checkInfo,因为提供该对象即选择报文检查,仍需有效的非空 appInfo。
12.5 Class C:保持预排重发,回显成功后取消
继续使用既有机制,不增加定时任务或新的重试调度。maxRetries 是首次发送以外的重试次数,maxRetries: 2 即最多构造 3 个参数下行动作。
当前接入合同使用 retryIntervalMs 控制预排间隔、echoTimeoutMs 控制总确认窗口。省略两者时,间隔等于 timeout,Class C 总窗口为 retryIntervalMs * maxRetries + timeout;历史已保存的窗口不会自动更新。目标宿主须核实这些新增参数,不能只凭库版本字符串判断。网站附带源码页是较早的 V3 快照,下面使用已有读写接口调整尚未提交的事务记录,兼容该快照与当前实现。以下仅适用于已授权、允许有限重试的参数操作:
// 示例时间需按实际链路确定;不代表已经确认实际发射时刻。
const queue = Utils.makeParaSetMSG({
version: 3,
device,
rpcName,
msgType: Utils.msgType.paras,
params: { target_value: params.target_value },
paraDownBuffer,
telemetryFields: ['target_value'],
classMode: 'ClassC',
timeout: 10000,
maxRetries: 2
});
const stateAction = queue.find(action => action?.type === 'modifyAttrs');
const patch = Utils.updateParaSetInfo({
device: stateAction.dnMsg,
rpcName,
version: 3,
updates: { rpcInfo: { timeout: 60000 } }
});
stateAction.dnMsg = { ...stateAction.dnMsg, ...patch };
return queue;结果:计划在相对 0、10、20 秒安排最多 3 包,确认窗口从创建记录起计 60 秒,队列重发间隔仍为 10 秒。已核实支持新增参数的宿主,也可直接传 retryIntervalMs: 10000, echoTimeoutMs: 60000 而省略后续补丁;当前实现不传 echoTimeoutMs 且不调整记录时,这组配置的默认总窗口为 30 秒,旧快照不具备该默认扩窗行为。10 秒是一般 RS-485+LoRaWAN 单次等待的工程基准,并非标准保证;慢处理、排队与反馈时延应实测调整。透传场景沿用第 12.4 节的通道和检查配置。不匹配反馈不会提前取消 Class C 队列;非幂等动作默认显式设 maxRetries: 0,页面不得在超时后另起一轮重发。
Class A 不能套用上述短总窗口:还须计入下一次上行接收机会与反馈上传。rpcInfo.ms 是事务创建时间,不是实际发射证明;Class A 重试由上行检查推进,需同时落实第 12.9 节的重试身份保护。
成功确认时物模型返回的 actions 会调用 mt_action 的 downBufferClear,需要目标设备具备对应 RPC 绑定和权限。当前请求没有携带具体命令标识,不应理解为按某条命令精确取消;已发出的包无法撤回。
到期不会主动调用检查函数;在之后调用 paraCheck() 时才可能写入 EXPIRED。getParaSetInfo() 也不会自动计算或写入过期状态。读取端如需及时显示超时,应结合 rpcInfo.ms + rpcInfo.timeout 判断,并显示“结果未确认”,不能声称已执行取消。
12.6 获取、修改事务信息
读取(可用于 RPC 或物模型脚本内部):
const info = Utils.getParaSetInfo({ device, rpcName, version: 3 });
// 找不到:null。
// 找到:{ version, rpcInfo, checkState } 的独立副本。
// 常用字段:info.rpcInfo.params、info.rpcInfo.timeout、info.checkState.phase。这只是脚本内部读取,不是一个已创建的“查询 RPC”,也不要把 info 当作下行动作数组返回。读取历史 V2 时省略 version 或传 2;历史命令记录缺少版本号时按 V2 解释,读取不会回写。
在 RPC 内修改并提交补丁:
const patch = Utils.updateParaSetInfo({
device,
rpcName,
version: 3,
updates: { rpcInfo: { timeout: 60000 } }
});
return [{ sleepTimeMs: 0, type: 'modifyAttrs', dnMsg: patch }];在物模型内修改时,返回 patch 或与本次结果的对应字段合并。不要直接修改只读 device,修改 getParaSetInfo() 返回的副本也不会保存。
| 可修改字段 | 说明 |
|---|---|
rpcInfo.timeout | 非负有限数字,单位毫秒,仍以原 ms 为起点 |
rpcInfo.params | 比较参数对象,整体替换;不会向设备重新下发 |
rpcInfo.checkInfo | 检查及映射对象,整体替换,V3 必须通过检查配置校验 |
rpcInfo.classMode | ClassA 或 ClassC |
rpcInfo.telemetryFields | V3 遥测白名单 |
checkState.maxRetries | 非负整数 |
不能修改版本、RPC 身份、时间戳、阶段和计数。不存在的事务会抛错,不会自动创建。改变已提交记录不撤回或重排已入队的下行;终态记录不会因调整超时而重新激活。
12.7 原 V2 的用法
RPC 保留原调用即可:
return Utils.makeParaSetMSG({ device, rpcName, params, paraDownBuffer });物模型继续使用原参数和返回字段转换:
const result = Utils.paraCheck(rpcName, device.server_attrs, parsedParams);
return {
server_attrs: result.sdata,
telemetry_data: result.tdata,
shared_attrs: result.pdata,
actions: result.actions
};透传旧调用继续使用原来的 transParent 槽位及 { device, msg } 输入。不要只把发送端切成 V3 而保留 V2 接收端。切换版本不会自动迁移或清理旧事务,等待中的命令和已有下行队列需单独处理。
12.8 接入检查
- 目标环境具备本地新增 API,发送和接收均使用同一事务版本。
- 沿用正确的设备身份、RPC 标识、通道、协议编码和反馈校验;遥测字段已在目标物模型声明。
- 回显分支返回全部 helper 结果,Class C 的清队列 RPC 已绑定且可执行。
- 保存后回读事务版本、目标值和状态;检查不匹配、成功、重复回复及过期分支。过程补丁是完整事务快照,不具备并发锁或原子比较更新能力。
- RPC 保留持久化、可查询执行日志,覆盖读取、修改、异常和无变化分支,并关联异步确认。本文片段只展示 helper 接入,不提供新的日志服务;
process_data不是执行日志。 - 平台受理、实际下发、真实反馈、遥测/MQTT 和页面结束等待分别验收。当前说明及本地示例不能替代这些环境验证。
12.9 子设备回读、参数回填与动作确认
本节适用于新建或本次修改的子设备 SET、GET、Action。发送 RPC 与接收物模型配套处理,保留真实设备身份、原 RPC 标识、协议、绑定和类型。只增加 version: 3 不会自动补齐以下业务保证。
只用本次有效反馈判断
先确认目标子设备、父采集器/接口、从机地址及接收模型,再校验报文;helper 不提供跨设备路由或通用 CRC 校验。
| 反馈通道/类型 | 接收端判断 |
|---|---|
| 214 参数帧 | 按真实 paraDef 解析;SET、GET、模型的地址、字段、类型、宽度一致。仅把本帧实际出现的字段传给 paraCheck |
| 51 单帧 Modbus FC05/FC06 | 检查完整 8 字节应答,包括地址、功能码、寄存器、值及 CRC,不能只检查值或帧头 |
| 51 单帧 Modbus FC10 | 应答包含地址、功能码、起始寄存器及数量;期望应答为请求前 6 字节加重新计算的应答 CRC,不能取请求的 CRC 或把请求数据段当回包 |
| 异常帧、多帧或非 Modbus | 异常应答不能作为成功;多帧、M-Bus 等按实际协议单独解析,不能统一截前 8 字节 |
| 非参数帧、部分反馈 | 非参数帧用空比较对象推进适用的事务检查;部分反馈只交实际字段,不能用缓存或目标值补齐。报文检查模式仅把有效目标帧交给解析器 |
例如本次目标是 { upper: 30, lower: 20 },当前帧只有 { upper: 30 },即使缓存已有 lower: 20 也不能判定成功。当前 helper 不提供分字段部分确认。GET 没有设置目标时只生成读取队列、保存实际读数,不制造写事务 SUCCESS。
事务身份、兼容与迟到反馈
生产脚本需要在 helper 外增加适配逻辑;tkl-opt 的 __makeParaV3、__checkParaV3、__getTransaction 是嵌入脚本的函数,不是 Utils 自带 API,也不会由平台自动加载。
- 同时处理 V2/V3 时,适配器读取实际存在的槽位与旧 RPC 别名,按
rpcInfo.ms选择最新记录,同时间优先 V3;最新记录已终态时不复活旧事务。helper 本身不自动回退 V2。V2 结果需转换为标准模型字段。 - Class A 自动回调携带内部标记
_tkl_v3_retry={version,rpc,ms,transactionId}。RPC 最外层在任何下发/复位之前,校验当前事务身份、调用目标及RETRY_READY阶段;不符则停止该过时回调。调用 helper 前剥离标记,无标记用户调用建立新事务,不沿用旧重试预算。 - 升级前无标记的在途回调无法可靠区分于新调用,须明确处理窗口。内部
transactionId不进入设备协议,不能证明设备回包属于本次请求。 - 透传共用
process_data.__para_set__transParent,同一槽位按顺序执行;它不是并发锁。没有协议请求号时,相同命令的迟到同值回包仍可能无法严格区分。 - 单帧 Modbus 适配器可生成并比较
expected_hex;这也是脚本层校验约定,helper 不内置此字段含义,发送与接收必须配套实现。
参数确认与实际开关机分开
确认参数优先写到目标模型已声明的 telemetry_data,名称、类型、单位保持一致。使用 telemetryFields 白名单或有效 checkInfo 内的显式 telemetry_data 映射;后者优先,显式 null 关闭自动遥测回填。旧表单需要时可双写已确认参数的 shared_attrs,不删除原业务属性或把密码、内部参数整体公开。
开关机写应答只说明接受命令。 应在完整、有效的 checkInfo 中设 telemetry_data: null,且不通过 shared/server 映射伪造实际 power_switch 或观测时间。页面等待本设备、本模型、本次命令之后的有效状态与目标一致,再显示“设备已回报开机/关机”。ACKNOWLEDGED、CONFIRMED_REPORTED_STATE 可以作为业务层阶段,但不是 helper 自动提供的通用状态。
表单“遥测优先”在应用读取层实现:先核实目标模型、字段、类型及有效性,缺失再回退兼容属性或已批准默认值;旧值保留原时间。不能虚构 default_from: "telemetry_data" 等平台枚举,更不能用表单回填值作事务确认。
模型输出、MQTT 与验收
按组保留 process_data、telemetry_data、shared_attrs、server_attrs 补丁及所有 actions,同名冲突明确处理。不要整组覆盖,也不要输出缓存旧遥测。过程数据顶层浅合并、缓冲持久化,不是原子锁或强持久化账本。
每次有效反馈由单一责任方通过平台已有模型/属性通知链发布可关联的 MQTT 业务通知,重复回包去重,后续真实状态变化仍正常通知;不额外并发发送一份 customMqtt。走属性通知时,核实最小 forward_fields 白名单与实际消息:forward: true 不绕过白名单,forward: false 不发送本次属性通知,配置白名单本身不会主动产生消息。事务 SUCCESS、过程数据、日志、rpcHook 和清队列动作均不能替代业务通知,也不提供 MQTT 网络层 exactly-once 保证。
到期先显示“结果未确认”,协议支持时执行一次有界只读查询;不自动重复原动作。持久化日志关联设备、RPC、事务及反馈阶段,覆盖校验、入队、异常、无变化与异步结果。Utils.withExecutionLog 只适配宿主 logger;仍需查询落库证据,日志失败不得重复物理下发。
至少联测错误地址/CRC、部分反馈、缓存同值、重复/迟到包、V2/V3 最新记录选择、Class A 过时重试、Class C 最后一包反馈、超时不重复动作,以及“写 ACK 到达但实际状态未到”。分别保留本地测试、平台保存回读、真实设备反馈、实际 MQTT 和页面结束等待的证据。
完整流程与验收表见 AN-26101301 子设备控制最佳实践。平台返回格式参见 RPC 模型和物模型。完整库实现见 tklHelper 代码。