一、概述与架构设计
1.1 OTA核心架构
ESP32的OTA功能建立在分层架构之上,确保升级过程的可靠性和安全性。
1.2 OTA工作流程详解
1.3 Flash分区结构深度解析
1.3.1 标准分区布局
| 分区名 | 类型 | 子类型 | 偏移地址 | 大小 | 作用描述 |
|---|---|---|---|---|---|
| bootloader |
bootloader |
– |
0x1000 |
28KB |
引导加载程序 |
| partition_table |
data |
– |
0x8000 |
4KB |
分区表 |
| nvs |
data |
nvs |
0x9000 |
16KB |
非易失存储 |
| otadata |
data |
ota |
0xD000 |
8KB |
OTA状态数据 |
| phy_init |
data |
phy |
0xF000 |
4KB |
PHY初始化数据 |
| factory |
app |
factory |
0x10000 |
1.5MB |
出厂固件 |
| ota_0 |
app |
ota_0 |
0x180000 |
1.5MB |
OTA槽位0 |
| ota_1 |
app |
ota_1 |
0x300000 |
1.5MB |
OTA槽位1 |
| storage |
data |
spiffs |
0x480000 |
1.0MB |
文件系统 |
1.3.2 分区表配置对比
| 配置方案 | 分区数量 | OTA槽位 | Factory分区 | 适用场景 |
|---|---|---|---|---|
| Factory + 2 OTA |
8个 |
2个 |
保留 |
标准应用 |
| Factory + 1 OTA |
7个 |
1个 |
保留 |
空间受限 |
| No Factory |
7个 |
2个 |
无 |
纯OTA部署 |
| 自定义 |
可变 |
可配置 |
可选 |
特殊需求 |
1.4 OTA数据分区结构
otadata分区是OTA升级的关键,其数据结构如下:
| 字段 | 大小 | 描述 | 示例值 |
|---|---|---|---|
| ota_seq |
4字节 |
OTA序列号 |
0x00000001 |
| crc |
4字节 |
数据CRC校验 |
0x12345678 |
| ota_state |
1字节 |
OTA状态 |
0x03 |
| ota_attempt |
1字节 |
启动尝试次数 |
0x00 |
| ota_partition |
1字节 |
当前引导分区 |
0x00 |
| reserved |
29字节 |
保留字段 |
0x00 |
OTA状态码表:
| 状态码 | 名称 | 含义 |
|---|---|---|
|
0x00 |
ESP_OTA_IMG_PENDING_VERIFY |
待验证 |
|
0x01 |
ESP_OTA_IMG_VALID |
验证通过 |
|
0x02 |
ESP_OTA_IMG_INVALID |
验证失败 |
|
0x03 |
ESP_OTA_IMG_ABORTED |
已中止 |
|
0x04 |
ESP_OTA_IMG_NEW |
新固件 |
二、原生API深度解析
2.1 核心API函数详解
2.1.1 esp_ota_begin() – 初始化OTA升级
函数原型:
esp_err_t esp_ota_begin(
const esp_partition_t *partition,
size_t image_size,
esp_ota_handle_t *out_handle
);
参数说明:
| 参数 | 类型 | 输入/输出 | 描述 |
|---|---|---|---|
|
partition |
const esp_partition_t* |
输入 |
目标分区指针 |
|
image_size |
size_t |
输入 |
固件大小,未知填 |
|
out_handle |
esp_ota_handle_t* |
输出 |
OTA操作句柄 |
返回错误码:
| 错误码 | 值 | 描述 | 解决方案 |
|---|---|---|---|
|
ESP_OK |
0 |
成功 |
– |
|
ESP_ERR_INVALID_ARG |
0x102 |
参数无效 |
检查分区指针 |
|
ESP_ERR_OTA_BASE |
0x1500 |
OTA基础错误 |
查看具体错误 |
|
ESP_ERR_NO_MEM |
0x101 |
内存不足 |
增大堆内存 |
|
ESP_ERR_OTA_PARTITION_CONFLICT |
0x151 |
分区冲突 |
检查分区表 |
2.1.2 esp_ota_write() – 写入固件数据
优化参数配置:
// 优化配置建议
#define OTA_WRITE_BUFFER_SIZE 4096 // 4KB对齐,提升性能
#define OTA_WRITE_RETRY_COUNT 3 // 写入重试次数
#define OTA_WRITE_TIMEOUT_MS 5000 // 写入超时时间
// 高级写入函数(带重试机制)
esp_err_t ota_write_with_retry(
esp_ota_handle_t handle,
const void *data,
size_t size,
int max_retries
) {
esp_err_t ret = ESP_FAIL;
int retry_count = 0;
while (retry_count < max_retries) {
ret = esp_ota_write(handle, data, size);
if (ret == ESP_OK) {
break;
}
ESP_LOGE("OTA", "Write failed (attempt %d): %s",
retry_count + 1, esp_err_to_name(ret));
vTaskDelay(pdMS_TO_TICKS(100 * (retry_count + 1))); // 退避延迟
retry_count++;
}
return ret;
}
2.1.3 esp_ota_end() – 结束OTA升级
验证流程:
// 完整的OTA结束流程
esp_err_t complete_ota_update(esp_ota_handle_t handle,
const esp_partition_t *target_partition) {
esp_err_t err;
// 1. 结束OTA写入
err = esp_ota_end(handle);
if (err != ESP_OK) {
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE("OTA", "Firmware validation failed!");
// 可选:记录验证失败到NVS
nvs_handle_t nvs;
nvs_open("ota_stats", NVS_READWRITE, &nvs);
uint32_t fail_count = 0;
nvs_get_u32(nvs, "validate_fails", &fail_count);
nvs_set_u32(nvs, "validate_fails", fail_count + 1);
nvs_commit(nvs);
nvs_close(nvs);
}
esp_ota_abort(handle); // 必须调用,释放资源
return err;
}
// 2. 验证分区可引导性
if (!esp_ota_check_rollback_is_possible(target_partition)) {
ESP_LOGW("OTA", "Rollback may not be possible for this partition");
}
// 3. 设置引导分区
err = esp_ota_set_boot_partition(target_partition);
if (err != ESP_OK) {
ESP_LOGE("OTA", "Failed to set boot partition: %s",
esp_err_to_name(err));
return err;
}
// 4. 验证设置成功
const esp_partition_t *boot_partition = esp_ota_get_boot_partition();
if (boot_partition != target_partition) {
ESP_LOGE("OTA", "Boot partition mismatch after setting!");
return ESP_FAIL;
}
ESP_LOGI("OTA", "OTA update completed successfully");
return ESP_OK;
}
2.2 辅助函数与工具集
2.2.1 分区管理函数
// 分区选择策略表
typedef enum {
OTA_SELECT_NEXT = 0, // 选择下一个空闲分区
OTA_SELECT_SPECIFIC = 1, // 选择指定分区
OTA_SELECT_SAFEST = 2, // 选择最安全的分区(验证通过)
OTA_SELECT_ROLLBACK = 3// 选择回滚分区
} ota_selection_strategy_t;
// 智能分区选择函数
const esp_partition_t* select_ota_partition(ota_selection_strategy_t strategy,
int specific_slot) {
const esp_partition_t *partition = NULL;
switch(strategy) {
case OTA_SELECT_NEXT:
partition = esp_ota_get_next_update_partition(NULL);
break;
case OTA_SELECT_SPECIFIC:
if (specific_slot == 0) {
partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_0,
NULL);
} else if (specific_slot == 1) {
partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_1,
NULL);
}
break;
case OTA_SELECT_SAFEST:
// 检查两个OTA分区,选择已验证的
partition = find_valid_ota_partition();
break;
case OTA_SELECT_ROLLBACK:
// 选择前一个版本的分区
partition = find_rollback_partition();
break;
}
if (partition == NULL) {
ESP_LOGE("OTA", "No suitable partition found with strategy %d", strategy);
}
return partition;
}
2.2.2 固件信息获取
// 获取固件详细信息的完整函数
void print_firmware_info(const esp_partition_t *partition) {
esp_app_desc_t app_desc;
if (esp_ota_get_partition_description(partition, &app_desc) == ESP_OK) {
printf("=== Firmware Information ===\n");
printf("Magic Word: 0x%08X\n", app_desc.magic_word);
printf("Secure Version: %d\n", app_desc.secure_version);
printf("Version: %s\n", app_desc.version);
printf("Project Name: %s\n", app_desc.project_name);
printf("Time: %s %s\n", app_desc.date, app_desc.time);
printf("IDF Version: %s\n", app_desc.idf_ver);
printf("App ELF SHA256: ");
for (int i = 0; i < 32; i++) {
printf("%02X", app_desc.app_elf_sha256[i]);
if ((i + 1) % 8 == 0) printf(" ");
}
printf("\n");
// 检查固件类型
if (partition->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
printf("Type: Factory Image\n");
} else if (partition->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MIN &&
partition->subtype <= ESP_PARTITION_SUBTYPE_APP_OTA_MAX) {
printf("Type: OTA Slot %d\n",
partition->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_MIN);
}
}
}
三、编程流程与优化实践
3.1 完整的OTA实现示例
// OTA管理器结构体
typedef struct {
esp_ota_handle_t handle;
const esp_partition_t *target_partition;
uint32_t total_size;
uint32_t written_size;
uint8_t *buffer;
size_t buffer_size;
SemaphoreHandle_t mutex;
TaskHandle_t task_handle;
ota_state_t state;
char firmware_url[256];
char firmware_version[32];
uint32_t crc32;
time_t start_time;
time_t end_time;
} ota_manager_t;
// OTA状态机
typedef enum {
OTA_STATE_IDLE = 0,
OTA_STATE_INITIALIZING,
OTA_STATE_DOWNLOADING,
OTA_STATE_WRITING,
OTA_STATE_VALIDATING,
OTA_STATE_COMPLETING,
OTA_STATE_SUCCESS,
OTA_STATE_FAILED,
OTA_STATE_ABORTED
} ota_state_t;
// 主OTA任务函数
void ota_task(void *pvParameters) {
ota_manager_t *manager = (ota_manager_t *)pvParameters;
esp_err_t err = ESP_OK;
manager->state = OTA_STATE_INITIALIZING;
// 1. 初始化OTA
ESP_LOGI("OTA", "Initializing OTA update");
manager->target_partition = esp_ota_get_next_update_partition(NULL);
if (manager->target_partition == NULL) {
ESP_LOGE("OTA", "No OTA partition found");
manager->state = OTA_STATE_FAILED;
vTaskDelete(NULL);
return;
}
// 2. 检查可用空间
size_t free_size = manager->target_partition->size;
if (free_size < manager->total_size) {
ESP_LOGE("OTA", "Insufficient space: %d < %d", free_size, manager->total_size);
manager->state = OTA_STATE_FAILED;
vTaskDelete(NULL);
return;
}
// 3. 开始OTA操作
err = esp_ota_begin(manager->target_partition,
manager->total_size,
&manager->handle);
if (err != ESP_OK) {
ESP_LOGE("OTA", "esp_ota_begin failed: %s", esp_err_to_name(err));
manager->state = OTA_STATE_FAILED;
vTaskDelete(NULL);
return;
}
manager->state = OTA_STATE_DOWNLOADING;
// 4. 下载并写入数据(示例:HTTP下载)
err = http_download_and_write(manager);
if (err != ESP_OK) {
ESP_LOGE("OTA", "Download/write failed: %s", esp_err_to_name(err));
esp_ota_abort(manager->handle);
manager->state = OTA_STATE_FAILED;
vTaskDelete(NULL);
return;
}
manager->state = OTA_STATE_VALIDATING;
// 5. 完成OTA
err = esp_ota_end(manager->handle);
if (err != ESP_OK) {
ESP_LOGE("OTA", "esp_ota_end failed: %s", esp_err_to_name(err));
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE("OTA", "Firmware validation failed!");
}
manager->state = OTA_STATE_FAILED;
vTaskDelete(NULL);
return;
}
manager->state = OTA_STATE_COMPLETING;
// 6. 设置引导分区
err = esp_ota_set_boot_partition(manager->target_partition);
if (err != ESP_OK) {
ESP_LOGE("OTA", "esp_ota_set_boot_partition failed: %s",
esp_err_to_name(err));
manager->state = OTA_STATE_FAILED;
vTaskDelete(NULL);
return;
}
manager->state = OTA_STATE_SUCCESS;
manager->end_time = time(NULL);
// 7. 记录升级信息
log_ota_event(manager);
// 8. 准备重启
ESP_LOGI("OTA", "OTA update successful. Rebooting in 3 seconds...");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
vTaskDelete(NULL);
}
3.2 关键增强逻辑实现
3.2.1 版本控制与校验
// 版本比较函数
int compare_versions(const char *ver1, const char *ver2) {
int major1, minor1, patch1;
int major2, minor2, patch2;
sscanf(ver1, "%d.%d.%d", &major1, &minor1, &patch1);
sscanf(ver2, "%d.%d.%d", &major2, &minor2, &patch2);
if (major1 != major2) return major1 - major2;
if (minor1 != minor2) return minor1 - minor2;
return patch1 - patch2;
}
// 版本控制策略
typedef enum {
VERSION_UPGRADE_ANY = 0, // 允许任何版本升级
VERSION_UPGRADE_HIGHER = 1, // 只允许升级到更高版本
VERSION_UPGRADE_SAME = 2, // 允许相同版本(重新刷写)
VERSION_UPGRADE_SPECIFIC = 3// 只允许特定版本
} version_upgrade_policy_t;
// 版本检查函数
bool check_version_compatibility(const char *current_ver,
const char *new_ver,
version_upgrade_policy_t policy) {
int comparison = compare_versions(current_ver, new_ver);
switch(policy) {
case VERSION_UPGRADE_ANY:
return true;
case VERSION_UPGRADE_HIGHER:
return comparison < 0; // 新版本 > 当前版本
case VERSION_UPGRADE_SAME:
return comparison == 0; // 版本相同
case VERSION_UPGRADE_SPECIFIC:
// 检查是否在允许的版本列表中
return is_version_allowed(new_ver);
default:
return false;
}
}
3.2.2 断电保护机制
// 断电保护上下文
typedef struct {
uint32_t last_written_offset;
uint32_t total_size;
uint32_t crc32;
uint8_t partition_slot;
time_t timestamp;
uint8_t retry_count;
} ota_backup_context_t;
// 保存OTA进度到NVS
esp_err_t save_ota_progress(ota_backup_context_t *ctx) {
nvs_handle_t handle;
esp_err_t err;
err = nvs_open("ota_backup", NVS_READWRITE, &handle);
if (err != ESP_OK) return err;
err = nvs_set_blob(handle, "context", ctx, sizeof(ota_backup_context_t));
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
return err;
}
// 恢复OTA进度
esp_err_t restore_ota_progress(ota_backup_context_t *ctx) {
nvs_handle_t handle;
esp_err_t err;
size_t required_size = 0;
err = nvs_open("ota_backup", NVS_READONLY, &handle);
if (err != ESP_OK) return err;
// 获取数据大小
err = nvs_get_blob(handle, "context", NULL, &required_size);
if (err != ESP_OK || required_size != sizeof(ota_backup_context_t)) {
nvs_close(handle);
return ESP_ERR_NVS_NOT_FOUND;
}
// 读取数据
err = nvs_get_blob(handle, "context", ctx, &required_size);
nvs_close(handle);
return err;
}
// 断点续传实现
esp_err_t resume_ota_update(const char *url, ota_backup_context_t *ctx) {
esp_err_t err;
esp_ota_handle_t handle;
const esp_partition_t *partition;
// 根据保存的槽位找到分区
partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ctx->partition_slot == 0 ?
ESP_PARTITION_SUBTYPE_APP_OTA_0 :
ESP_PARTITION_SUBTYPE_APP_OTA_1,
NULL);
if (partition == NULL) {
return ESP_ERR_NOT_FOUND;
}
// 从上次中断的位置继续
err = esp_ota_begin(partition, ctx->total_size, &handle);
if (err != ESP_OK) return err;
// HTTP恢复下载(设置Range头)
err = http_resume_download(url, ctx->last_written_offset, handle);
if (err != ESP_OK) {
esp_ota_abort(handle);
return err;
}
// 完成OTA
err = esp_ota_end(handle);
if (err != ESP_OK) return err;
// 清除备份数据
nvs_erase_key(nvs_open("ota_backup", NVS_READWRITE, NULL), "context");
return err;
}
3.2.3 回滚机制实现
// 回滚管理器
typedef struct {
uint8_t max_rollback_attempts;
uint8_t current_attempts;
uint32_t rollback_timeout_ms;
bool auto_rollback_enabled;
bool manual_rollback_triggered;
gpio_num_t rollback_gpio;
uint8_t rollback_gpio_level;
} rollback_manager_t;
// 初始化回滚管理器
void init_rollback_manager(rollback_manager_t *mgr) {
mgr->max_rollback_attempts = 3;
mgr->current_attempts = 0;
mgr->rollback_timeout_ms = 30000; // 30秒
mgr->auto_rollback_enabled = true;
mgr->manual_rollback_triggered = false;
mgr->rollback_gpio = GPIO_NUM_0;
mgr->rollback_gpio_level = 0;
// 配置回滚GPIO
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << mgr->rollback_gpio),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
}
// 检查是否需要回滚
bool should_rollback(rollback_manager_t *mgr) {
// 检查自动回滚条件
if (mgr->auto_rollback_enabled) {
// 检查启动失败计数器
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(esp_ota_get_running_partition(),
&ota_state) == ESP_OK) {
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
mgr->current_attempts++;
if (mgr->current_attempts >= mgr->max_rollback_attempts) {
return true;
}
}
}
}
// 检查手动回滚触发
if (mgr->manual_rollback_triggered) {
return true;
}
// 检查GPIO触发
if (gpio_get_level(mgr->rollback_gpio) == mgr->rollback_gpio_level) {
vTaskDelay(pdMS_TO_TICKS(5000)); // 持续5秒
if (gpio_get_level(mgr->rollback_gpio) == mgr->rollback_gpio_level) {
return true;
}
}
return false;
}
// 执行回滚操作
esp_err_t perform_rollback(rollback_manager_t *mgr) {
ESP_LOGW("ROLLBACK", "Initiating rollback procedure");
// 1. 标记当前固件为无效
esp_err_t err = esp_ota_mark_app_invalid_rollback_and_reboot();
if (err != ESP_OK) {
ESP_LOGE("ROLLBACK", "Failed to mark app as invalid: %s",
esp_err_to_name(err));
return err;
}
// 2. 获取回滚分区(上一个有效的OTA分区)
const esp_partition_t *rollback_partition = NULL;
const esp_partition_t *current = esp_ota_get_running_partition();
if (current->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) {
rollback_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_1,
NULL);
} else if (current->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_1) {
rollback_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_OTA_0,
NULL);
}
// 3. 如果没有可用的OTA分区,回退到工厂分区
if (rollback_partition == NULL) {
ESP_LOGW("ROLLBACK", "No OTA partition available, trying factory");
rollback_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_APP_FACTORY,
NULL);
}
if (rollback_partition == NULL) {
ESP_LOGE("ROLLBACK", "No valid rollback partition found");
return ESP_FAIL;
}
// 4. 设置引导分区
err = esp_ota_set_boot_partition(rollback_partition);
if (err != ESP_OK) {
ESP_LOGE("ROLLBACK", "Failed to set boot partition: %s",
esp_err_to_name(err));
return err;
}
ESP_LOGI("ROLLBACK", "Rollback to partition %s scheduled",
rollback_partition->label);
// 5. 重启设备
vTaskDelay(pdMS_TO_TICKS(2000));
esp_restart();
return ESP_OK;
}
四、测试与故障排查
4.1 分区表配置深度解析
4.1.1 分区表生成与验证
# 分区表生成脚本示例
import os
import sys
def generate_partition_table(ota_slots=2, factory_enabled=True, custom_size=None):
"""生成自定义分区表"""
partitions = []
# 固定分区
partitions.extend([
("bootloader", "0x1000", "0x7000"),
("partition_table", "0x8000", "0x1000"),
("nvs", "0x9000", "0x6000"),
("otadata", "0xd000", "0x2000"),
("phy_init", "0xf000", "0x1000"),
])
offset = "0x10000"
# 工厂分区(可选)
if factory_enabled:
partitions.append(("factory", "app", "factory", offset, "1M"))
offset = hex(int(offset, 16) + 0x100000) # 增加1MB
# OTA分区
for i in range(ota_slots):
size = custom_size[i] if custom_size else "1M"
partitions.append((f"ota_{i}", "app", f"ota_{i}", offset, size))
offset = hex(int(offset, 16) + parse_size(size))
# 文件系统分区(剩余空间)
partitions.append(("storage", "data", "spiffs", offset, ""))
# 生成CSV文件
with open("partitions.csv", "w") as f:
f.write("# Name, Type, SubType, Offset, Size, Flags\n")
for part in partitions:
f.write(",".join(str(x) for x in part) + "\n")
return partitions
def parse_size(size_str):
"""解析大小字符串"""
if size_str.endswith("K"):
return int(size_str[:-1]) * 1024
elif size_str.endswith("M"):
return int(size_str[:-1]) * 1024 * 1024
else:
return int(size_str)
4.1.2 分区表验证工具
// 分区表验证函数
void validate_partition_table(void) {
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY,
ESP_PARTITION_SUBTYPE_ANY,
NULL);
printf("=== Partition Table Validation ===\n");
printf("%-20s %-10s %-12s %-10s %-10s\n",
"Name", "Type", "Subtype", "Offset", "Size");
printf("------------------------------------------------------------\n");
bool factory_found = false;
bool ota_0_found = false;
bool ota_1_found = false;
bool otadata_found = false;
for (; it != NULL; it = esp_partition_next(it)) {
const esp_partition_t *part = esp_partition_get(it);
printf("%-20s 0x%02X 0x%02X 0x%08X 0x%08X\n",
part->label, part->type, part->subtype,
part->address, part->size);
// 检查关键分区
if (part->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
factory_found = true;
} else if (part->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) {
ota_0_found = true;
} else if (part->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_1) {
ota_1_found = true;
} else if (part->subtype == ESP_PARTITION_SUBTYPE_DATA_OTA) {
otadata_found = true;
}
}
esp_partition_iterator_release(it);
printf("\n=== Validation Results ===\n");
printf("Factory Partition: %s\n", factory_found ? "✓ Found" : "✗ Missing");
printf("OTA_0 Partition: %s\n", ota_0_found ? "✓ Found" : "✗ Missing");
printf("OTA_1 Partition: %s\n", ota_1_found ? "✓ Found" : "✗ Missing");
printf("OTAData Partition: %s\n", otadata_found ? "✓ Found" : "✗ Missing");
if (!ota_0_found || !ota_1_found) {
ESP_LOGW("PARTITION", "OTA requires at least two OTA partitions");
}
if (!otadata_found) {
ESP_LOGE("PARTITION", "otadata partition is required for OTA!");
}
}
4.2 故障排查与诊断
4.2.1 常见错误代码解析
| 错误码 | 十六进制 | 常量名 | 原因分析 | 解决方案 |
|---|---|---|---|---|
| 0x101 |
0x00000101 |
ESP_ERR_NO_MEM |
内存分配失败 |
增大堆内存,优化内存管理 |
| 0x102 |
0x00000102 |
ESP_ERR_INVALID_ARG |
参数无效 |
检查API调用参数 |
| 0x103 |
0x00000103 |
ESP_ERR_NOT_FOUND |
分区未找到 |
检查分区表配置 |
| 0x109 |
0x00000109 |
ESP_ERR_INVALID_SIZE |
大小无效 |
验证固件大小 |
| 0x115 |
0x00000115 |
ESP_ERR_INVALID_STATE |
状态无效 |
检查OTA操作顺序 |
| 0x1500 |
0x00001500 |
ESP_ERR_OTA_BASE |
OTA基础错误 |
查看具体OTA错误 |
| 0x1501 |
0x00001501 |
ESP_ERR_OTA_PARTITION_CONFLICT |
分区冲突 |
检查分区重叠 |
| 0x1502 |
0x00001502 |
ESP_ERR_OTA_SELECT_INFO_INVALID |
OTA信息无效 |
重置otadata分区 |
| 0x1503 |
0x00001503 |
ESP_ERR_OTA_VALIDATE_FAILED |
验证失败 |
检查签名和校验和 |
| 0x1504 |
0x00001504 |
ESP_ERR_OTA_SMALL_SEC_VER |
安全版本过低 |
更新bootloader |
| 0x1505 |
0x00001505 |
ESP_ERR_OTA_ROLLBACK_FAILED |
回滚失败 |
检查回滚分区 |
4.2.2 诊断工具函数
// OTA诊断工具
void diagnose_ota_issues(void) {
ESP_LOGI("DIAGNOSE", "Starting OTA diagnostics...");
// 1. 检查当前运行分区
const esp_partition_t *running = esp_ota_get_running_partition();
if (running) {
ESP_LOGI("DIAGNOSE", "Running partition: %s (0x%08X)",
running->label, running->address);
} else {
ESP_LOGE("DIAGNOSE", "Cannot determine running partition!");
}
// 2. 检查引导分区
const esp_partition_t *boot = esp_ota_get_boot_partition();
if (boot) {
ESP_LOGI("DIAGNOSE", "Boot partition: %s (0x%08X)",
boot->label, boot->address);
}
// 3. 检查分区一致性
if (running && boot && running != boot) {
ESP_LOGW("DIAGNOSE", "Warning: Running and boot partitions differ!");
}
// 4. 检查下一个更新分区
const esp_partition_t *next = esp_ota_get_next_update_partition(NULL);
if (next) {
ESP_LOGI("DIAGNOSE", "Next update partition: %s", next->label);
} else {
ESP_LOGE("DIAGNOSE", "No available partition for update!");
}
// 5. 检查ota_data分区状态
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
const char *state_str;
switch(ota_state) {
case ESP_OTA_IMG_PENDING_VERIFY: state_str = "Pending Verify"; break;
case ESP_OTA_IMG_VALID: state_str = "Valid"; break;
case ESP_OTA_IMG_INVALID: state_str = "Invalid"; break;
case ESP_OTA_IMG_ABORTED: state_str = "Aborted"; break;
case ESP_OTA_IMG_NEW: state_str = "New"; break;
default: state_str = "Unknown";
}
ESP_LOGI("DIAGNOSE", "OTA state: %s", state_str);
}
// 6. 检查所有OTA分区状态
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_ANY,
NULL);
for (; it != NULL; it = esp_partition_next(it)) {
const esp_partition_t *p = esp_partition_get(it);
if (p->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_MIN ||
p->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
esp_app_desc_t app_desc;
if (esp_ota_get_partition_description(p, &app_desc) == ESP_OK) {
printf(" %-10s: v%s (%s %s)\n",
p->label, app_desc.version, app_desc.date, app_desc.time);
// 检查固件完整性
if (check_firmware_crc(p) != ESP_OK) {
ESP_LOGW("DIAGNOSE", " CRC check failed for %s", p->label);
}
}
}
}
esp_partition_iterator_release(it);
// 7. 检查可用闪存空间
size_t total, used, free_space;
if (esp_spiram_get_chip_size(&total) == ESP_OK) {
// 简化估算
free_space = total - (running ? running->address + running->size : 0);
ESP_LOGI("DIAGNOSE", "Estimated free flash: %d KB", free_space / 1024);
}
}
4.2.3 服务器配置与调试
# OTA服务器模拟器(Python)
from http.server import HTTPServer, BaseHTTPRequestHandler
import ssl
import os
import json
class OTARequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
"""处理固件下载请求"""
if self.path == '/firmware.bin':
firmware_path = 'firmware/firmware.bin'
if not os.path.exists(firmware_path):
self.send_error(404, "Firmware not found")
return
# 支持断点续传
range_header = self.headers.get('Range')
file_size = os.path.getsize(firmware_path)
start_byte = 0
end_byte = file_size - 1
if range_header:
# 解析Range头
byte_range = range_header.replace('bytes=', '').split('-')
start_byte = int(byte_range[0]) if byte_range[0] else 0
end_byte = int(byte_range[1]) if byte_range[1] else file_size - 1
content_length = end_byte - start_byte + 1
self.send_response(206 if range_header else 200)
self.send_header('Content-Type', 'application/octet-stream')
self.send_header('Content-Length', str(content_length))
self.send_header('Accept-Ranges', 'bytes')
if range_header:
self.send_header('Content-Range',
f'bytes {start_byte}-{end_byte}/{file_size}')
self.end_headers()
# 发送固件数据
with open(firmware_path, 'rb') as f:
f.seek(start_byte)
remaining = content_length
while remaining > 0:
chunk = f.read(min(4096, remaining))
if not chunk:
break
self.wfile.write(chunk)
remaining -= len(chunk)
elif self.path == '/version':
# 提供版本信息
version_info = {
"version": "1.2.3",
"url": "https://ota.server.com/firmware.bin",
"size": os.path.getsize("firmware/firmware.bin"),
"sha256": "abc123...",
"release_notes": "Bug fixes and improvements",
"mandatory": False,
"min_hardware": "ESP32"
}
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(version_info).encode())
def do_POST(self):
"""处理OTA状态上报"""
if self.path == '/report':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
status = json.loads(post_data)
# 记录状态
with open('ota_log.txt', 'a') as f:
f.write(f"{status['device_id']}: {status['status']}\n")
self.send_response(200)
self.end_headers()
def log_message(self, format, *args):
"""自定义日志格式"""
print(f"[OTA Server] {args[0]} - {args[1]}")
def run_ota_server(port=8070, use_ssl=False):
"""启动OTA服务器"""
server_address = ('', port)
httpd = HTTPServer(server_address, OTARequestHandler)
if use_ssl:
# 配置SSL
httpd.socket = ssl.wrap_socket(
httpd.socket,
certfile='cert.pem',
keyfile='key.pem',
server_side=True
)
print(f"OTA HTTPS server running on port {port}")
else:
print(f"OTA HTTP server running on port {port}")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nServer stopped")
# 生成自签名证书
def generate_self_signed_cert():
import subprocess
subprocess.run([
'openssl', 'req', '-x509', '-newkey', 'rsa:2048',
'-keyout', 'key.pem', '-out', 'cert.pem',
'-days', '365', '-nodes', '-subj', '/CN=ota.local'
])
if __name__ == '__main__':
# generate_self_signed_cert() # 首次运行生成证书
run_ota_server(port=8070, use_ssl=False)
五、高级应用场景
5.1 Web Server本地升级实现
// Web服务器OTA处理器
static esp_err_t ota_update_handler(httpd_req_t *req) {
esp_err_t err = ESP_OK;
ota_update_context_t ctx = {0};
// 1. 检查请求方法
if (req->method != HTTP_POST) {
httpd_resp_send_err(req, HTTPD_405_METHOD_NOT_ALLOWED, NULL);
return ESP_FAIL;
}
// 2. 获取Content-Length
char content_length_str[32] = {0};
if (httpd_req_get_hdr_value_len(req, "Content-Length") > 0) {
httpd_req_get_hdr_value_str(req, "Content-Length",
content_length_str, sizeof(content_length_str));
ctx.content_length = atoi(content_length_str);
}
if (ctx.content_length <= 0) {
httpd_resp_send_err(req, HTTPD_411_LENGTH_REQUIRED, NULL);
return ESP_FAIL;
}
// 3. 检查可用空间
const esp_partition_t *target = esp_ota_get_next_update_partition(NULL);
if (target == NULL || target->size < ctx.content_length) {
httpd_resp_send_err(req, HTTPD_507_INSUFFICIENT_STORAGE,
"Insufficient storage space");
return ESP_FAIL;
}
// 4. 初始化OTA
err = esp_ota_begin(target, ctx.content_length, &ctx.handle);
if (err != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"OTA initialization failed");
return err;
}
// 5. 接收并写入数据
char buf[4096];
int received;
size_t total_received = 0;
while (total_received < ctx.content_length) {
// 接收数据块
received = httpd_req_recv(req, buf, sizeof(buf));
if (received <= 0) {
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
continue; // 重试
}
break; // 错误或连接关闭
}
// 写入OTA分区
err = esp_ota_write(ctx.handle, buf, received);
if (err != ESP_OK) {
esp_ota_abort(ctx.handle);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"OTA write failed");
return err;
}
total_received += received;
// 可选:发送进度更新
if (total_received % (100 * 1024) == 0) { // 每100KB
ESP_LOGI("OTA", "Received %d/%d bytes (%.1f%%)",
total_received, ctx.content_length,
(total_received * 100.0) / ctx.content_length);
}
}
// 6. 完成OTA
err = esp_ota_end(ctx.handle);
if (err != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"OTA validation failed");
return err;
}
// 7. 设置引导分区
err = esp_ota_set_boot_partition(target);
if (err != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Failed to set boot partition");
return err;
}
// 8. 返回成功响应
httpd_resp_set_status(req, "200 OK");
httpd_resp_set_type(req, "application/json");
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "status", "success");
cJSON_AddNumberToObject(root, "bytes_received", total_received);
cJSON_AddStringToObject(root, "partition", target->label);
const char *json_str = cJSON_Print(root);
httpd_resp_sendstr(req, json_str);
free((void*)json_str);
cJSON_Delete(root);
// 9. 延迟重启
ESP_LOGI("OTA", "Web OTA update successful. Rebooting in 5 seconds...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
return ESP_OK;
}
// 注册Web OTA路由
void register_ota_web_handler(httpd_handle_t server) {
httpd_uri_t ota_uri = {
.uri = "/update",
.method = HTTP_POST,
.handler = ota_update_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &ota_uri);
// 添加版本查询接口
httpd_uri_t version_uri = {
.uri = "/version",
.method = HTTP_GET,
.handler = version_info_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &version_uri);
}
5.2 MQTT批量升级系统
// MQTT OTA管理器
typedef struct {
esp_mqtt_client_handle_t mqtt_client;
char device_id[32];
char group_id[32];
char firmware_topic[128];
char status_topic[128];
char control_topic[128];
ota_state_t state;
uint32_t firmware_size;
char firmware_url[256];
char firmware_version[32];
SemaphoreHandle_t mutex;
TaskHandle_t ota_task;
} mqtt_ota_manager_t;
// MQTT OTA回调函数
static void mqtt_ota_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
mqtt_ota_manager_t *manager = (mqtt_ota_manager_t*)handler_args;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI("MQTT_OTA", "Connected to MQTT broker");
// 订阅相关主题
esp_mqtt_client_subscribe(manager->mqtt_client,
manager->control_topic, 1);
esp_mqtt_client_subscribe(manager->mqtt_client,
"firmware/updates", 1);
esp_mqtt_client_subscribe(manager->mqtt_client,
"firmware/commands", 1);
// 发布设备上线消息
publish_device_status(manager, "online");
break;
case MQTT_EVENT_DATA:
// 处理接收到的消息
handle_mqtt_message(manager, event->topic, event->data, event->data_len);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW("MQTT_OTA", "Disconnected from MQTT broker");
manager->state = OTA_STATE_IDLE;
break;
}
}
// 处理MQTT消息
static void handle_mqtt_message(mqtt_ota_manager_t *manager,
const char *topic, const char *data, int len) {
cJSON *json = cJSON_ParseWithLength(data, len);
if (json == NULL) return;
// 解析消息类型
cJSON *type_json = cJSON_GetObjectItem(json, "type");
if (type_json == NULL) {
cJSON_Delete(json);
return;
}
const char *msg_type = type_json->valuestring;
if (strcmp(msg_type, "firmware_update") == 0) {
// 处理固件更新命令
handle_firmware_update_cmd(manager, json);
} else if (strcmp(msg_type, "ota_start") == 0) {
// 开始OTA升级
start_mqtt_ota_update(manager, json);
} else if (strcmp(msg_type, "ota_status") == 0) {
// 状态查询
publish_ota_status(manager);
} else if (strcmp(msg_type, "rollback") == 0) {
// 回滚命令
handle_rollback_cmd(manager, json);
}
cJSON_Delete(json);
}
// 处理固件更新命令
static void handle_firmware_update_cmd(mqtt_ota_manager_t *manager, cJSON *json) {
cJSON *version_json = cJSON_GetObjectItem(json, "version");
cJSON *url_json = cJSON_GetObjectItem(json, "url");
cJSON *size_json = cJSON_GetObjectItem(json, "size");
cJSON *force_json = cJSON_GetObjectItem(json, "force");
if (!version_json || !url_json || !size_json) {
ESP_LOGE("MQTT_OTA", "Invalid firmware update command");
return;
}
// 检查是否已经在更新中
if (manager->state != OTA_STATE_IDLE &&
!(force_json && cJSON_IsTrue(force_json))) {
ESP_LOGW("MQTT_OTA", "OTA already in progress, ignoring update");
return;
}
// 保存固件信息
strlcpy(manager->firmware_version, version_json->valuestring,
sizeof(manager->firmware_version));
strlcpy(manager->firmware_url, url_json->valuestring,
sizeof(manager->firmware_url));
manager->firmware_size = (uint32_t)cJSON_GetNumberValue(size_json);
// 检查版本
esp_app_desc_t current_desc;
esp_ota_get_partition_description(esp_ota_get_running_partition(),
¤t_desc);
if (strcmp(current_desc.version, manager->firmware_version) == 0) {
ESP_LOGI("MQTT_OTA", "Already running version %s",
manager->firmware_version);
return;
}
// 发布确认消息
publish_ota_ack(manager, "update_received");
// 开始OTA更新(在单独的任务中)
xTaskCreate(mqtt_ota_update_task, "mqtt_ota", 8192, manager, 5,
&manager->ota_task);
}
// MQTT OTA更新任务
static void mqtt_ota_update_task(void *pvParameters) {
mqtt_ota_manager_t *manager = (mqtt_ota_manager_t*)pvParameters;
xSemaphoreTake(manager->mutex, portMAX_DELAY);
manager->state = OTA_STATE_DOWNLOADING;
xSemaphoreGive(manager->mutex);
// 发布开始状态
publish_ota_status(manager);
// 执行OTA更新
esp_err_t err = perform_http_ota_update(manager->firmware_url,
manager->firmware_size);
xSemaphoreTake(manager->mutex, portMAX_DELAY);
if (err == ESP_OK) {
manager->state = OTA_STATE_SUCCESS;
ESP_LOGI("MQTT_OTA", "OTA update completed successfully");
// 发布成功状态
publish_ota_status(manager);
// 延迟重启
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
} else {
manager->state = OTA_STATE_FAILED;
ESP_LOGE("MQTT_OTA", "OTA update failed: %s", esp_err_to_name(err));
// 发布失败状态
publish_ota_status(manager);
}
xSemaphoreGive(manager->mutex);
vTaskDelete(NULL);
}
// 发布OTA状态
static void publish_ota_status(mqtt_ota_manager_t *manager) {
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "device_id", manager->device_id);
cJSON_AddStringToObject(root, "group_id", manager->group_id);
const char *state_str;
switch(manager->state) {
case OTA_STATE_IDLE: state_str = "idle"; break;
case OTA_STATE_DOWNLOADING: state_str = "downloading"; break;
case OTA_STATE_SUCCESS: state_str = "success"; break;
case OTA_STATE_FAILED: state_str = "failed"; break;
default: state_str = "unknown";
}
cJSON_AddStringToObject(root, "ota_state", state_str);
// 添加固件信息
if (manager->firmware_version[0] != '\0') {
cJSON_AddStringToObject(root, "target_version",
manager->firmware_version);
}
// 添加当前版本
esp_app_desc_t desc;
if (esp_ota_get_partition_description(esp_ota_get_running_partition(),
&desc) == ESP_OK) {
cJSON_AddStringToObject(root, "current_version", desc.version);
}
char *json_str = cJSON_PrintUnformatted(root);
esp_mqtt_client_publish(manager->mqtt_client, manager->status_topic,
json_str, 0, 1, 0);
free(json_str);
cJSON_Delete(root);
}
六、安全机制深度解析
6.1 安全启动配置
6.1.1 安全启动流程
graph TD A[上电复位] --> B[ROM Bootloader] B --> C[加载一级Bootloader] C --> D[验证签名] D --> E{签名有效?} E -->|是| F[加载二级Bootloader] E -->|否| G[停止启动<br>红灯闪烁] F --> H[验证应用程序] H --> I{应用程序有效?} I -->|是| J[运行应用程序] I -->|否| K[尝试备用分区]
6.1.2 安全启动配置步骤
# 1. 生成安全启动密钥
espsecure.py generate_signing_key --version 2 secure_boot_key.pem
# 2. 配置项目启用安全启动
idf.py menuconfig
# → Security features
# → Enable flash encryption
# → Enable Secure Boot
# → Sign binaries during build
# 3. 烧写密钥到设备
espsecure.py burn_key --no-protect-key \
flash_encryption_key.bin FLASH_CRYPT_CNT 1
# 4. 编译并签名固件
idf.py build
espsecure.py sign_data --version 2 \
--keyfile secure_boot_key.pem \
--output build/firmware_signed.bin \
build/firmware.bin
# 5. 烧写固件
esptool.py write_flash 0x1000 build/bootloader/bootloader.bin
esptool.py write_flash 0x8000 build/partition_table/partition-table.bin
esptool.py write_flash 0x10000 build/firmware_signed.bin
6.2 加密通信实现
// HTTPS OTA实现
esp_err_t perform_https_ota_update(const char *url, const char *cert_pem) {
esp_http_client_config_t config = {
.url = url,
.cert_pem = cert_pem,
.timeout_ms = 30000,
.keep_alive_enable = true,
.disable_auto_redirect = false,
.max_redirection_count = 5,
.user_agent = "ESP32-HTTP-Client/1.0",
.method = HTTP_METHOD_GET,
};
// 创建HTTPS OTA客户端
esp_https_ota_config_t ota_config = {
.http_config = &config,
.http_client_init_cb = NULL, // 可选的初始化回调
.bulk_flash_erase = false, // 是否批量擦除
.partial_http_download = false, // 是否部分下载
.max_http_request_size = 0, // 0表示使用默认值
};
esp_https_ota_handle_t https_ota_handle = NULL;
esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
if (err != ESP_OK) {
ESP_LOGE("HTTPS_OTA", "Failed to start HTTPS OTA");
return err;
}
// 获取固件信息
esp_app_desc_t app_desc;
err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc);
if (err != ESP_OK) {
ESP_LOGE("HTTPS_OTA", "Failed to get image description");
esp_https_ota_abort(https_ota_handle);
return err;
}
ESP_LOGI("HTTPS_OTA", "New firmware version: %s", app_desc.version);
// 执行OTA升级
while (1) {
err = esp_https_ota_perform(https_ota_handle);
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
break;
}
// 可选:报告下载进度
int progress = esp_https_ota_get_image_len_read(https_ota_handle);
int total = esp_https_ota_get_image_size(https_ota_handle);
if (total > 0) {
ESP_LOGI("HTTPS_OTA", "Download progress: %d/%d (%.1f%%)",
progress, total, (progress * 100.0) / total);
}
}
if (err == ESP_OK) {
ESP_LOGI("HTTPS_OTA", "HTTPS OTA update successful");
// 完成OTA
esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle);
if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE("HTTPS_OTA", "Image validation failed");
return ota_finish_err;
}
ESP_LOGI("HTTPS_OTA", "Rebooting in 3 seconds...");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
} else {
ESP_LOGE("HTTPS_OTA", "HTTPS OTA update failed: %s",
esp_err_to_name(err));
esp_https_ota_abort(https_ota_handle);
}
return err;
}
七、性能优化技巧
7.1 内存优化策略
7.1.1 双缓冲技术实现
// 双缓冲OTA写入器
typedef struct {
uint8_t *buffer_a;
uint8_t *buffer_b;
size_t buffer_size;
size_t buffer_a_used;
size_t buffer_b_used;
bool buffer_a_ready;
bool buffer_b_ready;
SemaphoreHandle_t buffer_mutex;
TaskHandle_t fill_task;
TaskHandle_t write_task;
esp_ota_handle_t ota_handle;
} dual_buffer_writer_t;
// 初始化双缓冲
esp_err_t init_dual_buffer_writer(dual_buffer_writer_t *writer,
size_t buffer_size) {
writer->buffer_a = (uint8_t*)malloc(buffer_size);
writer->buffer_b = (uint8_t*)malloc(buffer_size);
if (!writer->buffer_a || !writer->buffer_b) {
free(writer->buffer_a);
free(writer->buffer_b);
return ESP_ERR_NO_MEM;
}
writer->buffer_size = buffer_size;
writer->buffer_a_used = 0;
writer->buffer_b_used = 0;
writer->buffer_a_ready = false;
writer->buffer_b_ready = false;
writer->buffer_mutex = xSemaphoreCreateMutex();
if (!writer->buffer_mutex) {
free(writer->buffer_a);
free(writer->buffer_b);
return ESP_FAIL;
}
return ESP_OK;
}
// 填充缓冲区任务
void fill_buffer_task(void *pvParameters) {
dual_buffer_writer_t *writer = (dual_buffer_writer_t*)pvParameters;
while (1) {
// 等待缓冲区可用
xSemaphoreTake(writer->buffer_mutex, portMAX_DELAY);
// 选择要填充的缓冲区
uint8_t *target_buffer = NULL;
bool *ready_flag = NULL;
size_t *used_size = NULL;
if (!writer->buffer_a_ready && writer->buffer_a_used < writer->buffer_size) {
target_buffer = writer->buffer_a;
ready_flag = &writer->buffer_a_ready;
used_size = &writer->buffer_a_used;
} else if (!writer->buffer_b_ready && writer->buffer_b_used < writer->buffer_size) {
target_buffer = writer->buffer_b;
ready_flag = &writer->buffer_b_ready;
used_size = &writer->buffer_b_used;
}
xSemaphoreGive(writer->buffer_mutex);
if (target_buffer) {
// 从网络填充数据
size_t fill_size = fill_from_network(target_buffer + *used_size,
writer->buffer_size - *used_size);
if (fill_size > 0) {
xSemaphoreTake(writer->buffer_mutex, portMAX_DELAY);
*used_size += fill_size;
// 如果缓冲区满了,标记为就绪
if (*used_size == writer->buffer_size) {
*ready_flag = true;
}
xSemaphoreGive(writer->buffer_mutex);
}
} else {
// 没有可用的缓冲区,短暂休眠
vTaskDelay(pdMS_TO_TICKS(10));
}
}
}
// 写入缓冲区任务
void write_buffer_task(void *pvParameters) {
dual_buffer_writer_t *writer = (dual_buffer_writer_t*)pvParameters;
while (1) {
// 检查哪个缓冲区就绪了
uint8_t *write_buffer = NULL;
size_t write_size = 0;
xSemaphoreTake(writer->buffer_mutex, portMAX_DELAY);
if (writer->buffer_a_ready) {
write_buffer = writer->buffer_a;
write_size = writer->buffer_a_used;
writer->buffer_a_ready = false;
writer->buffer_a_used = 0;
} else if (writer->buffer_b_ready) {
write_buffer = writer->buffer_b;
write_size = writer->buffer_b_used;
writer->buffer_b_ready = false;
writer->buffer_b_used = 0;
}
xSemaphoreGive(writer->buffer_mutex);
if (write_buffer && write_size > 0) {
// 写入闪存
esp_err_t err = esp_ota_write(writer->ota_handle,
write_buffer, write_size);
if (err != ESP_OK) {
ESP_LOGE("DUAL_BUFFER", "Write failed: %s",
esp_err_to_name(err));
break;
}
} else {
// 没有就绪的缓冲区,短暂休眠
vTaskDelay(pdMS_TO_TICKS(10));
}
}
}
7.2 网络优化策略
// 网络优化配置
void configure_network_for_ota(void) {
// 1. 配置TCP/IP参数
esp_netif_init();
// 2. 优化TCP窗口大小
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif) {
esp_netif_set_ip_info(netif, &(esp_netif_ip_info_t){
.ip = { .addr = IPADDR_ANY },
.gw = { .addr = IPADDR_ANY },
.netmask = { .addr = IPADDR_ANY }
});
}
// 3. 配置Wi-Fi为高性能模式
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_WIFI_SSID,
.password = CONFIG_WIFI_PASSWORD,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
// 4. 设置最大传输功率
esp_wifi_set_max_tx_power(84); // +20dBm
// 5. 配置Wi-Fi速率
esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_MCS7_SGI);
// 6. 禁用省电模式
esp_wifi_set_ps(WIFI_PS_NONE);
// 7. 配置LwIP参数
tcpip_adapter_init();
tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
// 8. 优化TCP参数
struct timeval tv;
tv.tv_sec = 30; // 30秒超时
tv.tv_usec = 0;
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock >= 0) {
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
// 启用TCP_NODELAY(禁用Nagle算法)
int flag = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
close(sock);
}
}
八、企业级部署方案
8.1 灰度发布实现
// 灰度发布管理器
typedef struct {
uint8_t release_group; // 发布组
uint8_t release_percentage; // 发布百分比
char release_version[32]; // 发布版本
char release_url[256]; // 固件URL
time_t release_time; // 发布时间
uint32_t device_seed; // 设备种子(用于分组)
bool force_update; // 强制更新
} gray_release_manager_t;
// 检查设备是否在灰度发布组
bool is_device_in_gray_release(gray_release_manager_t *mgr) {
// 使用设备ID或MAC地址生成确定性的分组
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
// 生成设备哈希值
uint32_t device_hash = 0;
for (int i = 0; i < 6; i++) {
device_hash = (device_hash << 5) - device_hash + mac[i];
}
// 计算分组(0-99)
uint8_t device_group = device_hash % 100;
// 检查是否在发布组内
return device_group < mgr->release_percentage;
}
// 灰度发布状态机
typedef enum {
GRAY_STAGE_TESTING = 0, // 测试阶段(内部)
GRAY_STAGE_EARLY = 1, // 早期阶段(10%)
GRAY_STAGE_BROAD = 2, // 广泛阶段(50%)
GRAY_STAGE_FULL = 3, // 全量阶段(100%)
GRAY_STAGE_COMPLETE = 4// 完成阶段
} gray_release_stage_t;
// 执行灰度发布
void execute_gray_release(gray_release_manager_t *mgr) {
gray_release_stage_t current_stage = get_current_release_stage(mgr);
switch (current_stage) {
case GRAY_STAGE_TESTING:
mgr->release_percentage = 5; // 内部测试5%
break;
case GRAY_STAGE_EARLY:
mgr->release_percentage = 10; // 早期用户10%
break;
case GRAY_STAGE_BROAD:
mgr->release_percentage = 50; // 广泛发布50%
break;
case GRAY_STAGE_FULL:
mgr->release_percentage = 100; // 全量发布100%
break;
case GRAY_STAGE_COMPLETE:
// 发布完成,清理状态
cleanup_gray_release(mgr);
return;
}
// 检查设备是否在发布组
if (is_device_in_gray_release(mgr) || mgr->force_update) {
ESP_LOGI("GRAY_RELEASE", "Device selected for gray release (group %d%%)",
mgr->release_percentage);
// 执行OTA更新
start_ota_update(mgr->release_url);
// 上报状态
report_gray_release_status(mgr, "updating");
} else {
ESP_LOGI("GRAY_RELEASE", "Device not in current release group (%d%%)",
mgr->release_percentage);
// 可选:延迟检查
schedule_next_gray_check(mgr, 3600); // 1小时后再次检查
}
}
8.2 设备状态监控系统
// 设备状态监控器
typedef struct {
char device_id[32];
char group_id[32];
char firmware_version[32];
char hardware_version[16];
uint32_t uptime;
uint32_t free_heap;
int8_t wifi_rssi;
uint8_t battery_level;
float temperature;
ota_state_t ota_state;
time_t last_report;
uint32_t report_interval;
esp_mqtt_client_handle_t mqtt_client;
} device_monitor_t;
// 收集设备状态
void collect_device_status(device_monitor_t *monitor) {
// 1. 系统信息
monitor->uptime = xTaskGetTickCount() * portTICK_PERIOD_MS / 1000;
monitor->free_heap = esp_get_free_heap_size();
// 2. 固件信息
esp_app_desc_t app_desc;
esp_ota_get_partition_description(esp_ota_get_running_partition(),
&app_desc);
strlcpy(monitor->firmware_version, app_desc.version,
sizeof(monitor->firmware_version));
// 3. Wi-Fi状态
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
monitor->wifi_rssi = ap_info.rssi;
} else {
monitor->wifi_rssi = 0;
}
// 4. 温度传感器(如果可用)
#ifdef CONFIG_TEMPERATURE_SENSOR_ENABLED
monitor->temperature = read_temperature();
#else
monitor->temperature = 0.0;
#endif
// 5. 电池电量(如果可用)
#ifdef CONFIG_BATTERY_MONITOR_ENABLED
monitor->battery_level = read_battery_level();
#else
monitor->battery_level = 100;
#endif
// 6. OTA状态
monitor->ota_state = get_current_ota_state();
}
// 上报设备状态
void report_device_status(device_monitor_t *monitor) {
cJSON *root = cJSON_CreateObject();
// 基础信息
cJSON_AddStringToObject(root, "device_id", monitor->device_id);
cJSON_AddStringToObject(root, "group_id", monitor->group_id);
cJSON_AddStringToObject(root, "firmware_version", monitor->firmware_version);
cJSON_AddStringToObject(root, "hardware_version", monitor->hardware_version);
// 系统状态
cJSON_AddNumberToObject(root, "uptime", monitor->uptime);
cJSON_AddNumberToObject(root, "free_heap", monitor->free_heap);
cJSON_AddNumberToObject(root, "wifi_rssi", monitor->wifi_rssi);
cJSON_AddNumberToObject(root, "battery_level", monitor->battery_level);
cJSON_AddNumberToObject(root, "temperature", monitor->temperature);
// OTA状态
const char *ota_state_str;
switch (monitor->ota_state) {
case OTA_STATE_IDLE: ota_state_str = "idle"; break;
case OTA_STATE_DOWNLOADING: ota_state_str = "downloading"; break;
case OTA_STATE_SUCCESS: ota_state_str = "success"; break;
case OTA_STATE_FAILED: ota_state_str = "failed"; break;
default: ota_state_str = "unknown";
}
cJSON_AddStringToObject(root, "ota_state", ota_state_str);
// 时间戳
cJSON_AddNumberToObject(root, "timestamp", time(NULL));
// 转换为JSON字符串
char *json_str = cJSON_PrintUnformatted(root);
// 发布到MQTT
char topic[128];
snprintf(topic, sizeof(topic), "devices/%s/status", monitor->device_id);
esp_mqtt_client_publish(monitor->mqtt_client, topic, json_str,
strlen(json_str), 1, 0);
// 清理
free(json_str);
cJSON_Delete(root);
// 更新最后上报时间
monitor->last_report = time(NULL);
}
// 状态监控任务
void device_monitor_task(void *pvParameters) {
device_monitor_t *monitor = (device_monitor_t*)pvParameters;
while (1) {
// 收集状态
collect_device_status(monitor);
// 检查是否需要上报
time_t now = time(NULL);
if (difftime(now, monitor->last_report) >= monitor->report_interval) {
report_device_status(monitor);
}
// 检查是否需要OTA
check_ota_updates(monitor);
// 休眠
vTaskDelay(pdMS_TO_TICKS(10000)); // 10秒
}
}
九、深度故障恢复机制
9.1 三级回滚策略完整实现
// 回滚管理器完整实现
typedef struct {
uint8_t max_boot_attempts; // 最大启动尝试次数
uint8_t current_boot_attempts; // 当前启动尝试次数
bool auto_rollback_enabled; // 自动回滚启用
bool manual_rollback_triggered; // 手动回滚触发
gpio_num_t rollback_gpio; // 回滚GPIO
uint8_t rollback_gpio_level; // 触发电平
uint32_t rollback_timeout_ms; // 回滚超时时间
char rollback_reason[64]; // 回滚原因
time_t rollback_timestamp; // 回滚时间戳
} rollback_manager_v2_t;
// 初始化回滚管理器
void init_rollback_manager_v2(rollback_manager_v2_t *mgr) {
memset(mgr, 0, sizeof(rollback_manager_v2_t));
// 默认配置
mgr->max_boot_attempts = 3;
mgr->auto_rollback_enabled = true;
mgr->manual_rollback_triggered = false;
mgr->rollback_gpio = GPIO_NUM_0;
mgr->rollback_gpio_level = 0;
mgr->rollback_timeout_ms = 30000;
// 从NVS加载配置
nvs_handle_t nvs;
if (nvs_open("rollback_cfg", NVS_READONLY, &nvs) == ESP_OK) {
nvs_get_u8(nvs, "max_attempts", &mgr->max_boot_attempts);
nvs_get_u8(nvs, "auto_enabled", (uint8_t*)&mgr->auto_rollback_enabled);
nvs_get_u32(nvs, "timeout_ms", &mgr->rollback_timeout_ms);
nvs_close(nvs);
}
// 配置GPIO
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << mgr->rollback_gpio),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
// 检查启动状态
check_boot_status(mgr);
}
// 检查启动状态
void check_boot_status(rollback_manager_v2_t *mgr) {
esp_ota_img_states_t ota_state;
const esp_partition_t *running = esp_ota_get_running_partition();
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
// 新固件待验证
// 从NVS读取尝试次数
nvs_handle_t nvs;
if (nvs_open("ota_stats", NVS_READWRITE, &nvs) == ESP_OK) {
nvs_get_u8(nvs, "boot_attempts", &mgr->current_boot_attempts);
mgr->current_boot_attempts++;
nvs_set_u8(nvs, "boot_attempts", mgr->current_boot_attempts);
nvs_commit(nvs);
nvs_close(nvs);
}
ESP_LOGW("ROLLBACK", "Boot attempt %d/%d for new firmware",
mgr->current_boot_attempts, mgr->max_boot_attempts);
if (mgr->current_boot_attempts >= mgr->max_boot_attempts) {
// 超过最大尝试次数,需要回滚
strlcpy(mgr->rollback_reason,
"Max boot attempts exceeded",
sizeof(mgr->rollback_reason));
mgr->rollback_timestamp = time(NULL);
// 执行回滚
perform_rollback_v2(mgr);
}
} else if (ota_state == ESP_OTA_IMG_VALID) {
// 固件验证通过,重置计数器
nvs_handle_t nvs;
if (nvs_open("ota_stats", NVS_READWRITE, &nvs) == ESP_OK) {
nvs_set_u8(nvs, "boot_attempts", 0);
nvs_commit(nvs);
nvs_close(nvs);
}
// 标记固件为有效
esp_ota_mark_app_valid_cancel_rollback();
}
}
}
// 执行回滚操作
esp_err_t perform_rollback_v2(rollback_manager_v2_t *mgr) {
ESP_LOGW("ROLLBACK", "Initiating rollback: %s", mgr->rollback_reason);
// 记录回滚事件
log_rollback_event(mgr);
// 1. 标记当前固件为无效
esp_err_t err = esp_ota_mark_app_invalid_rollback_and_reboot();
if (err != ESP_OK) {
ESP_LOGE("ROLLBACK", "Failed to mark app as invalid: %s",
esp_err_to_name(err));
return err;
}
// 2. 查找可回滚的分区
const esp_partition_t *rollback_partition = find_rollback_partition_v2();
if (rollback_partition == NULL) {
ESP_LOGE("ROLLBACK", "No valid rollback partition found");
return ESP_FAIL;
}
// 3. 设置引导分区
err = esp_ota_set_boot_partition(rollback_partition);
if (err != ESP_OK) {
ESP_LOGE("ROLLBACK", "Failed to set boot partition: %s",
esp_err_to_name(err));
return err;
}
// 4. 发送回滚通知(如果网络可用)
if (is_network_available()) {
send_rollback_notification(mgr, rollback_partition);
}
ESP_LOGI("ROLLBACK", "Rollback to %s scheduled. Rebooting...",
rollback_partition->label);
// 5. 延迟重启
vTaskDelay(pdMS_TO_TICKS(2000));
esp_restart();
return ESP_OK;
}
// 查找回滚分区
const esp_partition_t* find_rollback_partition_v2(void) {
const esp_partition_t *partitions[3] = {NULL};
int count = 0;
// 查找所有可用的APP分区
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_ANY,
NULL);
for (; it != NULL; it = esp_partition_next(it)) {
const esp_partition_t *p = esp_partition_get(it);
// 跳过当前运行的分区
if (p == esp_ota_get_running_partition()) {
continue;
}
// 检查分区状态
esp_ota_img_states_t state;
if (esp_ota_get_state_partition(p, &state) == ESP_OK) {
if (state == ESP_OTA_IMG_VALID) {
partitions[count++] = p;
if (count >= 3) break;
}
}
}
esp_partition_iterator_release(it);
// 选择回滚分区(优先选择最近的OTA分区)
if (count > 0) {
// 按类型排序:Factory > OTA_1 > OTA_0
for (int i = 0; i < count; i++) {
if (partitions[i]->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
return partitions[i];
}
}
for (int i = 0; i < count; i++) {
if (partitions[i]->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_1) {
return partitions[i];
}
}
return partitions[0]; // 返回第一个可用的
}
return NULL;
}
// 记录回滚事件
void log_rollback_event(rollback_manager_v2_t *mgr) {
nvs_handle_t nvs;
if (nvs_open("rollback_log", NVS_READWRITE, &nvs) == ESP_OK) {
// 读取历史记录
char log_key[16];
uint8_t log_count = 0;
nvs_get_u8(nvs, "count", &log_count);
// 保存新记录
snprintf(log_key, sizeof(log_key), "event_%d", log_count % 10);
cJSON *event = cJSON_CreateObject();
cJSON_AddStringToObject(event, "reason", mgr->rollback_reason);
cJSON_AddNumberToObject(event, "timestamp", mgr->rollback_timestamp);
cJSON_AddNumberToObject(event, "attempts", mgr->current_boot_attempts);
char *event_str = cJSON_PrintUnformatted(event);
nvs_set_str(nvs, log_key, event_str);
free(event_str);
cJSON_Delete(event);
// 更新计数
log_count++;
nvs_set_u8(nvs, "count", log_count);
nvs_commit(nvs);
nvs_close(nvs);
}
}
9.2 断点续传完整实现
// 断点续传管理器
typedef struct {
char url[256]; // 下载URL
uint32_t total_size; // 文件总大小
uint32_t downloaded_size; // 已下载大小
uint32_t file_crc32; // 文件CRC32
uint8_t partition_slot; // 目标分区槽位
time_t start_time; // 开始时间
time_t last_update; // 最后更新时间
char etag[64]; // ETag(用于验证)
char last_modified[64]; // Last-Modified
bool resume_supported; // 是否支持断点续传
} resume_manager_t;
// 初始化断点续传
esp_err_t init_resume_download(const char *url, resume_manager_t *resume) {
memset(resume, 0, sizeof(resume_manager_t));
strlcpy(resume->url, url, sizeof(resume->url));
// 从NVS加载之前的进度
nvs_handle_t nvs;
esp_err_t err = nvs_open("ota_resume", NVS_READWRITE, &nvs);
if (err == ESP_OK) {
// 检查是否有相同URL的进度
char saved_url[256];
size_t url_len = sizeof(saved_url);
if (nvs_get_str(nvs, "url", saved_url, &url_len) == ESP_OK) {
if (strcmp(saved_url, url) == 0) {
// 恢复进度
nvs_get_u32(nvs, "downloaded", &resume->downloaded_size);
nvs_get_u32(nvs, "total", &resume->total_size);
nvs_get_u8(nvs, "slot", &resume->partition_slot);
nvs_get_u32(nvs, "crc32", &resume->file_crc32);
size_t etag_len = sizeof(resume->etag);
nvs_get_str(nvs, "etag", resume->etag, &etag_len);
size_t mod_len = sizeof(resume->last_modified);
nvs_get_str(nvs, "lastmod", resume->last_modified, &mod_len);
ESP_LOGI("RESUME", "Resuming download from %u bytes",
resume->downloaded_size);
}
}
nvs_close(nvs);
}
// 检查服务器是否支持断点续传
check_resume_support(resume);
return ESP_OK;
}
// 检查服务器支持
bool check_resume_support(resume_manager_t *resume) {
esp_http_client_config_t config = {
.url = resume->url,
.method = HTTP_METHOD_HEAD,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) return false;
// 设置超时
esp_http_client_set_timeout_ms(client, 5000);
// 执行HEAD请求
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
// 检查Accept-Ranges头
char accept_ranges[32] = {0};
esp_http_client_get_header(client, "Accept-Ranges",
accept_ranges, sizeof(accept_ranges));
if (strstr(accept_ranges, "bytes") != NULL) {
resume->resume_supported = true;
}
// 获取文件大小
char content_length[32] = {0};
esp_http_client_get_header(client, "Content-Length",
content_length, sizeof(content_length));
if (content_length[0] != '\0') {
resume->total_size = atoi(content_length);
}
// 获取ETag和Last-Modified
esp_http_client_get_header(client, "ETag",
resume->etag, sizeof(resume->etag));
esp_http_client_get_header(client, "Last-Modified",
resume->last_modified,
sizeof(resume->last_modified));
}
esp_http_client_cleanup(client);
return resume->resume_supported;
}
// 执行断点续传下载
esp_err_t perform_resume_download(resume_manager_t *resume,
esp_ota_handle_t ota_handle) {
esp_http_client_config_t config = {
.url = resume->url,
.method = HTTP_METHOD_GET,
.timeout_ms = 30000,
.buffer_size = 4096,
.buffer_size_tx = 4096,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) return ESP_FAIL;
// 设置Range头(如果需要)
if (resume->resume_supported && resume->downloaded_size > 0) {
char range_header[64];
snprintf(range_header, sizeof(range_header),
"bytes=%u-", resume->downloaded_size);
esp_http_client_set_header(client, "Range", range_header);
ESP_LOGI("RESUME", "Resuming from byte %u", resume->downloaded_size);
}
// 执行请求
esp_err_t err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
esp_http_client_cleanup(client);
return err;
}
// 读取响应头
int status_code = esp_http_client_fetch_headers(client);
if (status_code == 206) { // Partial Content
ESP_LOGI("RESUME", "Server supports partial content");
} else if (status_code == 200) { // OK (从头开始)
if (resume->downloaded_size > 0) {
ESP_LOGW("RESUME", "Server doesn't support resume, starting over");
resume->downloaded_size = 0;
}
} else {
ESP_LOGE("RESUME", "HTTP error: %d", status_code);
esp_http_client_close(client);
esp_http_client_cleanup(client);
return ESP_FAIL;
}
// 下载数据
uint8_t buffer[4096];
int read_len;
uint32_t total_downloaded = resume->downloaded_size;
resume->start_time = time(NULL);
while (1) {
read_len = esp_http_client_read(client, (char*)buffer, sizeof(buffer));
if (read_len <= 0) {
break;
}
// 写入OTA分区
err = esp_ota_write(ota_handle, buffer, read_len);
if (err != ESP_OK) {
ESP_LOGE("RESUME", "OTA write failed: %s", esp_err_to_name(err));
break;
}
total_downloaded += read_len;
// 定期保存进度
if (total_downloaded % (1024 * 1024) == 0) { // 每1MB
save_download_progress(resume, total_downloaded, ota_handle);
// 计算下载速度
time_t now = time(NULL);
double elapsed = difftime(now, resume->start_time);
double speed = total_downloaded / elapsed / 1024; // KB/s
ESP_LOGI("RESUME", "Progress: %u/%u (%.1f%%) Speed: %.1f KB/s",
total_downloaded, resume->total_size,
(total_downloaded * 100.0) / resume->total_size,
speed);
}
}
// 检查下载完成
if (total_downloaded >= resume->total_size) {
ESP_LOGI("RESUME", "Download completed: %u bytes", total_downloaded);
// 清除进度保存
clear_download_progress();
err = ESP_OK;
} else {
// 保存进度以便恢复
save_download_progress(resume, total_downloaded, ota_handle);
ESP_LOGW("RESUME", "Download interrupted at %u bytes", total_downloaded);
err = ESP_FAIL;
}
esp_http_client_close(client);
esp_http_client_cleanup(client);
return err;
}
// 保存下载进度
void save_download_progress(resume_manager_t *resume,
uint32_t downloaded,
esp_ota_handle_t ota_handle) {
nvs_handle_t nvs;
if (nvs_open("ota_resume", NVS_READWRITE, &nvs) == ESP_OK) {
// 保存基本信息
nvs_set_str(nvs, "url", resume->url);
nvs_set_u32(nvs, "downloaded", downloaded);
nvs_set_u32(nvs, "total", resume->total_size);
// 保存OTA句柄信息(分区槽位)
const esp_partition_t *target = esp_ota_get_partition(ota_handle);
if (target) {
uint8_t slot = 0;
if (target->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) slot = 0;
else if (target->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_1) slot = 1;
nvs_set_u8(nvs, "slot", slot);
}
// 保存验证信息
if (resume->etag[0] != '\0') {
nvs_set_str(nvs, "etag", resume->etag);
}
if (resume->last_modified[0] != '\0') {
nvs_set_str(nvs, "lastmod", resume->last_modified);
}
// 保存CRC32(如果已计算)
if (resume->file_crc32 != 0) {
nvs_set_u32(nvs, "crc32", resume->file_crc32);
}
nvs_commit(nvs);
nvs_close(nvs);
resume->last_update = time(NULL);
}
}
// 清除下载进度
void clear_download_progress(void) {
nvs_handle_t nvs;
if (nvs_open("ota_resume", NVS_READWRITE, &nvs) == ESP_OK) {
// 删除所有相关键
nvs_erase_key(nvs, "url");
nvs_erase_key(nvs, "downloaded");
nvs_erase_key(nvs, "total");
nvs_erase_key(nvs, "slot");
nvs_erase_key(nvs, "etag");
nvs_erase_key(nvs, "lastmod");
nvs_erase_key(nvs, "crc32");
nvs_commit(nvs);
nvs_close(nvs);
}
}
十、射频敏感场景优化
10.1 Wi-Fi稳定性增强完整方案
// Wi-Fi优化管理器
typedef struct {
wifi_config_t wifi_config;
wifi_ps_type_t power_save_mode;
int8_t tx_power;
wifi_bandwidth_t bandwidth;
wifi_second_chan_t second_channel;
wifi_auth_mode_t auth_mode;
wifi_cipher_type_t cipher_type;
uint8_t retry_count;
uint32_t reconnect_interval;
time_t last_disconnect;
uint32_t disconnect_count;
bool static_ip_enabled;
tcpip_adapter_ip_info_t static_ip;
} wifi_optimizer_t;
// 初始化Wi-Fi优化
void init_wifi_optimizer(wifi_optimizer_t *optimizer) {
memset(optimizer, 0, sizeof(wifi_optimizer_t));
// 默认配置
optimizer->power_save_mode = WIFI_PS_NONE; // 禁用省电模式
optimizer->tx_power = 84; // +20dBm
optimizer->bandwidth = WIFI_BW_HT40; // 40MHz带宽
optimizer->retry_count = 5; // 重试次数
optimizer->reconnect_interval = 5000; // 5秒重连间隔
// 静态IP配置(可选)
optimizer->static_ip_enabled = false;
IP4_ADDR(&optimizer->static_ip.ip, 192, 168, 1, 100);
IP4_ADDR(&optimizer->static_ip.gw, 192, 168, 1, 1);
IP4_ADDR(&optimizer->static_ip.netmask, 255, 255, 255, 0);
// 从NVS加载配置
load_wifi_config_from_nvs(optimizer);
}
// 应用Wi-Fi优化配置
void apply_wifi_optimization(wifi_optimizer_t *optimizer) {
ESP_LOGI("WIFI_OPT", "Applying Wi-Fi optimization settings");
// 1. 配置电源模式
esp_wifi_set_ps(optimizer->power_save_mode);
// 2. 设置发射功率
esp_wifi_set_max_tx_power(optimizer->tx_power);
// 3. 配置带宽
esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, optimizer->bandwidth);
// 4. 配置重传机制
wifi_sta_config_t *sta_config = &optimizer->wifi_config.sta;
sta_config->listen_interval = 3; // 监听间隔
sta_config->threshold.authmode = optimizer->auth_mode;
// 5. 配置快速扫描
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time.active.min = 100,
.scan_time.active.max = 300,
};
esp_wifi_scan_start(&scan_config, false);
// 6. 配置LwIP参数
if (optimizer->static_ip_enabled) {
tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &optimizer->static_ip);
}
// 7. 配置TCP窗口大小
optimize_tcp_parameters();
// 8. 启用Wi-Fi事件监控
register_wifi_event_handlers(optimizer);
}
// 优化TCP参数
void optimize_tcp_parameters(void) {
// 调整LwIP内存池大小
#ifdef CONFIG_LWIP_TCP
// 增加TCP窗口大小
#define TCP_WND 32768
#define TCP_MSS 1460
// 调整TCP缓冲池
#define MEM_SIZE 16000
#define PBUF_POOL_SIZE 16
#define PBUF_POOL_BUFSIZE 1700
#endif
// 配置TCP重传参数
struct timeval tv;
tv.tv_sec = 30;
tv.tv_usec = 0;
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock >= 0) {
// 设置套接字选项
int keep_alive = 1;
int keep_idle = 5; // 5秒后开始发送保活包
int keep_interval = 5; // 5秒发送一次
int keep_count = 3; // 最多3次
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keep_interval, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keep_count, sizeof(int));
// 禁用Nagle算法
int no_delay = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &no_delay, sizeof(int));
close(sock);
}
}
// Wi-Fi事件处理器
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
wifi_optimizer_t *optimizer = (wifi_optimizer_t*)arg;
switch (event_id) {
case WIFI_EVENT_STA_START:
ESP_LOGI("WIFI", "Wi-Fi STA started");
break;
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI("WIFI", "Connected to AP");
optimizer->disconnect_count = 0;
break;
case WIFI_EVENT_STA_DISCONNECTED: {
wifi_event_sta_disconnected_t* event =
(wifi_event_sta_disconnected_t*) event_data;
optimizer->last_disconnect = time(NULL);
optimizer->disconnect_count++;
ESP_LOGW("WIFI", "Disconnected from AP (reason: %d)", event->reason);
// 根据断开原因调整策略
switch (event->reason) {
case WIFI_REASON_AUTH_EXPIRE:
case WIFI_REASON_AUTH_FAIL:
// 认证问题,可能需要重新配置
ESP_LOGW("WIFI", "Authentication issue, checking credentials");
break;
case WIFI_REASON_ASSOC_LEAVE:
// 正常离开
break;
default:
// 其他原因,尝试重连
if (optimizer->disconnect_count < optimizer->retry_count) {
ESP_LOGI("WIFI", "Reconnecting in %d ms...",
optimizer->reconnect_interval);
vTaskDelay(pdMS_TO_TICKS(optimizer->reconnect_interval));
esp_wifi_connect();
} else {
ESP_LOGE("WIFI", "Max reconnection attempts reached");
}
}
break;
}
case WIFI_EVENT_STA_AUTHMODE_CHANGE:
ESP_LOGI("WIFI", "Auth mode changed");
break;
}
}
// 动态调整Wi-Fi参数
void dynamic_wifi_adjustment(wifi_optimizer_t *optimizer, int8_t rssi) {
// 根据RSSI动态调整参数
if (rssi > -50) {
// 信号强,可以使用高性能模式
optimizer->tx_power = 78; // +19dBm
optimizer->bandwidth = WIFI_BW_HT40;
} else if (rssi > -70) {
// 信号中等
optimizer->tx_power = 82; // +20dBm
optimizer->bandwidth = WIFI_BW_HT20;
} else {
// 信号弱,使用保守设置
optimizer->tx_power = 84; // +20dBm(最大)
optimizer->bandwidth = WIFI_BW_HT20;
optimizer->retry_count = 8; // 增加重试次数
}
// 应用调整
esp_wifi_set_max_tx_power(optimizer->tx_power);
esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, optimizer->bandwidth);
ESP_LOGD("WIFI_ADJUST", "RSSI: %d, TX Power: %d, BW: %d",
rssi, optimizer->tx_power, optimizer->bandwidth);
}
// Wi-Fi健康检查
bool check_wifi_health(wifi_optimizer_t *optimizer) {
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK) {
return false;
}
// 检查RSSI
if (ap_info.rssi < -80) {
ESP_LOGW("WIFI_HEALTH", "Weak signal: %d dBm", ap_info.rssi);
return false;
}
// 检查信噪比(如果有)
if (ap_info.primary > 0 && ap_info.primary <= 14) {
// 2.4GHz频段,检查干扰
if (ap_info.rssi > -60 && ap_info.rssi - ap_info.noise < 20) {
ESP_LOGW("WIFI_HEALTH", "Poor SNR: %d dB",
ap_info.rssi - ap_info.noise);
return false;
}
}
// 检查连接稳定性
time_t now = time(NULL);
if (optimizer->disconnect_count > 3 &&
difftime(now, optimizer->last_disconnect) < 300) {
ESP_LOGW("WIFI_HEALTH", "Unstable connection (%d disconnects)",
optimizer->disconnect_count);
return false;
}
return true;
}
10.2 分时段升级策略实现
// 分时段升级调度器
typedef struct {
uint8_t upgrade_window_start; // 升级窗口开始时间(小时)
uint8_t upgrade_window_end; // 升级窗口结束时间(小时)
uint8_t max_duration_hours; // 最大升级时长(小时)
bool weekend_enabled; // 是否允许周末升级
bool holiday_excluded; // 是否排除节假日
char timezone[32]; // 时区
struct tm last_upgrade_time;// 上次升级时间
struct tm next_upgrade_time;// 下次计划升级时间
uint32_t upgrade_count; // 升级次数统计
SemaphoreHandle_t mutex; // 互斥锁
} upgrade_scheduler_t;
// 初始化升级调度器
void init_upgrade_scheduler(upgrade_scheduler_t *scheduler) {
memset(scheduler, 0, sizeof(upgrade_scheduler_t));
// 默认配置:凌晨1点到5点
scheduler->upgrade_window_start = 1;
scheduler->upgrade_window_end = 5;
scheduler->max_duration_hours = 2;
scheduler->weekend_enabled = true;
scheduler->holiday_excluded = true;
strlcpy(scheduler->timezone, "CST-8", sizeof(scheduler->timezone));
// 从NVS加载配置
nvs_handle_t nvs;
if (nvs_open("upgrade_sched", NVS_READONLY, &nvs) == ESP_OK) {
nvs_get_u8(nvs, "window_start", &scheduler->upgrade_window_start);
nvs_get_u8(nvs, "window_end", &scheduler->upgrade_window_end);
nvs_get_u8(nvs, "max_duration", &scheduler->max_duration_hours);
nvs_get_u8(nvs, "weekend_enabled", (uint8_t*)&scheduler->weekend_enabled);
nvs_get_u8(nvs, "holiday_excluded", (uint8_t*)&scheduler->holiday_excluded);
nvs_close(nvs);
}
scheduler->mutex = xSemaphoreCreateMutex();
// 初始化时间
initialize_system_time(scheduler->timezone);
// 计算下次升级时间
calculate_next_upgrade_time(scheduler);
}
// 检查当前是否在升级窗口内
bool is_in_upgrade_window(upgrade_scheduler_t *scheduler) {
struct tm timeinfo;
if (!get_local_time(&timeinfo, scheduler->timezone)) {
return false;
}
// 检查是否在允许的时间窗口内
int current_hour = timeinfo.tm_hour;
if (current_hour < scheduler->upgrade_window_start ||
current_hour >= scheduler->upgrade_window_end) {
return false;
}
// 检查周末是否允许
if (!scheduler->weekend_enabled) {
// 0=周日, 6=周六
if (timeinfo.tm_wday == 0 || timeinfo.tm_wday == 6) {
return false;
}
}
// 检查节假日(如果有节假日数据)
if (scheduler->holiday_excluded && is_holiday(&timeinfo)) {
return false;
}
// 检查设备是否空闲
if (!is_device_idle()) {
return false;
}
// 检查网络条件
if (!is_network_good_for_upgrade()) {
return false;
}
return true;
}
// 计算下次升级时间
void calculate_next_upgrade_time(upgrade_scheduler_t *scheduler) {
struct tm timeinfo;
get_local_time(&timeinfo, scheduler->timezone);
// 默认设置为明天的升级窗口开始时间
struct tm next_time = timeinfo;
next_time.tm_mday += 1;
next_time.tm_hour = scheduler->upgrade_window_start;
next_time.tm_min = 0;
next_time.tm_sec = 0;
// 调整周末和节假日
mktime(&next_time);
while (1) {
// 检查周末
if (!scheduler->weekend_enabled &&
(next_time.tm_wday == 0 || next_time.tm_wday == 6)) {
next_time.tm_mday += 1;
mktime(&next_time);
continue;
}
// 检查节假日
if (scheduler->holiday_excluded && is_holiday(&next_time)) {
next_time.tm_mday += 1;
mktime(&next_time);
continue;
}
break;
}
scheduler->next_upgrade_time = next_time;
ESP_LOGI("UPGRADE_SCHED", "Next upgrade scheduled for: %04d-%02d-%02d %02d:%02d",
next_time.tm_year + 1900, next_time.tm_mon + 1, next_time.tm_mday,
next_time.tm_hour, next_time.tm_min);
}
// 升级调度任务
void upgrade_scheduler_task(void *pvParameters) {
upgrade_scheduler_t *scheduler = (upgrade_scheduler_t*)pvParameters;
while (1) {
// 检查是否有待处理的升级
bool pending_upgrade = check_pending_upgrade();
if (pending_upgrade) {
// 检查是否在升级窗口内
if (is_in_upgrade_window(scheduler)) {
ESP_LOGI("UPGRADE_SCHED", "Starting scheduled upgrade");
// 执行升级
perform_scheduled_upgrade(scheduler);
// 更新统计
xSemaphoreTake(scheduler->mutex, portMAX_DELAY);
scheduler->upgrade_count++;
scheduler->last_upgrade_time = get_current_time();
xSemaphoreGive(scheduler->mutex);
// 计算下次升级时间
calculate_next_upgrade_time(scheduler);
} else {
// 不在升级窗口内,计算等待时间
time_t now = time(NULL);
struct tm *next_tm = &scheduler->next_upgrade_time;
time_t next_time = mktime(next_tm);
double seconds_to_wait = difftime(next_time, now);
if (seconds_to_wait > 0) {
ESP_LOGI("UPGRADE_SCHED", "Waiting %.0f seconds for next upgrade window",
seconds_to_wait);
// 休眠到升级窗口
vTaskDelay(pdMS_TO_TICKS((uint32_t)(seconds_to_wait * 1000)));
} else {
// 已经过了计划时间,立即检查
vTaskDelay(pdMS_TO_TICKS(60000)); // 1分钟后重试
}
}
} else {
// 没有待处理的升级,定期检查
vTaskDelay(pdMS_TO_TICKS(300000)); // 5分钟
}
}
}
// 执行计划升级
void perform_scheduled_upgrade(upgrade_scheduler_t *scheduler) {
// 记录开始时间
time_t start_time = time(NULL);
// 获取升级信息
upgrade_info_t upgrade_info;
if (!get_upgrade_info(&upgrade_info)) {
ESP_LOGE("UPGRADE_SCHED", "Failed to get upgrade info");
return;
}
// 检查固件大小
if (upgrade_info.firmware_size > get_available_space()) {
ESP_LOGE("UPGRADE_SCHED", "Insufficient space for upgrade");
return;
}
// 执行升级
esp_err_t err = perform_ota_update(upgrade_info.url,
upgrade_info.firmware_size);
// 检查升级时长
time_t end_time = time(NULL);
double duration_hours = difftime(end_time, start_time) / 3600.0;
if (duration_hours > scheduler->max_duration_hours) {
ESP_LOGW("UPGRADE_SCHED", "Upgrade took %.1f hours, exceeding limit of %d hours",
duration_hours, scheduler->max_duration_hours);
// 记录超时事件
log_upgrade_timeout(duration_hours);
}
if (err == ESP_OK) {
ESP_LOGI("UPGRADE_SCHED", "Scheduled upgrade completed successfully");
// 上报成功
report_upgrade_result("success", upgrade_info.version, duration_hours);
} else {
ESP_LOGE("UPGRADE_SCHED", "Scheduled upgrade failed: %s",
esp_err_to_name(err));
// 上报失败
report_upgrade_result("failed", upgrade_info.version, duration_hours);
// 检查是否需要重试
if (should_retry_upgrade(err)) {
schedule_upgrade_retry(scheduler);
}
}
}
// 检查设备是否空闲
bool is_device_idle(void) {
// 检查CPU使用率
uint32_t idle_time = xTaskGetIdleRunTimeCounter();
uint32_t total_time = xTaskGetTickCount() * portTICK_PERIOD_MS;
double cpu_usage = 100.0 - ((idle_time * 100.0) / total_time);
if (cpu_usage > 30.0) { // CPU使用率超过30%
ESP_LOGD("DEVICE_IDLE", "CPU usage too high: %.1f%%", cpu_usage);
return false;
}
// 检查内存使用
uint32_t free_heap = esp_get_free_heap_size();
uint32_t min_heap = esp_get_minimum_free_heap_size();
if (free_heap < 20000) { // 空闲堆内存小于20KB
ESP_LOGD("DEVICE_IDLE", "Free heap too low: %u bytes", free_heap);
return false;
}
// 检查网络活动
if (is_network_busy()) {
ESP_LOGD("DEVICE_IDLE", "Network is busy");
return false;
}
// 检查业务逻辑(如果有)
#ifdef CONFIG_BUSINESS_LOGIC_ENABLED
if (is_business_critical_operation()) {
ESP_LOGD("DEVICE_IDLE", "Business critical operation in progress");
return false;
}
#endif
return true;
}
// 检查网络条件
bool is_network_good_for_upgrade(void) {
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) != ESP_OK) {
return false;
}
// 检查信号强度
if (ap_info.rssi < -75) { // RSSI小于-75dBm
ESP_LOGD("NETWORK_CHECK", "Signal too weak: %d dBm", ap_info.rssi);
return false;
}
// 检查网络延迟(简单ping测试)
if (!ping_test("8.8.8.8", 3, 1000)) {
ESP_LOGD("NETWORK_CHECK", "Network latency too high");
return false;
}
// 检查下载速度(如果可能)
if (!check_download_speed(1000000)) { // 需要至少1Mbps
ESP_LOGD("NETWORK_CHECK", "Download speed too low");
return false;
}
return true;
}
十一、行业最佳实践
11.1 企业级OTA架构设计
11.2 能耗敏感设备优化
// 低功耗OTA管理器
typedef struct {
uint8_t battery_level; // 电池电量
uint8_t battery_threshold; // 升级电量阈值
bool charging; // 是否在充电
uint32_t download_chunk_size; // 下载分块大小
uint32_t sleep_interval; // 休眠间隔
wifi_ps_type_t wifi_power_save; // Wi-Fi省电模式
uint32_t max_download_time; // 最大下载时间
time_t optimal_upgrade_time; // 最佳升级时间
bool scheduled_upgrade; // 是否计划升级
} low_power_ota_manager_t;
// 初始化低功耗OTA
void init_low_power_ota(low_power_ota_manager_t *manager) {
memset(manager, 0, sizeof(low_power_ota_manager_t));
// 默认配置
manager->battery_threshold = 30; // 30%电量阈值
manager->download_chunk_size = 65536; // 64KB分块
manager->sleep_interval = 1000; // 1秒休眠
manager->wifi_power_save = WIFI_PS_MIN_MODEM; // 最小省电模式
manager->max_download_time = 300; // 5分钟最大下载时间
// 检测电池状态
manager->battery_level = read_battery_level();
manager->charging = is_charging();
ESP_LOGI("LOW_POWER_OTA", "Battery: %d%%%s",
manager->battery_level,
manager->charging ? " (Charging)" : "");
}
// 检查是否可以执行低功耗OTA
bool can_perform_low_power_ota(low_power_ota_manager_t *manager) {
// 检查电池电量
if (!manager->charging && manager->battery_level < manager->battery_threshold) {
ESP_LOGW("LOW_POWER_OTA", "Battery too low for OTA: %d%%",
manager->battery_level);
return false;
}
// 检查充电状态(如果电量低但正在充电,可以等待)
if (manager->battery_level < manager->battery_threshold && manager->charging) {
ESP_LOGI("LOW_POWER_OTA", "Waiting for battery to charge...");
// 等待充电到阈值
while (manager->battery_level < manager->battery_threshold) {
vTaskDelay(pdMS_TO_TICKS(60000)); // 每分钟检查一次
manager->battery_level = read_battery_level();
}
}
// 检查网络连接
if (!is_wifi_connected()) {
ESP_LOGW("LOW_POWER_OTA", "Wi-Fi not connected");
return false;
}
// 检查固件大小(估算下载时间)
uint32_t firmware_size = get_firmware_size();
uint32_t estimated_time = firmware_size / 50000; // 假设50KB/s速度
if (estimated_time > manager->max_download_time) {
ESP_LOGW("LOW_POWER_OTA", "Estimated download time too long: %d seconds",
estimated_time);
return false;
}
return true;
}
// 执行低功耗OTA
esp_err_t perform_low_power_ota(low_power_ota_manager_t *manager,
const char *firmware_url) {
ESP_LOGI("LOW_POWER_OTA", "Starting low-power OTA update");
// 1. 配置低功耗Wi-Fi模式
esp_wifi_set_ps(manager->wifi_power_save);
// 2. 获取固件信息
uint32_t firmware_size = get_remote_file_size(firmware_url);
if (firmware_size == 0) {
return ESP_FAIL;
}
// 3. 分块下载
esp_err_t err = ESP_OK;
uint32_t downloaded = 0;
uint8_t *buffer = (uint8_t*)malloc(manager->download_chunk_size);
if (!buffer) {
return ESP_ERR_NO_MEM;
}
// 4. 分块下载循环
while (downloaded < firmware_size) {
// 检查电池电量(如果在执行过程中电量下降)
if (!manager->charging) {
manager->battery_level = read_battery_level();
if (manager->battery_level < 10) { // 紧急阈值
ESP_LOGE("LOW_POWER_OTA", "Battery critical, aborting OTA");
err = ESP_FAIL;
break;
}
}
// 计算本次下载的大小
uint32_t chunk_size = manager->download_chunk_size;
if (downloaded + chunk_size > firmware_size) {
chunk_size = firmware_size - downloaded;
}
// 下载数据块
err = download_chunk(firmware_url, downloaded, chunk_size, buffer);
if (err != ESP_OK) {
break;
}
// 写入OTA分区
// ... OTA写入代码 ...
downloaded += chunk_size;
// 休眠以节省功耗
vTaskDelay(pdMS_TO_TICKS(manager->sleep_interval));
// 可选:进入轻睡眠
#ifdef CONFIG_PM_ENABLE
esp_light_sleep_start();
#endif
}
free(buffer);
// 恢复Wi-Fi模式
esp_wifi_set_ps(WIFI_PS_NONE);
return err;
}
// 智能充电检测
void smart_charging_detection(low_power_ota_manager_t *manager) {
// 检测充电器类型
charging_type_t charger_type = detect_charger_type();
switch (charger_type) {
case CHARGER_TYPE_SLOW:
// 慢速充电,使用更保守的策略
manager->battery_threshold = 50;
manager->download_chunk_size = 32768; // 32KB
break;
case CHARGER_TYPE_FAST:
// 快速充电,可以使用更激进的策略
manager->battery_threshold = 20;
manager->download_chunk_size = 131072; // 128KB
break;
case CHARGER_TYPE_WIRELESS:
// 无线充电,考虑效率
manager->battery_threshold = 40;
manager->download_chunk_size = 65536; // 64KB
break;
default:
// 未知充电器,使用保守策略
manager->battery_threshold = 50;
manager->download_chunk_size = 32768;
break;
}
ESP_LOGI("LOW_POWER_OTA", "Charger type: %d, threshold: %d%%, chunk: %dKB",
charger_type, manager->battery_threshold,
manager->download_chunk_size / 1024);
}
11.3 大规模部署建议
// 大规模部署配置
typedef struct {
uint32_t device_count; // 设备总数
uint32_t batch_size; // 批次大小
uint32_t batch_interval; // 批次间隔(秒)
char cdn_url[256]; // CDN URL
char regional_cdn[5][256]; // 区域CDN
bool load_balancing; // 是否负载均衡
bool geolocation_routing; // 是否地理位置路由
uint32_t retry_policy; // 重试策略
uint32_t timeout_policy; // 超时策略
char monitoring_url[256]; // 监控URL
char analytics_url[256]; // 分析URL
} mass_deployment_config_t;
// 大规模OTA调度
void mass_ota_scheduler(mass_deployment_config_t *config,
const char *firmware_version) {
ESP_LOGI("MASS_DEPLOY", "Starting mass deployment of version %s",
firmware_version);
// 1. 计算批次
uint32_t total_batches = (config->device_count + config->batch_size - 1) /
config->batch_size;
ESP_LOGI("MASS_DEPLOY", "Total devices: %d, Batch size: %d, Batches: %d",
config->device_count, config->batch_size, total_batches);
// 2. 分批执行
for (uint32_t batch = 0; batch < total_batches; batch++) {
uint32_t start_device = batch * config->batch_size;
uint32_t end_device = min((batch + 1) * config->batch_size,
config->device_count);
ESP_LOGI("MASS_DEPLOY", "Starting batch %d (devices %d-%d)",
batch + 1, start_device + 1, end_device);
// 3. 通知本批次设备开始升级
notify_devices_for_upgrade(start_device, end_device, firmware_version);
// 4. 监控本批次升级进度