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 字段说明

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

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


4. 系统缓冲区

以下五个缓冲区由 EBModel 管理,在用户代码中可通过 EBModel.APPEBModel.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. 缓冲区操作方法

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

规则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.PREExprCondition.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 会过滤重复包)。
  • 升级成功后设备将自动重启。
  • 编译固件中的 BzTypeBzVersion 至少有一个须与设备当前值不同,否则 EB 认为固件已是最新版本并跳过升级。
  • 电池供电设备务必设置 Battery: true;未设置将导致设备以 Class C 模式运行,耗尽电量。