固件 0.1.5~0.1.13-c5:刷机模式、多组 WiFi、热插拔初始化、总线锁误判、硬重启断 VBUS
- 0.1.5/0.1.6:刷机模式(flash_mode,最多 2 路,60 分钟可加钟)——窗口内顶高按 4450 mV 绝对值判、不断 VBUS - 0.1.7/0.1.8:反灌断 VBUS 阈值放宽到 -300 mA(adb reboot bootloader 过渡实测 -208 mA) - 0.1.9:WiFi 最多存 4 组,连不上轮换;NVS v1 自动迁移并双写(OTA 回滚仍能上网) - 0.1.10:热插拔进来的刀片重新初始化 PCA9555 + INA226(否则 VBUS 常开、电流恒读 0) - 0.1.11:i2cbus 嵌套检测改看本任务持锁(原全局标志让 key 随机回 busy、vbus 随机回 io) - 0.1.12:硬重启先断 VBUS,开机校验通过 60 s 后再接回(否则假重启 / 误判没开机 / 进关机充电) - 0.1.13:这一路被安全判据断电时一并退出刷机模式(事件 21 arg 254) 26 ctest 全绿;新增用例均先在旧代码上确认变红。板 B 已 OTA 到 0.1.13-c5 并实测。 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
这个提交包含在:
+121
-27
@@ -45,8 +45,28 @@
|
||||
static const char *TAG = "mlink";
|
||||
|
||||
#define NVS_NS "phonerail"
|
||||
#define NVS_KEY "net"
|
||||
#define NVS_VERSION 1 /* netcfg_t 布局一变就加一:旧版本的 blob 直接当没配置 */
|
||||
#define NVS_KEY "net2"
|
||||
#define NVS_VERSION 2 /* netcfg_t 布局一变就加一:旧版本的 blob 直接当没配置 */
|
||||
|
||||
/* ≤0.1.8 的配置(只有一组 WiFi)存在 "net" 键里。0.1.9 起:
|
||||
· 读:没有 "net2" 就读 "net" 迁过来(上架的板子升级后不用重新配网);
|
||||
· 写:两个键**都写**,"net" 里放表里第一组 —— OTA 回滚到老固件照样连得上网。 */
|
||||
#define NVS_KEY_V1 "net"
|
||||
#define NVS_VERSION_V1 1
|
||||
|
||||
typedef struct { /* ≤0.1.8 的 netcfg_t,一个字节都不能动 */
|
||||
char ssid[NETCFG_SSID_MAX + 1];
|
||||
char pass[NETCFG_PASS_MAX + 1];
|
||||
char uri[NETCFG_URI_MAX + 1];
|
||||
char user[NETCFG_USER_MAX + 1];
|
||||
char mpass[NETCFG_MPASS_MAX + 1];
|
||||
uint32_t tele_ms;
|
||||
} netcfg_v1_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t ver;
|
||||
netcfg_v1_t cfg;
|
||||
} nvs_blob_v1_t;
|
||||
|
||||
/* evse-ca(home 服务器上那个 mosquitto 的自签 CA,公钥证书,不是机密)。
|
||||
`main/CMakeLists.txt` 的 EMBED_TXTFILES 编进来,结尾带 0。只在 mqtts:// 时用。 */
|
||||
@@ -63,6 +83,8 @@ static SemaphoreHandle_t s_mtx; /* 护 s_st 与 s_mqtt 句柄 */
|
||||
static net_status_t s_st;
|
||||
static esp_mqtt_client_handle_t s_mqtt;
|
||||
static netcfg_t s_cur; /* 当前生效的配置(只在 t_link 里读写) */
|
||||
static volatile uint8_t s_idx; /* 正在连 / 已连上的是 s_cur.wifi 第几组 */
|
||||
static volatile bool s_leaving; /* wifi_apply 主动断开了一条好好的连接:那次断开事件不算失败 */
|
||||
static volatile bool s_dirty; /* 配置变了,等 t_link 来重配 */
|
||||
static esp_timer_handle_t s_retry;
|
||||
static uint32_t s_attempt;
|
||||
@@ -106,8 +128,22 @@ static hal_err_t nvs_save(void *ctx, const netcfg_t *cfg)
|
||||
if (nvs_open(NVS_NS, NVS_READWRITE, &h) != ESP_OK) {
|
||||
return HAL_ERR_BUS;
|
||||
}
|
||||
const nvs_blob_t b = { .ver = NVS_VERSION, .cfg = *cfg };
|
||||
esp_err_t e = nvs_set_blob(h, NVS_KEY, &b, sizeof(b));
|
||||
static nvs_blob_t b; /* ≈560 B,不放栈上(只有执行器任务进来) */
|
||||
static nvs_blob_v1_t v1;
|
||||
b.ver = NVS_VERSION;
|
||||
b.cfg = *cfg;
|
||||
memset(&v1, 0, sizeof(v1));
|
||||
v1.ver = NVS_VERSION_V1;
|
||||
memcpy(v1.cfg.ssid, cfg->wifi[0].ssid, sizeof(v1.cfg.ssid));
|
||||
memcpy(v1.cfg.pass, cfg->wifi[0].pass, sizeof(v1.cfg.pass));
|
||||
memcpy(v1.cfg.uri, cfg->uri, sizeof(v1.cfg.uri));
|
||||
memcpy(v1.cfg.user, cfg->user, sizeof(v1.cfg.user));
|
||||
memcpy(v1.cfg.mpass, cfg->mpass, sizeof(v1.cfg.mpass));
|
||||
v1.cfg.tele_ms = cfg->tele_ms;
|
||||
esp_err_t e = nvs_set_blob(h, NVS_KEY, &b, sizeof(b));
|
||||
if (e == ESP_OK) {
|
||||
e = nvs_set_blob(h, NVS_KEY_V1, &v1, sizeof(v1));
|
||||
}
|
||||
if (e == ESP_OK) {
|
||||
e = nvs_commit(h);
|
||||
}
|
||||
@@ -121,14 +157,29 @@ static bool nvs_load(netcfg_t *out)
|
||||
if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
nvs_blob_t b;
|
||||
size_t n = sizeof(b);
|
||||
const bool ok = nvs_get_blob(h, NVS_KEY, &b, &n) == ESP_OK && n == sizeof(b) &&
|
||||
b.ver == NVS_VERSION;
|
||||
nvs_close(h);
|
||||
static nvs_blob_t b; /* ≈560 B,不放栈上(只在启动时进来一次) */
|
||||
size_t n = sizeof(b);
|
||||
bool ok = nvs_get_blob(h, NVS_KEY, &b, &n) == ESP_OK && n == sizeof(b) &&
|
||||
b.ver == NVS_VERSION;
|
||||
if (ok) {
|
||||
*out = b.cfg;
|
||||
} else {
|
||||
static nvs_blob_v1_t v1;
|
||||
n = sizeof(v1);
|
||||
ok = nvs_get_blob(h, NVS_KEY_V1, &v1, &n) == ESP_OK && n == sizeof(v1) &&
|
||||
v1.ver == NVS_VERSION_V1;
|
||||
if (ok) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
memcpy(out->wifi[0].ssid, v1.cfg.ssid, sizeof(v1.cfg.ssid));
|
||||
memcpy(out->wifi[0].pass, v1.cfg.pass, sizeof(v1.cfg.pass));
|
||||
memcpy(out->uri, v1.cfg.uri, sizeof(v1.cfg.uri));
|
||||
memcpy(out->user, v1.cfg.user, sizeof(v1.cfg.user));
|
||||
memcpy(out->mpass, v1.cfg.mpass, sizeof(v1.cfg.mpass));
|
||||
out->tele_ms = v1.cfg.tele_ms;
|
||||
ESP_LOGI(TAG, "旧版网络配置(一组 WiFi)已迁移;下次改配置时写成新版");
|
||||
}
|
||||
}
|
||||
nvs_close(h);
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -280,10 +331,25 @@ static void mqtt_stop(void)
|
||||
|
||||
/* ---- WiFi 事件(系统事件循环) ------------------------------------------- */
|
||||
|
||||
/* 把 STA 配成表里第 idx 组(idx 必须 < count)。 */
|
||||
static void wifi_set_sta(uint8_t idx)
|
||||
{
|
||||
const netcfg_wifi_t *w = &s_cur.wifi[idx];
|
||||
wifi_config_t wc = { 0 };
|
||||
memcpy(wc.sta.ssid, w->ssid, strlen(w->ssid));
|
||||
memcpy(wc.sta.password, w->pass, strlen(w->pass));
|
||||
wc.sta.threshold.authmode = w->pass[0] ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN;
|
||||
esp_wifi_set_config(WIFI_IF_STA, &wc);
|
||||
ST_EDIT(s_st.wifi_idx = idx);
|
||||
}
|
||||
|
||||
static void retry_cb(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
if (s_st.wifi != NET_UP && s_cur.ssid[0] != '\0') {
|
||||
const int n = netcfg_wifi_count(&s_cur);
|
||||
if (s_st.wifi != NET_UP && n > 0) {
|
||||
const uint8_t i = s_idx < n ? s_idx : 0;
|
||||
wifi_set_sta(i);
|
||||
esp_wifi_connect();
|
||||
}
|
||||
}
|
||||
@@ -293,13 +359,24 @@ static void on_wifi(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
(void)arg;
|
||||
if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
const wifi_event_sta_disconnected_t *d = data;
|
||||
ESP_LOGW(TAG, "WiFi 断开,原因 %u,%u ms 后重试", (unsigned)d->reason,
|
||||
(unsigned)mlink_backoff_ms(s_attempt));
|
||||
ST_EDIT(s_st.wifi_reason = d->reason;if (s_st.wifi == NET_UP) { s_st.wifi_drops++; } s_st.wifi =
|
||||
s_cur.ssid[0] ? NET_CONNECTING : NET_OFF;
|
||||
const int n = netcfg_wifi_count(&s_cur);
|
||||
const bool was_up = s_st.wifi == NET_UP;
|
||||
ST_EDIT(s_st.wifi_reason = d->reason; if (was_up) { s_st.wifi_drops++; } s_st.wifi =
|
||||
n > 0 ? NET_CONNECTING : NET_OFF;
|
||||
s_st.ip[0] = '\0'; s_st.rssi = 0);
|
||||
if (s_cur.ssid[0] != '\0') {
|
||||
const uint32_t ms = mlink_backoff_ms(s_attempt++);
|
||||
if (s_leaving) {
|
||||
s_leaving = false; /* wifi_apply 自己断的,它紧接着就去连了 */
|
||||
return;
|
||||
}
|
||||
if (n > 0) {
|
||||
/* 连上过又掉的:先重试同一组;没连上的:换下一组。退避按「轮完一整圈」算 ——
|
||||
几组全连不上也是 1 s、2 s、4 s …… 地拉长,而不是每组各退各的。 */
|
||||
if (!was_up && n > 1) {
|
||||
s_idx = (uint8_t)((s_idx + 1) % n);
|
||||
}
|
||||
const uint32_t ms = mlink_backoff_ms(s_attempt++ / (uint32_t)n);
|
||||
ESP_LOGW(TAG, "WiFi 断开,原因 %u,%u ms 后连「%s」", (unsigned)d->reason, (unsigned)ms,
|
||||
s_cur.wifi[s_idx < n ? s_idx : 0].ssid);
|
||||
esp_timer_stop(s_retry);
|
||||
esp_timer_start_once(s_retry, (uint64_t)ms * 1000u);
|
||||
}
|
||||
@@ -308,7 +385,7 @@ static void on_wifi(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
s_attempt = 0;
|
||||
ST_EDIT(s_st.wifi = NET_UP;
|
||||
snprintf(s_st.ip, sizeof(s_st.ip), IPSTR, IP2STR(&e->ip_info.ip)));
|
||||
ESP_LOGI(TAG, "WiFi 已连上,IP %s", s_st.ip);
|
||||
ESP_LOGI(TAG, "WiFi「%s」已连上,IP %s", s_cur.wifi[s_idx].ssid, s_st.ip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,20 +393,22 @@ static void on_wifi(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
static void wifi_apply(void)
|
||||
{
|
||||
esp_timer_stop(s_retry);
|
||||
s_leaving = s_st.wifi == NET_UP;
|
||||
esp_wifi_disconnect();
|
||||
s_attempt = 0;
|
||||
if (s_cur.ssid[0] == '\0') {
|
||||
const int n = netcfg_wifi_count(&s_cur);
|
||||
if (n == 0) {
|
||||
s_leaving = false;
|
||||
ST_EDIT(s_st.wifi = NET_OFF);
|
||||
return;
|
||||
}
|
||||
wifi_config_t wc = { 0 };
|
||||
memcpy(wc.sta.ssid, s_cur.ssid, strlen(s_cur.ssid));
|
||||
memcpy(wc.sta.password, s_cur.pass, strlen(s_cur.pass));
|
||||
wc.sta.threshold.authmode = s_cur.pass[0] ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN;
|
||||
esp_wifi_set_config(WIFI_IF_STA, &wc);
|
||||
if (s_idx >= n) {
|
||||
s_idx = 0;
|
||||
}
|
||||
wifi_set_sta(s_idx);
|
||||
ST_EDIT(s_st.wifi = NET_CONNECTING);
|
||||
esp_wifi_connect();
|
||||
ESP_LOGI(TAG, "连 WiFi「%s」", s_cur.ssid); /* 只打 SSID,不打口令 */
|
||||
ESP_LOGI(TAG, "连 WiFi「%s」(%u/%d)", s_cur.wifi[s_idx].ssid, (unsigned)s_idx + 1, n); /* 只打 SSID,不打口令 */
|
||||
}
|
||||
|
||||
/* ---- 对外 ---------------------------------------------------------------- */
|
||||
@@ -391,9 +470,24 @@ void mlink_esp_poll(void)
|
||||
}
|
||||
if (s_dirty) {
|
||||
s_dirty = false;
|
||||
mqtt_stop();
|
||||
netcfg_get(&s_cur);
|
||||
wifi_apply();
|
||||
/* 加 / 删的不是正连着的那一组、口令也没变:WiFi 不断;broker 也没变:MQTT 也不断。
|
||||
经 USB 往表里加一组备用热点,不该让板子掉线一次。 */
|
||||
static netcfg_t next; /* ≈560 B,不放栈上(只有 t_link 进来) */
|
||||
netcfg_get(&next);
|
||||
const int j = (s_st.wifi == NET_UP) ? netcfg_wifi_find(&next, s_cur.wifi[s_idx].ssid) : -1;
|
||||
const bool wifi_same = j >= 0 && strcmp(next.wifi[j].pass, s_cur.wifi[s_idx].pass) == 0;
|
||||
const bool mqtt_same = strcmp(next.uri, s_cur.uri) == 0 && strcmp(next.user, s_cur.user) == 0 &&
|
||||
strcmp(next.mpass, s_cur.mpass) == 0;
|
||||
if (!(wifi_same && mqtt_same)) {
|
||||
mqtt_stop();
|
||||
}
|
||||
s_cur = next;
|
||||
if (wifi_same) {
|
||||
s_idx = (uint8_t)j;
|
||||
ST_EDIT(s_st.wifi_idx = s_idx);
|
||||
} else {
|
||||
wifi_apply();
|
||||
}
|
||||
}
|
||||
/* MQTT 的生命周期只在这里管:WiFi 有地址、配了 broker、还没建客户端 ⇒ 建。
|
||||
之后掉线重连由 esp-mqtt 自己做(network.reconnect_timeout_ms,默认 10 s)。 */
|
||||
|
||||
在新工单中引用
屏蔽一个用户