- snapshot.c/h: 新增 snap_rec_to_json() — SnapRec 64B → JSON (channels 对齐 0xC0, variation 3B 符号扩展, misc_type 全枚举) - tcp_json_srv.c: handle_log_stat/query/clear 加 stream 解析 + snapshot 分支 - iot_mqtt_srv.c: log_stat/query/clear 加 stream 解析 + snapshot 分支 - 事件流 count=0 按上限处理 (与 BLE 对齐); 快照 QUERY count≤2, CLEAR ~45ms - 新增 gcc 隔离单测 tests/test_snap_to_json.c 34 断言全过 - devlog V4.1
139 lines
7.5 KiB
C
139 lines
7.5 KiB
C
/**
|
||
******************************************************************************
|
||
* @file snapshot.h
|
||
* @author wfq
|
||
* @version V1.0
|
||
* @date 2026-08-12
|
||
* @brief DBN 传感快照日志 (W25Qxx 外部 SPI NOR, 环形, 与事件日志区分区独立)
|
||
*
|
||
* 用途: 0xC0 传感帧 (4 线圈) 按上报节奏落盘, 断网期间也有现场波形可查;
|
||
* 事件流管"发生了什么", 快照流管"波形长什么样"。
|
||
*
|
||
* 分区 (ROADMAP 2026-08-12 新规划):
|
||
* | 参数区 0x000000 64KB (固定)
|
||
* | OTA 区 0x010000 512KB (固定)
|
||
* | 事件区 0x090000 动态 (offlog 持有, g_offlog_part.area_size)
|
||
* | 快照区 事件区后 剩余容量 (总容量 - 固定区 - 事件区)
|
||
* 上电读 JEDEC ID (0x9F) 决定事件区/快照区容量 (g_snap_part 运行时分区表)
|
||
*
|
||
* 环形实现 (与 offlog 同模式):
|
||
* - 扇区 0 (区内) = 头扇区, 存元数据 (boot_seq/写位置/条数), 扇区切换时更新
|
||
* - 数据扇区每扇区 64 条 x 64B 定长记录 (4096/64)
|
||
* - 顺序写, 写满擦下一扇区 (环形覆盖), 天然磨损均衡
|
||
* - 上电从头部记录的写位置向后扫描, 找第一条无效记录 = 真实写位置 (掉电恢复)
|
||
*
|
||
* 线程模型 (关键, 2026-08-12 修正):
|
||
* - USART2 ISR 只逐字节喂 lup_feed_byte(), 完整帧由主循环 uart_srv() 调
|
||
* lup_process_frame → iot_sensor_ingest → snap_enqueue() — 全程主循环
|
||
* - snap_enqueue(): 打包 + RAM 环形暂存 (无 SPI, 无阻塞); 满时丢新不覆盖
|
||
* - snap_flush(): iot_mqtt_publish_sensor 每轮调用, RAM 暂存 → SPI 落盘
|
||
* (扇区切换擦除 ~45ms 阻塞发生在主循环, 与 offlog 同底线)
|
||
* - 单生产者单消费者无锁环形: 生产者只写 tail/count, 消费者只读 head
|
||
*
|
||
* 记录格式 SnapRec (64B 定长, 无 padding):
|
||
* magic(1) len(1) flags(1) rsvd(1) | seq(4) ts_ms(4) boot_seq(2) rsvd2(2) | coils(48)
|
||
* coils = 4 x 12B, 与 0xC0 线上线圈单元完全一致:
|
||
* [0] 配置1 freq_level(2)|direction(1)|freq_type(1)|sensitivity(4)
|
||
* [1] 条件 condition(4)|loop_state(1)|car_state(1)|misc_type(2)
|
||
* [2..4] 频率 3B LE
|
||
* [5..7] 变化量 3B LE (有符号补码)
|
||
* [8..11] 杂项 4B LE
|
||
******************************************************************************
|
||
*/
|
||
|
||
#ifndef _SNAPSHOT_H__
|
||
#define _SNAPSHOT_H__
|
||
|
||
#include <stdint.h>
|
||
#include "offlog.h" /* OFFLOG_CHIP_xxx + g_offlog_part (事件区大小) */
|
||
#include "loop_uart_proto.h" /* LUP_SensorReport / LUP_CoilSensor */
|
||
|
||
/*===========================================================================
|
||
* 分区与容量
|
||
*===========================================================================*/
|
||
/* 固定分区 (与 offlog 一致): 参数区 64KB + OTA 512KB */
|
||
#define SNAP_PARAM_SIZE 0x10000UL /* 参数区 64KB (固定) */
|
||
#define SNAP_OTA_SIZE 0x80000UL /* OTA 镜像暂存 512KB (固定) */
|
||
#define SNAP_FIXED_SIZE (SNAP_PARAM_SIZE + SNAP_OTA_SIZE) /* 0x090000 */
|
||
|
||
#define SNAP_HEAD_SECTOR 0 /* 区内扇区号: 头扇区 */
|
||
#define SNAP_REC_PER_SECTOR 64 /* 4096 / 64 */
|
||
|
||
/* 运行时分区表 (snap_init 由 JEDEC ID + g_offlog_part 填充; 默认 W25Q32) */
|
||
typedef struct {
|
||
uint8_t chip; /* OFFLOG_CHIP_xxx */
|
||
uint32_t area_base; /* 快照区起始 (事件区后) */
|
||
uint32_t area_size; /* 快照区大小 (剩余容量) */
|
||
uint32_t data_sectors; /* 数据扇区数 (不含头扇区) */
|
||
uint32_t max_records; /* 最大记录数 = data_sectors * 64 */
|
||
} SnapPart;
|
||
extern SnapPart g_snap_part;
|
||
|
||
/* 兼容宏: 展开为运行时值 */
|
||
#define SNAP_DATA_BASE (g_snap_part.area_base + 4096UL)
|
||
#define SNAP_AREA_SIZE (g_snap_part.area_size)
|
||
#define SNAP_DATA_SECTOR_CNT (g_snap_part.data_sectors)
|
||
#define SNAP_DATA_SIZE ((g_snap_part.data_sectors) * 4096UL)
|
||
#define SNAP_MAX_RECORDS (g_snap_part.max_records)
|
||
|
||
/* 头扇区记录 (32B, 扇区切换时更新) */
|
||
typedef struct {
|
||
uint8_t magic[4]; /* "SNAP" */
|
||
uint32_t boot_seq; /* 上次上电序号 (本 boot 由 init 时 +1) */
|
||
uint32_t wr_sector; /* 上次写扇区号 (区内 1..n) */
|
||
uint32_t wr_off; /* 上次写偏移 (绝对地址) */
|
||
uint32_t wr_seq; /* 下一条记录全局 seq (掉电恢复锚点) */
|
||
uint32_t count; /* 有效记录数 (0..max_records) */
|
||
uint32_t rsvd[2];
|
||
} SnapHead; /* 28B, 按 32B 对齐使用 */
|
||
|
||
#define SNAP_HEAD_MAGIC0 'S'
|
||
#define SNAP_HEAD_MAGIC1 'N'
|
||
#define SNAP_HEAD_MAGIC2 'A'
|
||
#define SNAP_HEAD_MAGIC3 'P'
|
||
|
||
/* 快照记录 (64B 定长, 字段重排保证自然对齐无 padding:
|
||
4×uint8 → uint32×2 → uint16×2 → coils[48] = 16+48 = 64) */
|
||
typedef struct {
|
||
uint8_t magic; /* 0xA6 (区别于事件 0xA5) */
|
||
uint8_t len; /* 线圈数据有效长度 = coil_count*12 (0..48) */
|
||
uint8_t flags; /* bit0: 分包帧 (预留) */
|
||
uint8_t rsvd;
|
||
uint32_t seq; /* 全局序号 (跨 boot 递增, flush 时分配) */
|
||
uint32_t ts_ms; /* boot 内相对时间 mstick() (采集时刻) */
|
||
uint16_t boot_seq; /* 启动序号 */
|
||
uint16_t rsvd2;
|
||
uint8_t coils[48]; /* 4 × 12B 线圈数据 (0xC0 线上格式原样) */
|
||
} SnapRec; /* 64B, 无 padding */
|
||
|
||
#define SNAP_MAGIC 0xA6
|
||
#define SNAP_REC_SIZE sizeof(SnapRec)
|
||
#define SNAP_COIL_BYTES 12
|
||
#define SNAP_COILS_MAX 4
|
||
|
||
/* RAM 暂存深度 (中断 enqueue → 主循环 flush 之间的缓冲)
|
||
Loop 帧最快 150ms, publish_sensor 每轮 flush, 8 深 ≈ 1.2s 余量;
|
||
CH32V208 SRAM 紧张: 64B × 8 = 512B, 远小于 SPI_FLASH_BUF 的 4KB */
|
||
#define SNAP_RAM_DEPTH 8
|
||
#define SNAP_MAX_QUERY_RECORDS 2 /* 快照 QUERY 分页上限: (132-2)/64 */
|
||
|
||
|
||
/*===========================================================================
|
||
* API
|
||
*===========================================================================*/
|
||
void snap_init(void); /* 上电初始化 (扫描恢复) */
|
||
void snap_delayed_init(void); /* 主循环延后初始化 (开机 3s 后, 2026-08-17) */
|
||
uint8_t snap_enabled(void); /* Flash 初始化成功 */
|
||
|
||
void snap_enqueue(const LUP_SensorReport *sr); /* 主循环入队: 打包→RAM 暂存 (满丢新) */
|
||
void snap_flush(void); /* 主循环: RAM 暂存→SPI 落盘 */
|
||
|
||
uint32_t snap_count(void); /* 有效记录数 */
|
||
uint32_t snap_seq_last(void); /* 最新一条记录全局序号 (0=空) */
|
||
uint32_t snap_boot_seq(void); /* 当前 boot 序号 */
|
||
int snap_read_idx(uint32_t idx, SnapRec *out); /* 按逻辑序号读, 0=成功 -1=越界 */
|
||
void snap_clear(void); /* 清空快照区 (审计写入事件流) */
|
||
int snap_rec_to_json(const SnapRec *r, char *buf, int buf_len); /* 记录 -> JSON (TCP/MQTT log_query) */
|
||
|
||
#endif /* _SNAPSHOT_H__ */
|