NB-IOT远程升级第3弹:移植代码分析

在物联网项目的开发过程中,必不可少的一项功能就是远程升级OTA(Over-the-Air),即使用WIFI、蓝牙、4G、NB-IOT等方式将升级包传输到MCU,MCU进行代码存储,完成升级

本系列文章将介绍基于电信AEP平台进行NB-IOT设备的远程升级,包含stm32内部flash分区、BootLoader代码编写,平台软件升级包制作,平台软件升级协议对接及参考源码等内容,后续几篇文章将陆续介绍

该系列文章目录大纲如下:

image

在前面两篇文章:NBIOT远程升级第1弹:BootLoader编写及软件包制作 中,介绍了BootLoader编写的几个要点及电信AEP平台软件包的制作

NB-IOT远程升级第2弹:软件升级协议、流程 介绍电信AEP平台远程升级使用的PCP协议,以及使用串口助手模拟远程升级流程,为后面敲代码做准备。

这一节通过分析一个开源的FOTA代码,进一步加深对PCP协议及平台远程升级流程的理解,方面大家自己做移植

1、源码介绍

基于小熊派开发板的ota远程升级代码案例,运行硬件环境如下所示:

image
image

结合参考案例代码与上一节 NB-IOT远程升级第2弹:软件升级协议、流程 的内容联合起来看,会达到事半功倍的效果,你会发现远程升级就那么回事,也没想象中的那么难。

2、源码分析

源码也挺简单的,大概介绍下主要流程的函数

2.1 接收数据解析

接收到电信AEP平台下发的数据,对数据进行解析,判断是不是PCP协议,是否是远程升级的相关命令。

可解析出起始标识位、版本号、消息码、校验码、数据区长度和数据区。

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */

int32_t sota_parse(const int8_t *in_buf, int32_t in_len, int8_t * out_buf, int32_t out_len) {
ota_pcp_head_s *phead;
char *databuf;
char *rlen;
int buflen;
int ret,cmd_crc_num;
char *buf;

if (in_buf == NULL || in_len < (sizeof(ota_pcp_head_s) - sizeof(WORD)) || out_buf == NULL)
{
    SOTA_LOG("in_buf:%p len:%d, out_buf:%p",in_buf,(int)in_len, out_buf);
    goto END;
}

rlen = strstr((const char*)in_buf,":");/*lint !e158*/
if (rlen == NULL)/*lint !e158*/
{
    SOTA_LOG("buflen invalid");
    goto END;
}
buflen = chartoint(rlen+1);
if (out_len < buflen)
{
    SOTA_LOG("out buf not enough");
    goto END; 
}

buflen = buflen * 2;
databuf = strstr(rlen,",");
if (databuf == NULL)
{
    SOTA_LOG("buf invalid");
    goto END;
}
buf = databuf + 1;

memset(out_buf, 0, out_len);
HexStrToByte((const unsigned char *)buf, (unsigned char *)out_buf, buflen);
phead = (ota_pcp_head_s *)out_buf;

cmd_crc_num = htons_ota(phead->chk_code);
phead->chk_code = 0;
ret = crc_check((const unsigned char *)out_buf, buflen/2);
phead->ori_id = htons_ota(phead->ori_id);
if (phead->data_len > BLOCK_HEAD && phead->msg_code == MSG_GET_BLOCK)
{
    phead->data_len = htons_ota(phead->data_len) - BLOCK_HEAD;
}
if (phead->ori_id != PCP_HEAD || (ret != cmd_crc_num) || \
        (phead->msg_code < MSG_GET_VER || phead->msg_code > MSG_NOTIFY_STATE))
{
    SOTA_LOG("head wrong! head magic:%X msg_code:%X ver_num:%X ret:%X crc:%X",
        phead->ori_id,phead->msg_code,phead->ver_num, ret, cmd_crc_num);
    goto END;
}

return SOTA_OK;

END:
return SOTA_FAILED;
}` </pre>

2.2 升级流程状态机

远程升级的流程状态机,根据平台下发命令的消息码作为状态标志

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */

int32_t sota_process(void *arg, const int8_t *buf, int32_t buflen) {
char sbuf[64] = {0};
const uint8_t *pbuf = NULL;
int ret = SOTA_OK;
ota_pcp_head_s *phead;
unsigned char msg_code;

phead =(ota_pcp_head_s *)buf;
msg_code = phead->msg_code;

if (phead->data_len > 0)
{
    pbuf = (uint8_t *)buf + VER_LEN/2;
}

SOTA_LOG("process sota msg %d", msg_code);

switch (msg_code)
{
    case MSG_GET_VER:
    { 
        char ver_ret[VER_LEN + 1] = {0};
        (void)g_flash_op.get_ver(ver_ret+1, VER_LEN);
        (void)ver_to_hex(ver_ret, (VER_LEN + 1), (char *)sbuf);
        (void)sota_at_send(MSG_GET_VER, (char *)sbuf, (VER_LEN + 1) * 2);
        ret = SOTA_OK;
        break;
    }
    case MSG_NOTIFY_NEW_VER:
    {
        if (phead->data_len > sizeof(ota_ver_notify_t))
        {
           ret = sota_new_ver_process(phead, pbuf);
        }
        else
        {
            ret = SOTA_INVALID_PACKET;
        }
        break;
    }  
    case MSG_GET_BLOCK:
    {
        if (phead->data_len > 0)
        {
           ret = sota_data_block_process(phead, pbuf);
        }
        else
        {
            ret = SOTA_INVALID_PACKET;
        }
        break;
    }
    case MSG_EXC_UPDATE:
    {
         ret = sota_update_exc_process(phead, pbuf);
         break;
    }
    default:
    {
        SOTA_LOG("Rx invalid packet");
        ret = SOTA_INVALID_PACKET;
        break;
    }
}
return ret;

}` </pre>

2.3 设备应答

设备往平台发送应答消息的接口函数

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */

static void sota_send_response_code(msg_code_e msg_code, response_code_e code) {
char ret_buf[1];
char sbuf[2];

ret_buf[0] = code;
(void)ver_to_hex(ret_buf, 1, (char *)sbuf);
(void)sota_at_send(msg_code, (char *)sbuf, 2);

}` </pre>

2.3 设备发送数据

设备往平台发送数据的接口函数

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */

static int sota_at_send(msg_code_e msg_code, char *buf, int len)
{
uint32_t ret;
char crcretbuf[5] = {0};
char tmpbuf[SEND_BUF_LEN + VER_LEN] = {0};
ota_pcp_head_s pcp_head = {0};
unsigned char atwbuf[SEND_BUF_LEN + VER_LEN] = {0};
unsigned char hbuf[64] = {0};
if (len >= SEND_BUF_LEN)
{
SOTA_LOG("payload too long");
return SOTA_FAILED;
}
pcp_head.ori_id = htons_ota(PCP_HEAD);
pcp_head.ver_num = 1;
pcp_head.msg_code = msg_code;
pcp_head.data_len = htons_ota(len / 2);
(void)ver_to_hex((const char *)&pcp_head, sizeof(ota_pcp_head_s), (char *)hbuf);

memcpy(atwbuf, hbuf, VER_LEN);
memcpy(atwbuf + VER_LEN, buf, len);

HexStrToByte(atwbuf, (unsigned char*)tmpbuf, len + VER_LEN); //strlen(atwbuf)
ret = (uint32_t)crc_check((unsigned char*)tmpbuf, (len + VER_LEN) / 2);
(void)snprintf(crcretbuf, sizeof(crcretbuf), "%04X", (unsigned int)ret);

memcpy(atwbuf + 8, crcretbuf, 4);
return g_flash_op.sota_send((char *)atwbuf, len + VER_LEN);

}` </pre>

2.4 新版本通知

设备收到下载新版本软件包通知后,设备向物联网平台返回应答消息,是否允许设备进行升级。

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */

static int32_t sota_new_ver_process(const ota_pcp_head_s *head, const uint8_t *pbuf) {
char ver[VER_LEN];
ota_ver_notify_t *notify = (ota_ver_notify_t *)pbuf;

(void)g_flash_op.get_ver(ver, VER_LEN);
if (strncmp(ver, (const char*)notify->ver, VER_LEN) == 0)
{
    SOTA_LOG("Already latest version %s", notify->ver);
    sota_send_response_code(MSG_NOTIFY_NEW_VER, DEV_LATEST_VER);
    g_at_update_record.state = IDLE;
    return SOTA_OK;
}

SOTA_LOG("Notify ver %s,%x, record ver:%s,%x", notify->ver, notify->ver_chk_code, 
    g_at_update_record.ver,g_at_update_record.ver_chk_code);
if ((strncmp(g_at_update_record.ver, (const char *)notify->ver, VER_LEN) == 0)
    && (notify->ver_chk_code == g_at_update_record.ver_chk_code))
{
    SOTA_LOG("state %d, downloaded %d blocks", g_at_update_record.state, g_at_update_record.block_num);
    if (g_at_update_record.block_num < g_at_update_record.block_totalnum
        && g_at_update_record.state == DOWNLOADING)
    {
        sota_send_request_block((char*)notify->ver);
        return SOTA_DOWNLOADING;
    }
    else if (g_at_update_record.block_num == g_at_update_record.block_totalnum
        && g_at_update_record.state == UPDATING)
    {
        sota_send_response_code(MSG_DOWNLOAD_STATE, DEV_OK);
        return SOTA_UPDATING;
    }
    else if (g_at_update_record.block_num == g_at_update_record.block_totalnum
        && g_at_update_record.state == UPDATED)
    {
        return SOTA_UPDATED;
    }
}

sota_reset_record_info(notify);
sota_send_request_block((char*)notify->ver);
return SOTA_DOWNLOADING;

}` </pre>

2.5 请求分片包

设备向物联网平台请求下载软件包函数

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */

static int32_t sota_data_block_process(const ota_pcp_head_s *head, const uint8_t *pbuf) {
uint16_t block_seq = 0;
int ret = SOTA_OK;

if (g_at_update_record.state != DOWNLOADING)
{
   return SOTA_UNEXPECT_PACKET;
}

if (*pbuf == UPDATE_TASK_EXIT)
{
    g_at_update_record.state = IDLE;
    return SOTA_EXIT;
}

block_seq = ((*(pbuf + 1) << 8) & 0XFF00) | (*(pbuf + 2) & 0XFF);
if (g_at_update_record.block_num != block_seq)
{
    SOTA_LOG("Download wrong,we need block %X, but rx %X:",(int)g_at_update_record.block_num, (int)block_seq);
    return SOTA_UNEXPECT_PACKET;
}
SOTA_LOG("off:%lx size:%x ",g_at_update_record.block_offset,head->data_len);
ret = g_storage_device->write_software(g_storage_device, g_at_update_record.block_offset,(const uint8_t *)(pbuf + BLOCK_HEAD), head->data_len);
if (ret != SOTA_OK)
{
    SOTA_LOG("write software failed. ret:%d", ret);
    sota_send_response_code(MSG_DOWNLOAD_STATE, DEV_NO_SPACE);
    return SOTA_WRITE_FLASH_FAILED;
}

g_at_update_record.block_offset += g_at_update_record.block_size;
g_at_update_record.block_tolen += head->data_len;
g_at_update_record.block_num++;

if ((g_at_update_record.block_num) < g_at_update_record.block_totalnum)
{
 SOTA_LOG("Rx total %d bytes downloading\r\n", g_at_update_record.block_tolen);
 sota_send_request_block(g_at_update_record.ver);
    return SOTA_DOWNLOADING;
} 
else
{ 
    SOTA_LOG("Rx total %d bytes, UPDATING...\r\n", g_at_update_record.block_tolen);
    ret = g_storage_device->write_software_end(g_storage_device, PACK_DOWNLOAD_OK, g_at_update_record.block_tolen);
    if (ret != SOTA_OK)
    {
        SOTA_LOG("write software end ret:%d", ret);
        sota_send_response_code(MSG_DOWNLOAD_STATE, FIRMWARE_CHECK_ERROR);
        return SOTA_WRITE_FLASH_FAILED;
    }
    else
    {
        g_at_update_record.state = UPDATING;
        sota_send_response_code(MSG_DOWNLOAD_STATE, DEV_OK);
        return SOTA_UPDATING;
    }
} 

}` </pre>

2.6 执行升级

设备收到物联网平台下发的执行升级消息后,将对收到消息后的执行动作进行应答

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */
static int32_t sota_update_exc_process(const ota_pcp_head_s *head, const uint8_t *pbuf) {
int ret = SOTA_OK;

SOTA_LOG("Begin excute update");
if (g_at_update_record.state != UPDATING)
{
    return SOTA_UNEXPECT_PACKET;
}

ret = g_storage_device->active_software(g_storage_device);
if (ret != SOTA_OK)
{
    SOTA_LOG("Active software failed ret:%d.", ret);
    sota_send_response_code(MSG_EXC_UPDATE, DEV_INNER_ERROR);
    return SOTA_WRITE_FLASH_FAILED;
}
else
{
    g_at_update_record.state = UPDATED;
   (void)flag_write(FLAG_APP, (void*)&g_at_update_record, sizeof(sota_update_info_t));
    sota_send_response_code(MSG_EXC_UPDATE, DEV_OK);
    return SOTA_UPDATED;
}

}` </pre>

2.7 上报升级结果

设备向物联网平台上报升级结果

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`/* 公众号:轻松学长 */
static int sota_status_check(void) {
upgrade_state_e state;
char sbuf[64] = {0};
char tmpbuf[VER_LEN+1] = {0};

memset(&g_at_update_record, 0, sizeof(sota_update_info_t));
if (flag_read(FLAG_APP, (char*)&g_at_update_record, sizeof(sota_update_info_t)))
{
    SOTA_LOG("flag read err");
    return SOTA_FAILED;
}
SOTA_LOG("state:%d flash ver:%s",g_at_update_record.state, g_at_update_record.ver);

if (g_flash_op.firmware_download_stage == BOOTLOADER
    && g_flash_op.current_run_stage == BOOTLOADER)
{
    if (g_at_update_record.state == DOWNLOADING)
    {
        sota_send_request_block(g_at_update_record.ver);
        return SOTA_DOWNLOADING;
    }
}
else
{
 (void)flag_upgrade_get_result(&state);
    SOTA_LOG("upgrade result: %d", state);
    if (state == OTA_SUCCEED)
    {
        SOTA_LOG("Update version %s success", g_at_update_record.ver);
        memcpy(tmpbuf + 1, g_at_update_record.ver, VER_LEN);
        (void)ver_to_hex(tmpbuf, VER_LEN+1, sbuf);
        (void)sota_at_send(MSG_NOTIFY_STATE, sbuf, (VER_LEN+1) * 2);
    }
}

memset(&g_at_update_record, 0, sizeof(sota_update_info_t));
(void)flag_write(FLAG_APP, (const void*)&g_at_update_record, sizeof(sota_update_info_t));
return SOTA_OK;

}` </pre>

2.8 超时处理

请求升级包或升级时设备超时处理

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">/* 公众号:轻松学长 */ void sota_timeout_handler(void) { if (g_at_update_record.state == DOWNLOADING) { SOTA_LOG("Download block %d over time", g_at_update_record.block_num); sota_send_response_code(MSG_EXC_UPDATE, DOWNLOAD_TIME_OUT); sota_send_request_block(g_at_update_record.ver); } else if (g_at_update_record.state == UPDATING) { SOTA_LOG("Download finish. excute over time"); sota_send_response_code(MSG_EXC_UPDATE, DOWNLOAD_TIME_OUT); sota_send_response_code(MSG_DOWNLOAD_STATE, DEV_OK); } } </pre>

3、踩坑记录

  • 电信AEP平台流量限制:对于NB-IOT通信设备,平台限制5分钟只允许300条交互,包含设备请求、平台下发内容,设备回复ACK,设备注册,设备订阅等。

  • 电信AEP平台只允许三台设备同时升级,除该三台设备之外其他设备处于排队状态,待该三台设备升级完成后,才开始下一批设备升级

  • 升级包大小2M以内,升级时按软件包中设置值分片,默认500字节

  • 升级包需打包为.zip类型的文件

  • 电信AEP平台目前仅支持LWM2M协议的设备升级

到这里,基于电信AEP平台的NB-IOT远程升级系列就结束啦

若有需要完整源码,工众号回复 OTA 自取哦

我是轻松学长,一个爱折腾的程序袁,工作之余,写写公众号,玩玩视频号,分享我的工作、我的生活

分享是一种博爱的心境,学会分享,就学会了生活

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容