Skip to content

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。

字段类型默认值描述
portNumber-1LoRaWAN 上行端口号。正数时端口不匹配的帧会被丢弃;-1 表示不校验。
dataLenNumber-1预期帧长(字节)。正数时长度不一致的帧会被丢弃;-1 表示不校验。
statusNumber-1帧内设备状态字节偏移。>= 0 时自动把该字节读为 uint8 写入 tdata.status。
batteryNumber-1帧内电池电压字节偏移。>= 0 时取 uint8 后按 (vbat * 1.6) / 254 + 2.0 换算为电压(V),保留 2 位小数写入 tdata.battery。
rssiBooleanfalsetrue 时从 msg.gwrx[0] 读取网关侧 RSSI 与 SNR,写入 tdata.rssi / tdata.snr。
tagListArray[]帧头校验数组,每项 { index, tag }:要求 payload[index] === tag,否则丢弃整帧。
subDeviceObject{ index: -1, type: "uint8" }多子设备场景下标识地址在帧中的位置与类型。index >= 0 时按 type 读取地址写入 tdata.addr;读出 0 则丢弃整帧。

示例:

javascript
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 是数组,每项描述一个要从帧中解析出的应用层变量。

字段类型描述
nameString前端展示用名称。
field_nameString程序与接口调用使用的字段名;也是 tdata 中的键。
unitString单位。
indexNumber变量在帧中的起始字节偏移。
typeString数据类型字符串(见 §3)。
coefficientNumber | String比例系数。数值:解析值乘以该数。字符串:当作另一变量的 field_name,在第一轮解析完成后再做二次乘法(适用于系数本身编码在帧中的场景)。默认 1。
decimalNumber保留的小数位数,默认 0。
illegalString数据合法性检查,格式 "<操作符><十六进制值>",例如 ">0x80"、"<0x10"、"=0xFF"。操作符为 > / < / =,命中时该字段被静默丢弃。检查值会按当前字段的 type 重新读取,十六进制位宽要与 type 对应。
optionsObject值映射表,例如 { 0: "normal", 1: "fault" }。命中时直接替换解析值;在 coefficient / decimal 之后、postProcess 跳过之前应用。
postProcessStringJS 表达式字符串,输入变量名 value。可写多语句,若不含 return 则自动补 return value;。例:"if (value > 8) value = 8; return value"。

示例:

javascript
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 / uint81
int16 / int16be / uint16 / uint16be2大端(默认)
int16le / uint16le2小端
int24 / int24be / uint24 / uint24be3大端
int24le / uint24le3小端
int32 / int32be / uint32 / uint32be4大端
int32le / uint32le4小端
int40 / uint40 / int48 / uint48 / int56 / uint56(含 BE / LE 变体)5 / 6 / 7
int64 / uint64(含 BE / LE 变体)8内部使用 BigInt64* 读取后 Number() 转换,超过 2⁵³ 会损失精度。

3.2 浮点类型 ​

类型字节数说明
floatbe / floatle4IEEE 754 单精度浮点,大/小端。
floatcdab4字节序 2-3-0-1,常见于部分 Modbus 设备。
intcdab4有符号 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. 物模型展示字段配置 ​

控制平台前端如何渲染设备数据:

json
{
    "fields": {
        "<field_name>": {
            "icon": null,
            "name": "<展示名称>",
            "type": "<number|string|boolean|object>",
            "unit": "<单位>",
            "order": 1,
            "field_name": "<必须与 appInfo.field_name 一致>"
        }
    }
}
字段类型描述
iconNull当前保持为 null。
nameString前端显示名。
typeStringnumber / string / boolean / object 之一。
orderNumber展示顺序(升序)。
field_nameString必须与对应 appInfo.field_name 一致。
unitString单位,可为空串。

5. BufferTKL — 底层 Buffer 读写 ​

BufferTKL 封装 Node.js Buffer,提供基于 §3 类型字符串的有类型读写。

javascript
// 读:传入已有 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 上行数据的标准解析。构造参数:

javascript
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:

javascript
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 参数说明:

参数默认值说明
version2显式传数字 3 启用 V3;每次 RPC 调用(含自动重试)都需固定传入。
msgTypeUtils.msgType.parasparas 为 214 端口;transParent 为 51 端口,沿用设备实际通道。
timeoutClass A:864000000;Class C:5000毫秒。Class C 的单次响应预算,并作为默认重发间隔;Class A 作为默认总确认窗口。工程接入应按实际链路显式选择,不直接照搬库默认值。
retryIntervalMstimeoutClass C 预排重发间隔,单位毫秒。
echoTimeoutMsClass C:retryIntervalMs * maxRetries + timeout;Class A:timeout总确认窗口,单位毫秒;保存到 rpcInfo.timeout。Class C 至少覆盖最后一包计划时间,工程上还应留足最后一包响应与排队裕量。
maxRetries3首次发送以外的重试次数。Class C 在首次调用时预排;Class A 在检查未匹配时按状态生成重试动作。
classModedevice.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。
alarmEventdevice.name告警事件名。
title""告警标题,自动加 [device.name] : 前缀。
desc""告警描述。
levelUtils.LEVEL.lowlow / mid / high / urgent。
actionUtils.ACTION.newno / new / clear。

alarm(info) 行为:

  1. 调用 _alarmProcess(info)。
  2. 若返回 false 或 action 为 no,返回 null。
  3. 否则返回标准触发返回对象:{ delayms: 0, abort_previous_timer: true, actions: [{ method: "alarm", params: {...} }] }。

10. BufferTKL 常量 ​

常量值说明
BufferTKL.PERIOD_DEFAULT900默认上报周期 15 分钟(秒)。
BufferTKL.PERIOD_DUMB86400 × 3650 ≈ 10 年"事实上禁用周期"的占位值。
BufferTKL.PERIOD_MAX86400 × 365周期上限判定值(1 年,秒)。
BufferTKL.PERIOD_TRIGGER"TRIGGER"表示"由触发驱动,无固定周期"。
BufferTKL.INVALID_NUM-1000000000数值类型的 sentinel(下发时跳过)。
BufferTKL.INVALID_STR"INVALID"字符串类型的 sentinel(下发时跳过)。

11. 完整示例 ​

javascript
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 是教学字段,替换成设备实际字段;不要把用户原始输入、密码或其他不参与反馈的字段整体传给比较参数。

js
// 示例:只把需要设备反馈确认的参数交给 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 的目标值代替设备反馈。协议层先检查端口、长度、地址、功能码及适用的校验码,再执行下面的片段。

js
// 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 示例:

js
// 教学协议:下行和回显都只有一个 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']
});

对应物模型的回显分支:

js
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 中增加显式映射,例如:

js
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 快照,下面使用已有读写接口调整尚未提交的事务记录,兼容该快照与当前实现。以下仅适用于已授权、允许有限重试的参数操作:

js
// 示例时间需按实际链路确定;不代表已经确认实际发射时刻。
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 或物模型脚本内部):

js
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 内修改并提交补丁:

js
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.classModeClassA 或 ClassC
rpcInfo.telemetryFieldsV3 遥测白名单
checkState.maxRetries非负整数

不能修改版本、RPC 身份、时间戳、阶段和计数。不存在的事务会抛错,不会自动创建。改变已提交记录不撤回或重排已入队的下行;终态记录不会因调整超时而重新激活。

12.7 原 V2 的用法 ​

RPC 保留原调用即可:

js
return Utils.makeParaSetMSG({ device, rpcName, params, paraDownBuffer });

物模型继续使用原参数和返回字段转换:

js
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 接入检查 ​

  1. 目标环境具备本地新增 API,发送和接收均使用同一事务版本。
  2. 沿用正确的设备身份、RPC 标识、通道、协议编码和反馈校验;遥测字段已在目标物模型声明。
  3. 回显分支返回全部 helper 结果,Class C 的清队列 RPC 已绑定且可执行。
  4. 保存后回读事务版本、目标值和状态;检查不匹配、成功、重复回复及过期分支。过程补丁是完整事务快照,不具备并发锁或原子比较更新能力。
  5. RPC 保留持久化、可查询执行日志,覆盖读取、修改、异常和无变化分支,并关联异步确认。本文片段只展示 helper 接入,不提供新的日志服务;process_data 不是执行日志。
  6. 平台受理、实际下发、真实反馈、遥测/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 代码。