edit

移动模块接入方案

词汇表

名词 含义
移动模块 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

也就是说:设备和云端传输数据时,需要进行二进制数据到16进制字符串的序列号和反序列化操作,实际在网络上传输的是字符串数据。

后面章节不再赘述。

1. 基本帧格式

上图是帧的基本格式,具体由以下几个部分组成:

1.1 帧头

帧头的长度为1个字节,是帧的起始标识,指定为H或十六进制0x48

1.2 帧长

帧长的长度为1个字节,是整个帧内容(包括帧头、帧长、校验码等)的字节个数,取值:[0x06, 0xFE]

1.3 帧类型

帧类型的长度为1个字节,用于识别该帧的类型,取值见“帧类型列表”。

1.4 帧序号

帧序号的长度为1个字节,取值[0x00, 0xFF],循环累加,用来标识帧的顺序,该字段主要用于一条消息需要分包发送的情况,默认可填充0x00,表示不分包;

1.5 msgid

msgid的长度为2个字节,取值[0x0000, 0xFFFF],表示该条消息的编号ID,回应消息的msgid在一些业务场景下必须要和请求消息的msgid保持一致,请注意和“帧序号”的区别;默认可填充0x0000, 表示该帧消息不关心该字段

  • 在APP端,建议msgid从app框架提供的以下接口获取 Hekr.msgId(function(msgid) { //do something })
  • 在设备端,根据需要自动生成或者使用请求中的msgid

1.6 有效数据

有效数据的长度不确定,跟具体业务有关,上图中使用n来代表实际长度。

当然,厂家也可以在厂家控制台->产品管理->添加新产品->管理->参数信息->产品参数信息页面里设计自己产品的有效数据格式。

1.7 校验码

校验码的长度为1个字节,是整个帧内容(包括帧头、帧长等)的数据和,超过0XFF取低8位(即1个字节)。

2. 通信协议

通信协议构建在基本帧格式之上,具体体现在如下的各个指令及其应答。

统一应答格式

一般指令的应答格式如下,个别指令的应答不一样,会单独列出。

48 {帧长 1byte} {帧类型 1byte} {帧序号 1byte} {msgid 2byte} {成功/错误码 4bytes} {校验码 1byte}

注意,统一应答格式里:

  • 帧类型和指令里的帧类型对应,对应表请参考帧类型取值表
  • 帧序号必须和指令里的帧序号一致
  • 成功/错误码请参考最下面的错误码表

帧类型取值表

码值 含义 备注
0x01 设备请求校验设备ID 对应2.1.1 小节
0x02 0x01的应答帧 对应2.1.1 小节
0x03 设备请求身份认证 对应2.1.2 小节
0x04 0x03的应答帧 对应2.1.2 小节
0x05 设备上报详情 对应2.3 小节
0x06 0x05的应答帧 对应2.3 小节
0x07 APP发送数据到设备 对应2.4 小节
0x08 0x07的应答帧 对应2.4 小节,注意云端需要将0x08转换为appSendResp
0x09 设备发送数据到APP 对应2.5 小节
0x0A 0x09的应答帧 对应2.5 小节
0x0B 设备发送心跳 对应2.6 小节
0x0C 0x0B的应答帧 对应2.6 小节

2.1 设备和云端建立通道

设备和云端建立通道,需要两个步骤:

  1. 设备请求云端校验设备ID。因为设备ID是由云端统一颁发的,所以具备唯一性,因此设备连接云端首先要校验其ID是否合法。
  2. 设备身份认证。设备需要使用其生产过程中写入的devPriKey来加密某些随机值,并发送到云端验证其是否具备连接云端的钥匙。

2.1.1 设备请求校验设备ID

指令格式
48 45 01 {帧序号 1byte} {prodKey 32bytes} {devTid 32bytes} {校验码 1byte}

其中01是该指令的帧类型。

应答格式

成功返回如下特殊格式,失败则返回统一应答。

48 25 02 {帧序号 1byte} {randomKey 32bytes} {校验码 1byte}

其中02是应答的帧类型。

调用说明

randomKey是云端返回的一个随机字符串。

2.1.2 设备身份认证

指令格式
48 15 03 {帧序号 1byte} {authKey 16bytes} {校验码 1byte}

其中03是该指令的帧类型。

应答格式

统一应答,且应答的帧类型是04

调用说明

authKey计算方法:

authKey = MD5(randomKey + devTid + devPriKey)  // '+'号代表内存中数据直接拼接

devTid和devPriKey的含义请参考词汇表。云端校验authKey成功后,设备身份认证最终通过。

已下面例子说明authKey的计算(D表示设备,C表示云端, 为增强可读性补充空格,实际发送时无空格):

D => C: 484501 00 6661343365313061343462633865363234643966303038613366656161613031 3965393832656435646432633463376361373434626337366566346166303434 24
C => D: 481502 00 4871745161336379676b71664c623554 2d
D => C: 481503 01 60f153ece1c40698910fb12b2035f96e 6c
C => D: 480904 01 0000000056

设备devTid为9e982ed5dd2c4c7ca744bc76ef4af044, privateKey为4a83550599a94f1db9345d8645f79234, randomKey 为 4871745161336379676B71664C623554, 注意云端向设备发送authKey时是按字符串写入,而不是字节转hex

let authKey = md5('4871745161336379676B71664C623554' + '9e982ed5dd2c4c7ca744bc76ef4af044' + '4a83550599a94f1db9345d8645f79234')
assertEqual( hex(authKey), '60f153ece1c40698910fb12b2035f96e' )

2.2 APP登录

指令格式
{
    "msgId" : 240,
    "action" : "appLogin",
    "params" : {
        "appTid" : "12345xxxxyyyy",
        // 用户在APP上登录账号后,认证授权服务返回的JWT Token中的access_token字段
        "token" : "eyJhbGc9.eyJhdWQiOlsic9.bZiprbK9h1o"
    }
}
应答格式

成功。

{
    "msgId" : 240,
    "action" : "appLoginResp",
    "code" : 200,
    "desc" : "success"
}

失败。

{
    "msgId" : 240,
    "action" : "appLoginResp",
    "code" : 错误码,
    "desc" : "错误描述"
}
调用说明

指令格式中的参数token,是APP调用认证授权API->登录接口返回的JWT Token,有效期为24小时。如果APP和云端建立通道前token已过期,云端会提示token无效,需要使用refreshToken获取新的token;如果联网之后连接不断,即使token过期,APP还能继续控制设备。

2.3 设备上报详情

指令格式
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 — 表示的是网络中的某一个小区,可以理解为基站

更多信息请参考:参考资料1参考资料2

2.4 APP发送数据到设备

指令格式
{
    "msgId" : 291,
    "action" : "appSend",
    "params" : {
        "devTid" : "ESP_245EC89",
        "ctrlKey" : 123456789123456789",
        "appTid" : "358974675345",
        "data" : {
            "raw" : "数据"
        }
    }
}

数据的具体格式如下。

48 {帧长 1byte} 07 {帧序号 1byte} {msgid 2byte} {appTid 64bytes} {有效数据 nbytes} {校验码 1byte}

apptid从页面URL中获取appId参数后去除web后缀 ,然后在转换时以空格补足64位然后转换成16进制。

其中07是帧类型。

应答格式

48 4B 08 {帧序号 1byte} {msgid 2byte} {appTid 64bytes} {成功/错误码 4bytes} {校验码 1byte}
云端需翻译成如下格式: 成功。

{
    "msgId" : 291;
    "action" : "appSendResp",
    "code" : 200,
    "desc" : "Success"
    "params": {
        "devTid": "fjkashfj123123", 
        "ctrlKey": "fashf123123r5fhhf", 
        "appTid": "123123123"
    }
}

失败。

{
    "msgId" : 291,
    "action" : "appSendResp",
    "code" : 错误码,
    "desc" : "错误描述"
}

2.5 设备发送数据到APP

指令格式
48 {帧长 1byte} 09 {帧序号 1byte} {msgid 2byte} {有效数据 nbytes} {校验码 1byte}

其中09是帧类型。

应答格式

统一应答,且应答的帧类型是0A

2.6 APP发送心跳

指令格式
{
   "msgId" : 98,
   "action" : "heartbeat"
}
应答格式

成功。

{
    "msgId" : 98,
    "action" : "heartbeatResp",
    "code" : 200,
    "desc" : "success"
}

失败。

{
    "msgId" : 98,
    "action" : "heartbeatResp",
    "code" : 错误码,
    "desc" : "错误描述"
}
调用说明

如果30秒内云端没有收到任何数据,则会主动断开与APP的连接,所以APP必要时需要发送心跳指令来保活连接。

2.7 设备发送心跳

指令格式
48 05 0B {帧序号 1byte} {校验码 1byte}

其中0B是帧类型。

应答格式

统一应答,且应答的帧类型是0C

调用说明

如果30秒内云端没有收到任何数据,则会主动断开与设备的连接,所以设备必要时需要发送心跳指令来保活连接。

2. 设备生产

设备生产时,需要在其MCU里写入几个值,包括:devTid、devPriKey、bindKey、devPrKey、serviceHost、servicePort。

  • prodKey,厂家可以在氦氪控制台添加新产品完成后,通过以下路径查看。氦氪控制台产品管理相应产品产品信息产品密钥查看。
  • devTid、devPriKey、bindKey:厂家可以在氦氪控制台申请N个唯一ID/设备私钥,并导出csv文件及相应的二维码。路径如下:氦氪控制台产品管理相应产品设备私钥批量生成导出批量生成的二维码及相应的csv文件。
  • serviceHost、servicePort,厂家根据设备使用地写入云端服务地址和端口。中国地区serviceHost写入asia-dev.hekr.me,不同的通讯方式使用不同的端口,明文通道:87(tcp)、88(websocket),加密通道:187(tcp ssl)、188(websocket ssl)。

最终,在设备生产阶段,,厂家需在MCU里写入devTid、prodKey、devPriKey、bindKey、serviceHost、servicePort。方案如下:

  • 在生成的二维码上增加识别号码以便于能清楚区分该二维码所表示的产品、批次、序号。
  • 厂家通过MCU烧写工具写入相应的devTid、devPriKey、bindKey
  • 烧写完成后对应的按序号对设备贴对应的二维码标识图并附带N份备用(根据后续设备需要粘贴的张数)

3. 移动模块接入流程

下图是移动模块接入的整理流程。

下面将使用时序图对重要的流程进行说明。

3.1 设备和用户绑定

3.2 设备发送数据到APP

3.3 APP发送数据到设备

错误码表

待补充。

示例代码

内存二进制内容序列化为16进制字符串

int bin2hex(unsigned char* src, int src_len,unsigned char* dst, int dst_len)
{
    static unsigned char  hex[] = "0123456789abcdef";

    if(dst_len < 2*src_len){
        return -1;
    }
    while (src_len--) {
        *dst++ = hex[*src >> 4];
        *dst++ = hex[*src++ & 0xf];
    }

    return 0;
}