软件升级协议
前言
本协议规定设备和天翼物联网平台(AIoT)(以下简称“平台”)之间的应用层升级协议(简称PCP协议),用于实现设备的升级。
通讯方式
1、PCP协议运行在应用层,底层可以是LWM2M/COAP/MQTT或者其他非流式协议
2、由于PCP协议消息没有使用单独的端口号,并且不依赖于底层协议,为了和设备业务消息区分,PCP协议固定以0XFFFE作为起始字节。因此要求设备的业务消息的前两个字节不能是0XFFFE,更多细节参考附录6.4
3、本协议消息采用一问一答模式,所有请求消息都有一个响应消息
数据类型
数据类型 | 描述和要求 |
---|---|
BYTE | 无符号一字节整数 |
WORD | 无符号二字节整数 |
DWORD | 无符号四字节整数 |
BYTE[n] | n字节的十六进制数 |
STRING | 字符串 |
Note
本协议采用网络序来传输WORD和DWORD
消息结构
字段名 | 字段类型 | 描述和要求 |
---|---|---|
起始标识 | WORD | 起始标识,固定为0XFFFE |
版本号 | BYTE | 高四位预留;低四位表示协议版本号,当前为1 |
消息码 | BYTE | 标识一个请求消息,应答消息的消息码和请求消息相同 |
校验码 | WORD | 从起始标识到数据区的最后一个字节的CRC16校验值,计算前先把校验码字段置为0,计算完成后把结果写到校验码字段。 |
数据区长度 | WORD | 数据区的长度 |
数据区 | BYTE[n] | 参见具体指令的定义 |
消息码定义:
消息码 | 描述 |
---|---|
0-18 | 平台预留 |
19 | 查询设备版本 |
20 | 新版本通知 |
21 | 请求升级包 |
22 | 上报升级包下载状态 |
23 | 执行升级 |
24 | 上报升级结果 |
25-127 | 平台预留 |
消息定义
i.查询设备版本
上报任意数据后触发,设备收到 NB查询设备现在版本号的消息码流方向:平台->设备
查询设备版本,例如此时设备端收到此消息:+NNMI:8,FFFE01134C9A0000
按照协议中的规定字段解读:FFFE为起始标识,此字段固定;01为版本号,当前为置为1;13为消息码,十进制即为19,协议中规定了消息码描述;4C9A即为校验码;0000为数据区长度;当设备接收到平台的查询软件版本后,根据协议此时设备上报一条当前版本号消息。方向:设备->平台。
AT+NMGS=25,FFFE0113164700110056322E31300000000000000000000000
协议中规定上报结果码以及当前版本号。按照例子,此时设备端应该回复的消息如下:
FFFE(起始标识)01(版本号)13(消息码)1647(校验码)0011(数据区长度)00(结果码) 56322E31300000000000000000000000(当前版本号, V2.10,规定长度为
byte[16])
字段 | 数据类型 | 描述及要求 |
---|---|---|
无数据区
响应消息:
字段 | 数据类型 | 描述及要求 |
---|---|---|
结果码 | BYTE | 0X00处理成功 |
当前版本号 | BYTE[16] | 当前版本号,由ASCII字符组成,位数不足时,后补“0X00”。 |
正常处理:平台根据版本号判断设备是否需要升级,如果需要,下发请求升级
异常处理:如果响应超时,平台中止升级任务
ii.新版本通知
平台对设备端发起新版本通知,方向:平台->设备。
假设此时在平台上上传的软件包,json 文件中定义的版本号为
V2.16,设备端收到消息码流FFFE011491B0001656322E3136000000000000000000000001F400813836,前面字段的解析方法与之前所介绍一致,01F4
为分片包大小(500 字节),0081 为分片总数, 3836 转换成字符串即为 json 文件中的
versioncheckcode 字段值。设备按照写中规定,返回允许升级:AT+NMGS=
9,FFFE0114D768000100,其中末尾的 0X00 为结果码,表示允许升级。
字段 | 数据类型 | 描述及要求 |
---|---|---|
目的版本号 | BYTE[16] | 目的版本号,由ASCII字符组成,位数不足时,后补“0X00”。 |
升级包分片大小 | WORD | 每个分片的大小 |
升级包分片总数 | WORD | 升级包分片总数 |
升级包校验码 | WORD | 升级包校验码。用户上传升级包时,需要在升级包描述文件里填写校验码 |
应答消息:
字段 | 数据类型 | 描述及要求 |
---|---|---|
结果码 | BYTE | 0X00 允许升级0X01设备使用中0X02信号质量差0X03已经是最新版本0X04电量不足0X05剩余空间不足0X09内存不足0X7F内部异常 |
正常处理:如果设备不允许升级,平台中止升级任务
异常处理:如果响应超时,而且没收到请求升级包消息,平台中止升级任务
iii.请求升级包
设备向平台一次请求升级的分片包,方向:设备->平台。
此时设备请求的第一个分片包,起始位0,发送码流:
FFFE0115A989001256322E313600000000000000000000000000,起始标识“FFFE”,版本号“01”,消息码为“15”对应10进制为21即为请求升级包,“A989”为CRC校验码,“0012”数据区长度,对应后面所加的数据为18个字节即为,其中的前16个字节为版本号V2.16,最后两字节“0000”为设备请求的第一个分片。
平台收到设备第一个分片包的请求后,回复的响应消息中携带结果吗、分片序号、分片数据,此例子中的第一个分片数据大小应该是byte[500]。则此时平台回复的消息样式应该为“FFFE0115xxxx01F7000000byte[500]”。码流中的xxxx为crc校验码,01F7为503字节的数据区长度,00是处理成功的结果码,0000是第一个分片包序号,byte[500]为具体的分片包数据。之后的升级包请求按照升级包序号进一步操作即可。
字段 | 数据类型 | 描述及要求 |
---|---|---|
目的版本号 | BYTE[16] | 目的版本号,由ASCII字符组成,位数不足时,后补“0X00”。 |
分片序号 | WORD | 表示请求获取的分片序号,从0开始计算。设备可以保存已经收到的分片,下次直接从缺失的分片开始请求,达到断点续传的效果。 |
响应消息:
字段 | 数据类型 | 描述及要求 |
---|---|---|
结果码 | BYTE | 0X00处理成功0X80升级任务不存在0X81指定的分片不存在 |
分片序号 | WORD | 表示返回的分片序号 |
分片数据 | BYTE[n] | 分片的内容,n为实际的分片大小。如果错误码不为0,则不带此字段 |
iv.上报升级包下载状态
当设备请求完分片包后,按照协议设备需上传一条消息码流查询设备的升级包下载状态,让平台知道升级包是否已经都下载完成。方向:设备->平台。
按照正确的升级流程,升级包下载后,设备在数据区回复“00”通知平台升级包下载成功,此时发的消息码流为:“FFFE0116850e000100”。平台也也回复相应的数据码流通知设备处理成功,之后步骤v中设备会进行升级。
字段 | 数据类型 | 描述及要求 |
---|---|---|
下载状态 | BYTE | 0X00下载成功0X05剩余空间不足0X06下载超时0X07升级包校验失败0X08升级包类型不支持 |
响应消息:
字段 | 数据类型 | 描述及要求 |
---|---|---|
结果码 | BYTE | 0X00处理成功0X80升级任务不存在 |
v.执行升级
设备上报完升级包下载状态后,平台通知设备执行升级。方向:平台->设备。
下发码流“FFFE0117CF900000”通知设备执行升级操作。设备收到后回复“FFFE0117B725000100”。
字段 | 数据类型 | 描述及要求 |
---|---|---|
无数据区
响应消息:
字段 | 数据类型 | 描述及要求 |
---|---|---|
结果码 | BYTE | 0X00处理成功0X01设备使用中0X04电量不足0X05剩余空间不足0X09内存不足 |
vi.上报升级结果
设备完成升级后,上报升级的结果和当前版本号告知平台,平台上更新设备当前所使用的软件版本。方向:设备->平台
按照此用例中,设备升级的目的版本号为V2.16,协议中规定了消息区中升级完后当前版本号的数据类型与长度。方向:设备->平台设备上报消息码流为:“FFFE0118AD2600110056322E31360000000000000000000000”告知平台升级成功,当前版本号已经更新为
V2.16。平台收到后会回复一个应答消息,数据区为空,回复码流:“FFFE01182AD50000”
字段 | 数据类型 | 描述及要求 |
---|---|---|
结果码 | BYTE | 0X00升级成功0X01设备使用中0X04电量不足0X05剩余空间不足0X09内存不足0X0A安装升级包失败0X7F内部异常 |
当前版本号 | BYTE[16] | 设备当前版本号 |
响应消息:
字段 | 数据类型 | 描述及要求 |
---|---|---|
结果码 | BYTE | 0X00处理成功0X80升级任务不存在 |
附录
i.结果码定义
结果码 | 描述 | 备注 |
---|---|---|
0X00 | 处理成功 | |
0X01 | 设备使用中 | |
0X02 | 信号质量差 | |
0X03 | 已经是最新版本 | |
0X04 | 电量不足 | |
0X05 | 剩余空间不足 | |
0X06 | 下载超时 | |
0X07 | 升级包校验失败 | |
0X08 | 升级包类型不支持 | |
0X09 | 内存不足 | |
0X0A | 安装升级包失败 | |
0X0B-0X7E | 预留错误码 | |
0X7F | 内部异常 | 表示无法识别的异常 |
0X80 | 升级任务不存在 | 0X80开始的为平台返回的错误码 |
0X81 | 指定的分片不存在 |
ii.升级流程
iii.断点续传
iv.PCP协议消息的识别
由于PCP协议消息和设备业务消息共用一个端口和URL通讯,平台收到设备的消息时,按照如下步骤判断是PCP协议消息还是业务消息:
1、检查设备是否支持软件升级(根据设备profile的omCapability.upgradeCapability定义),如果不支持,则认为是业务消息
2、检查设备软件升级协议是否是PCP,如果不是,则认为是业务消息
3、检查消息前两个字节是否为0XFFFE,如果不是,则认为是业务消息
4、检查版本号是否合法,如果不合法,则认为是业务消息
5、检查消息码是否合法,如果不合法,则认为是业务消息
6、检查校验码是否正确,如果不正确,则认为是业务消息
7、检查数据区长度是否正确,如果不正确,则认为是业务消息
8、如果以上检查都通过,认为是PCP协议消息
对设备的要求:需要设备保证业务消息的起始字节不是0XFFFE。
v.CRC16算法
【软件升级】CCITT标准CRC16(1021)算法Java代码:
public class CRC16Util { private static int crc16_ccitt_table[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; /** * * @param reg_init CRC校验时初值 * @param message 校验值 * @return */ public static int do_crc(int reg_init, byte[] message) { int crc_reg = reg_init; for (int i = 0; i < message.length; i++) { crc_reg = (crc_reg > > 8) ^ crc16_ccitt_table[(crc_reg ^ message[i]) & 0xff]; } return crc_reg; } /** * 根据数据生成CRC校验码 * * @param message * byte数据 * * @return int 返校验码 */ public static int do_crc(byte[] message) { // 计算CRC校验时初值从0x0000开始。 int crc_reg = 0x0000; return do_crc(crc_reg, message); } /** * db44检验方法 * * @param message 消息内容 * @param crc 检验码值 * @return */ private static boolean do_crc(byte[] message,byte[] crc) { // 计算CRC校验时初值从0x0000开始。 int crc_reg = 0x0000; int crc_value = (crc[0] & 0xff)*256+(crc[1] & 0xff); return crc_value ==do_crc(crc_reg, message); } /** * 供db44结构代码使用,数组后两位为CRC-校验码值 * @param messages * @return */ public static boolean do_crc_db44(byte[] messages) { // 计算CRC校验时初值从0x0000开始。 byte[] messageArray = new byte[messages.length-2]; byte[] crcArray = new byte[2]; System.arraycopy(messages, 0, messageArray, 0, messageArray.length); System.arraycopy(messages, messages.length-2, crcArray, 0, 2); return do_crc(messageArray, crcArray); } public static void main(String[] args) { //以FFFE011300000000为例 byte[] bytes = new byte[]{(byte)0xFF,(byte)0xFE,0x01,0x13,0x00,0x00,0x00,0x00}; ByteBuffer clone = ByteBuffer.wrap(bytes); //需要把校验码的值置0再进行计算 //clone.put(4, (byte)0).put(5, (byte)0); int calcChecksum = CRC16Util.do_crc(clone.array()); System.out.println("calcChecksum:"+Integer.toHexString(calcChecksum)); } }复制
例如使用“FFFE011300000000”计算出来的校验码是4C9A,把校验码替换到软件升级码流规定的位置得到:FFFE011300000000->FFFE01134C9A0000,这个就是平台下发的查询设备软件版本的码流了
【软件升级】CCITT标准CRC16(1021)算法C语言代码:
#include < stdio.h > typedef unsigned char byte; const unsigned short crc16_table[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; int do_crc(int reg_init, byte* data, int length) { int cnt; int crc_reg = reg_init; for (cnt = 0; cnt < length; cnt++) { crc_reg = (crc_reg > > 8) ^ crc16_table[(crc_reg ^ *(data++)) & 0xFF]; } return crc_reg; } int main() { // FFFE011300000000用byte数组表示: byte message[8] = {0xFF,0xFE,0x01,0x13,0x00,0x00,0x00,0x00}; // 计算CRC校验时初值从0x0000开始 int a = do_crc(0x0000, message,sizeof(message)); printf("a == > %x\n", a); }复制
例如使用“FFFE011300000000”计算出来的校验码是4C9A,把校验码替换到软件升级码流规定的位置得到:FFFE011300000000->FFFE01134C9A0000,这个就是平台下发的查询设备软件版本的码流了