# 合宙 IOT 通用报文协议 AirCloud -- 1.0
# 一、背景
合宙的设备,需要一个通用的报文协议,使得合宙的设备,能够按照这个通用协议,把设备采集的各种数据上报到合宙云平台做记录。
```
同时,该协议,也能够支持接收下发的指令。
该协议必须是基于连接的承载,可以是 TCP,也可以是 MQTT,以方便进行鉴权。
协议采用二进制方式,节约通信流量,降低功耗。
同时,该协议要足够简单,看完报文协议后,无需查阅,就可以理解报文的含义。
上行和下行,采用同样的报文格式。
```
# 二、协议报文详解
```
报文分为消息头和消息体两部分。
消息头只有一个,在报文开头,固定12个字节,分为设备ID,消息长度,版本标志三个字段。
消息体可以有多个,采用TLV的形式。
除了特殊约定外,报文的各个字节采用大端对齐的方式。
```
## 2.1 消息头
消息头一共 16 字节。
### 2.1.1 设备 ID - 8 字节
其中第一个字节,代表设备类型,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
```
### 2.1.2 流水号 - 2 字节
### 2.1.3 消息长度 - 2 字节
```
从消息标识开始的消息长度,最长不能大于 1400字节。
```
### 2.1.4 消息标识-4 字节
```
用来做协议和消息约定的32个bit。
bit0-3: 协议版本号;
bit4: 是否需要回复
bit5: 是否携带鉴权 key
bit6: 是否是 UDP 承载。
其余25个bit 为0.
```
## 2.2 消息体
消息体有多个 TLV 组成: T-字段类型,L-字段长度,V-字段取值,可以任意多个 TLV, 只要消息长度不超过 1400 字节即可。
1, 字段类型 - 2 字节
前面 12 个 bit, 代表字段含义,比如温度,湿度,转速,工作或者停止,等等;
后面 4bit,代表数据类型(整数,浮点,bool,ascii,binary)
2, 长度 - 2 字节
字段取值的长度
3, 字段值
实际的值。
## 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
|
|
......
|
|
|
|
|
|
|
|
## 2.4 设备鉴权
在设备建立连接之前,需要发送鉴权消息。
鉴权的方式是,通过 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.5 心跳约定
设备主动向云平台报告在线状态、维持连接活性。
心跳规则:
1. 频率:设备每 5 分钟必须至少发送一个数据包至云端。
2. 内容:任何上行数据包都算作心跳,遵循 airCloud 协议即可。
3. 超时: 若两个心跳周期未收到任何数据包,服务器将发起断链。
# 三、字段类型定义
字段类型一共 2 字节,分为两部分: **字段含义和数据类型**,其中字段含义 12bit,数据类型 4bit。
## 3.1 数据类型
数据类型占用 4 个 bit,理论上支持 16 种数据类型。
0000 - 整数
0001 - 浮点数
0002 - 布尔值
0003 - ASCII 可打印字符串,是可打印的字符串,直接 copy 出来就可以在文本编辑器人眼查看的。每个字节的值都在 0x20 到 0x2E 之间。
0004 - binary 字符串,不一定可打印, 每个字节是任意的。
0005 - UNICODE 字符串,UTF-8 编码方式。
## 3.2 字段含义
字段含义占用 12bit,理论上支持 4096 种字段含义。
其中,
(1) 0 - 15 为预留值,
(2) 16-255 为控制信令类型,
(3) 256-2047 为业务字段类型,分为传感类,资产管理类,设备参数类,其他每个类别 256 种字段。
(4) 2048-4095 为预留值。
### 3.2.1 控制信令类型 16-255
16 - 鉴权请求 - 上行
17 - 鉴权回复 - 下行
18 - 上报回应 - 下行,用于服务器对设备的上报的回应
19 - 控制命令 - 下行, 用于服务器对设备下发的控制命令
20 - 控制回应 - 上行, 用于对服务器发送的控制命令的回应
21 - iRTU 下行命令,
22 - iRTU 上行回复,
23 - 文件上传开始通知 - 上行(设备通知服务器开始上传文件)
24 - 文件上传完成通知 - 上行(上传完成后通知服务器结果)
### 3.2.2 业务字段类型
#### 3.2.2.1 传感类 256-511
256 - 温度
257 - 湿度
258 - 颗粒数
259 - 酸度
260 - 碱度
261 - 海拔
262 - 水位
263 - CPU 温度/环境温度
264 - 电量计量
#### 3.2.2.2 资产管理类 512-767
512- GNSS 经度
513 - GNSS 维度
514 - 行驶速度
515 - 最强的 4 颗 GNSS 卫星的 4 个 CN
516 - 搜到的所有卫星数
517 - 可见卫星数
518 - 航向角
519 - 基站定位/GNSS 定位标识
520 - GNSS 芯片型号和固件版本号
521- 方向
#### 3.2.2.3 设备参数类 768-1023
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 - 上传结果状态
#### 3.2.2.5 软件数据 1024-1279
1024 - Lua 核心库错误上报(用于 LuatOS 自动化测试)
1025 - Lua 扩展卡错误上报(用于 LuatOS 自动化测试)
1026 - Lua 业务错误上报(用于 LuatOS 自动化测试)
1027 - 固件版本号
1028 - SMS 转发
1029 - 来电转发
#### 3.2.2.4 设备无关数据 1280 - 1535
1280 - 时间
1281 - 无意义数据
### 3.2.3 控制信令的详细定义
#### 3.2.3.1 鉴权请求-16 - 上行
#### 3.2.3.2 鉴权回复-17 - 下行
#### 3.2.3.3 上报回应-18 - 下行
#### 3.2.3.4 控制命令-19 - 下行
#### 3.2.3.5 控制回应-20 - 上行
#### 3.2.3.6 iRTU 下行命令-21
T: 21
L: 消息的长度
V:实际的 iRTU 下行命令的全文
举例:
T: 0x0015 ----- 信令类型 21
L: 0x000B ---- 11 个字节
V: 字符串: “rrpc,getcsq”
#### 3.2.3.7 iRTU 上行回复-22
T: 22
L: 消息的长度
V:实际的 iRTU 上行命令全文
举例:
T: 0x0016 ----- 信令类型 22
L: 0x000E ---- 14 个字节
V: 字符串: “rrpc,getcsq,17”
23 - 文件上传开始通知 - 上行(设备通知服务器开始上传文件)
作用:通过 TCP/MQTT 通知服务器有文件上传。
TLV 结构:
- T: 0x0017 (信令类型 23)
- L: 实际长度
- V: 包含以下子 TLV:
- 文件上传类型 (1291): 整数(1:图片, 2:音频)
- 文件名称 (1292): ASCII 字符串
- 文件大小 (1293): 整数
24 - 文件上传完成通知 - 上行(上传完成后通知服务器结果)
作用:通过 TCP/MQTT 通知服务器文件上传成功。
TLV 结构:
- T: 0x0018 (信令类型 24)
- L: 实际长度
- V: 包含以下子 TLV:
- 文件上传类型 (1291): 整数
- 文件名称 (1292): ASCII 字符串
- 上传结果状态 (1294): 整数
- 0: 上传成功
- 1: 上传失败
## 3.3 附加信息
### 3.3.1 IMEI 第 15 位的计算方法
IMEI 的第 15 位数字是校验位,由 Luhn 算法计算得出,用于确保 IMEI 的有效性。具体计算方法如下:
1. **计算偶数位数字乘以 2 后的各位数字之和**:从左到右将 IMEI 的前 14 位数字中的偶数位数字分别乘以 2,然后计算这些乘积的个位数字和十位数字之和。例如,对于数字 5×2=10,其个位数字和十位数字之和为 0+1=1;对于 9×2=18,其个位数字和十位数字之和为 8+1=9。
2. **计算奇数位数字之和**:将 IMEI 的前 14 位数字中的奇数位数字相加。
3. **计算总和**:将第一步得到的偶数位数字之和与第二步得到的奇数位数字之和相加。
4. **确定校验位**:如果第三步计算出来的数值个位为 0,则校验位为 0;否则,校验位为 10 减个位数值。
例如,对于 IMEI 前 14 位数字 35890180697241,计算过程如下:
- 偶数位:5×2=10,9×2=18,1×2=2,0×2=0,9×2=18,2×2=4,1×2=2。个位数之和为 0+8+2+0+8+4+2=24,十位数之和为 1+1+1=3。
- 奇数位:3+8+0+8+6+7+4=36。
- 偶数位与奇数位数字之和:24+3+36=63。
- 第 15 位校验码为:10-3=7。
# 四、AirCloud 和遥测的关系
遥测的目的是检测设备的 mobile 信息,证明设备还活着,还具备通信能力;
airCloud 协议,目的是合宙设备驱动的业务,都有被记录到数据库的机会。
# 五、支持的通信承载
## 5.1、getip:
### 通信协议要求
1. 协议需要兼容老设备, 同时兼容负载均衡
2. 便于扩展到更多二进制数据的上传
3. 要包含鉴权, 上传后要自动归入设备对应的数据里
### 通信逻辑
1. 负载均衡服务器,支持生成图片上传的 url 和额外参数
2. 设备通过调用 getip 接口, 获取图片上传所需要的 url 和参数列表
3. 设备在指定条件下(定时/按键触发),使用上述参数执行上传
4. 设备平台收到数据后, 需要展示到页面中
### 通信接口设计
#### 请求(https)
```json
URL device.openluat.com/getip?key="....."
METHOD GET
```
key 表示用户的 key 通过 sha256 加密后的值
#### 响应(json 格式)
补充后的考虑如下:
```json
{
//设备连接信息 (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
}
}
```
#### AirCloud 的调用 getip 的时机参考:
1、首次没有获取 ip 前调用
2、连接获取 ip 端口后,如果 TCP 断连先进行重连,如果 3 次重连失败就去调用 getip 重新获取 ip 及端口。
3、更异常的情况:若 getip 如果失败,也应该尝试 3 次。
## 5.2 TCP
TCP server 收到数据后,直接发起写入数据库动作。
TCP server 在接受了 socket 连接后,等待设备的鉴权请求,如果超时或者鉴权请求不通过,主动发起断链。
## 5.3 MQTT
### 5.3.1 MQTT 角色
```
有三个角色: 设备,Broker,ServClient。
```
## 5.3.2 MQTT 主题
```
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
## 5.3.3 鉴权
```
设备和Broker建立MQTT 连接后,首先设备要发起鉴权,Broker把鉴权请求转发给ServClient,ServClient审核后,回复鉴权通过或者鉴权失败。
如果回复的是鉴权失败,ServClent需要在10秒钟之内通知Broker,把发起鉴权的设备进行断链处理。
如果设备超时没有发起鉴权,ServClient也要通知Broker,把设备断链。
```
## 5.4 UDP
AirCloud 当前不推荐 UDP 协议。
如果必须要用 UDP 的话,需要在消息标识的第 6 个 bit 设为 1, 并在消息头和 TLV 中间,放置 64 字节的 key。
服务器需要对每个 UDP 消息,进行 key 的合法性检查。
## 5.5 HTTP
通过 getip 获取的上报 url
### 设备上传流程:
#### 设备上传图片文件流程如下:
##### 请求(https)
```json
URL "https://image.openluat.com/iot/upload/file" , 根据getip的返回的数据决定,不能写死
METHOD POST
HEADERS 根据getip的返回数据进行填充,对应"imgheader"
BODY mulitpart格式
其中, body中文件的key 要使用 {data} 对应的值
```
##### 响应(json 格式)
正常返回码 200, 其他一律作为错误
响应内容
```json
{
"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"
}
}
```
#### 设备上传音频文件流程如下:
##### 请求(https)
```json
URL "https://audio.openluat.com/iot/upload/file" , 根据getip的返回的数据决定,不能写死
METHOD POST
HEADERS 根据getip的返回数据进行填充,对应"audheader"
BODY mulitpart格式
其中, body中文件的key 要使用 {data} 对应的值
```
##### 响应(json 格式)
正常返回码 200, 其他一律作为错误
响应内容
```json
{
"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"
}
}
```
#### 调用时机
按业务逻辑而定
### Aircloud 协议 http 文件上传:
## 上传控制信令:
23 - 文件上传开始通知 - 上行(设备通知服务器开始上传文件)
24 - 文件上传完成通知 - 上行(上传完成后通知服务器结果)
## 增加业务字段类型:
784 - 文件上传类型(图片/音频)
785 - 文件名称
786 - 文件大小
787 - 上传结果状态
## http 文件上传的逻辑大概是:

# 六、云端后台实现
## 6.1 云端后台记录
所有上报的数据,都记录到数据库
## 6.2 日志查询
提供 web 表单查询日志,并且可以导出到文件。
## 6.3 可视化展现
可视化展现,和数据存储做分离的实现。
## 6.4 API 查询
提供 web 后台的查询接口,后端和前端的实现可以分离。
# 七、应用
## 7.1 合宙内部挂测
### 7.1.1 合宙开发板
合宙的开发板,默认都使用一个 key, 上报当前的驻留小区,可以在后台查询日志。
### 7.1.2 合宙批量挂测
合宙的量产小板,用 AirCloud 批量挂测,做压力测试。
### 7.1.3 iRTU 内置支持
IRTU 取消对第三方云平台的支持, 只内置合宙 AirCloud 协议。
## 7.2 客户收费标准
客户可以用这个协议,搭建自己的后台;
也可以上报合宙的后台,合宙按照套餐收费。
合宙平台收费标准如下:
| 免费版
| 标准版
| 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(), 上报设备数据。