Skip to content

EB APP 参数 ​

本文档旨在为研发及技术支持人员提供一份详尽的 EdgeBus 功能配置指南,核心内容围绕通过 App 参数对 EdgeBus 进行配置。EdgeBus 的参数体系结构清晰,主要划分为 版本信息、运行参数 和 系统信息 三大类别,便于使用者理解与操作。

目标读者 ​

本文档主要面向需要与 EdgeBus 设备进行深度集成和调试的 研发工程师 与 技术支持工程师。

配置方式入门 ​

ThinkLink 用户可使用内置参数配置 RPC。对接 ChirpStack 或其他 LoRaWAN 网络服务器时,按照下面的协议生成应用层字节,再交给网络服务器下发即可。

通过 LoRaWAN 读取和写入 APP 参数 ​

端口、地址与编码约定 ​

项目约定
LoRaWAN FPort214(十六进制 0xD6),参数下行与参数回复均使用此端口
参数区本文参数表对应 app 区;表中地址是十进制字节偏移,不是 Modbus 寄存器地址。例如地址 40 在帧中写成 28
APP 功能码读取 8F,写入 CF,参数值回复 2F
帧头字段功能码、长度、起始地址、读写字节数各占 1 字节
参数值按参数类型编码;本页示例的 16/32 位整数使用小端。移植 RPC 时保留其 type,自定义区可能使用 floatBE、hexLE7 等其他格式
LoRaWAN 应用载荷直接发送下述命令字节;不加串口帧头 FF AA、Modbus CRC、帧尾或 DevEUI。LoRaWAN 加密和 MIC 由网络服务器处理
默认 confirmedtkl-helper 参数消息默认 false;需要时可在发送层改为 true。LoRaWAN ACK 不能代替参数回读

这里的 214 是 RPCHelper.makeMSG({msgType: "paras"}) 和 PayloadParser.paras() 使用的端口。下表地址 16 保存设备的参数读写端口;若设备已改过这个值,应使用设备实际端口,同时调整下行与上行解析。helper 的 paras 路径仍固定为 214,自定义端口需用 msgType: "user" 并显式传入 port。端口 51 是透传通道,不用于本节 APP 参数命令。

读取、写入和回复帧 ​

以下为十六进制字节,AA 表示起始地址,NN 表示参数字节数,LL 表示长度字段之后的字节数。

操作应用载荷格式长度规则
读取请求8F 02 AA NN固定 4 字节;02 表示后面有地址和字节数两个字节
参数值回复2F LL AA NN D0 ... D(N-1)LL = NN + 2,整段共 NN + 4 字节
写入请求CF LL AA NN D0 ... D(N-1)LL = NN + 2,整段共 NN + 4 字节

例如读取心跳周期:8F 02 28 04。若值为 86400 秒(0x00015180),参数回复为 2F 06 28 04 80 51 01 00。写入同一值为 CF 06 28 04 80 51 01 00。

多段命令可直接拼接在同一个 FRMPayload 中,没有额外包头、段数或分隔符。ThinkLink SET RPC 常将 writeBuffer 与 readBuffer 拼接,形成“写入后立即请求回读”:

text
CF 06 28 04 80 51 01 00  8F 02 28 04
└────── 写入 86400 秒 ─────┘  └─ 读取 4 字节 ─┘

接收端也应按 2 + LL 逐段移动,不能假设一个上行只包含一段。读取范围不能跨出实际参数区;由于 LL 只有 1 字节,带参数值的单段 NN 最大为 253,但这不是无线载荷上限。实际请求及预期回复均须满足当前区域、数据速率和设备固件的载荷限制;大范围读取应分批。

应用层协议 还列出写入结果短帧:成功 6F 01 00、失败 4F 01 EE,EE 为设备错误码。tkl-helper 的 PayloadParser.paras() 跳过这些短帧,只解析参数值回复;该源码没有提供完整错误码枚举,也不能据此保证每个固件都发送短帧。集成时保留原始错误码,并以回读地址、长度、参数值与预期一致作为设置成功的依据。不要仅凭 RPC 返回成功、入队成功或 LoRaWAN ACK 判定配置完成。

可直接核对的报文 ​

本表均使用 FPort 214;HEX 中的空格仅用于阅读。

用途下行 HEX参数含义 / 预期回读示例
读取版本信息8F 02 00 08地址 0 起的 8 字节只读信息
读取心跳周期8F 02 28 04地址 40,uint32le,单位秒
设置心跳 24 小时并回读CF 06 28 04 80 51 01 00 8F 02 28 0486400 秒;回读 2F 06 28 04 80 51 01 00
设置外设上电延时 100 ms 并回读CF 04 26 02 64 00 8F 02 26 02地址 38,uint16le;回读 2F 04 26 02 64 00
合并设置上电延时和心跳并回读CF 08 26 06 64 00 80 51 01 00 8F 02 26 06地址 38–43 连续 6 字节;回读 2F 08 26 06 64 00 80 51 01 00
执行所有查询和上传任务CF 03 09 01 02等价于 RPCHelper.redo();地址 9 为写入触发,不按普通参数回读
设备复位CF 03 09 01 01等价于 RPCHelper.reset()
重新入网CF 03 09 01 04等价于 RPCHelper.join()

EB 心跳使用地址 40–43 的 4 字节秒值。其他文档中的“2 字节分钟值”或地址 0x3A 示例不适用于这里的 EB 心跳。自定义采集/上传周期须从当前设备所用 RPC 的 paraDef 确认地址与单位,不能统一当作心跳。DTU 的配套参数要求见本文末尾“DTU 配置最佳实践”。

先从支持该设备的 ThinkLink RPC 中提取 paraDef、业务换算及帧拼接逻辑,再替换消息封装。

ThinkLink 中的内容移植方法
app_40: {type: "uint32le", ...}app 区,十进制地址 40,4 字节小端整数;field_name 是 RPC 入参名,不进入无线帧
params、coefficient、RPC 自定义计算保留写入前换算及读取后逆换算。例如 RPC 的分钟入参若先乘 60,线上仍发送秒值;buildFrame 写入前会除以数值型 coefficient
RPCHelper.paraWrite("app", type, addr, value)生成一段 CF 命令;paraRead("app", type, addr) 生成对应的 8F 命令
RPCHelper.buildFrame(...)可按地址合并连续参数,返回 writeBuffer 和 readBuffer;GET 发送读缓冲,SET 按原 RPC 拼接写/读缓冲
dnMsg.moteeuiChirpStack 设备 DevEUI
dnMsg.userdata.port / confirmedChirpStack 发送层的 fPort / confirmed
dnMsg.userdata.payloadhelper 已编码为 Base64,可直接作为 MQTT data,不要再将该字符串当作 HEX 或再次 Base64 编码
原始 dnBuffercodec 返回 {bytes: Array.from(dnBuffer)};或者应用服务将原始字节转 Base64 后直接入队
Utils.makeParaSetMSG、paraCheck、modifyAttrs、延时/重试动作属于 ThinkLink 调度与状态管理,不是无线帧。由自己的应用实现超时、重试、回读比较及状态保存,不要把整个 Action 数组发给 ChirpStack

地址 8、11、13、14 等包含多个位字段,普通 CF 命令会覆盖整个字节。修改其中一个标志前,先回读原字节,按 newByte = (oldByte & ~mask) | (targetBits & mask) 更新目标位并保留其他位。query_timeout 等参数还包含单位位或固件特定编码,须连同设备实际 RPC 的换算和位运算一起移植,不能直接将界面中的毫秒值当作线上整数发送。

如果原 RPC 使用 ds_、cf_、radio_ 或 fw_,不能将其地址直接套入 app 命令。helper 的普通参数区功能码如下;各区地址定义仍以设备 RPC 为准,下面的 ChirpStack codec 仅处理 app。

参数区读取写入参数值回复
app8FCF2F
cf89C929
fw81C121
radio82C222
ds84C424

ChirpStack v4 可复制的 APP 参数 codec ​

在 Device Profile 中选择自定义 JavaScript codec,放入以下代码。下行入参为 input.data.commands:读取使用 op/address/length;写入使用 op/address/type/value,或以 bytes 提供按原 RPC 编码的原始参数值。支持的数值类型为 uint8、uint16le、uint32le;其他类型先按原 RPC 编码后传 bytes。

写入默认追加同地址回读;地址 9 的功能触发须明确设 readBack: false。端口在入队/MQTT 消息中显式设为 214。代码按 ChirpStack codec 接口 返回原始字节数组,不返回 ThinkLink Action。已有业务上行解码器时,将本例参数分支合入原解码器;本例对其他端口只保留原始数据。

javascript
var EB_PORT = 214;

function integer(value, min, max, name) {
  if (!Number.isInteger(value) || value < min || value > max) {
    throw new Error(name + " must be an integer in " + min + ".." + max);
  }
  return value;
}

function valueBytes(command) {
  if (command.bytes !== undefined) {
    if (!Array.isArray(command.bytes) || command.bytes.length === 0) {
      throw new Error("bytes must be a non-empty array");
    }
    return command.bytes.map(function (b) { return integer(b, 0, 255, "byte"); });
  }
  var widths = { uint8: 1, uint16le: 2, uint32le: 4 };
  if (!Object.prototype.hasOwnProperty.call(widths, command.type)) {
    throw new Error("unsupported type; use uint8/uint16le/uint32le or bytes");
  }
  var width = widths[command.type];
  var value = integer(command.value, 0, Math.pow(256, width) - 1, "value");
  var bytes = [];
  for (var i = 0; i < width; i++) {
    bytes.push(value % 256);
    value = Math.floor(value / 256);
  }
  return bytes;
}

function encodeDownlink(input) {
  var commands = input.data && input.data.commands;
  if (!Array.isArray(commands) || commands.length === 0) {
    throw new Error("commands must be a non-empty array");
  }
  var bytes = [];
  commands.forEach(function (command) {
    var address = integer(command.address, 0, 255, "address");
    var values;
    var length;
    if (command.op === "read") {
      length = integer(command.length, 1, 253, "length");
    } else if (command.op === "write") {
      values = valueBytes(command);
      length = integer(values.length, 1, 253, "length");
      if (command.readBack !== undefined && typeof command.readBack !== "boolean") {
        throw new Error("readBack must be boolean");
      }
    } else {
      throw new Error("op must be read or write");
    }
    if (address + length > 256) throw new Error("address range exceeds 255");
    if (command.op === "read") {
      bytes = bytes.concat([0x8f, 2, address, length]);
    } else {
      bytes = bytes.concat([0xcf, length + 2, address, length], values);
      if (command.readBack !== false) {
        bytes = bytes.concat([0x8f, 2, address, length]);
      }
    }
  });
  // 调用方须按区域/DR限制命令及预期回复的长度,并按本页权限表选择可写地址。
  return { bytes: bytes };
}

function unsignedLE(bytes) {
  var value = 0;
  for (var i = bytes.length - 1; i >= 0; i--) value = value * 256 + bytes[i];
  return value;
}

function decodeUplink(input) {
  var bytes = input.bytes;
  if (input.fPort !== EB_PORT) {
    return { data: { fPort: input.fPort, raw: bytes } };
  }
  var blocks = [];
  var parameters = {};
  var offset = 0;
  if (bytes.length === 0) throw new Error("empty parameter reply");
  while (offset < bytes.length) {
    if (offset + 2 > bytes.length) throw new Error("truncated block header");
    var code = bytes[offset];
    var length = bytes[offset + 1];
    var end = offset + 2 + length;
    if (end > bytes.length) throw new Error("truncated block body");
    if (code === 0x2f) {
      if (length < 3) throw new Error("invalid APP reply length");
      var address = bytes[offset + 2];
      var count = bytes[offset + 3];
      if (count !== length - 2 || address + count > 256) {
        throw new Error("inconsistent APP reply range");
      }
      var values = bytes.slice(offset + 4, end);
      blocks.push({ kind: "app", address: address, bytes: values });
      // 仅解码当前块完整覆盖的字段;其他字段保留在 blocks 中供业务按 RPC 定义解析。
      [{ address: 38, size: 2, name: "pwron_delay" },
       { address: 40, size: 4, name: "period_heart" }].forEach(function (field) {
        var start = field.address - address;
        if (start >= 0 && start + field.size <= values.length) {
          parameters[field.name] = unsignedLE(values.slice(start, start + field.size));
        }
      });
    } else if ((code === 0x6f || code === 0x4f) && length === 1) {
      blocks.push({ kind: "write_result", code: code, result: bytes[offset + 2] });
    } else {
      blocks.push({ kind: "other", code: code, bytes: bytes.slice(offset + 2, end) });
    }
    offset = end;
  }
  return { data: { parameters: parameters, blocks: blocks } };
}

编码器测试输入(即 input.data,同时设置上电延时和心跳):

json
{
  "commands": [
    { "op": "write", "address": 38, "type": "uint16le", "value": 100 },
    { "op": "write", "address": 40, "type": "uint32le", "value": 86400 }
  ]
}

输出字节对应 CF04260264008F022602CF062804805101008F022804。本例保留两条独立写/读命令,与上表合并地址 38–43 的帧实现相同参数配置。只读心跳时改用 {"commands":[{"op":"read","address":40,"length":4}]};执行查询/上传触发时使用 {"commands":[{"op":"write","address":9,"type":"uint8","value":2,"readBack":false}]}。

收到 2F082606640080510100 后,decodeUplink 输出的 data.parameters 为 {"pwron_delay":100,"period_heart":86400}。若设备分两段回复,则按 blocks 的地址和字节内容处理;若分成两次上行,由应用按设备累计本次回读结果。短帧 6F0100 只进入 blocks,不会伪造已回读的参数。

ChirpStack MQTT 下发与结果核验 ​

启用 ChirpStack MQTT 集成的 JSON 消息格式,向默认主题 application/APPLICATION_ID/device/DEV_EUI/command/down 发布下行消息。替换应用 ID、DevEUI,并确保消息中的 DevEUI 与主题一致;APPLICATION_ID 不是 AppEUI/JoinEUI。主题若被管理员改过,以实际配置为准。ChirpStack MQTT 说明

已安装上述 codec 时,发送以下消息即可设置两个参数并请求回读:

json
{
  "devEui": "0102030405060708",
  "confirmed": false,
  "fPort": 214,
  "object": {
    "commands": [
      { "op": "write", "address": 38, "type": "uint16le", "value": 100 },
      { "op": "write", "address": 40, "type": "uint32le", "value": 86400 }
    ]
  }
}

也可不安装 codec,由应用直接发送原始帧的 Base64。以下读取心跳,原始 HEX 为 8F022804,Base64 为 jwIoBA==:

json
{
  "devEui": "0102030405060708",
  "confirmed": false,
  "fPort": 214,
  "data": "jwIoBA=="
}

使用 object 或 data 中的一种即可。HEX → Base64 的 Node.js 示例为 Buffer.from("8F022804", "hex").toString("base64");不能直接把 "8F022804" 放到 data。在支持 HEX 输入的队列界面中才输入 HEX。使用 gRPC DeviceService.Enqueue 时,SDK 的 queue_item.data 是原始 bytes;REST JSON 接口需要另外部署 ChirpStack REST 网关,再按其 API 使用 Base64。官方 gRPC 示例、REST 说明

核验流程:

  1. 确认设备已入网,参数端口正确。Class A 需等设备下一次上行后的接收窗口;Class C 仍需网络调度。短时间没有回复不能直接判定帧格式错误。
  2. 下发写入加回读命令,订阅 application/APPLICATION_ID/device/DEV_EUI/event/up,检查上行 fPort 和 data,或 codec 解码后的 object.parameters / object.blocks。
  3. 对比本次操作涉及的所有地址、长度和值。发送前记录预期值,按设备串行处理配置,避免旧上行与并发命令混淆;帧本身不含 RPC 请求 ID。
  4. 缺少回复或值不一致时,按设备上行周期设置超时,先重新读取再决定是否重写,并限制重试次数。复位、重新入网、执行任务等触发命令不可当作普通参数无限重放。

本节依据 tkl-opt/tooling/integration-tools/tklHelper.js v1.01.042 核对:RPCHelper.paraRead/paraWrite、buildFrame/_freshPara、makeMSG、PayloadParser.paras、Utils.makeParaSetMSG/paraCheck;38/40 的类型同时核对 tkl-opt/capabilities/templates/rpc/shared/mt_set_eb_para.js。协议示例可离线与 helper 对照;实际设备的固件、业务参数布局及写入生效行为仍需按上述流程回读验证。

版本信息 (Version Information) ​

版本信息参数提供了 EdgeBus 设备的固件与硬件标识,共占据 8 字节,所有数据均为 只读。

地址长度权限字段描述
01B只读fuotaversion hwtypebit0-bit3: FUOTA版本号,升级时,数据包中版本号需与此匹配。
bit4-bit7: 产品硬件型号低 4 位 (共 12 位)。
11B只读hwtype产品硬件型号高 8 位 (共 12 位)。
21B只读hwversionbit0-bit3: FUOTA 升级时拷贝的字节数 4 * (len + 1)。
bit4-bit7: 硬件版本号,范围 1-15。
31B只读swversion软件版本号,范围 1-255。
4-52B只读bztype对接的业务设备型号。
61B只读bzversion业务参数版本号。
71B只读filtermask
otamask
checkOk
bit0-bit2: filtermask。
bit3: 保留。
bit4-bit6: otamask。
bit7: checkOk 标志位。

运行参数 (Running Parameters) ​

运行参数决定了 EdgeBus 运行期间的工作模式与核心功能,共占据 16 字节。

地址长度权限字段描述
81B读/写fieldMode
RelayEnable
bit0: fieldMode。
bit1: RelayEnable,置 1 开启中继功能。
bit2-bit7: 保留。
91B写功能触发用于触发 EdgeBus 执行特定操作: - 写入 1: 设备复位。
- 写入 2: 执行所有查询和上传任务。
- 写入 4: 重新入网。
101B读/写Resv保留。
111B读/写唤醒与通道配置bit0: 置 1 允许通过 PC6 引脚输入低电平唤醒 EdgeBus。
bit1: 置 1 在 UART 发送数据前通过 PC2 输出低电平以唤醒客户 MCU。
bit2-bit3: 保留。
bit4-bit6: 回传通道选择 (0: LoRaWAN, 1: 4G)。
bit7: 保留。
121B读/写串口波特率值为 串口波特率/12。
例如,波特率为 9600 时,此值为 800 (9600/12)。
131B读/写串口通信参数
bit0-bit3: 数据位。
bit4-bit5: 停止位。
bit6-bit7: 校验位 (0: 无校验, 1: 奇校验, 2: 偶校验)。
注意: 不同硬件平台在偶校验下的数据位设置存在差异:
- OMx11: 8 位数据位 + 偶校验时,数据位(bit0-bit3)应设为 9,校验位(bit6-bit7)设为 2。
- OMx22: 8 位数据位 + 偶校验时,数据位(bit0-bit3)设为 8,校验位(bit6-bit7)设为 2。
141B读/写工作模式
bit0: 置 1 使 EdgeBus 保持接收状态,适用于对接设备主动上传数据的场景。
bit1: 置 1 启用低功耗模式。
bit2: 置 1 通过 UART1 与对接设备交换数据。
bit3: 置 1 启用透传模式。
bit4: 通道选择 (0: LoRaWAN, 1: Lora)。
bit5: 置 1 开启上电自动重新入网功能。
bit6: 置 1 使EdgeBus上电后通过 Lora通道发送自身信息。
bit7: 置 1 使 EdgeBus 在上电 60 秒内完成对时、模式切换等初始化工作。
151B读/写confirm 占空比当上行数据包序号为此值的整数倍时,发送 confirm 包,否则发送 unconfirm 包。
161B读/写参数读写端口用于读取或设置 EdgeBus 参数的端口号。
171B读/写透传通道端口透传模式下的通信端口号。
18-192B读/写定时复位周期EdgeBus 将根据此设置的小时数定时复位。
20-234B读/写上行时间偏移单位为毫秒 (ms)。EdgeBus 上传数据时会加上此时间偏移,通常设置为 0。

系统信息 (System Information) ​

系统信息参数提供了关于 EdgeBus 硬件状态和环境的实时数据。

地址长度权限字段描述
241B读/写batterybase电池电量基准值。
25-284B读/写UTC 时间自 1970 年 1 月 1 日以来的秒数。
29-302B只读芯片温度实际温度值 (°C) = (读取值 - 1000) / 10。
311B只读芯片供电电压供电电压 (V) = (读取值 / 254 ) * 1.6+ 2。
32-332B读/写查询超时时间bit0-bit13: 超时时间值。
bit14-bit15: 超时单位 (0: 秒, 1: 分, 2: 时, 3: 天)。
341B读/写超时重查次数查询超时后,重新尝试查询的次数。
351B读/写串口数据校准bit0: 置 1 开启串口数据校准。
bit1-bit2: 校准方式 (0: 或, 1: 与, 2: 直接替换)。
bit3-bit7: 保留。
361B读/写串口校准组数定义校准规则的数量。校准规则从地址 170 开始,每 3 字节为一组,分别定义起始索引、长度和校准值。
371B读/写周期性入网周期设备进行周期性重新入网的时间间隔。
38-392B读/写外设上电延时单位为毫秒 (ms)。适用于低功耗产品,确保外设 (如 RS485) 在执行操作前电源稳定。
40-434B读/写心跳周期单位 秒 默认 86400
44-5916B只读系统保留系统保留
601B读/写sub_device_counts多设备模式下,对接设备的个数 默认 0
61-644B只读系统保留系统保留
651B读/写query_index设备地址在查询命令中的起始索引
661B读/写sub_addr_size多设备模式下,子设备地址长度
67-nB读/写sub_addr多设备模式下,子设备地址,按照sub_addr_size 大小
依次子设备地址:
- 设备 1: 67 ~ 67 + sub_addr_size - 1
- 设备 2: 67 + Len ~ 67 + 2*sub_addr_size - 1
- 设备 N: 67 + (N-1)*sub_addr_size ~ 67 + N*sub_addr_size - 1

其他参数 (Other Parameters) ​

用户开放地址空间 ​

  • 地址范围: 70 - 200
  • 描述: 此段地址空间保留给用户自由使用,可用于存储自定义数据。

建议地址使用方法 ​

70-110 : 按4字节单位为秒保存周期(查询周期或者上行周期)

110-150:为COV的阈值存储区

150-200:为子设备地址或其他数据存储区

DTU 配置最佳实践 ​

心跳周期与外设上电延时 ​

⚠️ 重要提示:EB-DTU 出厂默认没有 EB 业务,心跳周期默认 3600 s(1 小时)。新机或复位后须通过参数配置 RPC 或设备配置模板将"业务参数 + pwron_delay + 心跳"一次配齐后方可投入使用。

推荐值(自集成版本 A17 起遵循):

参数地址推荐值原因
心跳周期(period_heart)40–4386400 s(24 h)减少无线传输次数,降低功耗。取代早先推荐的 8 h / 28800 s。
外设上电延时(pwron_delay)38–39≥ 100 ms超声波换能器及 RS-485 传感器上电后需要稳定时间。延时低于 100 ms 会导致 UART/RS-485 查询间歇性超时(上行帧 status 位 0x02 = 查询超时)。具体数值取决于传感器上电稳定时间规格。

SET RPC 必须同步设置:每次通过 SET RPC 配置采集/上传周期时,务必在同一次调用中同时设置 pwron_delay 和 period_heart。仅配置周期而不同步心跳和上电延时,会导致现场难以复现的时序问题。

pwron_delay 的默认值下发路径:

  • getOtaConfig 不支持设置 pwron_delay 的固件默认值。
  • 须通过设备配置模板(shared_attrs.pwron_delay.dftValue)配合SET RPC 在首次连接时下发。

以上规范自集成版本 A17 起遵循,之前已部署的设备无需回改。