Dialog OTA方案梳理以及小包传输改造

        Dialog 14585 OTA采用双备份方案,对外接口以一个单独的Service(0xF5FE,不使用他们的手机app进行OTA的话,也可以改为自己定义的UUID)嵌入到用户的应用中。

        如下图中,整个OTA方案包含一个ProductHeader和2个Image,每个image中又有一个ImageHeader,用来存储image信息,其中ProductHeader默认存储在0x38000这个地址,可以根据需求修改。

Dialog OTA方案梳理以及小包传输改造_第1张图片

Dialog OTA方案梳理以及小包传输改造_第2张图片

        固件用来处理ota的代码主要包括四个文件,suotar.c、suotar_task.c、app_suotar.c以及app_suotar_task.c。其中suotar.c中定义了ota的Service和attributes,可以在这里修改OTA的UUID,suotar_task.c处理各种read/write/notify 的Handler,手机进行OTA操作的时候,协议栈会将消息转发到这里,经过初步处理后转发到用户app层:app_suotar.c和app_suotar_task.c。

        这里如果直接采用官方的方案,可以不用修改任何代码,直接添加OTA服务即可,手机端也提供了开源的示例代码,照着写一套就完事儿了。这里对照固件端和移动端的官方代码梳理一下流程,大约是这样的:

固件端提供如下的OTA Service和attributes:
SPOTA_SERVICE:      //service,OTA主服务
SPOTA_SERV_STATUS:  //status notification,用于更新OTA的状态
SPOTA_GPIO_MAP:    //gpio设置(gpio应该由固件端定义,不需要修改,我将这个移除了)
SPOTA_MEM_DEV :     //memory information,定义数据存放到哪里(RAM/EEPROM/FLASH),一般都是flash
SPOTA_PATCH_LEN:    //每个数据包的长度,长度未发生变更的时候不需要重复写
SPOTA_PATCH_DATA:   //数据包

1.修改 MTU  23-->247                                  //ios手机某些型号最大只能将mtu改到186
  BluetoothGattCharacteristic.requestMtu(patchDataSize + 3);//patchDataSize 即为一包的长度	

2.打开notification :SPOTA_SERV_STATUS

3.写MEM_DEV :(value:0x13000000) 开始ota  //目前绝大多数应用都是使用flash方案,可以将这个值默认为0x13000000,这样移动端进行OTA的时候就可以不写这个了

4.分割patch 包,计算crc,crc的值附加到img的最后一个字节  	//patch包:(sizeof(img)+1),这个值可以修改,适配不同手机的时候可能需要设置为别的值,相应的,下一步的patchLen 也需要修改,注意最后一个包长度可能更短;
crc:
	byte crc_code = 0;
        for (int i = 0; i < this.bytesAvailable; i++) {
            Byte byteValue = this.bytes[i];
            int intVal = byteValue.intValue();
            crc_code ^= intVal;
        }

5.设置patch Len  =244,表示传输的 patchData长度,

6.上传 patch Data
...

7.上传最后一个包之前,如果长度发生变化,需要重新写patchLen
 比如设置patch Len=132
上传最后一个 patch Data.

8.所有的数据传输完成,MEM_DEV:写 end Signal  0xfe000000

9.重启设备,MEM_DEV:写reboot Signal   0xfd000000

        为了减少交互的流程,这里可以将一些常用的东西直接初始化一个默认值:

#if (SUOTA_DEF_CONFIG)
	memset(&suota_state,0x00,sizeof(suota_state));
	suota_state.mem_dev=0x13;       //设备类型,这里默认为flash
	suota_state.mem_base_add=0x00;	//初始地址 
	suota_state.gpio_map=0x05060300;//设置GPIO map	
#endif

        然后,我们遇到了一个很奇怪又很无奈又很悲伤的问题ε(┬┬﹏┬┬)3:每个Image包,开头64个字节是ImageHeader,Dialog的官方代码直接默认移动端OTA的时候,第一包就会将这个Header完整的传输完,如果第一包的长度小于64,就会发生错误!但是他们用于OTA示例的app又可以设置一个很低的patchDataSize,多少都可以...

        这个也没什么,一次传输的字节数越多,总消耗的时间就越少。但是问题来了:米家APP插件的SDK为了兼容不同的手机型号,不提供修改蓝牙MTU的api,满地打滚都不给做这个... ios手机不修改MTU也可以直接传那么多数据,android机如果不修改MTU的话,就只能传20个字节...

        然后固件开始改造OTA的服务来兼容数据包长度小于64个字节的的情况...

        几个关键的函数:

 static int suotar_patch_data_ind_handler(ke_msg_id_t const msgid,
                                          struct suotar_patch_data_ind const *param,
                                          ke_task_id_t const dest_id,
                                          ke_task_id_t const src_id);
void app_suotar_img_hdlr(void);
int app_read_image_headers(uint8_t image_bank, uint8_t* data, uint32_t data_len);

        这里如果数据包的长度小于64字节,那么ImageHeader只能分好几包来传,所以在原有的标记status的结构体上新增了个属性 flag_img_header_is_read 来标记是否已经完整读取了 image header:

// Holds the retainable variables of SUOTAR app
typedef struct
{
    uint8_t     mem_dev;
    uint32_t    mem_base_add;
    uint32_t    gpio_map;
    uint32_t    new_patch_len;
    uint8_t     suota_pd_idx;
    uint8_t     suota_image_bank;
    uint32_t    suota_block_idx;
    uint32_t    suota_img_idx;
    bool        flag_img_header_is_read;//新增一个属性来标记 image header是否已经完整读取了
    uint8_t     crc_calc;
    uint32_t    suota_image_len;
    void (*status_ind_func) (const uint8_t);
    uint8_t     reboot_requested;
}app_suota_state;

        开始OTA之前,需要清除掉这个标记,可以在函数app_suotar_start 中做这个。

        处理ImageHeader使其兼容:        

int app_read_image_headers(uint8_t image_bank, uint8_t* data, uint32_t data_len)
{
    image_header_t *pImageHeader;
    product_header_t *pProductHeader;
    image_header_t *pfwHeader;
    uint32_t codesize;
    int32_t ret;
    uint8_t mem_data_buff[64];
    uint32_t imageposition1;
    uint32_t imageposition2;
    uint8_t new_bank = ANY_IMAGE_BANK;
    uint8_t is_invalid_image1 = IMAGE_HEADER_OK;
    uint8_t is_invalid_image2 = IMAGE_HEADER_OK;
    uint8_t imageid  = IMAGE_ID_0;
    uint8_t imageid1 = IMAGE_ID_0;
    uint8_t imageid2 = IMAGE_ID_0;

//这里需要等到读取够64个字节之后再处理imageheader.

//suota_state.img_idx_at_img_header_is_read+=data_len;
	
//    if( data_len < sizeof(image_header_t) )
//    {
//        // block size should be at least image header size
//        return SUOTAR_INVAL_IMG_HDR;
//    }
//    else
//    {
//        // read header form first data block
//        pfwHeader = (image_header_t*)data;
//    }

	if(suota_state.suota_block_idx

        app_suotar_img_hdlr函数,校验image block并将其写入到flash中,这里移除掉了CRC的校验:

void app_suotar_img_hdlr(void)
{
	//其他逻辑
    ......

    //校验CRC,这里由于没有将suota_block_idx清零,将其移到另一个入口函数中了
    // Update CRC
    //for(i=0;i= ( suota_state.suota_img_idx + suota_state.suota_block_idx ))
                {
                    if (suota_state.suota_image_len < (suota_state.suota_img_idx + suota_state.suota_block_idx))
                        suota_state.suota_block_idx = suota_state.suota_image_len - suota_state.suota_img_idx;
					
                    ret = app_flash_write_data (suota_all_pd, (suota_state.mem_base_add + suota_state.suota_img_idx), suota_state.suota_block_idx);
                    					
					if( ret !=  suota_state.suota_block_idx){
                        status = SUOTAR_EXT_MEM_WRITE_ERR;
                    }
                    else
                    {
                        // Update block index
                        suota_state.suota_img_idx += suota_state.suota_block_idx;						
                    }
                }
                else
                {
                    status = SUOTAR_EXT_MEM_WRITE_ERR;
                }
            } 
            //只有在读取了image header之后才将index清零           
			//suota_state.suota_block_idx = 0;
            if(suota_state.flag_img_header_is_read){suota_state.suota_block_idx = 0;}
            mem_info = suota_state.suota_img_idx;
            suotar_send_mem_info_update_req(mem_info);
#else
            status = SUOTAR_INVAL_MEM_TYPE;
#endif //(!SUOTAR_SPI_DISABLE)
            break;

        default:
            status = SUOTAR_INVAL_MEM_TYPE;
            break;
    }

    // SUOTA finished successfully. Send Indication to initiator
    suotar_send_status_update_req((uint8_t) status);
}

         suotar_patch_data_ind_handler函数,这个是具体处理数据包的函数,主要是补充上在app_suotar_img_hdlr函数中移除掉的CRC校验:

static int suotar_patch_data_ind_handler(ke_msg_id_t const msgid,
                                         struct suotar_patch_data_ind const *param,
                                         ke_task_id_t const dest_id,
                                         ke_task_id_t const src_id)
{
    if( suota_state.mem_dev < SUOTAR_MEM_INVAL_DEV )
    {
        if (param->char_code)
        {
            if( suota_state.new_patch_len )
            {
                if( SUOTAR_IS_FOR_IMAGE( suota_state.mem_dev ) )
                {
                    //---------------------------- Handle SUOTAR image payload -----------------------
                    if( (suota_state.suota_block_idx + param->len) <= SUOTA_OVERALL_PD_SIZE)
                    {
                        memcpy(&suota_all_pd[suota_state.suota_block_idx], param->pd, param->len );
						suota_state.suota_block_idx += param->len;
						//suota_block_idx will reset to 0,then this check converm that new_patch_len equals param->len.
						//if( suota_state.new_patch_len == suota_state.suota_block_idx )
                        if( suota_state.new_patch_len == param->len )
                        {	
                            //改为在这里校验CRC	
							// Update CRC
							uint32_t     i;
							for(i=0;ilen;i++){
								suota_state.crc_calc ^= param->pd[i];
							}
					
                            app_suotar_img_hdlr();							
                        }
                        //只有读取完image header后才做这个检查 
						//if( suota_state.suota_block_idx > suota_state.new_patch_len  )
                        if( suota_state.flag_img_header_is_read&&suota_state.suota_block_idx > suota_state.new_patch_len  )
                        {
                            // Received patch len not equal to PATCH_LEN char value
                            suotar_send_status_update_req((uint8_t) SUOTAR_PATCH_LEN_ERR);
                        }
                    }
                    else
                    {
                        suotar_send_status_update_req((uint8_t) SUOTAR_INT_MEM_ERR);
                    }
                }
                else
                {
                    suotar_send_status_update_req((uint8_t) SUOTAR_INVAL_MEM_TYPE);
                }
            }
            else
            {
                // Inavild PATCH_LEN char value
                suotar_send_status_update_req((uint8_t) SUOTAR_PATCH_LEN_ERR);
            }
        }
    }
    else
    {
        suotar_send_status_update_req((uint8_t) SUOTAR_INVAL_MEM_TYPE);
    }

    return (KE_MSG_CONSUMED);
}

       如此,一个兼容20个字节的小包OTA方案就修改完成了。

你可能感兴趣的:(物联网,#,BLE)