合宙的设备,需要一个通用的报文协议,使得合宙的设备,能够按照这个通用协议,把设备采集的各种数据上报到合宙云平台做记录。
同时,该协议,也能够支持接收下发的指令。
该协议必须是基于连接的承载,可以是 TCP,也可以是 MQTT,以方便进行鉴权。
协议采用二进制方式,节约通信流量,降低功耗。
同时,该协议要足够简单,看完报文协议后,无需查阅,就可以理解报文的含义。
上行和下行,采用同样的报文格式。
报文分为消息头和消息体两部分。
消息头只有一个,在报文开头,固定12个字节,分为设备ID,消息长度,版本标志三个字段。
消息体可以有多个,采用TLV的形式。
除了特殊约定外,报文的各个字节采用大端对齐的方式。
消息头一共 16 字节。
其中第一个字节,代表设备类型,4G,WiFi,BLE,等。
0x01 - 4G 设备;
0x02 - WiFi 设备;
0x03 - BLE 设备;
0x04 - 以太网设备;
0x05 - 4G 主控,多网融合设备;
0x06 - WiFi 主控,多网融合设备
0x07 - BLE 主控,多网融合设备;
0x08 - 以太网主控,多网融合设备。
剩余 7 字节为设备 ID,可以是 IMEI,也可以是 MAC, 也可以是 Chip ID,不做特殊约定。
其中, IMEI 在这里只放前面14个数字,第15个数字,参见3.3.1 小节计算出来。
设备 ID 采用 BCD 编码方式。
设备 ID 长度不足 7 字节的,自动在高位补 0.
例 1,一个 4G 设备, IMEI 是 861234567890123 的,设备的 8 个字节的内容是:
0x0186123456789012
第 15 个数字 3, 是不在设备 ID 体现的。
例 2,一个 WIFI 设备的 MAC 地址是 00-1A-2B-3C-4D-5E,该设备的 8 个字节的内容是:
0x 02 00 00 1A 2B 3C 4D 5E
从消息标识开始的消息长度,最长不能大于 1400字节。
用来做协议和消息约定的32个bit。
bit0-3: 协议版本号;
bit4: 是否需要回复
bit5: 是否携带鉴权 key
bit6: 是否是 UDP 承载。
其余25个bit 为0.
消息体有多个 TLV 组成: T-字段类型,L-字段长度,V-字段取值,可以任意多个 TLV, 只要消息长度不超过 1400 字节即可。
1, 字段类型 - 2 字节
前面 12 个 bit, 代表字段含义,比如温度,湿度,转速,工作或者停止,等等;
后面 4bit,代表数据类型(整数,浮点,bool,ascii,binary)
2, 长度 - 2 字节
字段取值的长度
3, 字段值
实际的值。
| 字段 | **字段名** | **长度** | 含义 |
| 消息头 | 设备ID | 8 | 第一个字节,代表设备类型,4G,WiFi,BLE,等; 剩余7字节为设备ID,可以是IMEI,也可以是MAC, 也可以是Chip ID,不做特殊约定。 详细解释参见2.1.1节 |
| 消息头 | 流水号 | 2 | |
| 消息头 | 消息长度 | 2 | 从消息体(既不包含消息头和认证key(可选))开始的消息长度,长度不做限制。 |
| 消息头 | 消息标识 | 4 | bit0-3: 协议版本号; bit4: 是否需要回复 bit5: 是否是 UDP承载 其余26个bit 为0. |
| 认证key(可选) | key | 64 | 用于 UDP 报文的鉴权 |
| TLV1 | 字段类型 | 2 | bit0 到 bit11, 代表字段含义,比如温度,湿度,转速,工作或者停止,等等; bit12到bit15,代表数据类型(整数,浮点,bool,ascii,binary) |
| TLV1 | 长度 | 2 | |
| TLV1 | 字段值 | N | |
| TLV2 | 字段类型 | 2 | |
| TLV2 | 长度 | 2 | |
| TLV2 | 字段值 | N | |
| ...... | |||
在设备建立连接之前,需要发送鉴权消息。
鉴权的方式是,通过 TLV 传递用户的 key+IMEI(或者 MAC)+MUID,由服务器判断是否合法,如果不合法的话,服务器主动发起断链操作。
报文 value:用户 key+"-"+imei(或 mac)+"-"+muid(或 unique_id)
字段类型: 0003 - ASCII 可打印字符串
示例: 对于 4g 模组:
例如:
完整报文就是:X1zBmxSd1H2Gy69DtAyNytmUe7dudGXm-862419074073247-20250605190426A662704A3771265005
对于 wifi 模组:
用户 key:客户在平台创建项目的时候生成的字符串,可打印字符
Sta mac:wifi 模组的 sta mac,获取的 hex 格式的 mac 地址,为 16 进制可见字符串
muid:等待适配
例如:
用户 key 参考 iot 平台的如:X1zBmxSd1H2Gy69DtAyNytmUe7dudGXm
Sta mac:C8C2C68E12E6
完整报文:X1zBmxSd1H2Gy69DtAyNytmUe7dudGXm-C8C2C68E12E6-muid(等待适配)
对于以太网模组(mcu+ 网口):
用户 key:客户在平台创建项目的时候生成的字符串,可打印字符
以太网 mac:以太网的 mac,需要转为十六进制可见字符串
unique_id:muc 通过 mcu.unique_id()获取的唯一 id,需要转为十六进制可见字符串
示例:用户 key 参考 iot 平台的如:X1zBmxSd1H2Gy69DtAyNytmUe7dudGXm
服务器收到鉴权消息后解析出 key、imei(mac)、muid,并校验 imei 和 muid 是不是匹配,然后通过 key 找到对应的项目,在项目中查找是否存在该 imei(mac)设备,存在就返回鉴权成功,不存在就返回失败,并断开链接
服务器需要注意,生成的用户 key 不能有 "-"符号。
服务器校验成功后,需要下发"ok"或者"success"通知模组,字段类型为 17
设备主动向云平台报告在线状态、维持连接活性。
心跳规则:
字段类型一共 2 字节,分为两部分: 字段含义和数据类型,其中字段含义 12bit,数据类型 4bit。
数据类型占用 4 个 bit,理论上支持 16 种数据类型。
0000 - 整数
0001 - 浮点数
0002 - 布尔值
0003 - ASCII 可打印字符串,是可打印的字符串,直接 copy 出来就可以在文本编辑器人眼查看的。每个字节的值都在 0x20 到 0x2E 之间。
0004 - binary 字符串,不一定可打印, 每个字节是任意的。
0005 - UNICODE 字符串,UTF-8 编码方式。
字段含义占用 12bit,理论上支持 4096 种字段含义。
其中,
(1) 0 - 15 为预留值,
(2) 16-255 为控制信令类型,
(3) 256-2047 为业务字段类型,分为传感类,资产管理类,设备参数类,其他每个类别 256 种字段。
(4) 2048-4095 为预留值。
16 - 鉴权请求 - 上行
17 - 鉴权回复 - 下行
18 - 上报回应 - 下行,用于服务器对设备的上报的回应
19 - 控制命令 - 下行, 用于服务器对设备下发的控制命令
20 - 控制回应 - 上行, 用于对服务器发送的控制命令的回应
21 - iRTU 下行命令,
22 - iRTU 上行回复,
23 - 文件上传开始通知 - 上行(设备通知服务器开始上传文件)
24 - 文件上传完成通知 - 上行(上传完成后通知服务器结果)
256 - 温度
257 - 湿度
258 - 颗粒数
259 - 酸度
260 - 碱度
261 - 海拔
262 - 水位
263 - CPU 温度/环境温度
264 - 电量计量
512- GNSS 经度
513 - GNSS 维度
514 - 行驶速度
515 - 最强的 4 颗 GNSS 卫星的 4 个 CN
516 - 搜到的所有卫星数
517 - 可见卫星数
518 - 航向角
519 - 基站定位/GNSS 定位标识
520 - GNSS 芯片型号和固件版本号
521- 方向
768 - 高度
769 - 宽度
770 - 转速
771 - 电量(mV)
772 - 驻留频段
773 - 驻留小区和临区
774 - 元器件型号
775 - GPIO 高低电平
776 - 开机原因
777 - 开机次数
778 - 休眠模式
779- 定时唤醒间隔
780 - 设备入网的 IPV4/IPV6 标志
781 - 当前联网方式(4G/WiFi/以太网)
782 - 4G 信号强度
783 - ICCID
784 - 文件上传类型(图片/音频)
785 - 文件名称
786 - 文件大小
787 - 上传结果状态
1024 - Lua 核心库错误上报(用于 LuatOS 自动化测试)
1025 - Lua 扩展卡错误上报(用于 LuatOS 自动化测试)
1026 - Lua 业务错误上报(用于 LuatOS 自动化测试)
1027 - 固件版本号
1028 - SMS 转发
1029 - 来电转发
1280 - 时间
1281 - 无意义数据
T: 21
L: 消息的长度
V:实际的 iRTU 下行命令的全文
举例:
T: 0x0015 ----- 信令类型 21
L: 0x000B ---- 11 个字节
V: 字符串: “rrpc,getcsq”
T: 22
L: 消息的长度
V:实际的 iRTU 上行命令全文
举例:
T: 0x0016 ----- 信令类型 22
L: 0x000E ---- 14 个字节
V: 字符串: “rrpc,getcsq,17”
23 - 文件上传开始通知 - 上行(设备通知服务器开始上传文件)
作用:通过 TCP/MQTT 通知服务器有文件上传。
TLV 结构:
V: 包含以下子 TLV:
24 - 文件上传完成通知 - 上行(上传完成后通知服务器结果)
作用:通过 TCP/MQTT 通知服务器文件上传成功。
TLV 结构:
V: 包含以下子 TLV:
IMEI 的第 15 位数字是校验位,由 Luhn 算法计算得出,用于确保 IMEI 的有效性。具体计算方法如下:
例如,对于 IMEI 前 14 位数字 35890180697241,计算过程如下:
遥测的目的是检测设备的 mobile 信息,证明设备还活着,还具备通信能力;
airCloud 协议,目的是合宙设备驱动的业务,都有被记录到数据库的机会。
URL device.openluat.com/getip?key="....."
METHOD GET
key 表示用户的 key 通过 sha256 加密后的值
补充后的考虑如下:
{
//设备连接信息 (MQTT/TCP)
"conninfo": {
"host": "xxx", // 服务器地址
"port": xxx, // 端口
"user_name": "xxx", // 用户名
"password": "xxxxxxxx", // 密码
"transport": "tcp", //连接方式"tcp"或者"mqtt"
},
//图片上传信息
"imgupurl": "https://image.openluat.com/iot/upload/file",
"imgbody": {
"{data}": "f",
"auth": true,
"dir": "upload_image",
"mon": true,
"prefix": "123456789012_",
"index": true,
"thumb": "thumb",
"tip": "图片说明",
"rpt": false
},
"imgheader": {
"token": "2xb3….",
"salt": "80…",
"tu": 1
},
//音频上传信息
"audupurl": "https://audio.openluat.com/iot/upload/file",
** //下面具体字段还需要建栋确认。**
"audbody": {
"{data}": "audio_file", // 音频文件的字段名
"auth": true,
"dir": "upload_audio", // 服务端存储音频的目录
"mon": true,
"prefix": "123456789012_",
"index": true,
"size": 300, // 文件大小
"rpt": false//上传后成功是否返回详细信息。
},
"audheader": {
"token": "3yc4….",
"salt": "81…",
"tu": 1
}
}
1、首次没有获取 ip 前调用
2、连接获取 ip 端口后,如果 TCP 断连先进行重连,如果 3 次重连失败就去调用 getip 重新获取 ip 及端口。
3、更异常的情况:若 getip 如果失败,也应该尝试 3 次。
TCP server 收到数据后,直接发起写入数据库动作。
TCP server 在接受了 socket 连接后,等待设备的鉴权请求,如果超时或者鉴权请求不通过,主动发起断链。
有三个角色: 设备,Broker,ServClient。
ServClient 订阅所有主题,设备只订阅跟自己相关的主题。
设备的主题名字为: /AirCloud/direction/DeviceID/ServType
其中, AirCloud 是固定字符串;
direction 是传输方向:
(1)up:上行方向,表示从设备到服务器的通信
(2)down:下行方向,表示从服务器到设备的通信
DeviceID 是设备的 ID, 内容和消息头的 设备 ID 相同;
ServType 包括两种:
(1)auth
(2)all
其中, auth 是指鉴权报文, all 是指所有其他报文。
目前的 topic 主题如下:
设备鉴权上报:
/AirCloud/up/DeviceID/auth
设备数据上报:
/AirCloud/up/DeviceID/all
服务器鉴权响应下发:
/AirCloud/down/DeviceID/auth
服务器数据下发:
/AirCloud/down/DeviceID/all
设备和Broker建立MQTT 连接后,首先设备要发起鉴权,Broker把鉴权请求转发给ServClient,ServClient审核后,回复鉴权通过或者鉴权失败。
如果回复的是鉴权失败,ServClent需要在10秒钟之内通知Broker,把发起鉴权的设备进行断链处理。
如果设备超时没有发起鉴权,ServClient也要通知Broker,把设备断链。
AirCloud 当前不推荐 UDP 协议。
如果必须要用 UDP 的话,需要在消息标识的第 6 个 bit 设为 1, 并在消息头和 TLV 中间,放置 64 字节的 key。
服务器需要对每个 UDP 消息,进行 key 的合法性检查。
通过 getip 获取的上报 url
URL "https://image.openluat.com/iot/upload/file" , 根据getip的返回的数据决定,不能写死
METHOD POST
HEADERS 根据getip的返回数据进行填充,对应"imgheader"
BODY mulitpart格式
其中, body中文件的key 要使用 {data} 对应的值
正常返回码 200, 其他一律作为错误
响应内容
{
"code":0,//code为0表示成功
"value": {//这个是rpt请求参数为true的返回。如果没有携带rpt,则value返回"上传成功"
"uri": "/vsna/luatos/336677/upload_image/2025-09/86453607261497520250908144614.png",
"size": "26.00KB",
"thumb": "/vsna/luatos/336677/upload_image/2025-09/86453607261497520250908144614s.png"
}
}
URL "https://audio.openluat.com/iot/upload/file" , 根据getip的返回的数据决定,不能写死
METHOD POST
HEADERS 根据getip的返回数据进行填充,对应"audheader"
BODY mulitpart格式
其中, body中文件的key 要使用 {data} 对应的值
正常返回码 200, 其他一律作为错误
响应内容
{
"code":0,//code为0表示成功
"value": {//这个是rpt请求参数为true的返回。如果没有携带rpt,则value返回"上传成功"
"uri": "/vsna/luatos/336677/upload_image/2025-09/xxx.mp3",
"size": "26.00KB",
"thumb": "/vsna/luatos/336677/upload_image/2025-09/xxx.mp3"
}
}
按业务逻辑而定
23 - 文件上传开始通知 - 上行(设备通知服务器开始上传文件)
24 - 文件上传完成通知 - 上行(上传完成后通知服务器结果)
784 - 文件上传类型(图片/音频)
785 - 文件名称
786 - 文件大小
787 - 上传结果状态
所有上报的数据,都记录到数据库
提供 web 表单查询日志,并且可以导出到文件。
可视化展现,和数据存储做分离的实现。
提供 web 后台的查询接口,后端和前端的实现可以分离。
合宙的开发板,默认都使用一个 key, 上报当前的驻留小区,可以在后台查询日志。
合宙的量产小板,用 AirCloud 批量挂测,做压力测试。
IRTU 取消对第三方云平台的支持, 只内置合宙 AirCloud 协议。
客户可以用这个协议,搭建自己的后台;
也可以上报合宙的后台,合宙按照套餐收费。
合宙平台收费标准如下:
| 免费版 | 标准版 | VIP版 | |
| 年费 | 0 | ¥3000 | ¥10000 |
| 设备数 | 3 | 100(可扩容) | 1000(可扩容) |
| 每日上行消息限量 | 1000 | 1.5万 | 150万 |
| 数据保存时间 | 7天 | 6个月 | 12个月 |
| API 云端对接 | 不支持 | 支持 | 支持 |
| 日志导出文件 | 不支持 | 支持 | 支持 |
| 二级用户 | 不支持 | 10 | 100 |
扩容费用:
| 扩容量 | 价格 | |
| 设备数扩容 | 100 | 500 |
| 每日消息量扩容 | 10万 | 500 |
| 二级用户 | 100 | 200 |
| 数据存储 | 1亿报文1年 | 1000 |
excloud 扩展库, 调用 socket 核心库,mqtt 核心库,exnetif 扩展库。
扩展卡包含如下成员函数:
1, init(): 初始化参数,设备 ID,流水号初始化,指定协议版本,
2, auth_req(), 携带对收到鉴权回复的回调,以及超时的回调。
3, data_report(), 上报设备数据。