tklHelper
tklHelper 是 ThinkLink 平台为简化物模型(Thing Model)和 RPC 配置而提供的工具集。通过 JSON 风格的声明式配置,配合 PayloadParser / MSparser / RPCHelper / Utils / TriggerHelper 等类,可以快速实现上行数据解析、参数下发和告警触发,无需手写底层 Buffer 逻辑。
本说明书内容对照源码
source/tklHelper.js(version1.01.036)。在物模型脚本、RPC 脚本和触发模型脚本中,tklHelper暴露的所有类(BufferTKL/PayloadParser/MSparser/Utils/TriggerHelper/RPCHelper)均作为 sandbox 全局变量直接使用,无需 import。
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 参数下发状态机
| 方法 | 描述 |
|---|---|
Utils.makeParaSetMSG({ msgType, checkInfo, device, classMode, rpcName, params, paraDownBuffer, extraAppBuffer, timeout, maxRetries }) | 把"服务端属性更新 + 下行参数包 + 可选重试帧"打包为消息队列数组。classMode = "ClassC" 时按 maxRetries 追加重发帧;extraAppBuffer 用于追加额外 APP 段写包。 |
Utils.paraCheck(rpc, server_attrs, data) | 参数下发后上行回包的"校验 + 重试"状态机。未配置 checkInfo 时逐字段比对 params 与 data;配置了 checkInfo 则用其 frameInfo / appInfo 走一遍 PayloadParser.telemetry() 再比对。返回 { sdata, tdata, pdata, actions }。成功时 counter = -1;ClassC 成功还追加 mt_action: downBufferClear;未达 maxRetries 时返回 actions: [{ method: rpc, params }] 触发重发。 |
makeParaSetMSG 参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
msgType | Utils.msgType.paras | 下行通道类型。 |
timeout | 864000000(10 天) | 任务总超时(ms)。 |
maxRetries | 3 | 最大重试次数。 |
classMode | "ClassA" | "ClassA" 单发;"ClassC" 追加重试帧。 |
rpcName | — | 必填;作为 server_attrs 中追踪状态的键。 |
device | — | 当前设备对象。 |
params | — | RPC 参数映射。 |
paraDownBuffer | — | 原始下行 Buffer。 |
extraAppBuffer | — | 可选,主包之后追加的额外 APP 段 Buffer。 |
checkInfo | — | 配置后以 PayloadParser 检查代替字段逐一比对。 |
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
};
}