文件
phonerail/firmware/main/ota.c
T
zhou yang和Claude Opus 5.5 ae1aad3b7c 固件 C2:OTA 双槽 + 失败回滚(已上板实测正常 / 哈希错 / 回滚三条路径)
- 分区重排(16 MB,nvs 0x9000 不动):ota_0 / ota_1 各 3 MB、otadata 0x610000、evlog 挪到 0x612000;
  flash 大小改正为 16 MB;开 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
- ota.{h,c}(宿主机可测):URL / sha256 校验、判定(下载中 busy、待验证拒、同镜像不重下)、健康判据
- ota_esp.{h,c}:t_ota 任务经 HTTP 下载、边下边算 sha256、核 project_name 后切启动分区;
  新固件 120 s 内连不上 broker 就回滚(t_link 判 + esp_timer 兜底);重启前等回应发出、事件落盘
- 新命令 ota = 13(两条链路都收,reboot 默认 false:复位 = 五台手机断电一次);status 加 ota 一块;
  新事件 LOG_EV_OTA = 18;PROJECT_VER 取 PHONERAIL_FW_VERSION
- test_ota.c 13 例,全工程 442 例全绿;版本 0.1.0-c2
- 服务器:nginx 7765 只放行 /fw/<16 hex>.bin(不过门户,路由器转发已加);tools/ota_push.py 推送;
  rack protocol.py 加事件 18 / 命令 13(已部署)
- 板 B:推送到自证健康 19 s;错哈希不切分区;删掉 broker 账号后 120 s 回滚,bad 报出被回滚的版本

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 14:03:13 +08:00

190 行
4.7 KiB
C
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* OTA 的平台无关部分(C2)。说明在 ota.h。 */
#include "ota.h"
#include <string.h>
#include "hal_lock.h"
static ota_start_fn s_start;
static ota_reboot_fn s_reboot;
static void *s_ctx;
static ota_status_t s_st;
void ota_init(ota_start_fn start, ota_reboot_fn reboot, void *ctx)
{
hal_crit_enter();
memset(&s_st, 0, sizeof(s_st));
s_start = start;
s_reboot = reboot;
s_ctx = ctx;
hal_crit_exit();
}
bool ota_url_ok(const char *url)
{
static const char k_scheme[] = "http://";
if (url == NULL || strncmp(url, k_scheme, sizeof(k_scheme) - 1) != 0) {
return false;
}
const size_t n = strlen(url);
if (n <= sizeof(k_scheme) - 1 || n > OTA_URL_MAX) {
return false;
}
for (size_t i = 0; i < n; i++) {
const unsigned char ch = (unsigned char)url[i];
if (ch <= 0x20 || ch >= 0x7F || ch == '"' || ch == '\\') {
return false;
}
}
return true;
}
static int hexval(char c)
{
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
return -1; /* 大写也算错:同一个哈希只许一种写法(ota.h) */
}
bool ota_parse_sha256(const char *hex, uint8_t out[32])
{
if (hex == NULL || strlen(hex) != 64) {
return false;
}
uint8_t tmp[32];
for (int i = 0; i < 32; i++) {
const int hi = hexval(hex[2 * i]);
const int lo = hexval(hex[2 * i + 1]);
if (hi < 0 || lo < 0) {
return false;
}
tmp[i] = (uint8_t)((hi << 4) | lo);
}
memcpy(out, tmp, sizeof(tmp)); /* 半截错的不留在 out 里 */
return true;
}
void ota_sha256_hex(const uint8_t sha[32], char out[65])
{
static const char k[] = "0123456789abcdef";
for (int i = 0; i < 32; i++) {
out[2 * i] = k[sha[i] >> 4];
out[2 * i + 1] = k[sha[i] & 0x0F];
}
out[64] = '\0';
}
ota_action_t ota_plan(const ota_status_t *st, const ota_req_t *req)
{
if (st->phase == OTA_DL) {
return OTA_ACT_BUSY;
}
if (st->phase == OTA_READY && memcmp(st->sha256, req->sha256, sizeof(st->sha256)) == 0) {
return req->reboot ? OTA_ACT_REBOOT : OTA_ACT_NOTHING;
}
/* READY 了另一个镜像:重新下载会覆盖同一个备用槽,胶水里会先把启动分区切回当前的
(esp_ota_begin 本身就会让那个槽失效)—— 这里照常开工。 */
if (st->pending) {
return OTA_ACT_PENDING;
}
return OTA_ACT_START;
}
int ota_request(const ota_req_t *req)
{
ota_status_t st;
ota_status_get(&st);
const ota_action_t a = ota_plan(&st, req);
switch (a) {
case OTA_ACT_START:
if (s_start == NULL) {
return -1;
}
/* 先把状态置成 DL 再交任务:命令处理器下一条 status 立刻看得到「在下载」,
也挡住了紧跟着的第二条 ota(它会拿到 BUSY)。 */
st.phase = OTA_DL;
st.err = OTA_E_NONE;
st.got = 0;
st.size = req->size;
memcpy(st.sha256, req->sha256, sizeof(st.sha256));
ota_status_set(&st);
if (s_start(s_ctx, req) != HAL_OK) {
st.phase = OTA_IDLE;
ota_status_set(&st);
return -1;
}
return 0;
case OTA_ACT_REBOOT:
if (s_reboot == NULL) {
return -1;
}
s_reboot(s_ctx);
return 0;
case OTA_ACT_NOTHING:
return 0;
default:
return (int)a;
}
}
void ota_status_set(const ota_status_t *st)
{
hal_crit_enter();
s_st = *st;
hal_crit_exit();
}
void ota_status_get(ota_status_t *st)
{
hal_crit_enter();
*st = s_st;
hal_crit_exit();
}
const char *ota_phase_str(ota_phase_t p)
{
switch (p) {
case OTA_IDLE:
return "idle";
case OTA_DL:
return "dl";
case OTA_READY:
return "ready";
default:
return "?";
}
}
const char *ota_err_str(ota_err_t e)
{
static const char *const k[OTA_E_COUNT] = {
[OTA_E_NONE] = "", [OTA_E_HTTP] = "http", [OTA_E_SIZE] = "size",
[OTA_E_HASH] = "hash", [OTA_E_FLASH] = "flash", [OTA_E_IMAGE] = "image",
[OTA_E_BOOT] = "boot", [OTA_E_NOSLOT] = "noslot",
};
return ((unsigned)e < OTA_E_COUNT) ? k[e] : "?";
}
ota_health_t ota_health(const ota_health_in_t *in)
{
if (!in->pending) {
return OTA_H_NONE;
}
bool net_ok;
if (in->mqtt_set) {
net_ok = in->mqtt_up;
} else if (in->wifi_set) {
net_ok = in->wifi_up;
} else {
net_ok = true;
}
if (in->boot_done && net_ok) {
return OTA_H_VALID;
}
return (in->now_ms >= OTA_HEALTH_MS) ? OTA_H_ROLLBACK : OTA_H_WAIT;
}