乐鑫Esp32学习之旅28 熟悉自定义分区表 partition,拉取阿里云OSS对象存储的单片机/图片等较大文件保存在特定的存储位置,并读取出来做完整性校验,保证数据的完整性。(附带源码)


  • 本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途。如有不对之处,请留言,本人及时更改。

系列一:ESP32系列模组基础学习系列笔记

1、 爬坑学习新旅程,虚拟机搭建esp32开发环境,打印 “Hellow World”。
2、 巧用eclipes编辑器,官方教程在在Windows下搭建esp32开发环境,打印 “Hellow World”。
3、 认识基本esp32的GPIO接口,开始点亮您的第一盏 LED和中断回调实现按键功能 。
4、体会esp32的强大的定时器功能, 实现定时2s闪烁一盏LED灯。
5、接触实践esp32的pwm宽度脉冲功能, 实现呼吸效果闪烁一盏LED灯。
6、smartConfig和微信airKiss在esp32的实现,一键配网轻松快捷连接路由器。
7、利用GPIO中断做一个按键的短按和长按的回调事件,再也无须担心触发源。
8、esp32上实现本地 UDP 客户端和服务端角色,在局域网内实现通讯。
9、esp32上实现本地 TCP 客户端和服务端角色,可断线重连原路返回数据。
10、乐鑫esp32 SDK编程利用rmt驱动ws2812七彩灯,实现彩虹渐变效果。
11、入门 乐鑫esp-adf 音频框架开发,esp32造一个蓝牙耳机,实现切换歌曲,获取歌曲信息等功能。
12、开源一个微信公众号airkiss配网esp32以及局域网发现功能的工程,分享一个airkiss配网小工具。
13、esp32 内置 dns 服务器,无需外网访问域名返回指定网页。
14、esp32 sdk编程实现门户强制认证,连接esp32热点之后,自动强制弹出指定的登录界面。
15、认识本地离线语音唤醒识别框架 esp-skainet ,实现较低成本的硬件语音本地识别控制。
16、学习本地语音唤醒离线识别框架 esp-skainet ,如何修改唤醒词? 如何自定义命令词?如何做意图动作?
17、全网首发,乐鑫esp32 sdk直连京东微联·小京鱼 · IoT开放平台,实现叮咚音响语音智能控制。
18、入门京东微联·小京鱼的控制面板H5开发,读懂vue语法,做自己的控制页面。
19、重磅开源,如何在微信小程序上ble蓝牙配网esp32,blufi的那些事!
20、一篇好文,开发过程中编译esp32固件太大,无法正常启动?教你如何自定义分区表partitions.csv。
21、 esp32蓝牙配网blufi的高度封装,集成简单、使用简单、容易上手,提高开发效率!
22、讨论下程序员 “青春饭” 那些事,分享在esp32实现多种加密算法md5 |AES CBC-ECB| Sha1 | Sha256 等!
23、安信可 esp32-a1s 音频开发板移植最新 esp-adf 音频框架,小试牛刀如何实现在线文字转语音播放。
25、分享在 esp32 SDK实现冷暖光色温平滑调节的封装,轻松集成到您的项目去。
26、分享下如何在window下使用CMake编译,编译速度提高传统 make 编译一个档次,支持 ESP32 和 ESP32-C3。
27、windows10平台下自带的Linux安装 ESP8266/ESP32 环境,再无需额外安装虚拟机了。
28、熟悉自定义分区表 partition,拉取阿里云对象存储的单片机/图片等较大文件保存在特定的存储位置,并读取出来做完整性校验。

系列二:ESP32-Camera 摄像头开发板系列笔记

1、安信可 ESP32-Cam 摄像头开发板二次开发 C SDK编程,实现MQTT远程拍照传输到私有服务器。
2、安信可 ESP32-Cam 摄像头开发板二次开发 C SDK编程,实现本地视频流监控。
3、安信可 ESP32-Cam 摄像头开发板二次开发 C SDK编程,拍照图片通过有线串口传到上位机PC端。

系列三:ESP32-C3 模组系列笔记

1、【蓝牙Mesh笔记 ①】ESP32-C3 模组上实现天猫精灵蓝牙 BLE Mesh AliGenie 接入,无需WiFi 连接也可以实现天猫精灵语音控制。

系列四:ESP32-S3 模组系列笔记

1、安信可 ESP32-S3 模组上驱动摄像头 OV2640,实现远程拍照并 HTTP 传输到阿里云对象存储OSS,并显示在微信小程序上。。

系列四:ESP32模组系列笔记 LVGL LittlevGL

1、【LittlevGL ESP32 学习笔记 ①】移植最新的 LVGL 到安信可ESP32\C3模组,显示一个二维码。


一、前言

在开发一些模组+单片机MCU项目发现 ,在使用WiFi模组在开发对接MCU时候,MCU(比如STM32)的固件需要OTA升级时候,但是我们商用经常会遇到这样的问题:

1)希望WiFi模组能拉取MCU的固件时候,并且校验此固件的完整性,可对比MD5数值。
2)希望WiFi模组能拉取完整MCU固件后,再读取出去通过UART/SPI形式发送给MCU。

抑或,我在开发项目时候,因某些原因需要更新本地文件,比如HTTPS的域名证书,都是需要把网络上的较大文件保存在特定的存储位置,并且读取出来做完整性校验,进一步使用。

所以,这一期的代码分享,就带给大家了。愿大家平时摁住急功近利的心,成功需要厚积薄发,做技术也是如此。


整体逻辑思维

在这里插入图片描述


二、开发中所需掌握的知识点

1)自定义分区表

既然要保存文件,那么就需要自行处理FLASH里面的分区情况了,我之前的文章也提到这点:

开发过程中编译esp32固件太大,无法正常启动?教你如何自定义分区表partitions.csv。

上述只是针对运行的固件大小说明,那么我们如何自定义存储文件的位置大小呢?先看我定义的分区表:

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs,      data, nvs,     0x9000,  0x4000
otadata,  data, ota,     0xd000,  0x2000
phy_init, data, phy,     0xf000,  0x1000
ota_0,    0,    ota_0,   0x10000, 0xF0000
ota_1,    0,    ota_1,   0x110000,0xF0000
custom_data,  0x40, 0,  ,        1024K, 

主要的分区表说明,见官方文档:https://docs.espressif.com/projects/esp-idf/zh_CN/v4.3.1/esp32/api-guides/partition-tables.html

分区表中的每一列都有名字,类型(app,data或者其他),子类型,在 flash 中的偏移量(分区的加载地址)和对应的大小。通过名字(Name),类型(Type)和子类型(SubType)就可以读写相应的模块。

Name

Name 字段可以是任何有意义的名称,但不能超过 16 个字符(之后的内容将被截断),该字段对 ESP32 并不是特别重要。

Type

Type 字段可以指定为 app (0) 或者 data (1),也可以直接使用数字 0-254(或者十六进制 0x00-0xFE)。注意,0x00-0x3F 是预留给 esp-idf 的核心功能,如果应用程序需要保存数据,需要在 0x40-0xFE 内添加一个自定义分区类型

SubType

SubType 字段长度为 8 bit,内容与具体 Type 有关,具体可以参考 ESP-IDF SubType。

offset & size

分区若为指定偏移地址,则会紧跟着前一个分区之后开始。若此分区为首个分区,则将紧跟着分区表开始。

对于 ESP32, app 分区的偏移地址必须 64K(0x10000) 对齐,对于 ESP8266,app 分区的偏移地址必须要与 0x1000 (4K) 对齐。

app 分区的大小和偏移地址可以采用十进制数、以 0x 为前缀的十六进制数,且支持 K 或 M 的倍数单位(分别代表 1024 和 1024*1024 字节)。


2)代码中读取自定义分区表的内容

具体也可以参考:examples/storage/partition_api/partition_ops 具体的例程,下面我也注释了下。

void app_main(void)
{
    
    
    /*
    * This example uses the partition table from ../partitions_example.csv. For reference, its contents are as follows:
    *
    *  nvs,        data, nvs,      0x9000,  0x6000,
    *  phy_init,   data, phy,      0xf000,  0x1000,
    *  factory,    app,  factory,  0x10000, 1M,
    *  storage,    data, ,             , 0x40000,
    */

    // 在分区表找到自定义map的信息,这里是通过 Type这个字段来获取,当然了,你也可以通过SubType获取。
    const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
    assert(partition != NULL);

    static char store_data[] = "ESP-IDF Partition Operations Example (Read, Erase, Write)";
    static char read_data[sizeof(store_data)];

    // 先擦除 分区
    memset(read_data, 0xFF, sizeof(read_data));
    ESP_ERROR_CHECK(esp_partition_erase_range(partition, 0, partition->size));

    // 开始写入数据
    ESP_ERROR_CHECK(esp_partition_write(partition, 0, store_data, sizeof(store_data)));
    ESP_LOGI(TAG, "Written data: %s", store_data);

    // 读取数据,同时检查对比和写入数据的完整性
    ESP_ERROR_CHECK(esp_partition_read(partition, 0, read_data, sizeof(read_data)));
    assert(memcmp(store_data, read_data, sizeof(read_data)) == 0);
    ESP_LOGI(TAG, "Read data: %s", read_data);
    
    ESP_LOGI(TAG, "Example end");
}


3)数据完整性校验?

在提出这个问题时候,我们必须知道这个从flash的数据和谁对比?我们在通过HTTP请求时,服务器会在 header 返回关于下载文件的完整性数据,比如MD5、Sha256等,以阿里云OSS对象存储为例,是返回这个文件的MD5数值的。

所以,我们本地下载了文件之后做一个MD5数值,然后对比阿里云返回的文件MD5数值,这样就校验了数据的完整性了。另外还可以对比数据的长度。

下面是请求阿里云OSS对象存储的文件的Header内容(不懂HTTP协议的自行百度),可看到有 Content-MD5:这个字段,我们拿到了这个之后,Base64解码就是二进制的MD5数值了,以及Content-Length字段。

Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 247817
Content-MD5: BQUk+5KwFdJgPXLrGuQWtQ==
Content-Type: image/png
Date: Sun, 13 Mar 2022 01:37:53 GMT
ETag: "050524FB92B015D2603D72EB1AE416B5"
Last-Modified: Wed, 04 Mar 2020 14:20:02 GMT
Server: AliyunOSS
x-oss-hash-crc64ecma: 16969763075689951783
x-oss-object-type: Normal
x-oss-request-id: 622D4AF1DDEEC0303061A889
x-oss-server-time: 6
x-oss-storage-class: Standard

<contents>

三、代码走读

这里,只分享关键的代码块,不做整个代码的走读,源码获取方法在文章底部。

3.1 获取阿里云的文件头部信息

获取了阿里云的文件头部信息之后, 主要针对的是 Content-MD5Content-Length
这2个字段的数值。

  case HTTP_EVENT_ON_HEADER:
        ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
        //解析到了Header的Content-MD5数值
        if (strcmp(evt->header_key, "Content-MD5") == 0)
        {
    
    
            ESP_LOGI(TAG, "HTTP Get Header MD5, key=%s, value=%s", evt->header_key, evt->header_value);
            size_t len;
            int dst_buf_size = 100;
            p_header_md5_value = malloc(dst_buf_size);
            // 获取了之后, base64解码这个数值,就得到了具体的MD5数值了
            int result = mbedtls_base64_decode(p_header_md5_value, dst_buf_size, &len, (unsigned char *)(evt->header_value), strlen((char *)(evt->header_value)));
            if (result != 0)
            {
    
    
                ESP_LOGE(TAG, "fail mbedtls_base64_decode");
            }
        }
        //解析到了Header的Content-Length数值
        else if (strcmp(evt->header_key, "Content-Length") == 0)
        {
    
    
            Content_Length = atoi((evt->header_value));
            ESP_LOGI(TAG, "HTTP Get Content-Length, key=%d, value=%s", Content_Length, evt->header_value);
        }
        break;

3.2 下载数据到flash

这里下载数据到flash,以及边做MD5校验 。

while (1)
    {
    
    
        int data_read = esp_http_client_read(client, upgrade_data_buf, FLASH_SECTOR_SIZE);
        if (data_read == 0)
        {
    
    
            ESP_LOGI(TAG, "Connection closed,all data received");
            break;
        }
        if (data_read < 0)
        {
    
    
            ESP_LOGE(TAG, "Error: SSL data read error");
            break;
        }
        if (data_read > 0)
        {
    
    
            ESP_LOGI(TAG, "Written image offAdress %d , length :%d", binary_file_len, FLASH_SECTOR_SIZE);
            //注意先擦,必须以 1K 扇区的倍数去擦
            if (esp_partition_erase_range(find_partition, binary_file_len, FLASH_SECTOR_SIZE) != ESP_OK)
            {
    
    
                ESP_LOGE(TAG, "Erase partition error");
            }
            //开始写入
            if (esp_partition_write(find_partition, binary_file_len, (unsigned char *)upgrade_data_buf, data_read) != ESP_OK) 
            {
    
    
                ESP_LOGE(TAG, "Write partition data error");
            }
            //同时把写入的数据做MD5校验
            mbedtls_md5_update(&md5_ctx, (unsigned char *)upgrade_data_buf, data_read);
            
            binary_file_len += data_read;
        }
    }

    ESP_LOGE(TAG, "------------------------------");
    ESP_LOGE(TAG, "Get local rev buff MD5 : ");
    esp_log_buffer_hex(TAG, md5, 16);
    ESP_LOGE(TAG, "Get server buff MD5 : ");
    esp_log_buffer_hex(TAG, p_header_md5_value, 16);
    ESP_LOGE(TAG, "------------------------------");

    //校验MD5是否一致
    if (0 == memcmp(md5, p_header_md5_value, sizeof(&p_header_md5_value)))
    {
    
    
        // MD5校验成功
        ESP_LOGI(TAG, "MD5 verify success\r\n");
    }
    else
    {
    
     // MD5校验失败
        ESP_LOGE(TAG, "MD5 verify fail\r\n");
    }

3.3 从flash读取数据做校验

从flash读取数据做校验,思路:
1)我要每次 4KB 读取数据,先把整个数据的长度除于4096,得到读取的总次数;
2)最后一次可能不到 4KB ,所以当达到最后一次获取数据时候, 判断剩下 buff 多少,再去读取。否则,会读取多余的数据。

    //把总的文件大小除于每次取出来的大小,得到取出次数
    int length = binary_file_len / FLASH_SECTOR_SIZE;
    ESP_LOGI(TAG, "MD5 binary_file_len: %d %d ", FLASH_SECTOR_SIZE, length);

    for (int rd_offset = 0; rd_offset < binary_file_len; rd_offset += FLASH_SECTOR_SIZE)
    {
    
    
        //判断为最后一次,则获取剩余的buff
        if (++i > length)
        {
    
    
            int offLength = binary_file_len - rd_offset;
            ESP_ERROR_CHECK(esp_partition_read(find_partition, rd_offset, flash_read_buff, offLength));
            mbedtls_md5_update(&md5_ctx, (unsigned char *)flash_read_buff, offLength);
        }
        else
        {
    
    
            ESP_ERROR_CHECK(esp_partition_read(find_partition, rd_offset, flash_read_buff, FLASH_SECTOR_SIZE));
            mbedtls_md5_update(&md5_ctx, (unsigned char *)flash_read_buff, FLASH_SECTOR_SIZE);
        }
    }

    mbedtls_md5_finish(&md5_ctx, md5);

    ESP_LOGE(TAG, "------------------------------");
    ESP_LOGE(TAG, "Get local save buff MD5 : ");
    esp_log_buffer_hex(TAG, md5, 16);
    ESP_LOGE(TAG, "Get server buff MD5 : ");
    esp_log_buffer_hex(TAG, p_header_md5_value, 16);
    ESP_LOGE(TAG, "------------------------------");

    //校验MD5是否一致
    if (0 == memcmp(md5, p_header_md5_value, sizeof(&p_header_md5_value)))
    {
    
    
        // MD5校验成功
        ESP_LOGI(TAG, "MD5 verify success\r\n");
    }
    else
    {
    
     // MD5校验失败
        ESP_LOGE(TAG, "MD5 verify fail\r\n");
    }


源码免费获取:https://github.com/xuhongv/StudyInEsp32/tree/master/26_esp32_s2_s3_https_save_get_file


另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈!

  • 玩转无线物联网带你飞、免费加千人群讨论,群里众多国内ESP开发者,找到你一份归属,免费白嫖。
  • QQ讨论一群,点击加群:434878850
  • QQ讨论二群,点击加群:623325168
  • 个人微信公众号:徐宏blog , 不定时推送干货文章,不推送任何广告。
  • 个人邮箱:[email protected] 24小时在线,有发必回复!
  • esp8266源代码学习汇总(持续更新,欢迎star):https://github.com/xuhongv/StudyInEsp8266
  • esp32源代码学习汇总(持续更新,欢迎star):https://github.com/xuhongv/StudyInEsp32
  • 关注下面微信公众号二维码,干货多多,第一时间推送!

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xh870189248/article/details/122218620