Files
vd_960/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/snapshot.h
T
wangfq f1c9358aad feat(vd960DBN): TCP/MQTT log_* 命令支持快照流 stream=snapshot (2026-08-18)
- 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
2026-08-18 09:01:22 +08:00

139 lines
7.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
******************************************************************************
* @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__ */