feat(vd960DBN): 脱机事件日志系统 (W25Q32 环形) — 解决重复上线取证
背景: 设备挂平台测试出现重复上线(现场未断电), 无本地日志无法区分
设备真复位 vs MQTT 断连重连。iot_handle_suback 每次重连都发
initialize 是重复上线的直接证据, 日志需能区分二者。
实现 (V3.6, ROADMAP P1.2 事件流先行):
- offlog.c/h: W25Q32 256KB 环形事件日志 (头扇区+63数据扇区, 8064条)
- 32B 定长记录: magic/type/len/flags/seq/ts_ms/unix_ts/boot_seq/payload[12]
- 8 类事件: BOOT(复位原因)/IOT_CONNECT/READY/DISCONN/RECONN/
EVT_RETRY/GIVEUP/COIL/TIME_ANCHOR/LOG_CLEAR
- 掉电恢复: 头扇区写指针锚点 + 上电 seq 连续性扫描
- 编译期断言防 32B padding 回归
- 插桩 (全部主循环上下文, socket 中断内不写 SPI):
- main(): offlog_init + RCC_RSTSCKR 复位原因采集/清除
- iot_mqtt_poll(): MQTT 状态沿检测 (CONNECT/READY/DISCONN)
- 重连退避/TCP超时/CONNACK拒绝: RECONN/DISCONN(4)/(3)
- iot_evt_process/enqueue: EVT_RETRY/GIVEUP/COIL
- net_srv.c dev_time_sync: TIME_ANCHOR 时钟同步锚点
- tests/test_offlog.c: gcc 隔离单测 6 例 (mock W25Q32 NOR 语义)
关键坑: ①中断内写SPI阻塞 ②结构体36B padding致环形错乱
③扇区级覆盖粒度count扣减语义 (均单测抓出)
待办: P1.3 导出命令(log_query/stat/clear) + 快照流 + 复位原因板上验证
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "simple_json.h"
|
||||
#include "tcp_json_srv.h"
|
||||
#include "storage.h"
|
||||
#include "offlog.h"
|
||||
#include "ch32v20x_iwdg.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -37,6 +38,7 @@ extern ReportConfig g_report_cfg; // from tcp_json_srv.c
|
||||
extern uint8_t SocketId_TCP; // from net_srv.c, MQTT socket created by WCHNET_CreateTcpMqttSocket
|
||||
uint8_t g_iot_socket = 0xFF; // MQTT TCP socket ID
|
||||
IotMqttState g_iot_state = IOT_STATE_DISCONNECTED;
|
||||
static IotMqttState _prev_iot_state = IOT_STATE_DISCONNECTED; // 事件日志: 状态沿检测
|
||||
uint8_t g_iot_msg_id = 0; // MQTT packet identifier
|
||||
static uint32_t _iot_last_heartbeat = 0; // 上次 PINGREQ 时刻 (ms), MQTT 保活
|
||||
static uint32_t _iot_reconnect_deadline = 0;
|
||||
@@ -108,6 +110,8 @@ static void iot_evt_enqueue(uint8_t type, uint8_t ch, uint32_t value) {
|
||||
IotEvent *e = &_evt_queue[(_evt_head + _evt_count) % IOT_EVT_QUEUE_DEPTH];
|
||||
e->type = type; e->ch = ch; e->value = value;
|
||||
_evt_count++;
|
||||
/* 事件日志: 线圈事件 (主循环上下文) */
|
||||
offlog_coil((uint8_t)type + 1, ch, value); /* sub: 1=进 2=出 3=断开 4=恢复 */
|
||||
}
|
||||
_evt_gaveup = 0; // 新事件到达 → 解除挂起, 触发合并上报
|
||||
PRINT("EVT: enqueue type=%d ch=%d val=%lu (cnt=%d)\n", type, ch, value, _evt_count);
|
||||
@@ -242,11 +246,13 @@ static void iot_evt_process(void) {
|
||||
if (_evt_retry >= IOT_EVT_MAX_RETRY) {
|
||||
PRINT("EVT: retry exhausted msg_id=%lu, hold %d events\n",
|
||||
_evt_pend_id, _evt_count);
|
||||
offlog_evt_giveup(_evt_pend_id); // 事件日志: 重试耗尽
|
||||
_evt_pend_id = 0;
|
||||
_evt_gaveup = 1; // 挂起: 新事件或重连时再触发
|
||||
return;
|
||||
}
|
||||
_evt_retry++;
|
||||
offlog_evt_retry(_evt_pend_id, _evt_retry); // 事件日志: ACK 超时重发
|
||||
iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n); // 同 id/ts 重发
|
||||
_evt_sent_ms = now;
|
||||
return;
|
||||
@@ -613,6 +619,7 @@ static void iot_process_recv(void) {
|
||||
iot_mqtt_send_subscribe();
|
||||
} else {
|
||||
PRINT("IOT: CONNACK rejected, rc=%d\n", connack_rc);
|
||||
offlog_iot_disconnect(3); // 事件日志: broker 拒绝
|
||||
g_iot_state = IOT_STATE_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
@@ -912,6 +919,18 @@ void iot_watchdog_kick(void) {
|
||||
void iot_mqtt_poll(void) {
|
||||
iot_watchdog_kick(); // 喂硬件 IWDG
|
||||
|
||||
/* 事件日志: MQTT 状态沿检测 (主循环上下文, 不在 socket 中断里写 SPI)
|
||||
真复位 → 会有 BOOT 事件 + boot_seq 递增; 仅断连重连 → 只有以下网络事件 */
|
||||
if (g_iot_state != _prev_iot_state) {
|
||||
switch (g_iot_state) {
|
||||
case IOT_STATE_MQTT_CONNECTED: offlog_iot_connect(); break; // TCP+CONNACK 成功
|
||||
case IOT_STATE_READY: offlog_iot_ready(); break; // SUBACK → 将发 initialize
|
||||
case IOT_STATE_DISCONNECTED: offlog_iot_disconnect(1); break; // 断连(细分原因在各分支单独记)
|
||||
default: break;
|
||||
}
|
||||
_prev_iot_state = g_iot_state;
|
||||
}
|
||||
|
||||
/* 断线重连 */
|
||||
if (g_iot_state == IOT_STATE_DISCONNECTED && g_iot_socket == 0xFF) {
|
||||
if (_iot_reconnect_deadline == 0 || mstick() > _iot_reconnect_deadline) {
|
||||
@@ -926,6 +945,7 @@ void iot_mqtt_poll(void) {
|
||||
_iot_reconnect_backoff = IOT_MQTT_RECONNECT_MAX_MS;
|
||||
}
|
||||
_iot_reconnect_deadline = mstick() + _iot_reconnect_backoff;
|
||||
offlog_iot_reconn(_iot_reconnect_backoff); // 事件日志: 重连退避
|
||||
PRINT("IOT: reconnect in %lu ms\n", _iot_reconnect_backoff);
|
||||
}
|
||||
}
|
||||
@@ -936,6 +956,7 @@ void iot_mqtt_poll(void) {
|
||||
if (g_iot_state == IOT_STATE_TCP_CONNECTING) {
|
||||
if (mstick() - _iot_connect_start > 10000) {
|
||||
PRINT("IOT: TCP connect timeout\n");
|
||||
offlog_iot_disconnect(4); // 事件日志: 连接超时
|
||||
WCHNET_SocketClose(g_iot_socket, TCP_CLOSE_NORMAL);
|
||||
g_iot_socket = 0xFF;
|
||||
g_iot_state = IOT_STATE_DISCONNECTED;
|
||||
|
||||
Reference in New Issue
Block a user