Skip to content

EB Compiler SDK 使用说明 ​

1. 概述 ​

1.1 核心概念 ​

EB Compiler SDK 是一个基于事件驱动的 IoT 数据采集与传输框架,核心原则如下:

  • 事件驱动架构:所有操作由事件触发执行。
  • 周期性执行:查询事件和上行事件均按可配置周期运行。
  • 数据流处理:通过查询事件从子设备获取数据,经可选处理后,由上行事件发送至云端。

1.2 事件类型 ​

事件说明
QueryEvent周期性通过 UART/RS-485 向子设备发送指令并等待响应。
UpAfterQueryEvent类似 QueryEvent,但查询完成后直接上传原始响应,无需编写拷贝/转换规则。
LoraUpEvent周期性将组装好的 txBuffer 通过 LoRaWAN 发送至云端。

2. 环境搭建与编译 ​

2.1 前置条件 ​

  1. 安装 Node.js(推荐 LTS 版本)。
  2. 全局安装 ts-node:
bash
npm install -g ts-node
  1. 在 EBSDK 项目根目录安装依赖:
bash
npm install

2.2 执行编译 ​

将 TypeScript 文件放置于 project/ 目录下(例如 project/myMeter/myMeter.ts),然后运行:

bash
ts-node project/myMeter/myMeter.ts

2.3 输出文件 ​

编译完成后,在 project/myMeter/release/ 目录下生成四个文件:

文件说明
myMeter.bin原始编译二进制文件。
myMeter.json编译中间文件(用于调试)。
myMeter.otaOTA 升级描述符文件。
myMeter.obin最终升级包——上传时使用此文件。

3. OTA 配置(getOtaConfig) ​

入口文件必须调用 getOtaConfig 描述硬件和串口环境。

typescript
import { CheckbitEnum, getOtaConfig } from "@EBSDK/otaConfig";

let otaConfig = getOtaConfig({
    SwVersion: 31,
    BaudRate: 9600,
    StopBits: 1,
    DataBits: 8,
    Checkbit: CheckbitEnum.NONE,
    ConfirmDuty: 60,
    Battery: true,
    BzType: 101,   // 必填,2 字节
    BzVersion: 2,  // 必填,1 字节
});

3.1 字段说明 ​

字段类型必填说明
SwVersionNumber是EB 虚拟机固件版本,当前值为 31。必须与设备实际固件一致,不一致会导致升级失败。
BaudRateNumber是UART/RS-485 波特率,必须为 1200 的整数倍(如 9600、19200)。
StopBitsNumber是停止位(1 或 2)。
DataBitsNumber是数据位(通常为 8)。
CheckbitCheckbitEnum是校验位:NONE、ODD 或 EVEN。
ConfirmDutyNumber是Confirm 包占比。60 表示每 60 个上行包中发送 1 个 Confirm 包。
BatteryBoolean是true = 电池供电 → Class A 模式;false = 持续供电 → Class C 模式。
BzTypeNumber是业务类型标识(2 字节),用于识别所连接的仪表/设备类型。
BzVersionNumber是业务版本号(1 字节)。BzType 与 BzVersion 中至少一个须与设备当前值不同,否则 EB 认为固件已是最新版本而跳过升级。
HwTypeHwTypeEnum否硬件型号(如 OM422=40、OM822=51)。
HwVersionNumber否硬件版本号。
FuotaVersionNumber否FUOTA 版本号。

完整 APP 参数参考(TransparentBit、UpRawAfterQuery、端口配置等),请查阅 EB APP 参数。


4. 系统缓冲区 ​

以下五个缓冲区由 EBModel 管理,在用户代码中可通过 EBModel.APP、EBModel.TEMPLATE 等方式访问。

缓冲区大小权限说明
APP255 B只读应用参数。0–69 字节由系统管理;70–200 字节为用户自由使用区(参见 EB APP 参数)。
APP_STATUS32 B只读系统状态标志(查询超时等)。
SENSOR_DATA128 B只读用于 COV 比较的传感器快照,由 setupCov 写入。
TEMPLATE128 B读写用户临时缓冲区,可用于中间计算。
DEVICE_STATUS16 B只读设备状态:电池电量(字节 3)、LoRa RSSI/SNR 等。

4.1 常用状态读取示例 ​

typescript
// 查询超时标志:APP_STATUS[2] 的 bit1
const isQueryTimeOut = EBModel.APP_STATUS.readUint8(2).bitwiseAnd(2).rightShift(1);

// 电池电压:DEVICE_STATUS[3]
const batteryVoltage = EBModel.DEVICE_STATUS.readUint8(3);

5. 事件专属缓冲区 ​

缓冲区所属事件说明
cmdBufferQueryEvent发送给子设备的定长指令帧。
ackBufferQueryEvent接收子设备响应的缓冲区,长度须 ≥ 实际响应长度。
txBufferLoraUpEvent通过 LoRaWAN 发送的上行载荷。

6. 缓冲区操作方法 ​

所有缓冲区读写方法均返回 CalcData 或 CopyData 对象,支持链式调用。

规则:copy(CopyRule)与读写(CvtRule)是两种不同的规则类型,不能相互链式调用。在同一个查询事件中,所有 copy 操作必须先于读写操作执行。每个查询事件最多支持 15 条 CvtRule;每条 CvtRule 最多链式调用 6 个操作符。

6.1 拷贝(Copy) ​

typescript
targetBuffer.copy(sourceBuffer, sourceOffset, byteLength, targetOffset)

示例——将 ackBuffer 从偏移 3 起的 72 字节拷贝到 txBuffer 偏移 3 处:

typescript
quEvent1.pushEBData(upEvent1.txBuffer.copy(quEvent1.ackBuffer, 3, 72, 3));

6.2 整数读写 ​

无符号整数

方法字节序宽度
readUintLE(offset, byteLength) / writeUintLE(value, offset, byteLength)小端动态
readUintBE(offset, byteLength) / writeUintBE(value, offset, byteLength)大端动态
readUint8(offset) / writeUint8(value, offset)—8 位
readUint16LE(offset) / writeUint16LE(value, offset)小端16 位
readUint16BE(offset) / writeUint16BE(value, offset)大端16 位
readUint32LE(offset) / writeUint32LE(value, offset)小端32 位
readUint32BE(offset) / writeUint32BE(value, offset)大端32 位

有符号整数:命名规则相同,将 Uint 替换为 Int。

6.3 浮点数读写 ​

方法字节序精度
readFloatLE(offset) / writeFloatLE(value, offset)小端32 位
readFloatBE(offset) / writeFloatBE(value, offset)大端32 位
readDoubleLE(offset) / writeDoubleLE(value, offset)小端64 位
readDoubleBE(offset) / writeDoubleBE(value, offset)大端64 位

6.4 字符串读写 ​

方法格式
readAscii(offset, length) / writeAscii(value, offset, length)ASCII
readXaasc(offset, length) / writeXaasc(value, offset, length)XAASC
readXaf(offset, length) / writeXaf(value, offset, length)XAF

6.5 BCD 码 ​

方法说明
readBcd(offset, length)读取 BCD 编码值
writeBcd(value, offset, length)写入 BCD 编码值

6.6 计算操作符(CvtRule 链式调用) ​

操作符说明
multiply(n)乘法
divide(n)除法
add(n)加法
minus(n)减法
bitwiseAnd(n)按位与
bitwiseOr(n)按位或
bitwiseXOR(n)按位异或
power(n)幂运算
not()按位取反
leftShift(n)左移
rightShift(n)右移
round()四舍五入
ceil()向上取整
floor()向下取整
reverse()字节序反转
absolute()绝对值

示例——读取 ackBuffer[2] 起的 16 位小端整数,乘以 10,写入 txBuffer[3]:

typescript
quEvent1.pushEBData(
    upEvent1.txBuffer.writeUint16LE(
        quEvent1.ackBuffer.readUint16LE(2).multiply(10), 3
    )
);

7. 查询事件 ​

7.1 QueryEvent ​

创建 ​

typescript
let cmdBuffer1 = Buffer.from("0F0410 0A0024D5FD".replaceAll(" ", ""), "hex");
let ackBuffer1 = Buffer.alloc(77);

let quEvent1 = new QueryEvent("quEvent1", {
    cmdBuffer: cmdBuffer1,
    ackBuffer: ackBuffer1,
}).setPeriod(300);

MulDev_NewGrpStart: true 将此查询事件标记为多设备模式下新设备组的起始点。

setPeriod(seconds) ​

设置周期执行间隔(秒)。

typescript
quEvent1.setPeriod(300);  // 每 5 分钟执行一次

setPeriodFromApp(appAddress) ​

在运行时从 APP 参数段指定的 4 字节小端槽位读取周期,允许通过 RPC 动态调整,无需重新编译。

typescript
quEvent1.setPeriodFromApp(70);  // 周期存储在 APP[70..73]

setIfSelect(IfSelectEnum.NO_QUERY) ​

跳过串口查询及响应处理——仅执行 ExprCondition.PRE 或 ExprCondition.NONE 条件注册的操作。

typescript
quEvent1.setIfSelect(IfSelectEnum.NO_QUERY);

setQueryCrc — 下发指令校验 ​

各 CRC 模式详见第 9 节。

typescript
quEvent1.setQueryCrc({
    Mode: CrcMode.CRC16,
    placeIndex: -2,        // 填写在 cmdBuffer 倒数第 2 字节
    LittleEndian: true,
    crcCheckRange: { startIndex: 0, endIndex: cmdBuffer1.length - 3 }
});

setAckCrc — 响应数据校验 ​

typescript
quEvent1.setAckCrc({
    Mode: CrcMode.CRC16,
    placeIndex: -2,
    LittleEndian: true,
    crcCheckRange: { startIndex: 0, endIndex: ackBuffer1.length - 3 }
});

addAckCheckRule(index, expectedValue) ​

验证响应帧中指定位置的字节值,可添加多条规则。

typescript
quEvent1.addAckCheckRule(0, 0x0F);           // 第 0 字节必须为 0x0F
quEvent1.addAckCheckRule(1, cmdBuffer1[1]);   // 功能码回显验证

pushEBData(operation, options?) ​

在事件的动作列表中注册拷贝或转换规则。

typescript
// 发送前将 APP[31] 起的 1 字节拷贝到 cmdBuffer[10](PRE)
quEvent1.pushEBData(
    quEvent1.cmdBuffer.copy(EBModel.APP, 31, 1, 10),
    { condition: ExprCondition.PRE }
);

// 收到响应时,将 ackBuffer[2..3] 的 Int16LE 写入 txBuffer[3](ONTIME)
quEvent1.pushEBData(
    upEvent1.txBuffer.writeInt16LE(quEvent1.ackBuffer.readInt16LE(2), 3),
    { condition: ExprCondition.ONTIME }
);

setupCov — 变化量上报(COV) ​

setupCov 实现 COV(Change of Value) 功能。EB 读取子设备数值,与 SENSOR_DATA 中上次上报的快照比较,仅当绝对差值超过存储在 APP 缓冲区中的阈值时才触发上行。该机制可显著降低功耗和空口占用。

typescript
let sensorDataIndex = quEvent1.setupCov({
    ackBufferIndex: 0,           // ackBuffer 中的读取位置
    up: {
        event: upEvent1,         // 触发的上行事件
        txBufferIndex: 2         // txBuffer 中的写入位置
    },
    binaryDataType: "Uint16LE",              // 数据类型
    appBufferCovThresholdIndex: 110,         // COV 阈值在 APP 中的地址
    txCovResultIndex: 3                      // txBuffer 中记录 COV 状态的字节位置
});

返回值:当前快照在 SENSOR_DATA 中存储的索引。

7.2 UpAfterQueryEvent ​

构造方式与 QueryEvent 相同,但查询成功后直接上传原始 ackBuffer,无需编写 pushEBData 规则。

typescript
let quEvent1 = new UpAfterQueryEvent("quEvent1", {
    cmdBuffer: cmdBuffer1,
    ackBuffer: ackBuffer1,
}).setPeriod(300);

7.3 查询事件执行流程 ​

  1. (可选) 执行 ExprCondition.PRE 拷贝规则(向 cmdBuffer 填入动态字段)。
  2. 计算并填写查询校验和(如已配置)。
  3. 发送 cmdBuffer 至子设备;超时最多重试 2 次。
  4. 等待响应写入 ackBuffer;超时则跳至步骤 9。
  5. 验证响应校验和(如已配置 setAckCrc)。
  6. 按 addAckCheckRule 规则验证固定字节。
  7. (若为 UpAfterQueryEvent) 上传原始 ackBuffer 后结束。
  8. 执行 ExprCondition.ONTIME 拷贝规则。
  9. (超时时) 执行 ExprCondition.TIMEOUT 拷贝规则。
  10. 执行 CvtRule(读/写/计算);可通过 ActAfterCvt 触发上行。

8. 上行事件(LoraUpEvent) ​

8.1 创建 ​

typescript
let txBuffer1 = Buffer.alloc(20);
txBuffer1[0] = 0x01;  // 数据标识符

let upEvent1 = new LoraUpEvent("upEvent1", {
    txBuffer: txBuffer1,
    txPort: 12
}).setPeriod(86400 * 365);  // 约等于"永不自动上行,由触发器驱动"

8.2 注册拷贝/计算规则 ​

注册在 LoraUpEvent 上的规则在上行发送前立即执行。

typescript
upEvent1.pushEBData(
    upEvent1.txBuffer.copy(EBModel.TEMPLATE, 3, 4, 3),
    { condition: ExprCondition.ONTIME }
);

upEvent1.pushEBData(
    upEvent1.txBuffer.writeUint8(isQueryTimeOut, 1),
    { condition: ExprCondition.ONTIME, ActAfterCvt: ActionAfertExpr.NONE }
);

9. 操作条件 ​

9.1 ExprCondition — 规则触发时机 ​

值说明
ExprCondition.NONE始终执行。
ExprCondition.ONTIME仅当子设备响应成功时执行。
ExprCondition.TIMEOUT仅当查询超时时执行。
ExprCondition.PRE在发送查询指令之前执行(用于填充 cmdBuffer)。

9.2 ActAfterCvt — CvtRule 执行后的动作 ​

写操作的目标缓冲区必须是上行事件的 txBuffer,此参数才会生效。

值说明
ActionAfertExpr.NONE计算后不执行任何动作(默认)。
ActionAfertExpr.ALWAYS无论结果如何,始终触发上行。
ActionAfertExpr.UP_TO_RESULT仅当计算结果 > 0 时触发上行。
ActionAfertExpr.ALWAYS_REBOOT计算完成后重启设备。

9.3 Repeat — 循环执行 CvtRule ​

Repeat 将同一条读写操作重复执行 N 次,每次迭代源偏移量增加读取宽度,目标偏移量增加写入宽度。

typescript
// 从 ackBuffer 偏移 2 起读取 3 × 7 字节 BCD 值,写入 txBuffer 偏移 3 起
quEvent1.pushEBData(
    upEvent1.txBuffer.writeUintLE(quEvent1.ackBuffer.readBcd(2, 7), 3, 7),
    { Repeat: 3 }
);
// 等价于:
//   第 1 次:读 ackBuffer[2..8]  → txBuffer[3..9]
//   第 2 次:读 ackBuffer[9..15] → txBuffer[10..16]
//   第 3 次:读 ackBuffer[16..22]→ txBuffer[17..23]

10. 校验和配置 ​

10.1 CRC16(Modbus 标准) ​

typescript
quEvent1.setQueryCrc({
    Mode: CrcMode.CRC16,
    Poly: "a001",             // 默认 0xa001(Modbus),可省略
    placeIndex: -2,           // 校验和写入最后 2 字节
    LittleEndian: true,
    crcCheckRange: { startIndex: 0, endIndex: cmdBuffer1.length - 3 }
});

10.2 CCITT16 ​

typescript
quEvent1.setQueryCrc({
    Mode: CrcMode.CCITT16,
    Poly: "1021",             // 默认 0x1021,可省略
    placeIndex: -2,
    LittleEndian: true,
    crcCheckRange: { startIndex: 0, endIndex: cmdBuffer1.length - 3 }
});

10.3 SUM(字节累加校验) ​

typescript
quEvent1.setQueryCrc({
    Mode: CrcMode.SUM,
    CrcLen: 1,                // 校验和为 1 字节
    placeIndex: -2,
    LittleEndian: true,
    crcCheckRange: { startIndex: 0, endIndex: -2 }
});

placeIndex 支持负数:-2 表示缓冲区倒数第 2 字节。


11. 完整示例 ​

typescript
import { Buffer } from "buffer";
import { buildOtaFile } from "@EBSDK/run";
import {
    ActionAfertExpr, CrcMode, EBModel, ExprCondition,
    LoraUpEvent, QueryEvent
} from "@EBSDK/EBCompiler/all_variable";
import { CheckbitEnum, getOtaConfig } from "@EBSDK/otaConfig";

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

const MODBUS_TT = (ebModel: EBModel) => {
    // --- 查询事件配置 ---
    let cmdBuffer1 = Buffer.from("12345678b1b2b3b4b5b6b7b8b9".replaceAll(" ", ""), "hex");
    let ackBuffer1 = Buffer.from("a1a2a3a4a5a6a7a8a9".replaceAll(" ", ""), "hex");

    let quEvent1 = new QueryEvent("quEvent1", {
        cmdBuffer: cmdBuffer1,
        ackBuffer: ackBuffer1,
        MulDev_NewGrpStart: true
    }).setPeriod(300);

    // 下发指令校验(SUM,1 字节,末位)
    quEvent1.setQueryCrc({
        Mode: CrcMode.SUM,
        CrcLen: 1,
        placeIndex: -2,
        LittleEndian: true,
        crcCheckRange: { startIndex: 0, endIndex: -2 }
    });

    // 验证响应帧首尾字节
    quEvent1.addAckCheckRule(0, 0xa1);
    quEvent1.addAckCheckRule(ackBuffer1.length - 1, 0xa9);

    // --- 上行事件配置 ---
    let upEvent1 = new LoraUpEvent("upEvent1", {
        txBuffer: Buffer.from("c1c2c3c4c5c6c7c8c9c0d1d2d3d4d5d6d7d8d9".replaceAll(" ", ""), "hex"),
        txPort: 12
    }).setPeriod(86400 * 365);

    // 查询超时标志写入 txBuffer[1]
    const isQueryTimeOut = EBModel.APP_STATUS.readUint8(2).bitwiseAnd(2).rightShift(1);
    quEvent1.pushEBData(upEvent1.txBuffer.writeUint8(isQueryTimeOut, 1));

    // 将 APP[31] 的电池电压拷贝到 txBuffer[10]
    quEvent1.pushEBData(
        upEvent1.txBuffer.copy(EBModel.APP, 31, 1, 10),
        { condition: ExprCondition.ONTIME }
    );

    // 读取 ackBuffer[2] 的 Int16LE → txBuffer[3]
    quEvent1.pushEBData(
        upEvent1.txBuffer.writeInt16LE(quEvent1.ackBuffer.readInt16LE(2), 3),
        { condition: ExprCondition.ONTIME }
    );

    // 读取 ackBuffer[11] 的 Uint16LE → txBuffer[2];结果 > 0 时触发上行
    quEvent1.pushEBData(
        upEvent1.txBuffer.writeUint16LE(quEvent1.ackBuffer.readUint16LE(11), 2),
        { condition: ExprCondition.ONTIME, ActAfterCvt: ActionAfertExpr.UP_TO_RESULT }
    );

    return JSON.stringify(ebModel, null, 2);
};

buildOtaFile(__filename, otaConfig, MODBUS_TT);

12. 固件下载 ​

12.1 obin 文件格式 ​

.obin 文件是一个 JSON 格式文件,有效的升级包存储在 bin_dic 中,按 index(从 0 开始)排序。其他字段用于配置 ThinkLink、UART 和 SW 升级模式。

json
{
  "otaFile": {
    "bin_dic": {
      "0": { "index": 0, "buffer": "AgADAGQDWQQAAAA=", "bufferstring": "02 00 03 00 ..." },
      "1": { "index": 1, "buffer": "...", "bufferstring": "..." }
    },
    "otaMode": "gw",
    "otaPort": 201,
    "packets": 3,
    "isClassA": true
  }
}
  1. 登录 ThinkLink → 运维管理 → 升级。
  2. 在设备固件下点击新增,上传 .obin 文件。
  3. 创建升级任务并选择目标设备。

固件上传界面

创建升级任务

12.3 升级注意事项 ​

  • 将最大重试次数设为 1,包传输次数设为 1。
  • Class A 设备:每个下行数据包发送前,设备必须先发送一次上行以打开接收窗口。
  • Class C 设备:使用 UNCONFIRMED 包。若 NS 没有自动排队功能,需根据空口时间手动控制时序。可将每个包发送 2–3 次以提高可靠性(EB 会过滤重复包)。
  • 升级成功后设备将自动重启。
  • 编译固件中的 BzType 和 BzVersion 至少有一个须与设备当前值不同,否则 EB 认为固件已是最新版本并跳过升级。
  • 电池供电设备务必设置 Battery: true;未设置将导致设备以 Class C 模式运行,耗尽电量。