EB Compiler SDK 使用说明
1. 概述
1.1 核心概念
EB Compiler SDK 是一个基于事件驱动的 IoT 数据采集与传输框架,核心原则如下:
- 事件驱动架构:所有操作由事件触发执行。
- 周期性执行:查询事件和上行事件均按可配置周期运行。
- 数据流处理:通过查询事件从子设备获取数据,经可选处理后,由上行事件发送至云端。
1.2 事件类型
| 事件 | 说明 |
|---|---|
QueryEvent | 周期性通过 UART/RS-485 向子设备发送指令并等待响应。 |
UpAfterQueryEvent | 类似 QueryEvent,但查询完成后直接上传原始响应,无需编写拷贝/转换规则。 |
LoraUpEvent | 周期性将组装好的 txBuffer 通过 LoRaWAN 发送至云端。 |
2. 环境搭建与编译
2.1 前置条件
- 安装 Node.js(推荐 LTS 版本)。
- 全局安装
ts-node:
npm install -g ts-node- 在 EBSDK 项目根目录安装依赖:
npm install2.2 执行编译
将 TypeScript 文件放置于 project/ 目录下(例如 project/myMeter/myMeter.ts),然后运行:
ts-node project/myMeter/myMeter.ts2.3 输出文件
编译完成后,在 project/myMeter/release/ 目录下生成四个文件:
| 文件 | 说明 |
|---|---|
myMeter.bin | 原始编译二进制文件。 |
myMeter.json | 编译中间文件(用于调试)。 |
myMeter.ota | OTA 升级描述符文件。 |
myMeter.obin | 最终升级包——上传时使用此文件。 |
3. OTA 配置(getOtaConfig)
入口文件必须调用 getOtaConfig 描述硬件和串口环境。
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 字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
SwVersion | Number | 是 | EB 虚拟机固件版本,当前值为 31。必须与设备实际固件一致,不一致会导致升级失败。 |
BaudRate | Number | 是 | UART/RS-485 波特率,必须为 1200 的整数倍(如 9600、19200)。 |
StopBits | Number | 是 | 停止位(1 或 2)。 |
DataBits | Number | 是 | 数据位(通常为 8)。 |
Checkbit | CheckbitEnum | 是 | 校验位:NONE、ODD 或 EVEN。 |
ConfirmDuty | Number | 是 | Confirm 包占比。60 表示每 60 个上行包中发送 1 个 Confirm 包。 |
Battery | Boolean | 是 | true = 电池供电 → Class A 模式;false = 持续供电 → Class C 模式。 |
BzType | Number | 是 | 业务类型标识(2 字节),用于识别所连接的仪表/设备类型。 |
BzVersion | Number | 是 | 业务版本号(1 字节)。BzType 与 BzVersion 中至少一个须与设备当前值不同,否则 EB 认为固件已是最新版本而跳过升级。 |
HwType | HwTypeEnum | 否 | 硬件型号(如 OM422=40、OM822=51)。 |
HwVersion | Number | 否 | 硬件版本号。 |
FuotaVersion | Number | 否 | FUOTA 版本号。 |
完整 APP 参数参考(TransparentBit、UpRawAfterQuery、端口配置等),请查阅 EB APP 参数。
4. 系统缓冲区
以下五个缓冲区由 EBModel 管理,在用户代码中可通过 EBModel.APP、EBModel.TEMPLATE 等方式访问。
| 缓冲区 | 大小 | 权限 | 说明 |
|---|---|---|---|
APP | 255 B | 只读 | 应用参数。0–69 字节由系统管理;70–200 字节为用户自由使用区(参见 EB APP 参数)。 |
APP_STATUS | 32 B | 只读 | 系统状态标志(查询超时等)。 |
SENSOR_DATA | 128 B | 只读 | 用于 COV 比较的传感器快照,由 setupCov 写入。 |
TEMPLATE | 128 B | 读写 | 用户临时缓冲区,可用于中间计算。 |
DEVICE_STATUS | 16 B | 只读 | 设备状态:电池电量(字节 3)、LoRa RSSI/SNR 等。 |
4.1 常用状态读取示例
// 查询超时标志: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. 事件专属缓冲区
| 缓冲区 | 所属事件 | 说明 |
|---|---|---|
cmdBuffer | QueryEvent | 发送给子设备的定长指令帧。 |
ackBuffer | QueryEvent | 接收子设备响应的缓冲区,长度须 ≥ 实际响应长度。 |
txBuffer | LoraUpEvent | 通过 LoRaWAN 发送的上行载荷。 |
6. 缓冲区操作方法
所有缓冲区读写方法均返回 CalcData 或 CopyData 对象,支持链式调用。
规则:
copy(CopyRule)与读写(CvtRule)是两种不同的规则类型,不能相互链式调用。在同一个查询事件中,所有copy操作必须先于读写操作执行。每个查询事件最多支持 15 条 CvtRule;每条 CvtRule 最多链式调用 6 个操作符。
6.1 拷贝(Copy)
targetBuffer.copy(sourceBuffer, sourceOffset, byteLength, targetOffset)示例——将 ackBuffer 从偏移 3 起的 72 字节拷贝到 txBuffer 偏移 3 处:
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]:
quEvent1.pushEBData(
upEvent1.txBuffer.writeUint16LE(
quEvent1.ackBuffer.readUint16LE(2).multiply(10), 3
)
);7. 查询事件
7.1 QueryEvent
创建
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)
设置周期执行间隔(秒)。
quEvent1.setPeriod(300); // 每 5 分钟执行一次setPeriodFromApp(appAddress)
在运行时从 APP 参数段指定的 4 字节小端槽位读取周期,允许通过 RPC 动态调整,无需重新编译。
quEvent1.setPeriodFromApp(70); // 周期存储在 APP[70..73]setIfSelect(IfSelectEnum.NO_QUERY)
跳过串口查询及响应处理——仅执行 ExprCondition.PRE 或 ExprCondition.NONE 条件注册的操作。
quEvent1.setIfSelect(IfSelectEnum.NO_QUERY);setQueryCrc — 下发指令校验
各 CRC 模式详见第 9 节。
quEvent1.setQueryCrc({
Mode: CrcMode.CRC16,
placeIndex: -2, // 填写在 cmdBuffer 倒数第 2 字节
LittleEndian: true,
crcCheckRange: { startIndex: 0, endIndex: cmdBuffer1.length - 3 }
});setAckCrc — 响应数据校验
quEvent1.setAckCrc({
Mode: CrcMode.CRC16,
placeIndex: -2,
LittleEndian: true,
crcCheckRange: { startIndex: 0, endIndex: ackBuffer1.length - 3 }
});addAckCheckRule(index, expectedValue)
验证响应帧中指定位置的字节值,可添加多条规则。
quEvent1.addAckCheckRule(0, 0x0F); // 第 0 字节必须为 0x0F
quEvent1.addAckCheckRule(1, cmdBuffer1[1]); // 功能码回显验证pushEBData(operation, options?)
在事件的动作列表中注册拷贝或转换规则。
// 发送前将 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 缓冲区中的阈值时才触发上行。该机制可显著降低功耗和空口占用。
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 规则。
let quEvent1 = new UpAfterQueryEvent("quEvent1", {
cmdBuffer: cmdBuffer1,
ackBuffer: ackBuffer1,
}).setPeriod(300);7.3 查询事件执行流程
- (可选) 执行
ExprCondition.PRE拷贝规则(向cmdBuffer填入动态字段)。 - 计算并填写查询校验和(如已配置)。
- 发送
cmdBuffer至子设备;超时最多重试 2 次。 - 等待响应写入
ackBuffer;超时则跳至步骤 9。 - 验证响应校验和(如已配置
setAckCrc)。 - 按
addAckCheckRule规则验证固定字节。 - (若为
UpAfterQueryEvent) 上传原始ackBuffer后结束。 - 执行
ExprCondition.ONTIME拷贝规则。 - (超时时) 执行
ExprCondition.TIMEOUT拷贝规则。 - 执行 CvtRule(读/写/计算);可通过
ActAfterCvt触发上行。
8. 上行事件(LoraUpEvent)
8.1 创建
let txBuffer1 = Buffer.alloc(20);
txBuffer1[0] = 0x01; // 数据标识符
let upEvent1 = new LoraUpEvent("upEvent1", {
txBuffer: txBuffer1,
txPort: 12
}).setPeriod(86400 * 365); // 约等于"永不自动上行,由触发器驱动"8.2 注册拷贝/计算规则
注册在 LoraUpEvent 上的规则在上行发送前立即执行。
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 次,每次迭代源偏移量增加读取宽度,目标偏移量增加写入宽度。
// 从 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 标准)
quEvent1.setQueryCrc({
Mode: CrcMode.CRC16,
Poly: "a001", // 默认 0xa001(Modbus),可省略
placeIndex: -2, // 校验和写入最后 2 字节
LittleEndian: true,
crcCheckRange: { startIndex: 0, endIndex: cmdBuffer1.length - 3 }
});10.2 CCITT16
quEvent1.setQueryCrc({
Mode: CrcMode.CCITT16,
Poly: "1021", // 默认 0x1021,可省略
placeIndex: -2,
LittleEndian: true,
crcCheckRange: { startIndex: 0, endIndex: cmdBuffer1.length - 3 }
});10.3 SUM(字节累加校验)
quEvent1.setQueryCrc({
Mode: CrcMode.SUM,
CrcLen: 1, // 校验和为 1 字节
placeIndex: -2,
LittleEndian: true,
crcCheckRange: { startIndex: 0, endIndex: -2 }
});
placeIndex支持负数:-2表示缓冲区倒数第 2 字节。
11. 完整示例
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 升级模式。
{
"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
}
}12.2 通过 ThinkLink 上传
- 登录 ThinkLink → 运维管理 → 升级。
- 在设备固件下点击新增,上传
.obin文件。 - 创建升级任务并选择目标设备。


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