- 新增ota目录和esp32_ota_core.cpp实现ESP32 OTA核心功能,支持begin/write/finalize操作 - 修改CMakeLists.txt添加ota源文件和包含目录 - 重构ble_ota.cpp将OTA写入/收尾逻辑委托给Esp32OtaCore,移除mbedtls直接引用 - 新增BLE_OTA_CTRL_RESTART_ESP32(0xFA)命令用于重启ESP32设备 - 更新BLE_OTA_CTRL_TRIGGER_ESP32(0xF8)逻辑,要求前端提供SHA-256校验并比对 - 添加SHA-256校验不匹配错误码BLE_OTA_FINISH_ERR_SHA_MISMATCH(0x85) - 优化OTA收尾流程,设备端读取分区计算SHA-256并与前端提供的哈希比对 - 添加BLE_OTA_NOTIFY_SHA256通知类型用于上报设备端计算的SHA-256值 - 更新app_config.h中的BLE OTA命令描述文档 - 移除BleOta类中的OTA状态变量,仅保留BLE传输层相关状态 - 更新版本构建时间和Git哈希信息
305 lines
14 KiB
Python
305 lines
14 KiB
Python
import sys
|
||
|
||
with open('main/server/ble_ota.cpp','r',encoding='utf-8-sig') as f:
|
||
data=f.read()
|
||
|
||
# 1. 添加 SHA_MISMATCH 错误码宏
|
||
old='#define BLE_OTA_FINISH_ERR_GD32_SIZE 0x84 // GD32 固件超出分区大小'
|
||
new='#define BLE_OTA_FINISH_ERR_GD32_SIZE 0x84 // GD32 固件超出分区大小\n#define BLE_OTA_FINISH_ERR_SHA_MISMATCH 0x85 // ESP32 OTA SHA-256 不匹配'
|
||
assert old in data, 'macro old not found'
|
||
data=data.replace(old,new)
|
||
|
||
# 2. 修改 ble_esp32_finish_and_reboot_task 接受 frontend SHA 参数
|
||
old_task='''static void ble_esp32_finish_and_reboot_task(void *pvParameters)
|
||
{
|
||
(void)pvParameters;
|
||
DEBUG_INFO(TAG, "ESP32 OTA 异步收尾任务启动");
|
||
|
||
Esp32OtaCore &core = Esp32OtaCore::instance();
|
||
|
||
// 补发最终 ACK,让前端尽快得知最后一包已收到
|
||
if (BleOta::instance().m_esp32LastSeq != 0xFFFFU) {
|
||
sendWindowAck(BleOta::instance().m_esp32LastCmd,
|
||
BleOta::instance().m_esp32LastSeq,
|
||
(uint32_t)core.getReceived());
|
||
DEBUG_INFO(TAG, "ESP32 最终 ACK 已补发: seq=%u", BleOta::instance().m_esp32LastSeq);
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(150));
|
||
|
||
if (!core.isActive()) {
|
||
DEBUG_WARN(TAG, "异步收尾时 OTA 状态无效,放弃重启");
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
vTaskDelete(NULL);
|
||
return;
|
||
}
|
||
|
||
DEBUG_INFO(TAG, "ESP32 OTA 收尾: 共 %u 字节", (unsigned)core.getReceived());
|
||
|
||
// 通知前端:开始 finalize
|
||
sendFinishEvent(BLE_OTA_FINISH_BEGIN_OTA_END, 0);
|
||
|
||
// 委托核心完成: esp_ota_end → SHA 计算 → set_boot_partition → esp_restart
|
||
// BLE 不传前端 SHA(BLE 协议无 finalize 前校验机制),直接应用;SHA 通过 0xC1 上报供前端比对
|
||
char device_sha_hex[65] = {0};
|
||
esp_err_t err = core.finalize(/*frontend_sha_hex=*/nullptr,
|
||
/*reboot=*/true,
|
||
device_sha_hex);
|
||
// reboot=true 时 finalize 正常不会返回(core 内部 esp_restart)
|
||
|
||
if (err == ESP_OK) {
|
||
// 极端情况:reboot 未执行(理论上不会发生),仍上报并标记
|
||
if (device_sha_hex[0] != '\\0') {
|
||
// 上报设备端 SHA-256 供前端比对显示([0xC1] + [32 字节])
|
||
uint8_t sha_pkt[33] = {0};
|
||
sha_pkt[0] = BLE_OTA_NOTIFY_SHA256;
|
||
for (int i = 0; i < 32; ++i) {
|
||
unsigned int byte = 0;
|
||
sscanf(&device_sha_hex[i * 2], "%02x", &byte);
|
||
sha_pkt[1 + i] = (uint8_t)byte;
|
||
}
|
||
BleOta::instance().notifyResponse(sha_pkt, sizeof(sha_pkt));
|
||
}
|
||
sendFinishEvent(BLE_OTA_FINISH_DONE_OTA_END, 0);
|
||
sendFinishEvent(BLE_OTA_FINISH_REBOOT_NOW, 0);
|
||
} else {
|
||
DEBUG_ERR(TAG, "[FINISH] OTA 收尾失败: %s", esp_err_to_name(err));
|
||
sendFinishEvent(BLE_OTA_FINISH_ERR_OTA_END, (uint32_t)err);
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
}
|
||
|
||
vTaskDelete(NULL);
|
||
}'''
|
||
|
||
new_task='''static void ble_esp32_finish_and_reboot_task(void *pvParameters)
|
||
{
|
||
// pvParameters: heap-allocated char[65] frontend SHA hex string (or nullptr)
|
||
char *frontend_sha_hex = (char *)pvParameters;
|
||
DEBUG_INFO(TAG, "ESP32 OTA 异步收尾任务启动");
|
||
|
||
Esp32OtaCore &core = Esp32OtaCore::instance();
|
||
|
||
// 补发最终 ACK,让前端尽快得知最后一包已收到
|
||
if (BleOta::instance().m_esp32LastSeq != 0xFFFFU) {
|
||
sendWindowAck(BleOta::instance().m_esp32LastCmd,
|
||
BleOta::instance().m_esp32LastSeq,
|
||
(uint32_t)core.getReceived());
|
||
DEBUG_INFO(TAG, "ESP32 最终 ACK 已补发: seq=%u", BleOta::instance().m_esp32LastSeq);
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(150));
|
||
|
||
if (!core.isActive()) {
|
||
DEBUG_WARN(TAG, "异步收尾时 OTA 状态无效,放弃重启");
|
||
if (frontend_sha_hex) free(frontend_sha_hex);
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
vTaskDelete(NULL);
|
||
return;
|
||
}
|
||
|
||
DEBUG_INFO(TAG, "ESP32 OTA 收尾: 共 %u 字节", (unsigned)core.getReceived());
|
||
|
||
// 通知前端:开始 finalize
|
||
sendFinishEvent(BLE_OTA_FINISH_BEGIN_OTA_END, 0);
|
||
|
||
char device_sha_hex[65] = {0};
|
||
esp_err_t err = core.finalize(frontend_sha_hex,
|
||
/*reboot=*/true,
|
||
device_sha_hex);
|
||
// reboot=true 时 finalize 正常不会返回(core 内部 esp_restart)
|
||
|
||
if (err == ESP_OK) {
|
||
// 极端情况:reboot 未执行(理论上不会发生),仍上报并标记
|
||
if (device_sha_hex[0] != '\\0') {
|
||
uint8_t sha_pkt[33] = {0};
|
||
sha_pkt[0] = BLE_OTA_NOTIFY_SHA256;
|
||
for (int i = 0; i < 32; ++i) {
|
||
unsigned int byte = 0;
|
||
sscanf(&device_sha_hex[i * 2], "%02x", &byte);
|
||
sha_pkt[1 + i] = (uint8_t)byte;
|
||
}
|
||
BleOta::instance().notifyResponse(sha_pkt, sizeof(sha_pkt));
|
||
}
|
||
sendFinishEvent(BLE_OTA_FINISH_DONE_OTA_END, 0);
|
||
sendFinishEvent(BLE_OTA_FINISH_REBOOT_NOW, 0);
|
||
} else if (err == ESP_ERR_INVALID_VERSION) {
|
||
DEBUG_ERR(TAG, "[FINISH] SHA-256 不匹配,OTA 已中止");
|
||
sendFinishEvent(BLE_OTA_FINISH_ERR_SHA_MISMATCH, 0);
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
} else {
|
||
DEBUG_ERR(TAG, "[FINISH] OTA 收尾失败: %s", esp_err_to_name(err));
|
||
sendFinishEvent(BLE_OTA_FINISH_ERR_OTA_END, (uint32_t)err);
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
}
|
||
|
||
if (frontend_sha_hex) free(frontend_sha_hex);
|
||
vTaskDelete(NULL);
|
||
}'''
|
||
assert old_task in data, 'old_task not found'
|
||
data=data.replace(old_task,new_task)
|
||
|
||
# 3. 修改 ble_ota_ctrl_access: 扩展 buf 到 36,解析前端 SHA
|
||
old_ctrl=''' uint8_t buf[4] = {0};
|
||
int len = ble_ota_mbuf_to_buf(ctxt->om, buf, sizeof(buf));
|
||
if (len < 1) {
|
||
DEBUG_WARN(TAG, "CTRL 数据为空");
|
||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||
}
|
||
|
||
DEBUG_INFO(TAG, "CTRL 写入: cmd=0x%02X", buf[0]);
|
||
|
||
if (buf[0] == BLE_OTA_CTRL_TRIGGER_GD32) {
|
||
// 检查固件是否存储完毕 → 触发 GD32 OTA 中继
|
||
ota_meta_t meta;
|
||
esp_err_t err = OtaRelay::metaLoad(&meta);
|
||
if (err == ESP_OK &&
|
||
meta.total_size > 0 &&
|
||
meta.stored_size >= meta.total_size) {
|
||
DEBUG_INFO(TAG, "GD32 固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),触发中继",
|
||
meta.stored_size, meta.total_size);
|
||
BleOta::instance().signalRelay();
|
||
} else {
|
||
DEBUG_WARN(TAG, "GD32 固件未就绪 (stored=%" PRIu32 ", total=%" PRIu32 ")",
|
||
meta.stored_size, meta.total_size);
|
||
}
|
||
} else if (buf[0] == BLE_OTA_CTRL_TRIGGER_ESP32) {
|
||
if (!Esp32OtaCore::instance().isActive()) {
|
||
DEBUG_WARN(TAG, "ESP32 OTA 未激活,无法触发升级");
|
||
return 0;
|
||
}
|
||
if (BleOta::instance().m_esp32RebootPending) {
|
||
DEBUG_WARN(TAG, "ESP32 OTA 收尾已在进行中,忽略重复 CTRL");
|
||
return 0;
|
||
}
|
||
|
||
BleOta::instance().m_esp32RebootPending = true;
|
||
BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task,
|
||
"ble_ota_finish",
|
||
4096,
|
||
NULL,
|
||
5,
|
||
NULL);
|
||
if (ok != pdPASS) {
|
||
DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败");
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
} else {
|
||
DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建(由设备端异步完成)");
|
||
}
|
||
}
|
||
// 注: BLE_OTA_CTRL_VERIFY_SHA256 (0xF9) 路径已移除。
|
||
// SHA-256 现由 Esp32OtaCore 在 finalize 时统一从分区读回计算,
|
||
// 并通过 BLE_OTA_NOTIFY_SHA256 (0xC1) 通知前端比对,校验逻辑与 WiFi 完全一致。'''
|
||
|
||
new_ctrl=''' uint8_t buf[36] = {0};
|
||
int len = ble_ota_mbuf_to_buf(ctxt->om, buf, sizeof(buf));
|
||
if (len < 1) {
|
||
DEBUG_WARN(TAG, "CTRL 数据为空");
|
||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||
}
|
||
|
||
DEBUG_INFO(TAG, "CTRL 写入: cmd=0x%02X len=%d", buf[0], len);
|
||
|
||
if (buf[0] == BLE_OTA_CTRL_TRIGGER_GD32) {
|
||
// 检查固件是否存储完毕 → 触发 GD32 OTA 中继
|
||
ota_meta_t meta;
|
||
esp_err_t err = OtaRelay::metaLoad(&meta);
|
||
if (err == ESP_OK &&
|
||
meta.total_size > 0 &&
|
||
meta.stored_size >= meta.total_size) {
|
||
DEBUG_INFO(TAG, "GD32 固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),触发中继",
|
||
meta.stored_size, meta.total_size);
|
||
BleOta::instance().signalRelay();
|
||
} else {
|
||
DEBUG_WARN(TAG, "GD32 固件未就绪 (stored=%" PRIu32 ", total=%" PRIu32 ")",
|
||
meta.stored_size, meta.total_size);
|
||
}
|
||
} else if (buf[0] == BLE_OTA_CTRL_TRIGGER_ESP32) {
|
||
if (!Esp32OtaCore::instance().isActive()) {
|
||
DEBUG_WARN(TAG, "ESP32 OTA 未激活,无法触发升级");
|
||
return 0;
|
||
}
|
||
if (BleOta::instance().m_esp32RebootPending) {
|
||
DEBUG_WARN(TAG, "ESP32 OTA 收尾已在进行中,忽略重复 CTRL");
|
||
return 0;
|
||
}
|
||
|
||
// 解析前端 SHA-256: [0xF8] + [32 bytes binary SHA]
|
||
char *frontend_sha_hex = nullptr;
|
||
if (len >= 33) {
|
||
frontend_sha_hex = (char *)malloc(65);
|
||
if (frontend_sha_hex) {
|
||
for (int i = 0; i < 32; ++i) {
|
||
snprintf(&frontend_sha_hex[i * 2], 3, "%02x", buf[1 + i]);
|
||
}
|
||
frontend_sha_hex[64] = '\\0';
|
||
DEBUG_INFO(TAG, "前端 SHA-256: %s", frontend_sha_hex);
|
||
}
|
||
} else {
|
||
DEBUG_WARN(TAG, "CTRL 0xF8 数据长度不足 33,无法获取前端 SHA-256");
|
||
}
|
||
|
||
BleOta::instance().m_esp32RebootPending = true;
|
||
BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task,
|
||
"ble_ota_finish",
|
||
4096,
|
||
frontend_sha_hex, // heap string; task 负责 free
|
||
5,
|
||
NULL);
|
||
if (ok != pdPASS) {
|
||
DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败");
|
||
if (frontend_sha_hex) free(frontend_sha_hex);
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
} else {
|
||
DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建(前端 SHA 已传入)");
|
||
}
|
||
}
|
||
// 注: BLE_OTA_CTRL_VERIFY_SHA256 (0xF9) 路径已移除。
|
||
// SHA-256 现由 Esp32OtaCore 在 finalize 时统一从分区读回计算,
|
||
// 并通过 BLE_OTA_NOTIFY_SHA256 (0xC1) 通知前端比对,校验逻辑与 WiFi 完全一致。'''
|
||
assert old_ctrl in data, 'old_ctrl not found'
|
||
data=data.replace(old_ctrl,new_ctrl)
|
||
|
||
# 4. 移除 DATA_IN 中的自动收尾代码(ESP32 收到最后一包后自动创建 finish task)
|
||
old_auto=''' if ((total_size > 0 && Esp32OtaCore::instance().getReceived() >= total_size) || isLastPacket) {
|
||
DEBUG_INFO(TAG, "★ ESP32 固件接收完毕: %u / %" PRIu32 " 字节 (共 %u 块)%s",
|
||
(unsigned int)Esp32OtaCore::instance().getReceived(), total_size, seq + 1,
|
||
isLastPacket ? " [尾包]" : "");
|
||
BleOta::instance().m_esp32LastCmd = cmd;
|
||
BleOta::instance().m_esp32LastSeq = seq;
|
||
|
||
if (!BleOta::instance().m_esp32RebootPending) {
|
||
BleOta::instance().m_esp32RebootPending = true;
|
||
BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task,
|
||
"ble_ota_finish",
|
||
4096,
|
||
NULL,
|
||
5,
|
||
NULL);
|
||
if (ok != pdPASS) {
|
||
DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败");
|
||
ble_esp32_ota_abort(); // core 内部完成 abort + mutex release + 状态复位
|
||
BleOta::instance().m_esp32RebootPending = false;
|
||
return BLE_ATT_ERR_UNLIKELY;
|
||
}
|
||
DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建,等待设备端自动重启");
|
||
} else {
|
||
DEBUG_WARN(TAG, "ESP32 OTA 收尾任务已存在,忽略重复完成触发");
|
||
}
|
||
}
|
||
}'''
|
||
|
||
new_auto=''' if ((total_size > 0 && Esp32OtaCore::instance().getReceived() >= total_size) || isLastPacket) {
|
||
DEBUG_INFO(TAG, "★ ESP32 固件接收完毕: %u / %" PRIu32 " 字节 (共 %u 块)%s",
|
||
(unsigned int)Esp32OtaCore::instance().getReceived(), total_size, seq + 1,
|
||
isLastPacket ? " [尾包]" : "");
|
||
BleOta::instance().m_esp32LastCmd = cmd;
|
||
BleOta::instance().m_esp32LastSeq = seq;
|
||
// 注:与 WiFi 流程对齐,传输完成后由前端发送 0xF8 + SHA-256 触发 finalize,
|
||
// 设备端不再自动创建收尾任务。
|
||
}
|
||
}'''
|
||
assert old_auto in data, 'old_auto not found'
|
||
data=data.replace(old_auto,new_auto)
|
||
|
||
with open('main/server/ble_ota.cpp','w',encoding='utf-8-sig') as f:
|
||
f.write(data)
|
||
print('ble_ota.cpp updated')
|