MT-EB Integration Guide
Capability Overview
MT-EB is integrated with ThinkLink through ptl-d01 in edgebus-dtu mode. This guide documents the current deployable artifacts and their source-traceable versions.
| Item | Value |
|---|---|
| Project | mt-eb |
| Vendor | MT |
| Model | MT-EB |
| Business Code | Not applicable |
| Integration Mode | edgebus-dtu |
| Protocol | ptl-d01 |
Model catalog
Search for these models in ThinkLink by name or ID.
| Model type | Name | id_name | Platform model ID |
|---|---|---|---|
| RPC | [EB SET] easy para | eb_easy_para_set | 71718097953755141 |
| RPC | [MT ACT] down data | mt_act_dn_data | 28944881661513733 |
| RPC | [MT ACT] sw data down | mt_act_sw_dn | 41526370722910213 |
| RPC | [MT ACT] transparent | mt_act_transparent | 24232175968718853 |
| RPC | [MT ACT] action | mt_action | 24040650571780101 |
| RPC | [MT SET] app any para | mt_app_any_set | 65090370840563717 |
| RPC | [MT SET] uart para | mt_app_uart_set | 41601231033995269 |
| RPC | [MT SET] Class Mode | mt_class_mode_set | 132443389613207552 |
| RPC | [MT ACT] debug modbus | mt_debug_modbus | 55427921770188805 |
| RPC | [MT GET] any para | mt_get_any_para | 68445253878681605 |
| RPC | [MT SET] broad para | mt_set_broad | 41212245631307781 |
| RPC | [MT SET] eb para | mt_set_eb_para | 64665196043440133 |
| RPC | [MT SET] multi devices | mt_set_multi_device | 65014851788869637 |
| RPC | [MT SET] radio para | mt_set_radio_para | 38534049215549445 |
| RPC | [MT SET] sw paras | mt_sw_set_para | 41239002052825093 |
| RPC | [MT SET] period_heart | mt_up_period_set | 64780121331994629 |
| RPC | [MT EB ADR CONFIG] | mt_eb_adr_config | 130598892310786048 |
| Trigger | MT-EB ADR Trigger | mt_eb_adr_trigger | 130598893220950016 |
| Template | MT-EB-BATTERY | 5 | |
| Template | MT-EB-POWER | 10 | |
| Thing Model | MT-EB | mt_eb | 24220289923551238 |
| Thing Model | MT-EB-Emeter | mt_eb_emeter | 23052087352889349 |
| Thing Model | MT-EB-MULTIDEVICE | mt_eb_multi_device | 24570740992905221 |
Operations Skill
Use mt-eb-operations to query or set EB/UART/radio parameters, change LoRaWAN Class, issue transparent or Modbus actions, or diagnose EB ADR. It distinguishes current from deprecated RPCs and requires device-response or telemetry readback.
Key new and compatibility RPCs
| RPC | Key parameters | Behavior and verification |
|---|---|---|
mt_class_mode_set | class_mode: ClassA or ClassC | Sends CIO_MS on port 214 and writes shared_attrs.class_mode after queueing. Use only with ManThink firmware that supports CIO_MS; verify the actual class from later uplink/downlink timing. |
mt_eb_adr_config | lorawan_region, dr_min, dr_max | Writes server_attrs.eb_adr only; it does not immediately downlink. Regions are CN470, AS923, EU868, US915, AU915, KR920, and IN865. Later uplinks let mt_eb_adr_trigger call mt_set_eb_para. |
eb_easy_para_get has been removed. eb_easy_para_set and mt_up_period_set are deprecated. Use mt_action/mt_get_any_para for reads, mt_set_eb_para/mt_app_any_set for writes, and the period_heart variant of mt_set_eb_para for heartbeat period.
Features and Application Scope
This project covers MT-EB through ptl-d01. Its capability boundary, fields, and deployable models follow the current descriptor, requirements, and model sources.
EB ADR versus External ADR
- `mt_eb_adr_config` + `mt_eb_adr_trigger` implement EB parameter ADR. For CN470, AS923, and EU868, DR5 is the fixed highest target when `SNR > 0 dB`; the retained legacy `dr_min`/`dr_max` fields no longer cap that target.
- `platform/tkl/mt-adr` implements external LoRaWAN MAC ADR. Its `mt_adr_engine` evaluates link margin and sends `LinkADRReq`; it does not write the EB parameter register.
- ThinkLink owns only the persistent MT-EB configured `DR`; device-native ADR owns the actual per-frame uplink `datr`. A difference between them is not a configuration failure. The trigger reads configured DR from the current thing-model telemetry first and falls back to `shared_attrs.DR`. When configured DR is already 5, it remains silent even if native ADR uses another runtime DR.
- Missing, malformed, or region-invalid configured DR, absent/incomplete trigger configuration, `SNR <= 0 dB`, and unrecognized uplinks fail safe. Echo confirmation and bounded retry remain the responsibility of `mt_set_eb_para`/`paraCheck`.
To enable EB ADR, run `[MT EB ADR CONFIG]` with valid `lorawan_region`, `dr_min`, and `dr_max`, then ensure the template includes `MT-EB ADR Trigger`. This policy applies to CN470, EU868, and AS923; the legacy range does not need to include DR5.
Deployment and Data Acquisition
| DTU | Not applicable | | Power | battery | | LoRaWAN Class | A | | Interface | LoRaWAN | | Baud Rate | Not applicable |
Telemetry and Register Definitions
| Field | Name | Type | Unit | Address/Source | Description |
|---|---|---|---|---|---|
| datr | Uplink Data Rate | string | LoRaWAN uplink metadata | Actual uplink rate selected by device-native ADR; used only to validate a recognized uplink, not to infer configured-DR rollback | |
| snr | SNR | number | dB | LoRaWAN uplink metadata | Current uplink SNR used by EB ADR to decide whether to set DR5 |
| BzType | BzType | uint16LE | port 209/213/201 | Source field: BzType | |
| BzVersion | BzVersion | uint8 | port 209/213/201 | Source field: BzVersion | |
| HwType | HwType | bitLE | port 209/201 | 40=OM422, 51=OM822 | |
| HwVersion | HwVersion | bitLE | port 209/201 | Source field: HwVersion | |
| SwVersion | SwVersion | uint8 | port 209/201 | Source field: SwVersion | |
| chip_temperature | chip_temperature | uint16LE | °C | port 209 | Source field: chip_temperature |
| chip_voltage | chip_voltage | uint8 | V | port 209 | Source field: chip_voltage |
| BaudRate | BaudRate | uint8 | bps | port 201 | Source field: BaudRate |
| DataBits | DataBits | bitLE | port 201 | Source field: DataBits | |
| StopBits | StopBits | bitLE | port 201 | Source field: StopBits | |
| Checkbit | Checkbit | bitLE | port 201 | 0=none, 1=odd, 2=even | |
| Battery | Battery | bitLE | port 201 | Source field: Battery |
Parameter Definitions
| Parameter | Name | Type | Default | Range | Unit | Description |
|---|---|---|---|---|---|---|
| app_params | app_params | mixed | — | Source parameter: app_params | ||
| fw_params | fw_params | mixed | — | Source parameter: fw_params | ||
| cf_params | cf_params | mixed | — | Source parameter: cf_params | ||
| radio_params | radio_params | mixed | — | Source parameter: radio_params | ||
| ds_params | ds_params | mixed | — | Source parameter: ds_params |
Requirements Evidence
The following key facts were extracted from the requirements source; line references support verification.
| Category | Source Fact | Source |
|---|---|---|
| Protocol/Data | - Description: EdgeBus DTU MT-EB DTU 51/201/209/213 214 automatic detection OM422/OM822 | requirement/requirements.md:11 |
| Deployment | - Power: battery | requirement/requirements.md:17 |
| Protocol/Data | - Protocol: ptl-d01 | requirement/requirements.md:19 |
| Protocol/Data | - Interface: LoRaWAN | requirement/requirements.md:20 |
| Protocol/Data | - Baud rate: Not applicable | requirement/requirements.md:21 |
| Protocol/Data | Field Name Type Unit Address/Source Description | requirement/requirements.md:25 |
| Protocol/Data | BzType BzType uint16LE port 209/213/201 Source field: BzType | requirement/requirements.md:27 |
| Protocol/Data | BzVersion BzVersion uint8 port 209/213/201 Source field: BzVersion | requirement/requirements.md:28 |
| Protocol/Data | HwType HwType bitLE port 209/201 40=OM422, 51=OM822 | requirement/requirements.md:29 |
| Protocol/Data | HwVersion HwVersion bitLE port 209/201 Source field: HwVersion | requirement/requirements.md:30 |
| Protocol/Data | SwVersion SwVersion uint8 port 209/201 Source field: SwVersion | requirement/requirements.md:31 |
| Protocol/Data | chip_temperature chip_temperature uint16LE °C port 209 Source field: chip_temperature | requirement/requirements.md:32 |
| Protocol/Data | chip_voltage chip_voltage uint8 V port 209 Source field: chip_voltage | requirement/requirements.md:33 |
| Protocol/Data | BaudRate BaudRate uint8 bps port 201 Source field: BaudRate | requirement/requirements.md:34 |
| Protocol/Data | DataBits DataBits bitLE port 201 Source field: DataBits | requirement/requirements.md:35 |
| Protocol/Data | StopBits StopBits bitLE port 201 Source field: StopBits | requirement/requirements.md:36 |
| Protocol/Data | Checkbit Checkbit bitLE port 201 0=none, 1=odd, 2=even | requirement/requirements.md:37 |
| Protocol/Data | Battery Battery bitLE port 201 Source field: Battery | requirement/requirements.md:38 |
| Protocol/Data | mt_eb port number Port | requirement/requirements.md:45 |
| Protocol/Data | mt_eb BaudRate number Baud Rate | requirement/requirements.md:50 |
| Deployment | mt_eb pwron_delay number Power-on Delay | requirement/requirements.md:56 |
| Protocol/Data | mt_act_dn_data port number Port 12 | requirement/requirements.md:85 |
| Protocol/Data | mt_app_any_set addr number Modbus Address true | requirement/requirements.md:93 |
| Protocol/Data | mt_app_uart_set BaudRate number Baud Rate — bps | requirement/requirements.md:96 |
Thing Model and RPC Code
RPC: [EB SET] easy para
id_name: eb_easy_para_set · ID: 71718097953755141
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let paraDef = {}
let paraInput={...params}
let frames=RPCHelper.buildFrame({
paraDef:paraDef,
params:paraInput});
let redoBuffer=RPCHelper.redo()
let dnBuffer=Buffer.alloc(frames.writeBuffer.length+frames.readBuffer.length+redoBuffer.length);
frames.writeBuffer.copy(dnBuffer,0)
frames.readBuffer.copy(dnBuffer,frames.writeBuffer.length)
redoBuffer.copy(dnBuffer,frames.writeBuffer.length+frames.readBuffer.length);
logger.info("set easy para")
let msgServerAttrs={
sleepTimeMs: 0,
type:"modifyAttrs",
dnMsg: {
server_attrs: {
eb_easy_para_set: {
ms:Date.now(),
params:params
}
}
}
}
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.paras,
device:device,
dnBuffer:dnBuffer,
sleepTime:0,
})
return [msgServerAttrs,msg]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT ACT] down data
id_name: mt_act_dn_data · ID: 28944881661513733
import {BufferTKL} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let datastr=params.data.replaceAll(" ", "")
if ((datastr.length%2)===1) { datastr='0'+datastr;}
let downData=Buffer.from(datastr,'hex').toString("base64");
logger.info("down data")
let msgServerAttrs= {
type:"modifyAttrs",
sleepTimeMs: 0,
dnMsg: {
server_attrs: {
lorawan_down:{
rpc:"mt_act_dn_data",
ms:Date.now(),
port:params.port,
confirmed:params.confirmed,
down_data:downData,
params:params
}
}
}
}
let msg={
sleepTimeMs: 0,
dnMsg: {
"version":"3.0",
"type":params.type,
"if":"loraWAN",
"moteeui": device.eui,
"token": new Date().getTime(),
"userdata":{
"confirmed":params.confirmed,
"fpend":false,
"port":params.port,
"TxUTCtime":"",
"payload":downData,
"dnWaitms":0,
"type":"data",
"intervalms":0,
"specify": {
"gweui": params?.gweui==="auto"?"":params?.gweui
}
}
}
}
return [msgServerAttrs,msg]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: { data:"01 02 03", port:2, confirmed:true, type:"data" },
alarms: [],
logger: console,
org_params: {}
})RPC: [MT ACT] sw data down
id_name: mt_act_sw_dn · ID: 41526370722910213
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
if (device.shared_attrs?.swBW==undefined || device.shared_attrs?.swSF==undefined ||
device.shared_attrs?.swFreq==undefined || device.shared_attrs?.swPeriod==undefined ){
logger.warn("no sw paras in shared_attrs and read first");
let frame=RPCHelper.paraRead("cf","hexbe8",60);
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.paras, device:device, dnBuffer:frame, sleepTime:0,
})
return [msg]
}
let rawCmdBuf = Buffer.from(params.data.replaceAll(" ", ""), "hex")
if (rawCmdBuf.length<1) { return null }
logger.info("sw down data",{params:{
swFreq:device.shared_attrs.swFreq,
swPeriod:device.shared_attrs.swPeriod,
swSF:device.shared_attrs.swSF,
swBW:device.shared_attrs.swBW
}})
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.swDown,
device:device,
dnBuffer:rawCmdBuf,
sleepTime:0,
})
if (params.gwEui!=undefined && params.gwEui!="auto"){
msg.dnMsg.userdata.specify.gwEui=params.gwEui
}
let msgServerAttrs= {
type:"modifyAttrs",
sleepTimeMs: 0,
dnMsg: {
server_attrs: {
lora_sw_down:{
rpc:"mt_act_sw_dn",
ms:Date.now(),
port:msg.dnMsg.port,
down_data:msg.dnMsg.userdata.payload,
swFreq:device.shared_attrs.swFreq,
swPeriod:device.shared_attrs.swPeriod,
swSF:device.shared_attrs.swSF,
swBW:device.shared_attrs.swBW,
params:params
}
}
}
}
return [msgServerAttrs,msg]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT ACT] transparent
id_name: mt_act_transparent · ID: 24232175968718853
import {BufferTKL} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
logger.info("transparent")
let datastr=params.payload.replaceAll(" ", "")
if ((datastr.length%2)===1) { datastr='0'+datastr;}
let downData=Buffer.from(datastr,'hex').toString("base64");
let msgServerAttrs= {
type:"modifyAttrs",
sleepTimeMs: 0,
dnMsg: {
server_attrs: {
transparent:{
rpc:"mt_act_transparent",
ms:Date.now(),
down_data:downData,
params:params
}
}
}
}
let msg={
sleepTimeMs: 0,
dnMsg: {
"version":"3.0",
"type":"data",
"if":"loraWAN",
"moteeui": device.eui,
"token": new Date().getTime(),
"userdata":{
"confirmed":false,
"fpend":false,
"port":51,
"TxUTCtime":"",
"payload":downData,
"dnWaitms":0,
"type":"data",
"intervalms":0
}
}
}
return [msgServerAttrs,msg]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: { payload:"01 02 03" },
alarms: [],
logger: console,
org_params: {}
})RPC: [MT ACT] action
id_name: mt_action · ID: 24040650571780101
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let frame=""
if (params.action==="swDataDown") {
if (device.shared_attrs?.swBW==undefined || device.shared_attrs?.swSF==undefined ||
device.shared_attrs?.swFreq==undefined || device.shared_attrs?.swPeriod==undefined ){
logger.warn("no sw paras in shared_attrs and read first");
frame=RPCHelper.paraRead("cf","hexbe8",60);
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.paras, device:device, dnBuffer:frame, sleepTime:0,
})
return [msg]
}
let rawCmdBuf = Buffer.from("C70403123456",'hex');
logger.info("sw down data")
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.swDown,
device:device,
dnBuffer:rawCmdBuf,
sleepTime:0,
})
if (params.gwEui!=undefined && params.gwEui!="auto"){
msg.dnMsg.userdata.specify.gwEui=params.gwEui
}
return [msg]
}else if (params.action==="downBufferClear") {
let msg=RPCHelper.makeDnDataClear()
return [msg]
}
switch(params.action) {
case "swPara": frame=RPCHelper.paraRead("cf","hexbe8",60); break;
case "uartPara": frame=RPCHelper.paraRead("app","hexbe2",12); break;
case "reset": frame=RPCHelper.reset();break;
case "redo" : frame=RPCHelper.redo();break;
case "join" : frame=RPCHelper.join();break;
case "app": frame=RPCHelper.paraRead("app","hexbe70",0); break;
case "radio" :frame=RPCHelper.paraRead("radio","hexbe90",0); break;
case "cf": frame=RPCHelper.paraRead("cf","hexbe68",0); break;
case "ds": frame=RPCHelper.paraRead("ds","hexbe16",0); break;
case "multiDevice":frame=RPCHelper.paraRead("app","hexbe100",60); break;
// case "modbusRead": frame=RPCHelper.buildmodbusFrameRead(params.addr,params.code,params.reg,params.count)
}
logger.info("action", {params: params})
if (frame.length===0) { return null}
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.paras, device:device, dnBuffer:frame, sleepTime:0,
})
return [msg]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT SET] app any para
id_name: mt_app_any_set · ID: 65090370840563717
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let frame=""
if (params?.addr==undefined){ return null}
if (params?.type==undefined) { return null}
if (params?.value==undefined) { return null}
let value=Utils.parseVal(params.value)
let writeBuffer=RPCHelper.paraWrite("app",params.type,params.addr,value);
let readBuffer=RPCHelper.paraRead("app",params.type,params.addr,value);
let frameBuffer=Buffer.alloc(writeBuffer.length+readBuffer.length);
writeBuffer.copy(frameBuffer,0)
readBuffer.copy(frameBuffer,writeBuffer.length)
logger.info("set paras", {params: params})
// 下行重发:makeParaSetMSG 记录待设态 + ClassC 预排重发。这是通用低层写(type/addr/value),
// 回读 addr 不映射到固定具名字段,无法 echo 比对,故不接 paraCheck(ClassA 仅单发,ClassC 定时重发)。
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let msgQue=Utils.makeParaSetMSG({
msgType:Utils.msgType.paras,
device:device,
classMode:classMode,
rpcName:"mt_app_any_set",
params:params,
paraDownBuffer:frameBuffer,
})
if (msgQue.length==0) return null
return msgQue
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT SET] uart para
id_name: mt_app_uart_set · ID: 41601231033995269
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let intervalms = classMode === "ClassA" ? 500 : 5000;
let paraDef = {
app_12:{name: "BaudRate", field_name: "BaudRate", unit: "bps", index: 12, type: "uint8", coefficient: 1200},
app_13:{name: "DataBits", field_name: "DataBits", unit: "", index: 13, type: "bitLE0-3"},
app_13_1:{name: "StopBits", field_name: "StopBits", unit: "", index: 13, type: "bitLE4-5"},
app_13_2:{name: "Checkbit", field_name: "Checkbit", unit: "", index: 13, type: "bitLE6-7", options: {0: "none", 1: "odd", 2: "even", 3: "none"}},
};
let writeBuffer=Buffer.from([0xCF,0x04,12,0x02,0x00,0x01])
writeBuffer[4]=params.BaudRate/1200
writeBuffer[5]=params.DataBits|(params.StopBits<<4)|(params.Checkbit<<6);
let readBuffer=Buffer.from([0x8F,0x02,12,0x02]);
//let frames=RPCHelper.buildFrame({paraDef:paraDef,params:params});
logger.info("set uart para")
let resetFrame=RPCHelper.reset();
let dnBuffer=Buffer.alloc(writeBuffer.length+readBuffer.length);
writeBuffer.copy(dnBuffer,0)
readBuffer.copy(dnBuffer,writeBuffer.length)
// 下行重发:SET 帧走 makeParaSetMSG(记录 server_attrs.mt_app_uart_set 待设态 + ClassC 预排重发;
// mt_eb port-214 解析尾部 paraCheck("mt_app_uart_set") 比对回读 BaudRate/DataBits/StopBits/Checkbit
// 解码域——BaudRate 解析器带 coefficient 1200,与入参 bps 同域——命中即停、ClassA 不符重派),随后追加复位帧。
let setParams={
BaudRate:params.BaudRate,
DataBits:params.DataBits,
StopBits:params.StopBits,
Checkbit:params.Checkbit,
}
if (params._retries != null) setParams._retries=params._retries
let msgQue=Utils.makeParaSetMSG({
msgType:Utils.msgType.paras,
device:device,
classMode:classMode,
rpcName:"mt_app_uart_set",
params:setParams,
paraDownBuffer:dnBuffer,
})
let msgRest=RPCHelper.makeMSG({
msgType:Utils.msgType.paras,
device:device,
dnBuffer:resetFrame,
intervalms:intervalms,
sleepTime:0,
})
return [ ...msgQue, msgRest]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT ACT] debug modbus
id_name: mt_debug_modbus · ID: 55427921770188805
import {RPCHelper, Utils} from '#tklHelper';
function rpc_script({ device, params, alarms, logger, org_params }) {
let dnBuffer=RPCHelper.modbusAction(params.addr,params.code,params.regAddr,params.counts)
logger.info("debug")
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.transParent, device:device, dnBuffer:dnBuffer, sleepTime:0,
})
let msgServerAttrs= {
type:"modifyAttrs",
sleepTimeMs: 0,
dnMsg: {
server_attrs: {
transparent:{
rpc:"mt_debug_modbus",
ms:Date.now(),
down_data:msg.dnMsg.userdata.payload,
params:params
}
}
}
}
return [msgServerAttrs,msg]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: { addr:"01", code:3, regAddr:0, counts:1 },
alarms: [],
logger: console,
org_params: {}
})RPC: [MT GET] any para
id_name: mt_get_any_para · ID: 68445253878681605
import {RPCHelper, Utils} from '#tklHelper';
function rpc_script({ device, params, alarms, logger, org_params }) {
if (params?.addr==undefined){ return null}
if (params?.type==undefined) { return null}
if (params?.size==undefined) { return null}
let readBuffer=RPCHelper.paraRead(params.type,"hexBE"+params.size.toString(10),params.addr);
logger.info("get paras")
let msg=RPCHelper.makeMSG({
msgType:Utils.msgType.paras, device:device, dnBuffer:readBuffer, sleepTime:0,
})
return [msg]
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT SET] broad para
id_name: mt_set_broad · ID: 41212245631307781
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let paraDef = {
cf_2: { name: "mulDevAddr", field_name: "mulDevAddr", unit: "", type: "hexLE4"},
cf_6: { name: "mulNSkey", field_name: "mulNSkey", unit: "", type: "hexBE16" },
cf_22: { name: "mulASkey", field_name: "mulASkey", unit: "", type: "hexBE16"},
};
let frames=RPCHelper.buildFrame({
paraDef:paraDef,params:params});
logger.info("set group board cast", {params: frames.log})
let dnBuffer=Buffer.alloc(frames.writeBuffer.length+frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer,0)
frames.readBuffer.copy(dnBuffer,frames.writeBuffer.length)
// 下行重发:makeParaSetMSG 记录 server_attrs.mt_set_broad 待设态 + ClassC 预排重发,确保组播密钥下发到位。
// 不接 paraCheck echo 校验:组播密钥(mulNSkey/mulASkey)多为只写、回读不返,无法可靠比对(否则每次空跑 maxRetries)。
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let msgQue=Utils.makeParaSetMSG({
msgType:Utils.msgType.paras,
device:device,
classMode:classMode,
rpcName:"mt_set_broad",
params:params,
paraDownBuffer:dnBuffer,
})
if (msgQue.length==0) return null
return msgQue
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT SET] eb para
id_name: mt_set_eb_para · ID: 64665196043440133
import {BufferTKL, RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
const APP14_BITS = [
{ name: "KeepRx", bit: 0 },
{ name: "Battery", bit: 1 },
{ name: "Uart1Used", bit: 2 },
{ name: "TransparentBit", bit: 3 },
{ name: "SwUp", bit: 4 },
{ name: "JoinRst", bit: 5 },
{ name: "PowerCtrl", bit: 6 },
{ name: "Wait60s", bit: 7 },
];
function parseBit(value) {
if (value === true || value === 1 || value === "1" || value === "true") return true;
if (value === false || value === 0 || value === "0" || value === "false") return false;
return null;
}
function buildApp14Value(targetBattery) {
const sharedAttrs = device?.shared_attrs ?? {};
let app14Value = 0;
for (const field of APP14_BITS) {
const currentValue = parseBit(sharedAttrs[field.name]);
if (currentValue == null) {
logger.warn(
"Battery change rejected: refresh EB parameters and ensure the latest value is available for "
+ field.name,
);
return null;
}
const finalValue = field.name === "Battery" ? targetBattery : currentValue;
if (finalValue) app14Value |= (1 << field.bit);
}
return app14Value;
}
// variant 提交为嵌套键值对:params.para = { value: 判别值, <子字段名>: 值 }(子字段名=判别值,KB-28)。
// 下行重发再派发(paraCheck → {method:"mt_set_eb_para", params:{<which>:value}})走扁平入参,故两种都接:
// 有 params.para 走变体;否则取首个非 "_" 键为 which(KB-41:存进 server_attrs 的是扁平标量字段集)。
let which, raw;
if (params?.para?.value != null) {
which = params.para.value;
raw = params.para[which];
} else {
which = Object.keys(params || {}).find(k => !k.startsWith("_"));
raw = which != null ? Reflect.get(params, which) : null;
}
if (which == null || which === "nc") { return null; }
if (raw == null || raw === "nc" || raw === "") { return null; }
let value = which === "Battery"
? parseBit(raw)
: ((typeof raw === "number") ? raw : Utils.parseVal(raw));
if (value == null) { return null; }
function buildFrame(register, type, addr, val) {
let writeBuffer = RPCHelper.paraWrite(register, type, addr, val);
let readBuffer = RPCHelper.paraRead(register, type, addr, val);
let frameBuffer = Buffer.alloc(writeBuffer.length + readBuffer.length);
writeBuffer.copy(frameBuffer, 0);
readBuffer.copy(frameBuffer, writeBuffer.length);
return frameBuffer;
}
let frame = "";
switch (which) {
case "Battery": {
const app14Value = buildApp14Value(value);
if (app14Value == null) return null;
frame = buildFrame("app", "uint8", 14, app14Value);
break;
}
case "ConfirmDuty": frame = buildFrame("app", "uint8", 15, value); break;
case "query_timeout": {
// shared_attrs.query_timeout is already decoded to ms by the PARA parser, and the input unit is ms,
// so the backfilled value and the input are the same ms domain — backfill is safe here like the other params.
let setVal = value;
if (value < 260000) { setVal = value | 0xC000; } else { setVal = value / 1000; }
frame = buildFrame("app", "uint16le", 32, setVal); break;
}
case "retries": frame = buildFrame("app", "uint8", 34, value); break;
case "pwron_delay": frame = buildFrame("app", "uint16le", 38, value); break;
case "period_heart": frame = buildFrame("app", "uint32le", 40, value); break;
case "sub_device_counts": frame = buildFrame("app", "uint8", 60, value); break;
case "dr": frame = buildFrame("ds", "uint8", 0, value); break;
case "swFreq": frame = buildFrame("cf", "uint32le", 62, value); break;
default: return null;
}
if (frame.length === 0) { return null; }
logger.info("set eb para: " + which + "=" + value);
// 下行重发:makeParaSetMSG 记录 server_attrs.mt_set_eb_para 待设态 + ClassC 预排重发;
// mt_eb port-214 解析尾部 paraCheck("mt_set_eb_para") 比对回读 shared_attrs[which](解码域),
// 命中即停、ClassA 不符重派。存扁平 {[which]:value} 对齐解析器字段集(KB-41)。
// 注:query_timeout 因寄存器编码/解码非严格互逆,echo 可能不命中 → 退化为有界重发(maxRetries 后停,不无限)。
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let msgQue = Utils.makeParaSetMSG({
msgType: Utils.msgType.paras,
device: device,
classMode: classMode,
rpcName: "mt_set_eb_para",
params: { [which]: value },
paraDownBuffer: frame,
});
if (msgQue.length === 0) return null;
if (which === "Battery") {
const resetMsg = RPCHelper.makeMSG({
msgType: Utils.msgType.paras,
device: device,
dnBuffer: RPCHelper.reset(),
intervalms: classMode === "ClassA" ? 500 : 5000,
sleepTime: 0,
});
if (resetMsg == null) return null;
return [...msgQue, resetMsg];
}
return msgQue;
}
rpc_script({
device: { server_attrs: { }, shared_attrs: { period_heart: 28800 } },
params: { para: { value: "period_heart", period_heart: 28800 } },
alarms: [],
logger: console,
org_params: {}
})RPC: [MT SET] multi devices
id_name: mt_set_multi_device · ID: 65014851788869637
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
function processSubAddr(subAddr, subAddrSize, isHex) {
let hexStr=subAddr.replaceAll(" ","")
if (isHex){
if (hexStr.length % 2 === 1) { hexStr = '0' + hexStr; }
}else {
hexStr = parseInt(subAddr, 10).toString(16);
}
const buffer = Buffer.alloc(subAddrSize);
const numBuffer = Buffer.from(hexStr, 'hex');
if (numBuffer.length !==subAddrSize) {
logger.warn('subAddr ' + subAddr + ' exceeds size ' + subAddrSize + ', truncating');
}
numBuffer.copy(buffer, 0, 0,subAddrSize);
return buffer;
}
function build(params) {
let queryIndex = params.query_index || 0;
let subAddrSize = params.sub_addr_size || 1;
let deviceCounts = 0;
let payHex=""
for (let i = 1; i <= 6; i++) {
// 修复 off-by-one:原为 params['sub_addr'+(i+1)] 读 sub_addr2..7,导致模板 sub_addr1
// 永远收不到、sub_addr7 不存在。现读 sub_addr1..6,与模板字段及 shared_attrs 回填对齐。
const subAddr = params['sub_addr' + i];
if(subAddr===undefined) {continue}
const saddr = subAddr.trim().toLowerCase();
if (saddr=== ""||saddr==="nc") { break; }
deviceCounts++;
const payload = processSubAddr(subAddr, subAddrSize, params.isHex);
payHex=payHex+payload.toString("hex")
}
let subDeviceBuffer=Buffer.from(payHex,"hex");
let frameSetSubDevice = Buffer.alloc(6 + subAddrSize * deviceCounts);
frameSetSubDevice[0] = 0xCF;
frameSetSubDevice[1] = 4 + subAddrSize * deviceCounts;
frameSetSubDevice[2] = 65;
frameSetSubDevice[3] = subAddrSize * deviceCounts+2;
frameSetSubDevice.writeUInt8(queryIndex, 4);
frameSetSubDevice.writeUInt8(subAddrSize, 5);
subDeviceBuffer.copy(frameSetSubDevice,6);
let devCountsBuffer = RPCHelper.paraWrite("app","uint8",60,deviceCounts);
let readBuffer=RPCHelper.paraRead("app","hexbe100",60);
let dnBuffer=Buffer.alloc(frameSetSubDevice.length+devCountsBuffer.length+readBuffer.length);
frameSetSubDevice.copy(dnBuffer)
devCountsBuffer.copy(dnBuffer,frameSetSubDevice.length)
readBuffer.copy(dnBuffer,frameSetSubDevice.length+devCountsBuffer.length);
return dnBuffer
}
let dnDataBuffer = build(params);
logger.info("set multi devices", {params: params})
// 下行重发:makeParaSetMSG 记录待设态 + ClassC 预排重发。不接 paraCheck echo 校验:入参含 isHex(表单专用、
// 设备不回读)且 sub_addr 十进制/十六进制与回读零填充格式有歧义,强行比对会每次空跑 maxRetries;
// 故按 ClassC 定时重发为主(与 mt_up_period_set/mt_sw_set_para 同级)。
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let msgQue=Utils.makeParaSetMSG({
msgType:Utils.msgType.paras,
device:device,
classMode:classMode,
rpcName:"mt_set_multi_device",
params:params,
paraDownBuffer:dnDataBuffer,
})
if (msgQue.length==0) return null
return msgQue
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT SET] radio para
id_name: mt_set_radio_para · ID: 38534049215549445
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let paraDef = {
radio_0: { order: 0, name: "RX2Frequency", field_name: "dn2Freq", unit: "MHz", type: "uint32le" },
radio_4: { order: 1, name: "DLSetting", field_name: "DLSetting", unit: "", type: "hexbe1" },
radio_5: { order: 2, name: "channelMap", field_name: "channelMap", unit: "", type: "hexle2" },
radio_7: { order: 3, name: "power", field_name: "power", unit: "dBm", type: "int8" },
radio_8: { order: 4, name: "Freq1", field_name: "Freq1", unit: "MHz", type: "uint32le" },
radio_12: { order: 5, name: "DRRange1", field_name: "DRRange1", unit: "", type: "hexbe1" },
radio_13: { order: 6, name: "Freq2", field_name: "Freq2", unit: "MHz", type: "uint32le" },
radio_17: { order: 7, name: "DRRange2", field_name: "DRRange2", unit: "", type: "hexbe1" },
radio_18: { order: 8, name: "Freq3", field_name: "Freq3", unit: "MHz", type: "uint32le" },
radio_22: { order: 9, name: "DRRange3", field_name: "DRRange3", unit: "", type: "hexbe1" },
radio_23: { order: 10, name: "Freq4", field_name: "Freq4", unit: "MHz", type: "uint32le" },
radio_27: { order: 11, name: "DRRange4", field_name: "DRRange4", unit: "", type: "hexbe1" },
radio_28: { order: 12, name: "Freq5", field_name: "Freq5", unit: "MHz", type: "uint32le" },
radio_32: { order: 13, name: "DRRange5", field_name: "DRRange5", unit: "", type: "hexbe1" },
radio_33: { order: 14, name: "Freq6", field_name: "Freq6", unit: "MHz", type: "uint32le" },
radio_37: { order: 15, name: "DRRange6", field_name: "DRRange6", unit: "", type: "hexbe1" },
radio_38: { order: 16, name: "Freq7", field_name: "Freq7", unit: "MHz", type: "uint32le" },
radio_42: { order: 17, name: "DRRange7", field_name: "DRRange7", unit: "", type: "hexbe1" },
radio_43: { order: 18, name: "Freq8", field_name: "Freq8", unit: "MHz", type: "uint32le" },
radio_47: { order: 19, name: "DRRange8", field_name: "DRRange8", unit: "", type: "hexbe1" },
radio_48: { order: 20, name: "Freq9", field_name: "Freq9", unit: "MHz", type: "uint32le" },
radio_52: { order: 21, name: "DRRange9", field_name: "DRRange9", unit: "", type: "hexbe1" },
radio_53: { order: 22, name: "Freq10", field_name: "Freq10", unit: "MHz", type: "uint32le" },
radio_57: { order: 23, name: "DRRange10", field_name: "DRRange10", unit: "", type: "hexbe1" },
radio_58: { order: 24, name: "Freq11", field_name: "Freq11", unit: "MHz", type: "uint32le" },
radio_62: { order: 25, name: "DRRange11", field_name: "DRRange11", unit: "", type: "hexbe1" },
radio_63: { order: 26, name: "Freq12", field_name: "Freq12", unit: "MHz", type: "uint32le" },
radio_67: { order: 27, name: "DRRange12", field_name: "DRRange12", unit: "", type: "hexbe1" },
radio_68: { order: 28, name: "Freq13", field_name: "Freq13", unit: "MHz", type: "uint32le" },
radio_72: { order: 29, name: "DRRange13", field_name: "DRRange13", unit: "", type: "hexbe1" },
radio_73: { order: 30, name: "Freq14", field_name: "Freq14", unit: "MHz", type: "uint32le" },
radio_77: { order: 31, name: "DRRange14", field_name: "DRRange14", unit: "", type: "hexbe1" },
radio_78: { order: 32, name: "Freq15", field_name: "Freq15", unit: "MHz", type: "uint32le" },
radio_82: { order: 33, name: "DRRange15", field_name: "DRRange15", unit: "", type: "hexbe1" },
radio_83: { order: 34, name: "Freq16", field_name: "Freq16", unit: "MHz", type: "uint32le" },
radio_87: { order: 35, name: "DRRange16", field_name: "DRRange16", unit: "", type: "hexbe1" }
};
function getFreq(value){
if (!value) {return null }
let freq=value.replaceAll(" ","").split(",")
if (freq.length!==4){
return null
}
let drRangeL=parseInt(freq[1])
let drRangeH=parseInt(freq[2])
return {
freq: Number(freq[0]*1000000),
drRange: drRangeL|(drRangeH<<4),
enable:JSON.parse(freq[3]),
}
}
const hasEnabledChannel = Object.entries(params).some(([key, val]) => {
if (!key.startsWith("freq")) return false;
const info = getFreq(val);
return info && info.enable;
});
if (!hasEnabledChannel) {
logger.error("set radio para rejected: all channels disabled (channelMap=0)");
return null;
}
// RPCHelper.buildFrame calls RPCHelper._preDealParams (hardcoded, not subclass override),
// so freqN composites and dn2Freq unit conversion must be done here before buildFrame.
const frameParams = {};
let channelMap = 0;
Object.entries(params).forEach(([key, val]) => {
if (!key.startsWith("freq")) return;
const info = getFreq(val);
if (!info) return;
const fIndex = parseInt(key.substring(4));
if (!info.enable) return;
frameParams["Freq" + fIndex.toString()] = info.freq;
frameParams["DRRange" + fIndex.toString()] = info.drRange.toString(16);
channelMap |= 1 << (fIndex - 1);
});
frameParams.channelMap = channelMap.toString(16);
// BUG-058: the device's power register holds dBm+2, and the mt_eb parser undoes it on
// readback (pradio.power -= 2). Write dBm+2 here so the echoed register decodes back to
// params.power → paraCheck (archived params.power vs decoded echo) converges.
if (params.power != null) frameParams.power = params.power + 2;
if (params.dn2Freq != null) frameParams.dn2Freq = Math.round(params.dn2Freq * 1000000); // MHz→Hz
if (params.DLSetting != null) frameParams.DLSetting = params.DLSetting;
let frames=RPCHelper.buildFrame({
paraDef:paraDef,params:frameParams});
let dnBuffer=Buffer.alloc(frames.writeBuffer.length+frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer,0)
frames.readBuffer.copy(dnBuffer,frames.writeBuffer.length)
logger.error("set radio paras downlink", {params: frames.log, hex: dnBuffer.toString("hex").toUpperCase(), len: dnBuffer.length})
// Downlink resend (was a single fire-and-forget makeMSG, no retry): makeParaSetMSG records
// the pending-set state in server_attrs.mt_set_radio_para (counter/params/timeout/classMode)
// and, for ClassC, pre-queues maxRetries retries at intervalms:timeout. The echo-validated
// early-stop + ClassA re-dispatch is driven by Utils.paraCheck("mt_set_radio_para", …) in the
// mt_eb port-214 parser. dnBuffer is write+read, so the device returns a 0x22 radio frame the
// parser decodes; `params` here is the form's freqN composites (already in the decoded
// shared_attrs domain the parser re-synthesizes), so paraCheck's default equality compare
// matches and avoids the KB-41 domain-mismatch trap.
//
// KB-46: Normalize disabled-channel params before storing in server_attrs so paraCheck
// can match the echo. When the form is opened before the device has sent its first
// port-214 frame (e.g. device just powered on), shared_attrs.freqN may be empty and
// the form submits ",,,false" (empty MHz). paraCheck then compares "923.4,0,5,false"
// (echo decoded) vs ",,,false" (stored) → permanent mismatch → EB reinits LoRa on
// every retry → device appears offline.
// Fix: replace empty-MHz disabled channels with the device's actual shared_attrs value;
// if that is also unknown, prefix with _ so paraCheck skips the field entirely.
const normalizedParams = { ...params };
for (const [key, val] of Object.entries(params)) {
if (!key.startsWith("freq")) continue;
const info = getFreq(val);
// info.freq===0 means MHz was empty in the form (e.g. ",,,false")
if (!info || (!info.enable && info.freq === 0)) {
const actual = device?.shared_attrs?.[key];
const actualInfo = getFreq(actual);
if (actualInfo && actualInfo.freq > 0) {
normalizedParams[key] = actual; // use device's real value for paraCheck
} else {
normalizedParams["_" + key] = val; // unknown → skip in paraCheck
delete normalizedParams[key];
}
}
}
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
let msgQue=Utils.makeParaSetMSG({
msgType:Utils.msgType.paras,
device:device,
classMode:classMode,
rpcName:"mt_set_radio_para",
params:normalizedParams,
paraDownBuffer:dnBuffer,
})
if (msgQue.length==0) return null
return msgQue
}
// Test 1: empty-MHz disabled channels (KB-46) — should use shared_attrs fallback or _ prefix
const r1 = rpc_script({
device: { server_attrs: {}, shared_attrs: {
class_mode: "ClassA",
freq9: "923.4,0,5,false", freq10: "923.6,0,5,false", freq11: "923.8,0,5,false",
freq12: "924,0,5,false", freq13: "924.2,0,5,false", freq14: "924.4,0,5,false",
freq15: "924.6,0,5,false"
}},
params: {
freq1: "921.8,0,5,true", freq2: "922,0,5,true", freq3: "922.2,0,5,true",
freq4: "922.4,0,5,true", freq5: "922.6,0,5,true", freq6: "922.8,0,5,true",
freq7: "923,0,5,true", freq8: "923.2,0,5,false",
freq9: ",,,false", freq10: ",,,false", freq11: ",,,false", freq12: ",,,false",
freq13: ",,,false", freq14: ",,,false", freq15: ",,,false",
freq16: "924.8,0,5,true",
power: 22, dn2Freq: 923.2, DLSetting: "02"
},
alarms: [], logger: { error: (m,d) => { if(d?.params) { const p=d.params; const bad=Object.entries(p).filter(([k,v])=>k.startsWith('freq')&&v===',,,false'); console.log('KB-46 test1: bad empty-MHz fields remaining:', bad.length===0?'PASS':'FAIL', bad); } }, info:()=>{} }, org_params: {}
});
console.log("KB-46 test1 result:", r1 ? "has output" : "null (FAIL - no enabled channels?)");
// Test 2: normal call
rpc_script({
device: { server_attrs: {}, shared_attrs: { class_mode: "ClassA" } },
params: {
freq1: "921.8,0,5,true", freq2: "922,0,5,true", freq3: "922.2,0,5,true",
freq4: "922.4,0,5,true", freq5: "922.6,0,5,true", freq6: "922.8,0,5,true",
freq7: "923,0,5,true", freq8: "923.2,0,5,false",
freq9: "923.4,0,5,false", freq10: "923.6,0,5,false", freq11: "923.8,0,5,false",
freq12: "924,0,5,false", freq13: "924.2,0,5,false", freq14: "924.4,0,5,false",
freq15: "924.6,0,5,false", freq16: "924.8,0,5,true",
power: 22, dn2Freq: 923.2, DLSetting: "02"
},
alarms: [],
logger: {
error: (msg, data) => {
console.log("[error]", msg);
if (data?.params) console.log(" params:", JSON.stringify(data.params));
if (data?.hex) {
// Verify dn2Freq encoded as 923200000 Hz = 0x370AEB00 → LE: 00 EB 0A 37
const hex = data.hex;
console.log(" hex:", hex);
console.log(" dn2Freq LE bytes in hex:", hex.substring(0, 8), "(expect 00EB0A37 for 923.2MHz)");
console.log(" channelMap 2-byte big-endian in frame (expect bit6=freq7,bit0=freq1 + bit15=freq16)");
}
if (data) console.log(" len:", data.len);
},
info: (msg, data) => console.log("[info]", msg, data)
},
org_params: {}
})RPC: [MT SET] sw paras
id_name: mt_sw_set_para · ID: 41239002052825093
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
const rpcName="mt_sw_set_para";
let paraDef = {
cf_60: { name: "swSF", field_name: "swSF", unit: "", type: "uint8" },
cf_61: { name: "swBW", field_name: "swBW", unit: "", type: "uint8", options: { 6: "62.5kHz", 7: "125kHz", 8: "250kHz", 9: "500kHz" } },
cf_62: { name: "swFreq", field_name: "swFreq", unit: "MHz", type: "uint32le" },
cf_66: { name: "swPeriod", field_name: "swPeriod", unit: "ms",type: "uint8", coefficient: 50 },
};
let paraInput={...params}
switch (params.swBW){
case "62.5kHz": paraInput.swBW=6;break;
case "125kHz": paraInput.swBW=7;break;
case "250kHz": paraInput.swBW=8;break;
case "500kHz": paraInput.swBW=9;break;
}
logger.info("set sw para")
let frames=RPCHelper.buildFrame({
paraDef:paraDef,params:paraInput});
let dnBuffer=Buffer.alloc(frames.writeBuffer.length+frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer,0)
frames.readBuffer.copy(dnBuffer,frames.writeBuffer.length)
let msgQue=Utils.makeParaSetMSG({
device:device,
classMode:classMode,
rpcName:rpcName,
params:params,
paraDownBuffer:dnBuffer,
})
if (msgQue.length==0) return null
return msgQue
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})RPC: [MT SET] period_heart
id_name: mt_up_period_set · ID: 64780121331994629
import {RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function rpc_script({ device, params, alarms, logger, org_params }) {
let classMode = (device && device.shared_attrs && device.shared_attrs.class_mode) || "ClassA";
// rpcName 必须等于注册 id_name "mt_up_period_set"——server_attrs 待设态键、paraCheck 查找键、
// 失败重派 action.method 三者同源;旧值 "mt_period_up_set" 与 id_name 不符会致 ClassA 重派打到不存在的 RPC。
const rpcName="mt_up_period_set"
// app_40 在 mt-eb 上是 period_heart(心跳/周期上报间隔),与解码器 mt_eb.js port-214(app_40→period_heart)
// 及 mt_set_eb_para 的 period_heart 分支同源。曾误标 period_up(沿用自 KS 系列 LoRaWAN 传感器,那里 app_40=上报周期),
// 致 paraCheck 比对回读 dataAfter.period_heart 时找不到 period_up 字段 → echo 永不命中、ClassA 重派至失败(KB-41)。
let paraDef= {
app_40: {name: "period_heart", field_name: "period_heart", unit: "s", type: "uint32LE"},
}
let frames=RPCHelper.buildFrame({
paraDef:paraDef,params:params});
logger.info("set paras")
let dnBuffer=Buffer.alloc(frames.writeBuffer.length+frames.readBuffer.length);
frames.writeBuffer.copy(dnBuffer,0)
frames.readBuffer.copy(dnBuffer,frames.writeBuffer.length)
let msgQue=Utils.makeParaSetMSG({
device:device,
classMode:classMode,
rpcName:rpcName,
params:params,
paraDownBuffer:dnBuffer,
})
if (msgQue.length==0) return null
return msgQue
}
rpc_script({
device: { server_attrs: {}, shared_attrs: {} },
params: {},
alarms: [],
logger: console,
org_params: {}
})Thing Model: MT-EB
id_name: mt_eb · ID: 24220289923551238
import {PayloadParser, RPCHelper, Utils} from '#tklHelper';
import {Buffer} from "buffer";
function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
let port = msg?.userdata?.port || null;
const uplinkDatr = msg?.moteTx?.datr
?? msg?.userdata?.moteTx?.datr
?? msg?.userdata?.motetx?.datr
?? msg?.userdata?.datr;
let freq=msg?.moteTx?.freq??0
let hwType=device.shared_attrs?.HwType??"OM822"
let swFreq=device.shared_attrs?.swFreq??600000000
let classMode=device.shared_attrs?.class_mode
let share_attrs_change={}
if ( freq<520 && hwType!=="OM422") { share_attrs_change.HwType="OM422" }
if ( freq>520 && hwType!=="OM822") { share_attrs_change.HwType="OM822" }
if ( (msg?.userdata?.class!=undefined)&&(msg?.userdata?.class!==classMode)){
share_attrs_change.class_mode=msg.userdata.class
}
if ( freq<520 && swFreq>520000000 ) {
share_attrs_change.swFreq=477300000
share_attrs_change.swBW="500kHz"
share_attrs_change.swSF=7
share_attrs_change.period=3000
}
if (freq>800 && swFreq<800000000) {
share_attrs_change.swFreq=901300000
share_attrs_change.swBW="500kHz"
share_attrs_change.swSF=7
share_attrs_change.period=3000
}
if (port!==51&&port!==201&&port!==213&&port!==209&&port!==214) {
let checkData=Utils.paraCheck("eb_easy_para_set",device.server_attrs,device.shared_attrs)
if(share_attrs_change=={}) share_attrs_change=null
return {
telemetry_data: uplinkDatr == null ? null : { datr: uplinkDatr },
server_attrs: checkData.sdata,
shared_attrs: share_attrs_change,
actions: checkData.actions,
}
}
let frame213={
port:213, dataLen:28, status:-1, battery:-1, rssi:false,
tagList:[{ index:0, tag:0x81}, { index:1, tag:0}, { index:2, tag:0},]
}
let info213=[
{name: "BzType", field_name: "BzType", unit:"", index: 12, type: "UInt16LE"},
{name: "BzVersion", field_name: "BzVersion", unit:"", index: 14, type: "UInt8"}
]
let frame209={
port:209, dataLen:20, status:-1, battery:-1, rssi:false,
tagList:[{ index:0, tag:0x81}, { index:1, tag:0x21}, { index:2, tag:0x03},]
}
let info209=[
{name: "FuotaVersion", field_name: "FuotaVersion", unit:"", index: 3, type: "bitLE0-3"},
{name: "HwType", field_name: "HwType", unit:"", index: 3, type: "bitLE4-15", options: {40:"OM422",51:"OM822"}},
{name: "HwVersion", field_name: "HwVersion", unit:"", index: 5, type: "bitLE4-7"},
{name: "SwVersion", field_name: "SwVersion", unit:"", index: 6, type: "unit8"},
{name: "BzType", field_name: "BzType", unit:"", index: 7, type: "uint16le"},
{name: "BzVersion", field_name: "BzVersion", unit:"", index: 9, type: "uint8"},
{name: "chip_temperature", field_name: "chip_temperature", unit:"℃", index: 10, type: "uint16le"},
{name: "chip_voltage", field_name: "chip_voltage", unit:"v", index: 12, type: "int8"},
{name: "pwron_delay", field_name: "pwron_delay", unit:"S", index: 13, type: "uint16le"},
{name: "period_heart", field_name: "period_heart", unit:"S", index: 15, type: "uint32le"},
{name: "sub_device_counts", field_name: "sub_device_counts", unit:"", index: 19, type: "uint8"},
]
let frame201={
port:201, dataLen:28, status:-1, battery:-1, rssi:false,
tagList:[{ index:0, tag:0x81}, { index:7, tag:0x00}]
}
let info201 = [
{name: "FuotaVersion", field_name: "FuotaVersion", unit: "", index: 8, type: "bitLE0-3"},
{name: "HwType", field_name: "HwType", unit: "", index: 8, type: "bitLE4-15", options: {40: "OM422", 51: "OM822"}},
{name: "HwVersion", field_name: "HwVersion", unit: "", index: 10, type: "bitLE4-7"},
{name: "SwVersion", field_name: "SwVersion", unit: "", index: 11, type: "uint8"},
{name: "BzType", field_name: "BzType", unit: "", index: 12, type: "uint16le"},
{name: "BzVersion", field_name: "BzVersion", unit: "", index: 14, type: "uint8"},
{name: "FilterMask", field_name: "FilterMask", unit: "", index: 15, type: "bitLE0-2"},
{name: "BaudRate", field_name: "BaudRate", unit: "", index: 16, type: "uint8", coefficient: 1200},
{name: "DataBits", field_name: "DataBits", unit: "", index: 17, type: "bitLE0-3"},
{name: "StopBits", field_name: "StopBits", unit: "", index: 17, type: "bitLE4-5"},
{name: "Checkbit", field_name: "Checkbit", unit: "", index: 17, type: "bitLE6-7"},
{name: "KeepRx", field_name: "KeepRx", unit: "", index: 18, type: "bitLE0-0", options: {0: false, 1: true}},
{name: "Battery", field_name: "Battery", unit: "", index: 18, type: "bitLE1-1", options: {0: false, 1: true}},
{name: "Uart1Used", field_name: "Uart1Used", unit: "", index: 18, type: "bitLE2-2", options: {0: false, 1: true}},
{name: "TransparentBit", field_name: "TransparentBit", unit: "", index: 18, type: "bitLE3-3", options: {0: false, 1: true}},
{name: "SwUp", field_name: "SwUp", unit: "", index: 18, type: "bitLE4-4", options: {0: false, 1: true}},
{name: "JoinRst", field_name: "JoinRst", unit: "", index: 18, type: "bitLE5-5", options: {0: false, 1: true}},
{name: "PowerCtrl", field_name: "PowerCtrl", unit: "", index: 18, type: "bitLE6-6", options: {0: false, 1: true}},
{name: "Wait60s", field_name: "Wait60s", unit: "", index: 18, type: "bitLE7-7", options: {0: false, 1: true}},
{name: "ConfirmDuty", field_name: "ConfirmDuty", unit: "", index: 19, type: "uint8"},
{name: "portPara", field_name: "portPara", unit: "", index: 20, type: "uint8"},
{name: "portTransparent", field_name: "portTransparent", unit: "", index: 21, type: "uint8"},
{name: "RstHours", field_name: "RstHours", unit: "", index: 22, type: "uint16le"},
{name: "TimeOffset", field_name: "TimeOffset", unit: "", index: 24, type: "uint32le"},
];
let radioDefine = {
radio_0: { order: 0, name: "RX2Frequency", field_name: "dn2Freq", unit: "MHz", type: "uint32le" },
radio_4: { order: 1, name: "DLSetting", field_name: "DLSetting", unit: "", type: "hexbe1" },
radio_5: { order: 2, name: "channelMap", field_name: "channelMap", unit: "", type: "hexle2" },
radio_7: { order: 3, name: "power", field_name: "power", unit: "dBm", type: "int8" },
radio_8: { order: 4, name: "Freq1", field_name: "Freq1", unit: "MHz", type: "uint32le" },
radio_12: { order: 5, name: "DRRange1", field_name: "DRRange1", unit: "", type: "hexbe1" },
radio_13: { order: 6, name: "Freq2", field_name: "Freq2", unit: "MHz", type: "uint32le" },
radio_17: { order: 7, name: "DRRange2", field_name: "DRRange2", unit: "", type: "hexbe1" },
radio_18: { order: 8, name: "Freq3", field_name: "Freq3", unit: "MHz", type: "uint32le" },
radio_22: { order: 9, name: "DRRange3", field_name: "DRRange3", unit: "", type: "hexbe1" },
radio_23: { order: 10, name: "Freq4", field_name: "Freq4", unit: "MHz", type: "uint32le" },
radio_27: { order: 11, name: "DRRange4", field_name: "DRRange4", unit: "", type: "hexbe1" },
radio_28: { order: 12, name: "Freq5", field_name: "Freq5", unit: "MHz", type: "uint32le" },
radio_32: { order: 13, name: "DRRange5", field_name: "DRRange5", unit: "", type: "hexbe1" },
radio_33: { order: 14, name: "Freq6", field_name: "Freq6", unit: "MHz", type: "uint32le" },
radio_37: { order: 15, name: "DRRange6", field_name: "DRRange6", unit: "", type: "hexbe1" },
radio_38: { order: 16, name: "Freq7", field_name: "Freq7", unit: "MHz", type: "uint32le" },
radio_42: { order: 17, name: "DRRange7", field_name: "DRRange7", unit: "", type: "hexbe1" },
radio_43: { order: 18, name: "Freq8", field_name: "Freq8", unit: "MHz", type: "uint32le" },
radio_47: { order: 19, name: "DRRange8", field_name: "DRRange8", unit: "", type: "hexbe1" },
radio_48: { order: 20, name: "Freq9", field_name: "Freq9", unit: "MHz", type: "uint32le" },
radio_52: { order: 21, name: "DRRange9", field_name: "DRRange9", unit: "", type: "hexbe1" },
radio_53: { order: 22, name: "Freq10", field_name: "Freq10", unit: "MHz", type: "uint32le" },
radio_57: { order: 23, name: "DRRange10", field_name: "DRRange10", unit: "", type: "hexbe1" },
radio_58: { order: 24, name: "Freq11", field_name: "Freq11", unit: "MHz", type: "uint32le" },
radio_62: { order: 25, name: "DRRange11", field_name: "DRRange11", unit: "", type: "hexbe1" },
radio_63: { order: 26, name: "Freq12", field_name: "Freq12", unit: "MHz", type: "uint32le" },
radio_67: { order: 27, name: "DRRange12", field_name: "DRRange12", unit: "", type: "hexbe1" },
radio_68: { order: 28, name: "Freq13", field_name: "Freq13", unit: "MHz", type: "uint32le" },
radio_72: { order: 29, name: "DRRange13", field_name: "DRRange13", unit: "", type: "hexbe1" },
radio_73: { order: 30, name: "Freq14", field_name: "Freq14", unit: "MHz", type: "uint32le" },
radio_77: { order: 31, name: "DRRange14", field_name: "DRRange14", unit: "", type: "hexbe1" },
radio_78: { order: 32, name: "Freq15", field_name: "Freq15", unit: "MHz", type: "uint32le" },
radio_82: { order: 33, name: "DRRange15", field_name: "DRRange15", unit: "", type: "hexbe1" },
radio_83: { order: 34, name: "Freq16", field_name: "Freq16", unit: "MHz", type: "uint32le" },
radio_87: { order: 35, name: "DRRange16", field_name: "DRRange16", unit: "", type: "hexbe1" }
};
let fwDefine = {
fw_79:{order:1,name: "reTx", field_name: "reTx", unit:"", type: "uint8"},
// fw_80:{order:2,name: "globalDutyRate", field_name: "globalDutyRate", unit:"", type: "uint8"},
// fw_81:{order:3,name: "dutyBand1", field_name: "dutyBand1", unit:"", type: "uint16le"},
// fw_83:{order:4,name: "dutyBand2", field_name: "dutyBand2", unit:"", type: "uint16le"},
// fw_85:{order:5,name: "dutyBand3", field_name: "dutyBand3", unit:"", type: "uint16le"},
// fw_87:{order:6,name: "dutyBand4", field_name: "dutyBand4", unit:"", type: "uint16le"},
// fw_89:{order:7,name: "devTimeReqDuty", field_name: "devTimeReqDuty", unit:"", type: "uint8"},
fw_90:{order:8,name: "swPublic", field_name: "swPublic", unit:"", type: "bitle0-0"},
}
let cfDefine = {
cf_0:{order:0,name: "sleepWakeEnable", field_name: "sleepWakeEnable", unit:"", type: "bitLE3-3"},
cf_0_1:{order:1,name: "standard", field_name: "standard", unit:"", type: "bitLE4-7",
options: {
0: "EU433/CN470/EU868",
1: "US902",
2: "KR923",
3: "AS923",
4: "AU915",
}
},
cf_1: { name: "Tmode", field_name: "Tmode", unit: "", type: "bit0-0", options: { 0: false, 1: true } },
cf_1_1: { name: "SWRXON", field_name: "SWRXON", unit: "",type: "bit2-2", options: { 0: false, 1: true } },
cf_1_2: { name: "SWTXF", field_name: "SWTXF", unit: "", type: "bit3-3", options: { 0: false, 1: true } },
cf_2: { name: "mulDevAddr", field_name: "mulDevAddr", unit: "", type: "hexLE4"},
cf_6: { name: "mulNSkey", field_name: "mulNSkey", unit: "", type: "hexBE16" },
cf_22: { name: "mulASkey", field_name: "mulASkey", unit: "", type: "hexBE16"},
cf_60: { name: "swSF", field_name: "swSF", unit: "", type: "uint8" },
cf_61: { name: "swBW", field_name: "swBW", unit: "", type: "uint8", options: { 6: "62.5kHz", 7: "125kHz", 8: "250kHz", 9: "500kHz" } },
cf_62: { name: "swFreq", field_name: "swFreq", unit: "Hz", type: "uint32le" },
cf_66: { name: "swPeriod", field_name: "swPeriod", unit: "ms",type: "uint8", coefficient: 50 },
cf_67: { name: "upRepeat", field_name: "upRepeat", unit: "", type: "uint8" }
}
let dsDefine = {
ds_0:{order:0,name: "DR", field_name: "DR", unit:"", type: "uint8"},
ds_1:{order:1,name: "BattLevel", field_name: "BattLevel", unit:"v", type: "uint8"},
ds_2:{order:2,name: "RetryCounts", field_name: "RetryCounts", unit:"", type: "uint8"},
ds_3:{order:3,name: "RSSI", field_name: "RSSI", unit:"", type: "uint8"},
ds_4:{order:4,name: "SNR", field_name: "SNR", unit:"", type: "int8"},
ds_5:{order:5,name: "ChanIndex", field_name: "ChanIndex", unit:"", type: "uint8"},
ds_6:{order:6,name: "RxSlot", field_name: "RxSlot", unit:"", type: "uint8"},
ds_7:{order:7,name: "Rsv", field_name: "Rsv", unit:"", type: "uint8"},
ds_8:{order:8,name: "MacReq", field_name: "MacReq", unit:"", type: "uint16LE"},
ds_10:{order:10,name: "DeviceTime", field_name: "DeviceTime", unit:"", type: "uint32LE"},
ds_14:{order:14,name: "linkMargin", field_name: "linkMargin", unit:"", type: "uint8"},
ds_15:{order:15,name: "linkGWCount", field_name: "linkGWCount", unit:"", type: "uint8"},
}
let appDefine= {
app_0:{name: "FuotaVersion", field_name: "FuotaVersion", unit: "", index: 0, type: "bitLE0-3"},
app_0_1:{name: "HwType", field_name: "HwType", unit: "", index: 0, type: "bitLE4-15", options: {40: "OM422", 51: "OM822"}},
app_2:{name: "HwVersion", field_name: "HwVersion", unit: "", index: 2, type: "bitLE4-7"},
app_3:{name: "SwVersion", field_name: "SwVersion", unit: "", index: 3, type: "uint8"},
app_4:{name: "BzType", field_name: "BzType", unit: "", index: 4, type: "uint16le"},
app_6:{name: "BzVersion", field_name: "BzVersion", unit: "", index: 6, type: "uint8"},
app_10:{name: "minorVersion", field_name: "minorVersion", unit: "", index: 10, type: "uint8"},
app_11:{name: "FilterMask", field_name: "FilterMask", unit: "", index: 11, type: "bitLE0-2"},
app_12:{name: "BaudRate", field_name: "BaudRate", unit: "bps", index: 12, type: "uint8", coefficient: 1200},
app_13:{name: "DataBits", field_name: "DataBits", unit: "", index: 13, type: "bitLE0-3"},
app_13_1:{name: "StopBits", field_name: "StopBits", unit: "", index: 13, type: "bitLE4-5"},
app_13_2:{name: "Checkbit", field_name: "Checkbit", unit: "", index: 13, type: "bitLE6-7"},
app_14:{name: "KeepRx", field_name: "KeepRx", unit: "", index: 14, type: "bitLE0-0", options: {0: false, 1: true}},
app_14_1:{name: "Battery", field_name: "Battery", unit: "", index: 14, type: "bitLE1-1", options: {0: false, 1: true}},
app_14_2:{name: "Uart1Used", field_name: "Uart1Used", unit: "", index: 14, type: "bitLE2-2", options: {0: false, 1: true}},
app_14_3:{name: "TransparentBit", field_name: "TransparentBit", unit: "", index: 14, type: "bitLE3-3", options: {0: false, 1: true}},
app_14_4:{name: "SwUp", field_name: "SwUp", unit: "", index: 14, type: "bitLE4-4", options: {0: false, 1: true}},
app_14_5:{name: "JoinRst", field_name: "JoinRst", unit: "", index: 14, type: "bitLE5-5", options: {0: false, 1: true}},
app_14_6:{name: "PowerCtrl", field_name: "PowerCtrl", unit: "", index: 14, type: "bitLE6-6", options: {0: false, 1: true}},
app_14_7:{name: "Wait60s", field_name: "Wait60s", unit: "", index: 14, type: "bitLE7-7", options: {0: false, 1: true}},
app_15:{name: "ConfirmDuty", field_name: "ConfirmDuty", unit: "", index: 15, type: "uint8"},
app_16:{name: "portPara", field_name: "portPara", unit: "", index: 16, type: "uint8"},
app_17:{name: "portTransparent", field_name: "portTransparent", unit: "", index: 17, type: "uint8"},
app_18:{name: "RstHours", field_name: "RstHours", unit: "h", index: 18, type: "uint16le"},
app_20:{name: "TimeOffset", field_name: "TimeOffset", unit: "", index: 20, type: "uint32le"},
app_24: { name: "battery_base", field_name: "battery_base", unit: "v", index: 24, type: "uint8" }, // 实际值 = raw * 0.01 + 2.0?
app_25: { name: "utc_seconds", field_name: "utc_seconds", unit: "s", index: 25, type: "uint32le" },
app_29: { name: "chip_temperature", field_name: "chip_temperature", unit: "°C", index: 29, type: "uint16le"},
app_31: { name: "chip_voltage", field_name: "chip_voltage", unit: "V", index: 31, type: "uint8"},
app_32: { name: "query_timeout", field_name: "query_timeout", unit: "ms", index: 32, type: "uint16le",},
app_34: { name: "retries", field_name: "retries", unit: "", index: 34, type: "uint8" },
app_35: {name: "uart_calibration", field_name: "uart_calibration", unit: "", index: 35, type: "bitLE0-0"},
app_35_1: {name: "calibration_method", field_name: "calibration_method", unit: "", index: 35, type: "bitLE1-2",
options: {
0: "Internal RC", 1: "External Crystal", 2: "GPS PPS", 3: "Reserved"
}
},
app_36: { name: "calibration_groups", field_name: "calibration_groups", unit: "", index: 36, type: "uint8" },
app_37: { name: "join_interval", field_name: "join_interval", unit: "min", index: 37, type: "uint8" },
app_38: { name: "pwron_delay", field_name: "pwron_delay", unit: "ms", index: 38, type: "uint16le" },
app_40: { name: "period_heart", field_name: "period_heart", unit: "s", index: 40, type: "uint32le" },
app_60: { name: "sub_device_counts", field_name: "sub_device_counts", unit: "", index: 60, type: "uint8" },
app_65: { name: "query_index", field_name: "query_index", unit: "", index: 65, type: "uint8" },
app_66: { name: "sub_addr_size", field_name: "sub_addr_size", unit: "", index: 66, type: "uint8" }
}
let pdata={}
let frameInfo={}
let appInfo={}
let paraInfo={}
if (port===51) {
let tdata={}
let payBuffer=Buffer.from(msg?.userdata?.payload,"hex")
tdata.port=port
tdata.payload=payBuffer.toString("hex")
tdata.rssi = msg.gwrx[0].rssi;
tdata.snr = msg.gwrx[0].lsnr;
if (uplinkDatr != null) tdata.datr = uplinkDatr;
if(share_attrs_change=={}) share_attrs_change=null
return {
telemetry_data: tdata,
server_attrs: null,
shared_attrs: share_attrs_change,
};
}
if (port===209) {
frameInfo=frame209
appInfo=info209
}else if(port===213){
frameInfo=frame213
appInfo=info213
}else if (port===201) {
frameInfo=frame201
appInfo=info201
}else if (port===214){
paraInfo=appDefine
}else {
return null
}
if (port===214) {
let fwParser = new PayloadParser({
device: device,
msg: msg,
paraInfo: fwDefine,
})
let pfw=fwParser.paras()
let cfParser=new PayloadParser({
device: device,
msg: msg,
paraInfo: cfDefine,
})
let pcf=cfParser.paras()
let radioParser=new PayloadParser({
device: device,
msg: msg,
paraInfo: radioDefine
})
let pradio=radioParser.paras()
if (pradio) {
if (pradio["power"]!==undefined){ pradio["power"] -= 2;}
if (pradio["dn2Freq"]!==undefined){ pradio["dn2Freq"] /= 1000000;}
for(let i=0;i<16;i++) {
if (pradio["Freq"+(i+1).toString(10)]===undefined) { continue}
pradio["Freq"+(i+1).toString(10)]&=0xFFFFFFFC
pradio["Freq"+(i+1).toString(10)]/=1000000
}
// Compose freq1..16 composite strings ("<MHz>,<drRangeL>,<drRangeH>,<enable>") into
// shared_attrs so the [MT SET] radio para RPC form can backfill the device's CURRENT
// channel plan (mt_set_radio_para fields use default_from:shared_attrs + attr_field_name:"freqN";
// getFreq() reverses this exact format). Without it the form falls back to a fixed factory plan
// and an operator who only tweaks power would resend defaults and clobber the live channel plan.
// enable is set for BOTH active and inactive channels (KB-18): an undefined enable would let a
// form resend silently drop active channels.
if (pradio["channelMap"]!==undefined) {
let chMap = parseInt(pradio["channelMap"], 16);
for (let i = 1; i <= 16; i++) {
// KB-46/freq16: a partial SET echo only reads back the channels it wrote
// (up to the highest enabled channel), so higher channels (e.g. Freq16 when
// only ch8-15 were enabled) are absent from this frame. Fall back to the
// device's last-known shared_attrs Freq/DRRange so EVERY channel's composite
// is refreshed from channelMap — otherwise the absent channel keeps a stale
// enable flag (reads back true after being disabled) and the SET's paraCheck
// never converges (compares stored "...,false" vs stale live "...,true").
let mhz = pradio["Freq" + i];
if (mhz === undefined) { mhz = device?.shared_attrs?.["Freq" + i]; }
let drStr = pradio["DRRange" + i];
if (drStr === undefined) { drStr = device?.shared_attrs?.["DRRange" + i]; }
if (mhz === undefined || drStr === undefined) { continue; }
let drRange = parseInt(drStr, 16);
let drL = drRange & 0x0F;
let drH = (drRange >> 4) & 0x0F;
let enable = ((chMap >> (i - 1)) & 1) === 1;
pradio["freq" + i] = mhz + "," + drL + "," + drH + "," + enable;
}
}
}
let dsParser=new PayloadParser({
device: device,
msg: msg,
paraInfo: dsDefine
})
let pds=dsParser.paras()
let appParser=new PayloadParser({
device: device,
msg: msg,
paraInfo: appDefine
})
let papp=appParser.paras()
pdata = {
...(papp ?? {}),
...(pfw ?? {}),
...(pcf ?? {}),
...(pds ?? {}),
...(pradio ?? {}),
};
}else {
let payParser = new PayloadParser({
device: device,
msg: msg,
frameInfo: frameInfo,
appInfo: appInfo,
})
pdata = payParser.telemetry()
}
if(pdata?.chip_temperature!==undefined){ pdata.chip_temperature=((pdata.chip_temperature - 1000) / 10).toFixed(1);}
if(pdata?.chip_voltage!==undefined){pdata.chip_voltage = ((pdata.chip_voltage*1.6)/254 + 2).toFixed(2);}
if(pdata?.DataBits!==undefined){ if (pdata.DataBits>8) pdata.DataBits=8;}
if(pdata?.battery_base!==undefined){}
if (pdata?.query_timeout!==undefined){
let raw=pdata.query_timeout;
let timeout_unit = (raw >> 14) & 0x03;
let timeout_value = raw & 0x3FFF;
switch(timeout_unit) {
case 0: pdata.query_timeout= timeout_value * 1000; break; // 秒 -> ms
case 1: pdata.query_timeout= timeout_value * 60000; break; // 分钟 -> ms
case 2: pdata.query_timeout= timeout_value * 3600000; break; // 小时 -> ms
case 3: pdata.query_timeout= timeout_value * 86400000; break; // 小时 -> ms
default: pdata.query_timeout= timeout_value; // ms
}
}
if (pdata!=undefined){
if (uplinkDatr != null) pdata.datr = uplinkDatr;
pdata.class_mode=msg?.userdata?.class
if (msg?.gwrx?.[0] ) {
pdata.rssi = msg.gwrx[0].rssi;
pdata.snr = msg.gwrx[0].lsnr;
}
}
let dataAfter={
...(device.shared_attrs),
...(pdata ?? {}),
}
// Echo-validated downlink resend for makeParaSetMSG-based EB SET RPCs: each RPC stored its
// pending-set state under server_attrs[rpcName]; paraCheck compares the stored params against
// this read-back (dataAfter = shared_attrs ∪ decoded pdata), advancing the counter, clearing
// the ClassC downlink buffer on success, or re-dispatching the RPC on mismatch until success
// or maxRetries. Run for every resend-capable RPC and merge — only the one with a live counter
// returns sdata/actions. mt_set_radio_para added 2026-06-20; eb_easy_para_set kept for compat.
let serverOut={}; let actionsOut=[];
// 全部 makeParaSetMSG 类 EB SET RPC 都参与 echo 重发。前 5 个回读字段可净比对→命中即停;
// 后 3 个(mt_set_broad 组播密钥可能只写不回读 / mt_set_multi_device 的 sub_addr 在 mt_eb_multi_device
// 模型解码、本模型只见 counts / mt_app_any_set 通用写无固定具名字段)无法保证命中→退化为有界重发
// (存的是完整表单入参,重派可正确重建;比对失败仅令其重发至 maxRetries 后停,绝不误判成功/不无限)。
for (const rpcName of ["mt_set_radio_para","mt_set_eb_para","mt_app_uart_set","mt_up_period_set","mt_sw_set_para","mt_set_multi_device","mt_set_broad","mt_app_any_set","eb_easy_para_set"]) {
let checkData=Utils.paraCheck(rpcName,device.server_attrs,dataAfter)
if (checkData.sdata) Object.assign(serverOut,checkData.sdata)
if (checkData.actions) actionsOut.push(...checkData.actions)
}
let result = {
telemetry_data: pdata,
server_attrs: Object.keys(serverOut).length?serverOut:null,
shared_attrs: {...pdata,...share_attrs_change},
actions: actionsOut.length?actionsOut:null,
};
// ── rpcHook (设备对接通用规则) ──
if (typeof RPCHelper.buildRpcHookAction === "function") {
const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "mt_eb", name: "MT-EB", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
}
return result;
}
console.log(payload_parser(
{ telemetry_data: {} },
{
"if": "loraWAN",
"gwrx": [
{
"eui": "5a53012501030221",
"chan": 0,
"lsnr": 11.5,
"rfch": 0,
"rssi": -35,
"time": "2026-06-23T04:03:19.803673Z",
"tmms": 0,
"tmst": 65565302,
"ftime": 0
}
],
"type": "data",
"token": 4254031,
"moteTx": {
"codr": "4/5",
"datr": "SF12BW125",
"freq": 923.4,
"modu": "LORA",
"macAck": " [down] 6088e00132200e0098b8b8bb",
"macCmd": ""
},
"geoInfo": {
"type": "gw:wifi",
"accuracy": 50,
"altitude": 0,
"latitude": 34.20034,
"longitude": 108.86365
},
"moteeui": "6353012b00018125",
"version": "3.0",
"userdata": {
"adr": false,
"port": 214,
"class": "ClassA",
"seqno": 24,
"payload": "YgEAYgEAIgoACADqBjcCgH8OIiorKADqBjdQQPcJN1CABA03UMAREDdQAB8TN1BALBY3UIA5GTdQwEYcN1A=",
"confirmed": true
}
},
"1",
{ telemetry_data: true },
{}
))Thing Model: MT-EB-Emeter
id_name: mt_eb_emeter · ID: 23052087352889349
import {BufferTKL, RPCHelper} from '#tklHelper';
import {Buffer} from "buffer";
function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
let payload = Buffer.from(msg?.userdata?.payload, "base64");
let port=msg?.userdata?.port;
let preTelemetry = device?.telemetry_data?.[thingModelId];
function parseTelemetry(payload){
if (port!=12||payload[0]!=0x21||payload[1]!=0x05) { return {sdevice:null,tdata:null}}
if (payload.length <39) {return {sdevice:null,tdata:null}}
if ((payload[38]&0x01)==0x01) {return {sdevice:null,tdata:null}}
let telemetryData={}
let subDevice=payload.readUInt32LE(2).toString(10).padStart(12, '0')
telemetryData.ua=Number((payload.readUInt16LE(10)/10).toFixed(1))
telemetryData.ub=Number((payload.readUInt16LE(12)/10).toFixed(1))
telemetryData.uc=Number((payload.readUInt16LE(14)/10).toFixed(1))
telemetryData.ia=Number((payload.readUInt16LE(16)/10).toFixed(1))
telemetryData.ib=Number((payload.readUInt16LE(18)/10).toFixed(1))
telemetryData.ic=Number((payload.readUInt16LE(20)/10).toFixed(1))
telemetryData.yz=Number((payload.readUInt32LE(22)/10).toFixed(1))
telemetryData.yf=Number((payload.readUInt32LE(26)/10).toFixed(1))
telemetryData.wz=Number((payload.readUInt32LE(30)/10).toFixed(1))
telemetryData.wf=Number((payload.readUInt32LE(34)/10).toFixed(1))
return {
sdevice: subDevice,
tdata: telemetryData
}
}
let appData= parseTelemetry(payload)
let result = {
sub_device: appData.sdevice,
telemetry_data: appData.tdata,
server_attrs: (appData.sdevice===null)?null:{"sub_addr":appData.sdevice},
shared_attrs: null
};
// ── rpcHook (设备对接通用规则) ──
if (typeof RPCHelper.buildRpcHookAction === "function") {
const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "mt_eb_emeter", name: "MT-EB-Emeter", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
}
return result;
}
console.log(payload_parser(
{ telemetry_data: {} },
{ userdata: { port: 11, payload: "" } },
"1",
{ telemetry_data: true },
{}
))Thing Model: MT-EB-MULTIDEVICE
id_name: mt_eb_multi_device · ID: 24570740992905221
import {BufferTKL, RPCHelper} from '#tklHelper';
import {Buffer} from "buffer";
function payload_parser(device, msg, thingModelId, noticeAttrs, org_params) {
let payload = Buffer.from(msg?.userdata?.payload, "base64");
let port=msg?.userdata?.port || null;
function parseSharedAttrs(payload) {
if (port !== 214 || payload[0] !== 0x2F) { return null; }
let pdata = {};
if (payload.length < 6) { return null; }
let regAddress = payload[2];
if (regAddress!==60) { return null; }
pdata.sub_device_counts=payload[4] //60
pdata.query_index=payload[9] //65
pdata.sub_addr_size=payload[10] //66
let size = payload.length - 11;
for (let i = 0; i < 12; i++) {
if (size<i*pdata.sub_addr_size) { break}
const subBuf = payload.subarray(11+i*pdata.sub_addr_size, 11+i*pdata.sub_addr_size+pdata.sub_addr_size);
switch (i) {
case 0: pdata.sub_addr1 = subBuf.toString('hex'); break;
case 1: pdata.sub_addr2 = subBuf.toString('hex'); break;
case 2: pdata.sub_addr3 =subBuf.toString('hex');break;
case 3: pdata.sub_addr4 = subBuf.toString('hex'); break;
case 4: pdata.sub_addr5 = subBuf.toString('hex'); break;
case 5: pdata.sub_addr6 =subBuf.toString('hex');break;
case 6: pdata.sub_addr7 = subBuf.toString('hex'); break;
case 7: pdata.sub_addr8 = subBuf.toString('hex'); break;
case 8: pdata.sub_addr9 =subBuf.toString('hex');break;
case 9: pdata.sub_addr10 = subBuf.toString('hex'); break;
case 10: pdata.sub_addr11 = subBuf.toString('hex'); break;
case 11: pdata.sub_addr12 =subBuf.toString('hex');break;
default: break;
}
}
if (Object.keys(pdata).length < 1) { return null; }
return pdata;
}
let pdata=parseSharedAttrs(payload)
if (pdata==null) { return null; } // 非 port-214 子设备配置帧:解析器对该模型不适用,返回 null(避免对 null 设 class_mode)
pdata.class_mode=msg?.userdata?.class
let result = {
telemetry_data: pdata,
server_attrs: null,
shared_attrs: pdata
};
// ── rpcHook (设备对接通用规则) ──
if (typeof RPCHelper.buildRpcHookAction === "function") {
const __a = RPCHelper.buildRpcHookAction({ device, msg, thingModelId, idName: "mt_eb_multi_device", name: "MT-EB-MULTIDEVICE", parsed: { telemetry_data: result.telemetry_data, server_attrs: result.server_attrs, shared_attrs: result.shared_attrs } });
if (__a) { result.actions = result.actions || []; result.actions.push(__a); }
}
return result;
}
console.log(payload_parser(
{ telemetry_data: {} },
{ userdata: { port: 11, payload: "" } },
"1",
{ telemetry_data: true },
{}
))Thing Model Field Definitions
| Thing Model | Field | Type | Unit | Name |
|---|---|---|---|---|
| mt_eb | datr | string | Uplink Data Rate | |
| mt_eb | snr | number | dB | SNR |
| mt_eb | port | number | Port | |
| mt_eb | rssi | number | dBm | RSSI |
| mt_eb | BzType | number | Business Type | |
| mt_eb | Battery | number | Battery | |
| mt_eb | payload | string | Payload | |
| mt_eb | BaudRate | number | Baud Rate | |
| mt_eb | Checkbit | number | Parity | |
| mt_eb | DataBits | number | Data Bits | |
| mt_eb | StopBits | number | Stop Bits | |
| mt_eb | BzVersion | number | Business Version | |
| mt_eb | ConfirmDuty | number | Confirm Duty Cycle | |
| mt_eb | pwron_delay | number | Power-on Delay | |
| mt_eb | period_heart | number | Heartbeat Period | |
| mt_eb | sub_device_counts | number | Sub-device Count | |
| mt_eb_emeter | ia | number | A | Phase A Current |
| mt_eb_emeter | ib | number | A | Phase B Current |
| mt_eb_emeter | ic | number | A | Phase C Current |
| mt_eb_emeter | ua | number | V | Phase A Voltage |
| mt_eb_emeter | ub | number | V | Phase B Voltage |
| mt_eb_emeter | uc | number | V | Phase C Voltage |
| mt_eb_emeter | wf | number | kvarh | Reverse Reactive Energy |
| mt_eb_emeter | wz | number | kvarh | Forward Reactive Energy |
| mt_eb_emeter | yf | number | kWh | Reverse Active Energy |
| mt_eb_emeter | yz | number | kWh | Forward Active Energy |
RPC Parameter Definitions
| RPC | Parameter | Type | Description | Default | Unit |
|---|---|---|---|---|---|
| mt_act_dn_data | data | string | Data | 1234 | hex |
| mt_act_dn_data | port | number | Port | 12 | |
| mt_act_dn_data | type | string | Type | data | |
| mt_act_dn_data | gweui | string | Gateway EUI | auto | hex |
| mt_act_dn_data | confirmed | boolean | Confirmed | false | |
| mt_act_sw_dn | data | string | Data | C70403123456 | |
| mt_act_sw_dn | gwEui | string | Gateway EUI | auto | |
| mt_act_transparent | payload | string | Payload | 123456 | hex |
| mt_action | action | string | Action | app | |
| mt_app_any_set | addr | number | Modbus Address | true | |
| mt_app_any_set | type | string | Type | uint8 | |
| mt_app_any_set | value | number | Value | 0 | |
| mt_app_uart_set | BaudRate | number | Baud Rate | — | bps |
| mt_app_uart_set | Checkbit | number | Parity | — | |
| mt_app_uart_set | DataBits | number | Data Bits | — | |
| mt_app_uart_set | StopBits | number | Stop Bits | — | |
| mt_debug_modbus | addr | string | Modbus Address | 1 | |
| mt_debug_modbus | code | string | Modbus Function Code | 03 | |
| mt_debug_modbus | counts | number | Register Count | 1 | |
| mt_debug_modbus | regAddr | string | Register Address | 0000 | |
| mt_get_any_para | addr | string | Modbus Address | 0 | |
| mt_get_any_para | size | number | Size | 30 | |
| mt_get_any_para | type | string | Type | app | |
| mt_set_broad | mulASkey | string | Multicast AppSKey | — | |
| mt_set_broad | mulNSkey | string | Multicast NwkSKey | — | |
| mt_set_broad | mulDevAddr | string | Multicast DevAddr | — | |
| mt_set_eb_para | para | variant | Parameter | period_heart | |
| mt_set_multi_device | isHex | boolean | Is Hex | true | |
| mt_set_multi_device | sub_addr1 | string | Sub-address 1 | nc | |
| mt_set_multi_device | sub_addr2 | string | Sub-address 2 | nc | |
| mt_set_multi_device | sub_addr3 | string | Sub-address 3 | nc | |
| mt_set_multi_device | sub_addr4 | string | Sub-address 4 | nc | |
| mt_set_multi_device | sub_addr5 | string | Sub-address 5 | nc | |
| mt_set_multi_device | sub_addr6 | string | Sub-address 6 | nc | |
| mt_set_multi_device | query_index | number | Query Index | — | |
| mt_set_multi_device | sub_addr_size | number | Sub-address Count | — | |
| mt_set_radio_para | freq1 | string | Frequency 1 | — | |
| mt_set_radio_para | freq2 | string | Frequency 2 | — | |
| mt_set_radio_para | freq3 | string | Frequency 3 | — | |
| mt_set_radio_para | freq4 | string | Frequency 4 | — | |
| mt_set_radio_para | freq5 | string | Frequency 5 | — | |
| mt_set_radio_para | freq6 | string | Frequency 6 | — | |
| mt_set_radio_para | freq7 | string | Frequency 7 | — | |
| mt_set_radio_para | freq8 | string | Frequency 8 | — | |
| mt_set_radio_para | freq9 | string | Frequency 9 | — | |
| mt_set_radio_para | power | number | Power | — | dBm |
| mt_set_radio_para | freq10 | string | Frequency 10 | — | |
| mt_set_radio_para | freq11 | string | Frequency 11 | — | |
| mt_set_radio_para | freq12 | string | Frequency 12 | — | |
| mt_set_radio_para | freq13 | string | Frequency 13 | — | |
| mt_set_radio_para | freq14 | string | Frequency 14 | — | |
| mt_set_radio_para | freq15 | string | Frequency 15 | — | |
| mt_set_radio_para | freq16 | string | Frequency 16 | — | |
| mt_set_radio_para | dn2Freq | number | RX2 Frequency | — | MHz |
| mt_set_radio_para | DLSetting | string | Downlink Setting | — | |
| mt_sw_set_para | swBW | string | LoRa Bandwidth | — | |
| mt_sw_set_para | swSF | number | LoRa Spreading Factor | — | |
| mt_sw_set_para | swPeriod | number | Switch Period | — | ms |
| mt_up_period_set | period_heart | number | Heartbeat Period (s) | — |
Trigger Definitions
No project-specific triggers are defined.
Third-Party Platform Subscription
MQTT Topic: /v32/{Organization Account}/tkl/up/telemetry/{eui}
{
"eui": "6353012af10a9331",
"active_time": "2026-07-13T08:35:48.000Z",
"thingModelId": "Use the platform model ID from the model inventory",
"thingModelIdName": "Use the current telemetry model id_name",
"telemetry_data": {
"snr": 13.5,
"rssi": -51,
"battery": 3.37,
"BzType": 1,
"BzVersion": 2,
"HwType": 3.75,
"HwVersion": 5,
"SwVersion": 5,
"chip_temperature": 6,
"chip_voltage": 7,
"BaudRate": 8
}
}Template Selection and Verification
Search ThinkLink by project name, business code, or the id_name and ID values in the model inventory. After deployment, use real uplinks to verify fields, units, RPCs, and trigger behavior.
Supplementary Notes
Requirements and decisions: requirement/requirements.md.
Base templates and historical RPCs
Base templates provide an EdgeBus device skeleton; select the matching protocol application, acquisition parameters and business models. RPCs marked disuse are listed only to identify old bindings, not for new installations. Read actual parameters and inspect the current device template before migration; do not replace bindings in bulk.
| Type | Name | id_name | Platform model ID |
|---|---|---|---|
| Template | MT-EB-BATTERY | 5 | |
| Template | MT-EB-POWER | 10 | |
| Template | default | 1 | |
| RPC | [MT KS52] get cov paras<disuse> | mtget_ks52_paras | 24014202611961861 |
| RPC | [MT APP] get multi device count<disuse> | mtget_app_device_count | 24846903489335301 |
| RPC | [MT APP] get multi devices<disuse> | mtget_app_multi_device_para | 24577580598300677 |
| RPC | [MT APP] get data period<disuse> | mtget_period_data_disuse | 35431228547010565 |
| RPC | [MT APP] configure multi devices<disuse> | mtset_dtu_multi_device | 24597181948235781 |
| RPC | [MT APP] set period <disuse> | mt_set_period_heart_disuse | 20932439878668293 |
| RPC | [MT APP] get heart period <disuse> | mtget_period_heart | 24574845555576837 |
| RPC | [MT RADIO] get frequencies<disuse> | mtget_frequencies_disuse | 38273241294311429 |