Skip to content

EBHelper

EBHelper 是面向 EB 代码开发的"配置即代码"工具集。无需手动构造 QueryEvent / LoraUpEvent 实例并逐条编写 pushEBData / CRC 规则,只需在一份 UserConfUPItem JSON 配置中描述设备布局,调用 EventInfoItem 即可完成所有工作。

为什么使用 EBHelper 而不是原始 EBSDK?

  • 自动生成 Modbus、DL/T 645-2007、CJ/T 188 协议的查询帧。
  • 自动计算 CRC 规则(Modbus 使用 CRC16,仪表协议使用 SUM)。
  • 自动分配 APP 参数段中的周期、COV 阈值和动态参数槽位。
  • 生成统一的上行帧结构(version / dataType / covStatus / status / battery / addr / appData),适用于所有设备类型。

本文档对照源码 source/EBSDK/EBCompiler/plugins/EBHelper.ts(version 1.02.011)编写。


1. 支持的协议

protocol 字段取值(大小写敏感):

协议适用场景子设备地址长度
modbus标准 RTU Modbus1 字节(默认)
DLT64507DL/T 645-2007 电表强制 6 字节addrSize<6 时自动覆盖为 6)
CJ188CJ/T 188 水/气/热表强制 7 字节(自动覆盖为 7)
any任意自定义协议(手填 cmd / ack沿用 addrSize 配置
modbusBitCov已弃用,新工程不要再用

协议强制覆盖在 EventInfoItem.protocolBuild 中执行:若 addrSize 小于协议最小值,将被静默覆盖。


2. 标准上行帧结构

每个 UserConfUPItem 配置生成的 txBuffer 结构如下:

字段偏移大小说明
version01 B协议版本号,默认 0x83
dataType11 B数据类型编码;若未设置,编译时按 0x31 起自动递增。
covStatusindexCovStatus(默认 2)1 BCOV 状态位,由各 ValItem 按位或累加。
statusindexStatus(默认 3)1 B来自 EBModel.APP_STATUS[2] 的查询事件状态。
batteryindexBattery(默认 4)1 B来自 EBModel.DEVICE_STATUS[3] 的电池电量。
addr[]indexAddr(默认 5)addrSize B子设备地址;长度受协议覆盖(见 §1)。
appData[]indexAddr + addrSizequInfo 累加应用层数据;解析由物模型完成。

indexApp 自动等于 indexAddr + addrSize不要手动设置


3. UserConfUPItem:上行事件配置

字段类型默认值说明
nameStringevent_<i>上行事件名(用于日志识别)。
portNumber22LoRaWAN 上行端口。
versionString / Number0x83协议版本字节。
dataTypeString / Number自增(0x31 起)数据类型编码。同一编译过程中每个 UserConfUPItem 依次递增。
indexCovStatusNumber2COV 状态字节偏移。
indexStatusNumber3状态字节偏移。
indexBatteryNumber4电量字节偏移。
indexAddrNumber5地址字段起始偏移。
addrSizeNumber1(受协议覆盖)地址长度(字节)。
upPeriodString周期字面量:"900s""15m""1h""1d""1y"
upPeriodIndexNumberAPP 参数段 4 字节小端周期寄存器地址。-1ConfigPeriod.periodIndex(起始 70)自动分配。
quInfoArray查询事件配置列表,见 §4。

周期优先级(ConfigPeriod 逻辑)

  1. 若设置了 upPeriodIndex(含 -1 自动分配)→ 索引模式:周期存储在 APP 参数段,可通过 RPC 运行时调整。
  2. 否则若 upPeriod"trigger" → 使用 PERIOD_DUMB(约 10 年):永不自动上行,完全由 ActAfterCvt 触发。
  3. 否则若设置了 upPeriod → 由 Utils.parseSeconds() 解析(单位:s m h d y)。
  4. 两者均未设置 → 自动分配 APP 槽位,值回退为 PERIOD_DUMB

4. quInfo 条目(UserConfQueryItem

字段类型默认值说明
nameString<protocol>_<idx>查询事件名。
protocolStringmodbus见 §1。
preambleNumber5CJ188 / DLT64507 帧前导 0xFE 字节数。
isLastBoolean自动true 时,此查询完成后立即触发上行。当 upPeriodIndex 已使用且周期 ≥ PERIOD_MAX(1 年)时,最后一个 quInfo 条目自动设为 true
periodStringupPeriod 语义相同,但作用于此查询事件。
periodIndexNumberupPeriodIndex 语义相同,但作用于此查询事件。
codeStringModbus 功能码(十六进制),默认 0x03;若为空,取自 cmd[1]
addrString子设备地址(十六进制字符串,如 "0x02")。
cmdString自动自定义查询帧(十六进制)。设置后将完全覆盖自动生成帧(但 addrcode 仍会覆写字节 0–1)。
ackString自定义响应模板(十六进制)。提供后进入"freeValue"模式,不再自动计算 Modbus 寄存器布局。
payIndexNumber协议默认值ACK 帧数据起始偏移。Modbus:3,CJ188:0,DLT64507:0
ackAddrIndexNumber协议默认值ACK 帧内地址偏移。Modbus:0,CJ188:2,DLT64507:1
indexAPP / indexCMD / copySizeNumberAPP 参数注入三元组,见 §5。
listTagArray逐字节 ACK 验证规则,见 §4.2。
listValArray必填数据提取规则,见 §6。

4.1 Modbus 自动生成指令帧

未设置 cmd 时,QuItemModBus 根据 addrcode 以及从 listVal 自动检测的寄存器范围(通过 QuFrameInfo)构建指令帧:

text
[addr(1B)] [code(1B)] [起始寄存器高位] [起始寄存器低位] [寄存器数量高位] [寄存器数量低位] [CRC低位] [CRC高位]

QuFrameInfo 自动计算覆盖所有 listVal 条目的最小连续寄存器范围,无需手动计算寄存器数量。

4.2 listTag — 固定字节 ACK 验证

每个条目 { index, val } 断言 ack[index] === val。用于验证协议起始/结束字节或地址回显。

typescript
// 断言第 0 字节为 0x68,最后字节为 0x16(DL/T 645 帧标志)
listTag: [
    { index: 0,  val: "0x68" },
    { index: -1, val: "0x16" }   // 负索引 = 从末尾计数
]

4.3 CJ188 / DLT64507 帧结构

两种协议均使用若干 0xFE 前导字节加协议帧的格式。preamble 字段(���认 5)设置前导字节数量。EBHelper 自动处理:

  • 将前导字节数添加到 ConfigPreCopyindexCMD 偏移。
  • 为协议起止标志(0x68 / 0x16)设置 addAckCheckRule
  • 在帧体上配置 SUM 校验和。

5. ConfigPreCopy:向指令帧注入 APP 参数

用于在编译时将子设备地址(或任意 APP 参数段值)动态填入下发查询帧。多设备模式正是通过此机制实现:将存储在 APP 中的不同地址在每次查询前复制到指令帧。

字段说明
indexAPPAPP 参数段起始偏移。-1ConfigPreCopy.appIndex(起始 150)自动分配,每次按 copySize 递增。
indexCMD目标偏移(cmd 内)。
copySize拷贝字节数。

示例——将存储在 APP[150] 的 1 字节 Modbus 地址注入 cmd[0]:

typescript
{
    protocol: "modbus",
    addr: "0x01",
    indexAPP: 150,
    indexCMD: 0,
    copySize: 1,
    listVal: [...]
}

indexCMDcopySize 等于 Utils.INVALID_NUM,则整个预拷贝配置被禁用。


6. ValItem:数据提取与 COV

每个 listVal 条目:

字段类型说明
start / endString闭区间。Modbus 下为寄存器地址,其他协议下为字节偏移。支持十六进制字符串(如 "0x0102")。
covTypeStringCOV 数据类型(§6.1)。省略或设为 "INVALID" 时禁用 COV——字节直接原样拷贝。
covAppIndexNumberCOV 阈值在 APP 参数段的偏移。-1ValItem.covIndex(起始 110)自动分配,每次递增 4。

6.1 支持的 covType

类型说明
Uint8 / Int88 位无符号/有符号整数
Uint16BE / Uint16LE / Int16BE / Int16LE16 位整数
Uint32BE / Uint32LE / Int32BE / Int32LE32 位整数
FloatBE / FloatLE32 位浮点数
FloatCDAB / IntCDAB / UintCDABCDAB 字节序 32 位值
BCDBCD 编码值
HEX逐字节比较;任意字节变化即触发 COV
INVALID禁用 COV;仅原样拷贝字节

6.2 COV 触发逻辑

  • INVALID(或省略)→ [start, end] 范围内的字节原样拷贝到 txBuffer
  • HEX → 与 SENSOR_DATA 中的上次快照逐字节比较;有差异时设置 txBuffer[indexCovStatus] 对应位,上行后更新快照。
  • 数值类型 → 调用 quEvent.setupCov(...),用 APP 中 covAppIndex 处存储的阈值进行绝对差值比较。

6.3 Modbus 寄存器范围映射

QuFrameInfo 从所有 listVal.start / end 值中计算最小覆盖范围。每个寄存器映射到 payIndex + (寄存器 − 起始寄存器) × 2 处的 2 字节 ACK 数据。功能码 0x01 / 0x02(位读取)将各位合并为 ceil(bitCount/8) 字节。


7. APP 参数段地址规划

EBHelper 使用三个不重叠的静态类变量自动分配 APP 范围,这些变量在同一编译过程的所有 EventInfoItem 实例间累加:

范围用途自动起始步长
70 – 109周期参数(每个 4 字节)ConfigPeriod.periodIndex = 70+4
110 – 149COV 阈值缓存(每个 4 字节)ValItem.covIndex = 110+4
150 – 199动态参数源(ConfigPreCopyConfigPreCopy.appIndex = 150+copySize

不要将上述范围内的偏移硬编码用于其他用途。若显式设置了 upPeriodIndexcovAppIndexindexAPP 的值,EBHelper 将直接使用该值,不会为该槽位推进计数器。


8. Utils 常量

常量含义
Utils.INVALID_NUM-1000000000数值哨兵——表示"未配置/自动分配"。
Utils.INVALID_STR"INVALID"字符串哨兵。
Utils.PERIOD_DEFAULT90015 分钟(秒)。
Utils.PERIOD_DUMB86400 × 3650约 10 年;等效于"永不自动上行"。
Utils.PERIOD_TRIGGER"trigger"upPeriod 的字符串字面量;映射到 PERIOD_DUMB
Utils.PERIOD_MAX86400 × 3651 年;用于判断是否自动将最后一个 quInfo 条目的 isLast 设为 true

9. 导出成员

typescript
export {
    version, Utils,
    UserConfQueryItem, UserConfUPItem, TypeVal, TypeProtocol, TypeQuItem, QuItemMap,
    ConfigPreCopy, ConfigPeriod, TagItem, ValItem, EventConfig,
    QuFrameInfo, QuItemBase, QuItemModBus, QuItemModBusBitCov, QuItemDLT64507, QuItemCJ188,
    EventInfoItem
}

实际使用中几乎只需要 UserConfUPItem + EventInfoItem:填写配置,构造实例,调用 upEventSetup(),再调用 eventInstall()


10. 完整示例

10.1 单 Modbus 设备 + COV

typescript
import { EBModel } from "@EBSDK/EBCompiler/all_variable";
import { UserConfUPItem, EventInfoItem } from "@EBSDK/EBCompiler/plugins/EBHelper";
import { CheckbitEnum, getOtaConfig } from "@EBSDK/otaConfig";
import { buildOtaFile } from "@EBSDK/run";

const eventInfo: UserConfUPItem[] = [
    {
        name: "meter1",
        dataType: "0x31",
        upPeriodIndex: 70,          // 周期存储在 APP[70..73],可通过 RPC 调整
        quInfo: [
            {
                protocol: "modbus",
                addr: "0x01",
                code: "0x03",
                periodIndex: 74,    // 查询周期存储在 APP[74..77]
                listVal: [
                    { start: "0",     end: "1",  covType: "Uint32BE", covAppIndex: 110 },
                    { start: "2",     end: "3",  covType: "Uint32BE", covAppIndex: 114 },
                    { start: "4",     end: "7" }   // 原样拷贝,不启用 COV
                ]
            }
        ]
    }
];

let otaConfig = getOtaConfig({
    SwVersion: 31, BaudRate: 9600, StopBits: 1, DataBits: 8,
    Checkbit: CheckbitEnum.NONE, Battery: true, ConfirmDuty: 60,
    BzType: 10101, BzVersion: 1
});

const MODBUS_TT = (ebModel: EBModel) => {
    for (const cfg of eventInfo) {
        const ev = new EventInfoItem(cfg);
        ev.upEventSetup();
        ev.eventInstall();
    }
    return JSON.stringify(ebModel, null, 2);
};

buildOtaFile(__filename, otaConfig, MODBUS_TT);

10.2 同一编译过程中两路上行配置

typescript
const eventInfo: UserConfUPItem[] = [
    {
        name: "channel_A",
        dataType: "0x31",
        upPeriodIndex: 70,
        quInfo: [
            {
                protocol: "modbus", addr: "0x01", code: "0x03", periodIndex: 74,
                listVal: [{ start: "0", end: "3", covType: "Uint32BE", covAppIndex: 110 }]
            }
        ]
    },
    {
        name: "channel_B",
        dataType: "0x32",
        upPeriodIndex: 78,          // channel_A 占用 70–77 后的下一个 4 字节槽位
        quInfo: [
            {
                protocol: "modbus", addr: "0x02", code: "0x03", periodIndex: 82,
                listVal: [{ start: "0", end: "1", covType: "Uint16BE", covAppIndex: 114 }]
            }
        ]
    }
];
// 静态计数器自动推进,无需手动管理偏移。

10.3 DL/T 645-2007 电表

typescript
const eventInfo: UserConfUPItem[] = [
    {
        name: "elecMeter",
        dataType: "0x41",
        upPeriodIndex: 70,
        quInfo: [
            {
                protocol: "DLT64507",
                addr: "010203040506",   // 6 字节表地址(十六进制)
                preamble: 4,            // 4 个 0xFE 前导字节
                listVal: [
                    { start: "0", end: "3", covType: "BCD" }
                ]
            }
        ]
    }
];
// addrSize 自动强制为 6;SUM 校验和与帧标志自动配置。

11. 最佳实践

  • 尽量减少查询拆分:单次 ACK 可容纳约 250 字节——将连续寄存器合并为一次查询,比多次分开查询更���省功耗。
  • 仅在间隔过大时才拆分:只有无效/无用数据超过约 50 字节时,拆分才值得。
  • 优先使用索引模式配置upPeriodIndex: -1periodIndex: -1covAppIndex: -1 允许通过 RPC 运行时调整,无需重新编译。
  • 不要在 70/110/150 范围内硬编码偏移,除非明确需要固定值并防止自动分配占用该槽位。
  • 为每个上行配置显式设置 dataType,确保跨固件版本的物模型解析结果一致。