Go-IoT 双引擎平台 — 设备端接入文档¶
目标读者:4G 模块固件开发工程师(嵌入式 C) 最后更新:2026-07-06
1. 概述¶
| 项目 | 说明 |
|---|---|
| 平台名称 | Go-IoT 双引擎平台 |
| 接入域名 | emqx.iot.devyunmu.com(EMQX MQTT Broker) |
| 通信协议 | MQTT v3.1.1 |
| 业务载荷 | Modbus RTU 帧(二进制 Hex 字符串) |
| 字节序 | Big-Endian(大端序),平台使用 binary.BigEndian 解析所有多字节字段 |
设备与平台之间通过 MQTT 协议通信:设备上报数据使用 Publish,平台下发指令通过设备 Subscribe 接收。业务数据载荷统一使用 Modbus RTU 帧格式,与 幻特4G通信协议V0.0.1.pdf 中的帧定义一致。
2. MQTT 连接参数¶
2.1 连接地址¶
| 连接方式 | 端口 | 说明 |
|---|---|---|
| TCP(非加密) | 1883 |
内网 / 开发调试 |
| TCP TLS/SSL | 8883 |
推荐,生产环境必须使用 |
| WebSocket | 8083 |
仅限 PC 端调试工具 |
2.2 连接参数¶
| 参数 | 值 | 说明 |
|---|---|---|
| Broker 地址 | emqx.iot.devyunmu.com |
MQTT Broker 域名 |
| MQTT 版本 | v3.1.1 | 4G 模组通用 |
| Keep Alive | 60 秒 |
心跳间隔 |
| Clean Session | true |
每次重连后重新订阅主题 |
| QoS | 0(上行)/ 1(下行) |
上行数据量大用 QoS 0,指令用 QoS 1 |
2.3 认证方式¶
开发阶段:
用户名: device_{sn} (例如: device_TEST-FIXED-001)
密码: iot2026 (固定密码)
{sn}替换为设备自身的序列号。- 如果 EMQX 当前部署为开放模式(未开启认证),可以不填用户名密码。但建议尽快开启正式认证。
- 生产环境将升级为动态 Token 认证,届时另行通知。
2.4 连接流程伪代码(C 风格)¶
// 4G 模组 AT 指令示例(以常见的 MQTT AT 固件为例)
AT+MQTTCONN="emqx.iot.devyunmu.com",1883,"device_TEST-FIXED-001",60,1
AT+MQTTSUB="device/TEST-FIXED-001/down",1
AT+MQTTSUB="device/TEST-FIXED-001/ota",1
3. 数据上行(设备 → 平台)¶
3.1 发布主题¶
device/{sn}/up
{sn}替换为设备序列号。- 例如:
device/TEST-FIXED-001/up
3.2 载荷格式¶
Modbus FC3 响应帧(读保持寄存器响应),格式如下:
| NODE(1) | FUN(0x03) | ID(2) | NR_BYTE(2) | DATA(n) | CRC(2) |
| 字段 | 字节数 | 说明 |
|---|---|---|
| NODE | 1 | 固定 0x36 |
| FUN | 1 | 固定 0x03(FC3 读保持寄存器) |
| ID | 2 | 起始寄存器地址,固定 3600(大端 0x0E10) |
| NR_BYTE | 2 | 数据段字节数(= 寄存器数量 × 2) |
| DATA | n | 寄存器数据(Big-Endian) |
| CRC | 2 | CRC-16/Modbus 校验值(大端) |
3.3 寄存器映射表¶
标识信息(只读)¶
| 寄存器 | 长度 | 类型 | 说明 |
|---|---|---|---|
| 3600 | 3 regs (6B) | ASCII | 硬件版本,如 HW1.0 |
| 3603 | 3 regs (6B) | ASCII | 软件版本,如 SW2.1 |
| 3608 | 6 regs (12B) | ASCII | 设备序列号(SN),尾部 \x00 填充 |
运行统计¶
| 寄存器 | 长度 | 类型 | 说明 |
|---|---|---|---|
| 3614 | 2 regs (4B) | uint32 | 总运行分钟(含待机) |
| 3616 | 2 regs (4B) | uint32 | 总工作分钟(不含待机) |
| 3618 | 1 reg (2B) | uint16 | 可用时间剩余(分钟) |
| 3619 | 1 reg (2B) | uint16 | 单次工作设定时间(分钟) |
| 3620 | 1 reg (2B) | uint16 | 单次工作倒计时(秒),0xFFFF = 空闲 |
温度数据(偏移 -40℃)¶
重要:所有温度寄存器使用 -40℃ 偏移编码。
- 写入原始值 = 摄氏度 + 40
- 读取摄氏度 = 原始值 − 40
- 示例:85℃ → 写入
85 + 40 = 125(0x007D)
| 寄存器 | 长度 | 类型 | 说明 |
|---|---|---|---|
| 3621 | 1 reg (2B) | int16 | 加热器实时温度 |
| 3623 | 1 reg (2B) | int16 | 喷射启动温度 |
| 3624 | 1 reg (2B) | int16 | 停止喷射温度 |
| 3625 | 1 reg (2B) | int16 | 自动控制温度 |
| 3626 | 1 reg (2B) | int16 | 一级高温保护阈值 |
| 3627 | 1 reg (2B) | int16 | 一级高温保护恢复 |
| 3628 | 1 reg (2B) | int16 | 二级高温保护阈值 |
| 3629 | 1 reg (2B) | int16 | 二级高温保护恢复 |
流量参数¶
| 寄存器 | 长度 | 类型 | 说明 |
|---|---|---|---|
| 3630 | 1 reg (2B) | uint16 | 泵最大流量(ms) |
| 3631 | 1 reg (2B) | uint16 | 泵最小流量(ms) |
生产计量(共享设备 — 新增)¶
2026-07-06 新增:为支持共享设备"按产量扣费"业务模式,新增产量上报寄存器。 老固件可不包含此寄存器,平台向后兼容(payload 不足时默认为 0)。
| 寄存器 | 长度 | 类型 | 说明 |
|---|---|---|---|
| 3632 | 2 regs (4B) | uint32 | 本次生产毫升数(mL),设备完成一次工作后上报 |
- 上报时机:设备工作状态从"喷射(2)"变为"待机(0)"时,在当次上行帧中携带。
- 清零时机:下次启动命令(FC5 写 1500 = 0xA5A5)后清零。
- 取值含义:当前工作会话中累积生产的次氯酸溶液毫升数,平台据此扣费(TotalCost = ProducedML × UnitPrice)。
- 注意:该字段使用 uint32 编码(跨 2 个寄存器,4 字节 Big-Endian)。
状态寄存器¶
| 寄存器 | 长度 | 类型 | 说明 |
|---|---|---|---|
| 3636 | 1 reg (2B) | uint16 | 内部状态(位字段) |
| 3637 | 1 reg (2B) | uint16 | 告警状态(位字段) |
| 3638 | 1 reg (2B) | uint16 | 工作状态(枚举) |
位字段定义¶
寄存器 3636(内部状态):
| 位 | 掩码 | 说明 |
|---|---|---|
| Bit0 | 0x0001 |
加热器运行 |
| Bit1 | 0x0002 |
泵运行 |
| Bit2 | 0x0004 |
液位正常 |
| Bit4 | 0x0010 |
远程锁定 |
寄存器 3637(告警状态):
| 位 | 掩码 | 说明 |
|---|---|---|
| Bit2 | 0x0004 |
一级高温告警 |
| Bit3 | 0x0008 |
二级高温告警 |
| Bit4 | 0x0010 |
液位告警 |
| Bit6 | 0x0040 |
需手动清洗 |
寄存器 3638(工作状态枚举):
| 值 | 含义 |
|---|---|
| 0 | 待机 |
| 1 | 加热 |
| 2 | 喷射 |
| 3 | 清洗 |
| 4 | 异常 |
| 5 | 故障 |
3.4 上报频率¶
| 寄存器组 | 上报时机 | 说明 |
|---|---|---|
| 3600~3608 | 开机联网成功后上报 1 次 | 标识信息 |
| 3614~3618 | 工作中每 1 分钟;待机每 10 分钟 | 运行统计 |
| 3619~3621 | 每 1 秒 | 实时倒计时、加热温度 |
| 3622~3635 | 每 60 秒 | 温度阈值、流量参数 |
| 3636~3638 | 状态变化时立即上报 | 内部状态、告警、工作状态 |
3.5 完整帧示例¶
以下是设备 TEST-FIXED-001 的一条完整上行帧(88 字节),包含所有寄存器(3600~3638):
36030E10004E
4857312E3000 ← 3600 "HW1.0\x00"(硬件版本)
5357322E3100 ← 3603 "SW2.1\x00"(软件版本)
00000000 ← 3606 保留
544553542D46495845442D303030 ← 3608 "TEST-FIXED-000"(序列号)
00003039 ← 3614 总运行 = 12345 分钟
00001A85 ← 3616 总工作 = 6789 分钟
012C ← 3618 可用时间剩余 = 300 分钟
003C ← 3619 设定工作时间 = 60 分钟
0708 ← 3620 倒计时 = 1800 秒
007D ← 3621 加热温度 = 85℃(raw=125)
0000 ← 3622 保留
00C8 ← 3623 喷射启动 = 160℃(raw=200)
008C ← 3624 停止喷射 = 100℃(raw=140)
00BE ← 3625 自动控制 = 150℃(raw=190)
00DC ← 3626 一级高温阈值 = 180℃(raw=220)
00C8 ← 3627 一级高温恢复 = 160℃(raw=200)
00F0 ← 3628 二级高温阈值 = 200℃(raw=240)
00DC ← 3629 二级高温恢复 = 180℃(raw=220)
0190 ← 3630 泵最大流量 = 400 ms
0064 ← 3631 泵最小流量 = 100 ms
...(保留区略)
0007 ← 3636 Bit0+1+2=1 → 加热器+泵+液位正常
0000 ← 3637 无告警
0001 ← 3638 工作状态 = 加热
XXXX ← CRC(2 字节)
提示:建议将当前所有寄存器合并为一帧上报,减少 MQTT 消息数量。平台会自动识别帧长,按偏移解析各字段。
3.6 构造帧的注意事项¶
- ASCII 字段必须用
\x00填充到指定长度。例如 SN 为TEST-001(8 字节),需右侧补 4 字节\x00到 12 字节。 - 温度字段直接写原始值,不要写摄氏度。85℃ → 写入
125(0x007D)。 - CRC 覆盖 NODE + FUN + ID + NR_BYTE + DATA(即 CRC 之前的所有字节),不含 CRC 自身。
- 所有多字节字段使用 Big-Endian。例如 uint32 值
12345=0x00003039,先发0x00、0x00、0x30、0x39。
4. 指令下行(平台 → 设备)¶
4.1 订阅主题¶
设备必须订阅以下主题以接收平台下发的控制指令:
device/{sn}/down
- QoS 建议设为
1(至少一次送达)。 - 重连后(Clean Session = true)必须重新订阅。
4.2 载荷格式¶
下行指令使用 Modbus FC5(写单个线圈) 或 FC16(写多个保持寄存器) 帧。
FC5 帧结构(8 字节)¶
| NODE(1) | FUN(0x05) | REG_ID(2) | DATA(2) | CRC(2) |
FC16 帧结构(可变长度)¶
| NODE(1) | FUN(0x10) | START_REG(2) | NR_REGS(2) | NR_BYTE(1) | DATA(n) | CRC(2) |
4.3 指令表¶
FC5 控制指令¶
| 操作 | 寄存器 | 数据值 | 完整帧(Hex) |
|---|---|---|---|
| 启动设备 | 1500 | 0xA5A5 |
360505DCA5A590B3 |
| 停止设备 | 1500 | 0xA55A |
360505DCA55AD0F3 |
| 锁定设备 | 1503 | 0xFFA5 |
360505DFFFA53079 |
| 解锁设备 | 1503 | 0x0000 |
360505DF000051D9 |
| 重启设备 | 1505 | 0xFF02 |
360505E1FF024659 |
帧解析说明(以启动指令为例): -
36— NODE 地址 -05— 功能码 FC5 -05DC— 寄存器地址 1500(大端) -A5A5— 启动指令数据 -90B3— CRC-16/Modbus
FC16 参数写入指令¶
| 操作 | 寄存器 | 示例值 | 完整帧(Hex) |
|---|---|---|---|
| 设置工作时间 | 5006 | 60 分钟 | 3610138E000102003C5FE8 |
| 设置清洗时间 | 5007 | 5 秒 | 3610138F0001020005D53F |
| 设置喷射温度 | 5008 | 170℃(raw=210) | 3610139000010200D2AD6B |
| 设置泵最大流量 | 5017 | 350 ms | 36101399000102015E016B |
| 设置泵最小流量 | 5018 | 80 ms | 3610139A00010200506ABB |
| 设置无液判断时间 | 5021 | 30(0.1秒) | 3610139D000102001E662B |
批量写入参数(FC16 连续寄存器)¶
可以一次性写入多个连续寄存器,减少指令条数。例如同时设置工作时间(60 分钟)、清洗时间(5 秒)、喷射温度(160℃):
3610138E000306003C000500C83CD0
| 字段 | 值 | 说明 |
|---|---|---|
36 |
NODE | 固定地址 |
10 |
FUN | FC16 |
138E |
START_REG | 起始寄存器 5006 |
0003 |
NR_REGS | 3 个寄存器 |
06 |
NR_BYTE | 6 字节数据 |
003C |
DATA[0] | 5006 = 60 分钟 |
0005 |
DATA[1] | 5007 = 5 秒 |
00C8 |
DATA[2] | 5008 = 160℃ → raw=200 |
3CD0 |
CRC | CRC-16 |
4.4 响应要求¶
- 无需通过 MQTT 单独回复确认帧。
- 设备执行指令后,应通过下一次周期上报(或立即触发上报)携带最新的状态寄存器(3636 / 3637 / 3638)来反映执行结果。
- 例如:收到启动指令后 → 加热器启动 → 立即上报一帧,3638 =
1(加热中)、3636 Bit0 =1。
5. OTA 固件升级¶
5.1 订阅主题¶
device/{sn}/ota
5.2 载荷格式(JSON)¶
平台通过此主题下发 JSON 格式的 OTA 通知:
{
"cmd": "ota",
"version": "v1.0.1",
"url": "https://oss.iot.devyunmu.com/firmware/firmware.bin?X-Amz-Expires=1800&device_sn=TEST-FIXED-001",
"md5": "5d41402abc4b2a76b9719d911017c592",
"size": 1048576
}
| 字段 | 类型 | 说明 |
|---|---|---|
cmd |
string | 固定 "ota" |
version |
string | 固件版本号 |
url |
string | 固件下载地址(MinIO 预签名 URL,有效期 30 分钟) |
md5 |
string | 固件文件 MD5 校验值(32 位小写十六进制) |
size |
number | 固件文件大小(字节) |
5.3 升级流程¶
平台下发 OTA ──→ 设备收到 JSON
│
▼
从 url 下载固件
(直连 MinIO,不经 Go 服务)
│
▼
下载完成后校验 MD5
(MD5 不匹配 → 终止,等待重试)
│
▼
烧录新固件并重启
│
▼
开机上报寄存器 3603
(携带新软件版本号)
5.4 超时与重试¶
- 固件下载 URL 有效期为 30 分钟。
- 若 30 分钟内未完成升级(未收到新版本号上报),平台会重新下发 OTA 指令。
5.5 注意事项¶
- 下载时应使用 Range 分片下载,以支持断点续传(4G 网络不稳定)。
- 烧录前务必校验 MD5,防止固件损坏导致设备变砖。
- 升级过程中设备应暂停正常的周期上报,避免占用 4G 带宽。
6. 心跳与保活¶
6.1 MQTT Keep Alive¶
- MQTT 协议内置 Keep Alive 机制,建议设为 60 秒。
- 4G 模组通常自动发送 PINGREQ,无需应用层干预。
6.2 平台侧离线判定¶
平台会监控设备最后上报时间:
- 若超过 3 倍上报周期未收到数据,标记设备为 offline。
- 例如:工作中应每分钟上报一次,超过 3 分钟无数据 → offline。
6.3 建议¶
- 设备每 60 秒至少发送一条数据。
- 即便状态未变化,也可重复发送 3614~3618 组(运行统计)。
- 待机时上报间隔可延长至 10 分钟,平台侧对此容差较大。
7. 测试环境验证¶
7.1 模拟设备上行¶
使用 mosquitto_pub 发送一条完整的 Modbus FC3 帧:
mosquitto_pub -h emqx.iot.devyunmu.com -p 1883 \
-t "device/TEST-FIXED-001/up" \
-m "36030E10004E4857312E30005357322E310000000000544553542D46495845442D3030300000303900001A85012C003C0708007D000000C8008C00BE00DC00C800F000DC019000640000000000000000000700000001000031AA"
7.2 订阅下行指令¶
mosquitto_sub -h emqx.iot.devyunmu.com -p 1883 \
-t "device/TEST-FIXED-001/down"
7.3 模拟平台下发启动指令¶
mosquitto_pub -h emqx.iot.devyunmu.com -p 1883 \
-t "device/TEST-FIXED-001/down" \
-m "360505DCA5A590B3"
7.4 使用 Go 模拟器批量测试¶
项目 scripts/simulate_device.go 提供了完整的模拟设备工具:
WEBHOOK_URL=http://localhost:8080/mqtt/webhook go run scripts/simulate_device.go
8. 常见问题¶
Q1:如何获取设备序列号(SN)?¶
SN 由平台方统一分配,贴在设备机身二维码中。设备出厂时需烧录唯一 SN 到 Flash。扫码绑定后,平台后台将 SN 与设备关联。
Q2:Modbus CRC 如何计算?¶
使用标准 CRC-16/Modbus 算法:
- 多项式:0x8005(反射多项式 0xA001)
- 初始值:0xFFFF
- 输出不取反
- CRC 在帧内以 Big-Endian 存储
C 语言实现见附录 A。
Q3:设备断线重连后需要重新订阅吗?¶
如果 Clean Session = true(推荐),重连后必须重新订阅 device/{sn}/down 和 device/{sn}/ota。
建议在 CONNACK 成功回调中执行订阅逻辑:
void on_mqtt_connected(void) {
mqtt_subscribe("device/YOUR-SN/down", 1);
mqtt_subscribe("device/YOUR-SN/ota", 1);
}
Q4:是否可以同时上报多个寄存器组?¶
可以,且推荐这样做。 将当前时间点的所有寄存器(3600~3638)合并为一帧发送,减少 MQTT 消息数量。平台会根据 NR_BYTE 长度自适应解析——寄存器不全也可以,缺失的字段将以零值处理。
Q5:倒计时寄存器 3620 为 0xFFFF 是什么意思?¶
0xFFFF 表示倒计时空闲(设备未在工作)。设备在工作时写入实际剩余秒数(0~3600)。此寄存器每 1 秒上报一次。
Q6:平台如何区分不同设备的上行数据?¶
通过 MQTT 主题 device/{sn}/up 中的 {sn} 字段区分。请勿在载荷帧中遗漏寄存器 3608(序列号),平台会交叉校验主题 SN 与帧内 SN,确保数据安全。
Q7:下行指令 QoS 为什么是 1 而不是 0?¶
QoS 1 保证指令至少送达一次。控制类指令(启动/停止/锁定)不能丢失。4G 模块应有去重处理(MQTT Broker 会保证 QoS 1 层面不丢,但可能重复投递)。
Q8:温度值为负数怎么编码?¶
温度编码使用 -40℃ 偏移,因此 -40℃ → 0,0℃ → 40(0x0028),可以覆盖 -40℃ 到 65535-40=65495℃ 的完整范围。
附录 A:CRC-16/Modbus C 语言实现¶
/**
* crc16_modbus - 计算 CRC-16/Modbus 校验值
*
* 多项式: 0x8005 (反射: 0xA001)
* 初始值: 0xFFFF
* 输出不取反
*
* @param data 输入数据缓冲区
* @param len 数据长度(字节)
* @return CRC-16 校验值
*
* 参考: MODBUS over Serial Line Specification V1.02
*/
uint16_t crc16_modbus(const uint8_t *data, size_t len)
{
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= (uint16_t)data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
使用示例¶
#include <stdio.h>
#include <string.h>
// 构造启动指令帧(FC5, 寄存器 1500, 值 0xA5A5)
void build_start_cmd(uint8_t *frame) {
frame[0] = 0x36; // NODE
frame[1] = 0x05; // FUN (FC5)
frame[2] = 0x05; // REG_ID 高字节 (1500 = 0x05DC)
frame[3] = 0xDC; // REG_ID 低字节
frame[4] = 0xA5; // DATA 高字节 (0xA5A5)
frame[5] = 0xA5; // DATA 低字节
uint16_t crc = crc16_modbus(frame, 6);
frame[6] = (uint8_t)(crc >> 8); // CRC 高字节 (Big-Endian)
frame[7] = (uint8_t)(crc & 0xFF); // CRC 低字节
}
int main(void) {
uint8_t frame[8];
build_start_cmd(frame);
printf("Start command: ");
for (int i = 0; i < 8; i++) {
printf("%02X", frame[i]);
}
printf("\n");
// 输出: 360505DCA5A590B3
return 0;
}
CRC 验证¶
可使用以下已知帧验证 CRC 实现是否正确:
| 帧数据(不含 CRC) | CRC 期望值 | 完整帧 |
|---|---|---|
36 05 05 DC A5 A5 |
0x90B3 |
360505DCA5A590B3 |
36 05 05 DC A5 5A |
0xD0F3 |
360505DCA55AD0F3 |
36 05 05 DF FF A5 |
0x3079 |
360505DFFFA53079 |
36 05 05 E1 FF 02 |
0x4659 |
360505E1FF024659 |
附录 B:温度编码速查表¶
| 实际温度(℃) | 寄存器原始值(Decimal) | 寄存器原始值(Hex) |
|---|---|---|
| -40 | 0 | 0x0000 |
| 0 | 40 | 0x0028 |
| 25 | 65 | 0x0041 |
| 85 | 125 | 0x007D |
| 100 | 140 | 0x008C |
| 150 | 190 | 0x00BE |
| 160 | 200 | 0x00C8 |
| 170 | 210 | 0x00D2 |
| 180 | 220 | 0x00DC |
| 200 | 240 | 0x00F0 |
附录 C:寄存器速查总表¶
只读寄存器(上行 FC3)¶
| 寄存器 | 名称 | 类型 | 字节 |
|---|---|---|---|
| 3600 | 硬件版本 | ASCII(6) | 6 |
| 3603 | 软件版本 | ASCII(6) | 6 |
| 3608 | 序列号 | ASCII(12) | 12 |
| 3614 | 总运行分钟 | uint32 | 4 |
| 3616 | 总工作分钟 | uint32 | 4 |
| 3618 | 可用时间剩余 | uint16 | 2 |
| 3619 | 设定工作时间 | uint16 | 2 |
| 3620 | 倒计时 | uint16 | 2 |
| 3621 | 加热器温度 | int16(-40) | 2 |
| 3623 | 喷射启动温度 | int16(-40) | 2 |
| 3624 | 停止喷射温度 | int16(-40) | 2 |
| 3625 | 自动控制温度 | int16(-40) | 2 |
| 3626 | 一级高温阈值 | int16(-40) | 2 |
| 3627 | 一级高温恢复 | int16(-40) | 2 |
| 3628 | 二级高温阈值 | int16(-40) | 2 |
| 3629 | 二级高温恢复 | int16(-40) | 2 |
| 3630 | 泵最大流量 | uint16 | 2 |
| 3631 | 泵最小流量 | uint16 | 2 |
| 3636 | 内部状态 | uint16(bits) | 2 |
| 3637 | 告警状态 | uint16(bits) | 2 |
| 3638 | 工作状态 | uint16(0-5) | 2 |
可写寄存器(下行指令)¶
| 寄存器 | 名称 | 功能码 | 示例值 |
|---|---|---|---|
| 1500 | 启动/停止 | FC5 | 0xA5A5 启动 / 0xA55A 停止 |
| 1503 | 锁定/解锁 | FC5 | 0xFFA5 锁定 / 0x0000 解锁 |
| 1505 | 重启 | FC5 | 0xFF02 |
| 5006 | 工作时间 | FC16 | 1~180(分钟) |
| 5007 | 清洗时间 | FC16 | 0~20(秒) |
| 5008 | 喷射温度 | FC16 | 摄氏度 + 40 |
| 5017 | 泵最大流量 | FC16 | 20~500(ms) |
| 5018 | 泵最小流量 | FC16 | 20~500(ms) |
| 5021 | 无液判断时间 | FC16 | 0.1 秒为单位 |
| 5023 | 单价(分/mL) | FC16 | uint16,启动时下发 |
| 5024 | 最大配额(mL) | FC16 | uint32,启动时下发 |
| 5025 | 本次消费(分) | FC16 | uint32,结算时下发 |
| 5026 | 剩余余额(分) | FC16 | uint32,结算时下发 |
附录 D:共享设备业务指令(新增)¶
2026-07-06 新增:以下指令仅用于共享设备(次氯酸发生器)的"预充值 + 按产量扣费"业务模式。 固定设备模式下不会收到这些指令。
D.1 启动时配置帧(FC16)¶
平台在启动设备前,先下发业务配置帧,包含单价(寄存器 5023)和最大配额(寄存器 5024)。 设备收到后应缓存这些参数,在工作过程中实时比较累计产量与最大配额,达到后自动停机。
寄存器布局:
| 寄存器 | 类型 | 字节 | 说明 |
|---|---|---|---|
| 5023 | uint16 | 2 | 单价(分/mL),例如 1 = 0.01 元/mL |
| 5024 | uint32 | 4 | 最大可用额度(mL) |
示例帧(单价 = 1 分/mL,配额 = 5000 mL):
3610139F000306000100001388XXXX
帧解析:
- 36 — NODE
- 10 — FC16
- 139F — 起始寄存器 5023
- 0003 — 3 个寄存器
- 06 — 6 字节数据
- 0001 — 5023 = 1(分/mL,uint16)
- 00001388 — 5024 = 5000(mL,uint32)
- XXXX — CRC
D.2 结算帧(FC16)¶
设备完成工作并上报产量后,平台扣费成功即下发结算帧,包含本次消费金额(寄存器 5025)和剩余余额(寄存器 5026)。 设备收到后应在屏幕/指示灯上显示,或通过蜂鸣器提示。
寄存器布局:
| 寄存器 | 类型 | 字节 | 说明 |
|---|---|---|---|
| 5025 | uint32 | 4 | 本次总消费(分) |
| 5026 | uint32 | 4 | 扣费后剩余余额(分) |
示例帧(消费 = 500 分 = 5.00 元,余额 = 95000 分 = 950.00 元):
361013A1000408000001F400017318XXXX
帧解析:
- 13A1 — 起始寄存器 5025
- 0004 — 4 个寄存器
- 08 — 8 字节数据
- 000001F4 — 5025 = 500(分,uint32)
- 00017318 — 5026 = 95000(分,uint32)
D.3 完整工作流程¶
1. 平台下发 FC16 配置帧(5023+5024)
→ 设备缓存单价和最大配额到 RAM
2. 平台下发 FC5 启动帧(1500 = 0xA5A5)
→ 设备检查是否已收到配置帧,若未收到则忽略
→ 开始工作,累计产量实时记录
3. 工作过程中:
→ 若累计产量 >= 最大配额(5024),立即自动停机
→ 上报 FC3 帧:3632 = 产量(mL),3638 = 0(待机)
4. 平台扣费成功后下发 FC16 结算帧(5025+5026)
→ 设备在屏幕/指示灯显示消费金额和余额
→ 若 3 秒内未收到结算帧,视为已扣费(可从小程序查询)
D.4 时序要求¶
- 配置帧 → 启动帧:间隔 ≤ 500ms
- 产量上报 → 结算帧:设备应等待 ≤ 3 秒
文档版本:v1.0 维护方:Go-IoT 平台后端团队 反馈渠道:提交 Issue 至项目仓库