移动模块接入方案¶
词汇表¶
名词 | 含义 |
---|---|
移动模块 | 2G、3G、4G模块 |
ctrlKey | 控制设备时使用的key, 云端颁发,32个字节 |
bindKey | 绑定/解绑设备时使用的key, 云端颁发,32个字节 |
prodKey | 产品唯一标识(即产品密钥), 开发者在控制台上添加产品时自动生成,32个字节 |
devTid | 设备唯一ID,该值由云端分配 |
devPriKey | Device Private Key,即设备私钥,该值由云端分配,并和devTid一一对应 |
serviceHost | 云端服务地址 |
servicePort | 云端服务端口 |
文档背景¶
需要制定移动模块的接入流程,并最终形成一个方案。
前置信息¶
以下所有章节里,数据均为16进制值。假如文档描述的数据格式(内存数据)为:
48 05 04 01 52
则实际发送和接收的数据均是序列化后的16进制字符串:
4805040152
c 语言对应的输出结果为:printf("%2x",0x4805040152)
也就是说:设备和云端传输数据时,需要进行二进制数据到16进制字符串的序列号和反序列化操作,实际在网络上传输的是字符串数据。
后面章节不再赘述。
基本帧格式¶
上图是帧的基本格式,具体由以下几个部分组成:
帧头¶
帧头
的长度为1个字节,是帧的起始标识,指定为H
或十六进制0x48
。
帧长¶
帧长
的长度为1个字节,是整个帧内容(包括帧头、帧长、校验码等)的字节个数,取值:[0x06, 0xFE]
。
帧类型¶
帧类型
的长度为1个字节,用于识别该帧的类型,取值见“帧类型列表”。
帧序号¶
帧序号
的长度为1个字节,取值[0x00, 0xFF]
,循环累加,用来标识帧的顺序,该字段主要用于一条消息需要分包发送的情况,默认可填充0x00,表示不分包;
msgid¶
msgid
的长度为2个字节,取值[0x0000, 0xFFFF]
,表示该条消息的编号ID,回应消息的msgid在一些业务场景下必须要和请求消息的msgid保持一致,请注意和“帧序号”的区别;默认可填充0x0000, 表示该帧消息不关心该字段
- 在APP端,建议
msgid
从app框架提供的以下接口获取 Hekr.msgId(function(msgid) { //do something }) - 在设备端,根据需要自动生成或者使用请求中的msgid
有效数据¶
有效数据
的长度不确定,跟具体业务有关,上图中使用n来代表实际长度。
当然,厂家也可以在厂家控制台
->产品管理
->添加新产品
->管理
->参数信息
->产品参数信息
页面里设计自己产品的有效数据格式。
校验码¶
校验码
的长度为1个字节,是整个帧内容(包括帧头、帧长等)的数据和,超过0XFF取低8位(即1个字节)。
通信协议¶
通信协议构建在基本帧格式之上,具体体现在如下的各个指令及其应答。
统一应答格式¶
一般指令的应答格式如下,个别指令的应答不一样,会单独列出。
48 {帧长 1byte} {帧类型 1byte} {帧序号 1byte} {msgid 2byte} {成功/错误码 4bytes} {校验码 1byte}
注意,统一应答格式里:
- 帧类型和指令里的帧类型对应,对应表请参考
帧类型取值表
- 帧序号必须和指令里的帧序号一致
- 成功/错误码请参考最下面的
错误码表
帧类型取值表¶
码值 | 含义 |
---|---|
0x00 | 通用错误返回 |
0x01 | 设备请求校验设备ID |
0x02 | 0x01的应答帧 |
0x03 | 设备请求身份认证 |
0x04 | 0x03的应答帧 |
0x05 | 设备上报详情 |
0x06 | 0x05的应答帧 |
0x07 | APP发送数据到设备 |
0x08 | 0x07的应答帧 |
0x09 | 设备发送数据到APP |
0x0A | 0x09的应答帧 |
0x0B | 设备发送心跳 |
0x0C | 0x0B的应答帧 |
0x0D | 设备从云端同步时间 |
0x0E | x0D的应答帧 |
设备和云端建立通道¶
设备和云端建立通道,需要两个步骤:
- 设备请求云端校验设备ID。因为设备ID是由云端统一颁发的,所以具备唯一性,因此设备连接云端首先要校验其ID是否合法。
- 设备身份认证。设备需要使用其生产过程中写入的devPriKey来加密某些随机值,并发送到云端验证其是否具备连接云端的钥匙。
通用错误返回¶
返回格式¶
48 09 00 {帧序号 1byte} {error code 4bytes} {校验码 1byte}
设备请求校验设备ID¶
指令格式¶
48 45 01 {帧序号 1byte} {prodKey 32bytes} {devTid 32bytes} {校验码 1byte}
其中01
是该指令的帧类型。
应答格式¶
成功返回如下特殊格式
,失败则返回统一应答。
48 25 02 {帧序号 1byte} {hexRandomKey 16bytes} {校验码 1byte}
其中02
是应答的帧类型。
调用说明¶
hexRandomKey 是云端返回的一个随机字符串(16字节), randomKey=printf("%2x",hexRandomKey)。
设备身份认证¶
指令格式¶
48 15 03 {帧序号 1byte} {authKey 16bytes} {校验码 1byte}
其中03
是该指令的帧类型。
应答格式¶
统一应答,且应答的帧类型是04
。
调用说明¶
authKey计算方法:
authKey = MD5(randomKey + devTid + devPriKey) // '+'号代表内存中数据直接拼接
devTid和devPriKey的含义请参考词汇表。云端校验authKey成功后,设备身份认证最终通过。
已下面例子说明authKey的计算(D表示设备,C表示云端, 为增强可读性补充空格,实际发送时无空格):
proKey:fa43e10a44bc8e624d9f008a3feaaa01
设备devTid:9e982ed5dd2c4c7ca744bc76ef4af044
privateKey:4a83550599a94f1db9345d8645f79234
D => C: 484501 00 6661343365313061343462633865363234643966303038613366656161613031 3965393832656435646432633463376361373434626337366566346166303434 24
C => D: 481502 00 4871745161336379676b71664c623554 2d
D => C: 481503 01 60f153ece1c40698910fb12b2035f96e 6c
C => D: 480904 01 0000000056
6661343365313061343462633865363234643966303038613366656161613031
则是通过将fa43e10a44bc8e624d9f008a3feaaa01
字符串转16进制后得到的。
3965393832656435646432633463376361373434626337366566346166303434
则是通过将 9e982ed5dd2c4c7ca744bc76ef4af044
字符串转16进制后得到的。
得到16进制的结果 result=484501 00 6661343365313061343462633865363234643966303038613366656161613031 3965393832656435646432633463376361373434626337366566346166303434 24
(注意去除空格,空格只是为了看着容易区分帧)通过c程序的printf("%2x",result)得到的字符串,然后将这个字符串发往云端服务器。
randomKey 为 4871745161336379676B71664C623554
,即c程序通过printf("%2x",hexRandomKey)
,
注意云端向设备发送authKey时是按字符串写入,即通过 printf("%2x",authKey)得到的字符串
let authKey = md5('4871745161336379676B71664C623554' + '9e982ed5dd2c4c7ca744bc76ef4af044' + '4a83550599a94f1db9345d8645f79234')
assertEqual( hex(authKey), '60f153ece1c40698910fb12b2035f96e' )
设备上报详情¶
指令格式¶
48 5e 05 {帧序号 1byte} {模块网络制式编号 1byte} {基站信息1 4bytes} {基站信息2 4bytes} {基站信息3 4bytes} {基站信息4 bytes} {基站信息5 4bytes} {基站信息6 4bytes} {经度整数部分 2bytes} {经度小数部分 4bytes} {纬度整数部分 2bytes} {纬度小数部分 4bytes} {预留 52bytes} {校验和 1byte}
其中05
是该指令的帧类型。
应答格式¶
统一应答,且应答的帧类型是06
。
调用说明¶
模块网络制式编号表
模块网络制式 | 编号 | 基站信息说明(按1~6顺序) | 备注 |
---|---|---|---|
移动2G | 0 | {lac} {cellid} {偏离角} {传输距离} {预留} {预留} | 参数都是可选 |
移动3G | 1 | {lac} {cellid} {偏离角} {传输距离} {预留} {预留} | 参数都是可选 |
移动4G | 2 | {是否4G基站} {lac} {enodebid} {cellid} {偏离角} {传输距离} | 参数都是可选 |
联通2G | 3 | {lac} {cellid} {偏离角} {传输距离} {预留} {预留} | 参数都是可选 |
联通3G | 4 | {lac} {cellid} {偏离角} {传输距离} {预留} {预留} | 参数都是可选 |
联通4G | 5 | {是否4G基站} {lac} {enodebid} {cellid} {偏离角} {传输距离} | 参数都是可选 |
电信2G | 6 | {sid} {nid} {bid} {预留} {预留} {预留} | 参数都是可选 |
电信3G | 7 | {sid} {nid} {bid} {预留} {预留} {预留} | 参数都是可选 |
电信4G | 8 | {是否4G基站} {lac} {enodebid} {cellid} {偏离角} {传输距离} | 参数都是可选 |
表中专业词汇解释
- lac — location area code 位置区编码,也叫小区号
- cellid — 基站编号
- enodebid — 基站编号,该词汇在4G中专用
- sid — 系统识别码,每个地级市只有一个sid,是唯一的
- nid — 网络识别码,由各本地网管理,也就是由地级分公司分配。每个地级市可能有1到3个nid
- bid — 表示的是网络中的某一个小区,可以理解为基站
APP发送数据到设备¶
指令格式¶
{
"msgId" : 291,
"action" : "appSend",
"params" : {
"devTid" : "ESP_245EC89",
"ctrlKey" : 123456789123456789",
"appTid" : "358974675345",
"data" : {
"raw" : "48xx07xx"
}
}
}
data.raw
的具体格式如下。
48 {帧长 1byte} 07 {帧序号 1byte} {msgid 2byte} {appTid 64bytes} {有效数据 nbytes} {校验码 1byte}
apptid 和 app登录的时候保持一致。
其中07
是帧类型。
设备应答格式¶
48 4B 08 {帧序号 1byte} {msgid 2byte} {appTid 64bytes} {成功/错误码 4bytes} {校验码 1byte}
最佳实践:成功/错误码直接填充 00000000
,当有错误的时候发一个devSend(也即是向app发送消息)即可。
app接收数据则是如下格式: 成功。
{
"msgId" : 291;
"action" : "appSendResp",
"code" : 200,
"desc" : "Success"
"params": {
"devTid": "fjkashfj123123",
"ctrlKey": "fashf123123r5fhhf",
"appTid": "123123123"
}
}
失败。
{
"msgId" : 291,
"action" : "appSendResp",
"code" : 错误码,
"desc" : "错误描述"
}
设备发送数据到APP¶
指令格式¶
48 {帧长 1byte} 09 {帧序号 1byte} {msgid 2byte} {有效数据 nbytes} {校验码 1byte}
其中09
是帧类型。
应答格式¶
统一应答,且应答的帧类型是0A
。
APP 收到的数据格式如下:
{
"msgId" : 291,
"action" : "appSendResp",
"code" : 200,
"desc" : "success",
"params" : {
"devTid" : "ESP_2M_245EC89",
"ctrlKey" : "123456789123456789",
"appTid" : "358974675345",
"data" : {
"raw" : "48xx0Axx"
}
}
}
设备发送心跳¶
指令格式¶
48 05 0B {帧序号 1byte} {校验码 1byte}
其中0B
是帧类型。
应答格式¶
统一应答,且应答的帧类型是0C
。
调用说明¶
如果30秒内云端没有收到任何数据,则会主动断开与设备的连接,所以设备必要时需要发送心跳指令来保活连接。
设备从云端同步时间¶
指令格式¶
48 05 0D {帧序号 1byte} {校验码 1byte}
其中0D
是帧类型。
应答格式¶
48 0D 0E {帧序号1Byte} {毫秒时间戳unint64 8Byte,大端} {校验和 1Byte}
如返回:480d0e01000001689e56e36f13 则 时间戳为00001689e56e36f,也就是 1548844721007
设备生产¶
设备生产时,需要在其MCU里写入几个值,包括:devTid、devPriKey、bindKey、devPrKey、serviceHost、servicePort。
- prodKey,厂家可以在氦氪控制台添加新产品完成后,通过以下路径查看。氦氪控制台产品管理相应产品产品信息产品密钥查看。
- devTid、devPriKey、bindKey:厂家可以在氦氪控制台申请N个唯一ID/设备私钥,并导出csv文件及相应的二维码。路径如下:氦氪控制台产品管理相应产品设备私钥批量生成导出批量生成的二维码及相应的csv文件。
- serviceHost、servicePort,厂家根据设备使用地写入云端服务地址和端口。中国地区serviceHost写入hub.hekr.me,不同的通讯方式使用不同的端口,明文通道:87(tcp)、88(websocket),加密通道:187(tcp ssl)、188(websocket ssl)。
最终,在设备生产阶段,,厂家需在MCU里写入devTid、prodKey、devPriKey、bindKey、serviceHost、servicePort。方案如下:
- 在生成的二维码上增加识别号码以便于能清楚区分该二维码所表示的产品、批次、序号。
- 厂家通过MCU烧写工具写入相应的devTid、devPriKey、bindKey
- 烧写完成后对应的按序号对设备贴对应的二维码标识图并附带N份备用(根据后续设备需要粘贴的张数)
移动模块接入流程¶
下图是移动模块接入的整理流程。
下面将使用时序图对重要的流程进行说明。
设备和用户绑定¶
设备发送数据到APP¶
APP发送数据到设备¶
错误码表¶
错误码 | 提示信息 | 中文释义 | 可能造成的原因 |
---|---|---|---|
1200000 | Success | 调用成功 | 无 |
1400000 | Error | 未知错误 | 该行以下所有1400开头的错误码需要单独处理 |
1400001 | Json parse error | json解析错误 | json格式错误 |
1400002 | JWT parse error | jwt_token解析错误 | jwt token错误 |
1400003 | The field {0} contains a value that is too high | 属性值过高 | 发送报文中某属性的值超过了其上限 |
1400004 | The field {0} contains a value that is too low | 属性值过低 | 发送报文中某属性的值低于了其下限 |
1400005 | The value of the field {0} must be an enumerated value | 属性值必须为范围内枚举值 | 发送报文中某属性值不符合其定义的取值范围 |
1400006 | Field not exist | 属性不存在 | 发送报文中存在了未定义的属性 |
1400008 | devTid not match | 设备ID不匹配 | 报文填写的devTid与登录设备的devTid不一致 |
1400009 | App repeat login | APP重复登录 | 同一个appTid的app重复登录 |
1400010 | User does not exist | 用户不存在 | 用户不存在或者uid填错了 |
1400011 | The device does not have this instruction | 设备不具有该指令 | 设备不具备该指令 |
1400012 | Device does not belong to user | 设备不属于该用户 | 设备不再属于该用户 |
1400013 | Device repeat login | 设备重复登录 | 设备同时登录 |
1400014 | Frame parse error | 帧解析错误 | 报文格式或内容错误 |
1400015 | Device last token can not use | 设备上一次token已过期无法使用 | 使用的旧token已经超过上限 |
1400016 | Action not support | 该帧行为不被支持 | 报文中的action填错了 |
1400017 | Device token can not verification | 设备token校验错误 | 设备token错误 |
1400018 | Device not online | 设备不在线 | 设备离线 |
1400019 | App is not logged in | app未登录 | app未登录或其他原因导致云端认为app已经离线 |
1400020 | Device is not logged in | 设备未登录 | 设备未登录或其他原因导致云端认为设备已经离线 |
1400022 | The device is not found | 找不到指定(devTid)设备 | 当前产品下不存在该设备 |
1400023 | appTid does not match | app设备id不匹配 | 当前发送报文的appTid与绑定设备时的appTid不一致 |
1400024 | You report info does not match your connect server | 上报节点信息与实际不符 | 上报的内容与当时连接节点信息不一致 |
1400025 | RAW not valid, Please check your protocol template | 协议不合法,请参照协议模板 | 48协议串不合法 |
1400026 | AuthKey can't auth | authKey 认证失败 | 填写了错误的authkey |
1400027 | Product key not available | 不是有效的pk | 填写了错误的pk |
1400028 | PinCode or SSID not available | pinCode或者ssid无效 | 填写了无效的pinCode或者ssid |
1400029 | Bind failed due to timeout error | 绑定设备超时错误 | 绑定设备超时 |
1400030 | Can not bind other manufacture's device | 无法绑定其它厂商的设备 | APP绑定了非该厂家的设备 |
1400039 | Device license can not verification | 设备license不合法 | 请填写正确的license |
1400031 | Can not force bind device | 无法强绑设备 | 设备设定为无法强绑,强绑失败 |
1400032 | Invalid Param | 参数不合法 | 协议参数不合法 |
1400040 | frame payload error | 帧内容校验失败 | 可能帧内容或者求和不对 |
1500000 | Internal error | 内部错误 | 服务内部错误 |
1500001 | Link error | 链路错误 | 链路错误 |