Files
CCU621M/BSP/flash_file_mgr/ocpp_mv_offline_flash_impl.h
T

196 lines
11 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 ocpp_mv_offline_flash_impl.h
* @brief OCPP 离线 MeterValues:仿 `fault_flash_impl` — SPI Flash 线性池 + FRAM 流水号→序号映射
*
* **为何更简单**
* - Flash 侧为「定长记录数组」:每条在 **该枪半区** 内逻辑序号与物理槽一一对应(`seq` → 地址由宏公式算出)。
* - FRAM 侧存:**每枪下一单调序号** `u32_next_seq[2]`、以及若干行「流水号(tx)+枪号 ↔ 起始序号 + 条数」;
* 上送时 `u8_ocpp_mv_offline_map_get(gun, tx...)` 得 `seq_start,count`,再
* `for (k=0;k<count;k++) u8_ocpp_mv_offline_mv_read_by_seq(gun, seq_start+k, &buf)`。
*
* **Flash 分区(双枪互不穿插)**
* - 区间 `0x2C0000`~`0x2CFFFF`64KB)见 `flash_external_data.h`**前半 32KB 仅枪0、后半 32KB 仅枪1**
* 保证连续 Flash 地址均为同一把枪的环形池;双枪同时充电时不会在物理上交错两条订单的 MV。
*
* **Flash 格式(与故障记录一致思路)**
* - 每条:`RECORD_HEADER`(1B) + `S_OCPP_MV_OFFLINE_MV`(32B) + CRC8低字节(1B),总长 `OCPP_MV_OFFLINE_FRAME_BYTES`。
* - 扇区内可容纳条数 = `SPI_SECTOR_SIZE / OCPP_MV_OFFLINE_FRAME_BYTES`(向下取整,尾部截断不用)。
* - **每枪**最大条数 = `OCPP_MV_OFFLINE_POOL_MAX_PER_GUN`(每枪 8 扇区 × 每扇区条数)。
*
* **环形滚动(按枪)**
* - 每枪单调序号 `u32_next_seq[gun]`(存 FRAM):`seq = next_seq[gun]++`;物理槽 `idx = seq % POOL_MAX_PER_GUN`(仅在该枪半区内取模)。
* - 池满后自然覆盖该枪最旧物理槽;上送完成后应 `u8_ocpp_mv_offline_map_remove(gun, tx...)` 删除对应行。
*/
#ifndef OCPP_MV_OFFLINE_FLASH_IMPL_H
#define OCPP_MV_OFFLINE_FLASH_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "externalflash/flash_external_data.h"
#include "eeprom/fm24cl16.h"
/** @brief 单包 MeterValues 负载长度(字节),与结构体 `S_OCPP_MV_OFFLINE_MV` 一致(与 BS_ocpp_ctrl 已实现 measurand 对齐) */
#define OCPP_MV_OFFLINE_PAYLOAD_BYTES 32u
/** @brief 单条落盘帧长:帧头 + 负载 + CRC1(与 `fault_flash_impl` 相同思路) */
#define OCPP_MV_OFFLINE_FRAME_BYTES (1u + OCPP_MV_OFFLINE_PAYLOAD_BYTES + 1u)
/** @brief 每个物理扇区内可容纳的 MV 条数(4096 / FRAME,向下取整,尾部截断) */
#define OCPP_MV_OFFLINE_RECORDS_PER_SECTOR (SPI_SECTOR_SIZE / OCPP_MV_OFFLINE_FRAME_BYTES)
/** @brief 枪号:0 = 前半区(枪1),1 = 后半区(枪2) */
#define OCPP_MV_OFFLINE_GUN_CNT 2u
/** @brief 每个枪在 Flash 内占用的连续扇区数(半区 32KB / 4096 */
#define OCPP_MV_OFFLINE_SECTORS_PER_GUN (DATAFLASH_OCPP_MV_OFFLINE_HALF_SIZE / SPI_SECTOR_SIZE)
/** @brief 单枪环形池最大条数(每枪 8 扇区 × 每扇区条数) */
#define OCPP_MV_OFFLINE_POOL_MAX_PER_GUN (OCPP_MV_OFFLINE_RECORDS_PER_SECTOR * OCPP_MV_OFFLINE_SECTORS_PER_GUN)
/** @brief FRAM 中可同时保存的「流水号→序号区间」映射行数(受 128B 控制块限制) */
#define OCPP_MV_OFFLINE_MAP_ROWS 6u
/** @brief 负载内魔数(小端可视 OCMV) */
#define OCPP_MV_OFFLINE_FLASH_MAGIC 0x564D434Fu
/** @brief 负载格式版本(32B 布局为 4;与旧 64B 不兼容,升级后建议整池擦除) */
#define OCPP_MV_OFFLINE_FLASH_VER 4u
/** @brief FRAM 管理块头 */
#define OCPP_MV_OFFLINE_FRAM_HEAD 0x5AA9u
/** @brief FRAM 管理块布局版本(变更字段时递增;5:每枪 next_seq + 映射行含枪号) */
#define OCPP_MV_OFFLINE_FRAM_MNG_VER 5u
/**
* @brief 一条待补发的 MeterValues「测量快照」负载(固定 32B)
*
* 字段与 `BS_ocpp_ctrl.c` 中 `u8_get_ocpp_measurand_data` **已实现** 的 measurand 对齐,便于离线存盘与联网后按同口径组 SampledValue
* - `ENUM_EnergyActiveImportRegister`:累计 Wh(源码为 u64/10,此处存 u32,超高 Wh 截断)
* - `ENUM_EnergyActiveImportInterval`:事务段增量 Wh → `u32_energy_interval_wh`
* - `ENUM_Voltage` / `ENUM_CurrentImport`:电表电压 0.1V、电流 0.01A(原 0.1A×10
* - `ENUM_PowerActiveImport`:有功功率 W(饱和 65535,极大桩可再约定用比例)
* - `ENUM_PowerOffered`:额定功率,单位 **0.1 kW**(与 `E_BS_GET_SYS_DATA_RATED_POWER` 一致)
* - `ENUM_CurrentOffered`:最大电流 0.01A
* - `ENUM_SoC` + `u8_soc_data_type`SOC% 与 0当前/1起始/2结束(与函数入参 `u8_data_type` 一致)
*
* 流水号 **tx** 不在本结构重复保存,以 FRAM 映射 `aMap[]` 为准。
*/
typedef struct
{
U32_T u32_magic; /**< 须为 OCPP_MV_OFFLINE_FLASH_MAGIC */
U32_T u32_timestamp_unix; /**< 取样时刻日历秒:`xDate2Seconds(GetCurrentTime)`,与补发 JSON 中 timestamp 一致(勿存上电单调秒) */
U32_T u32_meter_import_wh; /**< Energy.Active.Import.Register:累计 Whu64/10 截断 u32 */
U32_T u32_energy_interval_wh; /**< Energy.Active.Import.Interval:事务段增量 Wh */
U16_T u16_voltage_v_x10; /**< Voltage:输出电压 0.1V */
U16_T u16_current_import_a_x100; /**< Current.Import:电流 0.01A */
U16_T u16_power_active_w; /**< Power.Active.Import:功率 W(饱和) */
U16_T u16_power_offered_01kw; /**< Power.Offered:额定功率 0.1kW 单位 */
U16_T u16_current_offered_a_x100; /**< Current.Offered:最大电流 0.01A */
U8_T u8_soc; /**< SoC0~100 */
U8_T u8_soc_data_type; /**< SoC 上下文:0 当前 / 1 Transaction.Begin / 2 Transaction.End */
U8_T u8_connector_id; /**< 连接器号 */
U8_T u8_evse_id; /**< EVSE/枪逻辑号 */
U16_T u16_version; /**< 须为 OCPP_MV_OFFLINE_FLASH_VER */
} S_OCPP_MV_OFFLINE_MV;
/**
* @brief FRAM 中一行:会话键(当前实现为开始充电秒)↔ 在 Flash 池中的 **连续序号区间**
*
* 第 k 条(0<=k<count)在该枪下的单调序号为 `u32_seq_start + k`,读 Flash 时用
* `idx = (u32_seq_start + k) % OCPP_MV_OFFLINE_POOL_MAX_PER_GUN`(与本模块 `mv_read_by_seq` 一致)。
*/
typedef struct
{
U32_T u32_tx_sn_lo; /**< 当前:`xDate2Seconds(StartChargeTime)`;字段名历史兼容 */
U32_T u32_tx_sn_hi; /**< 当前约定恒 0(保留);非 64 位键的高半字 */
U32_T u32_seq_start; /**< 该流水号在该枪下第一条 MV 的单调序号 */
U16_T u16_count; /**< 连续条数 */
U8_T u8_gun_no; /**< 0=前半区枪11=后半区枪2(与 `u8_ocpp_mv_offline_mv_save` 入参一致) */
U8_T u8_reserved; /**< 预留 */
} S_OCPP_MV_OFFLINE_MAP_ROW;
/**
* @brief FRAM 管理块(128B`EEPROM_ADDR_OCPP_MV_OFFLINE_CTRL`
*
* CRC:整结构 `u16_crc` 置 0 后按 `u16_calculate_crc` 计算(与未结算模块相同)。
*/
typedef struct
{
U16_T u16_head; /**< OCPP_MV_OFFLINE_FRAM_HEAD */
U16_T u16_version; /**< OCPP_MV_OFFLINE_FRAM_MNG_VER */
U16_T u16_crc; /**< CRC-16 */
U8_T u8_flags; /**< 预留标志位 */
U8_T u8_reserved1; /**< 预留 */
U32_T u32_next_seq[OCPP_MV_OFFLINE_GUN_CNT]; /**< 每枪下一条将分配的单调序号(掉电连续) */
S_OCPP_MV_OFFLINE_MAP_ROW aMap[OCPP_MV_OFFLINE_MAP_ROWS]; /**< 会话键+枪→序号区间 */
U8_T u8_reserved2[16]; /**< 补齐至 128 字节 */
} S_OCPP_MV_OFFLINE_FRAM_MNG;
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_MV) == OCPP_MV_OFFLINE_PAYLOAD_BYTES, "MV payload 32B");
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) == 128u, "FRAM mng 128B");
_Static_assert(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) <= EEPROM_OCPP_MV_OFFLINE_CTRL_SIZE, "FRAM fit");
#else
/* ARMCC v5 不支持 _Static_assert,使用编译期断言替代 */
typedef int __ocpp_mv_size_check_1[(sizeof(S_OCPP_MV_OFFLINE_MV) == OCPP_MV_OFFLINE_PAYLOAD_BYTES) ? 1 : -1];
typedef int __ocpp_mv_size_check_2[(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) == 128u) ? 1 : -1];
typedef int __ocpp_mv_size_check_3[(sizeof(S_OCPP_MV_OFFLINE_FRAM_MNG) <= EEPROM_OCPP_MV_OFFLINE_CTRL_SIZE) ? 1 : -1];
#endif
/** @brief 上电:从 FRAM 恢复管理块 RAM 镜像,非法则默认并写回 */
void v_ocpp_mv_offline_init(void);
/**
* @brief 保存一条离线 MV:分配该枪单调序号、写 Flash、更新/追加映射行
* @param u8_gun_no 枪号:0=前半区(枪1),1=后半区(枪2)
* @param tx_sn_lo 会话键:当前为 `xDate2Seconds(StartChargeTime)`(单路 U32
* @param tx_sn_hi 当前恒 0(与 `u32_tx_sn_hi` 一致;接口双参仅为与 FRAM 行字段对齐)
* @param out_seq 输出本次在该枪下的单调序号
* @return 0 成功;1 参数非法(含枪号越界);2 映射表满;3 Flash 失败;4 FRAM 失败;
* 5 流水号已存在但本次序号不连续(调用顺序错误)
*/
U8_T u8_ocpp_mv_offline_mv_save(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, const S_OCPP_MV_OFFLINE_MV *mv,
U32_T *out_seq);
/**
* @brief 按该枪单调序号读一条 MV(物理槽:`seq % POOL_MAX_PER_GUN`,落在该枪半区)
* @return 0 成功;1 参数非法;2 校验/帧头失败
*/
U8_T u8_ocpp_mv_offline_mv_read_by_seq(U8_T u8_gun_no, U32_T seq, S_OCPP_MV_OFFLINE_MV *out);
/** @brief 判断负载是否为当前版本有效数据 */
U8_T u8_ocpp_mv_offline_mv_is_valid(const S_OCPP_MV_OFFLINE_MV *mv);
/**
* @brief 查询指定枪上会话键(`tx_sn_lo` + `tx_sn_hi`,当前 hi 恒 0)对应的序号区间(用于上送循环)
* @return 0 找到;1 空指针;2 未找到
*/
U8_T u8_ocpp_mv_offline_map_get(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi, U32_T *seq_start, U16_T *count);
/**
* @brief 上送完成后删除该枪上映射行(不擦整池 Flash;若需整池清空在 `ocpp_mv_offline_flash_impl.c` 内维护调用)
* @return 0 成功;1 未找到;2 FRAM 写失败
*/
U8_T u8_ocpp_mv_offline_map_remove(U8_T u8_gun_no, U32_T tx_sn_lo, U32_T tx_sn_hi);
/**
* @brief 新一笔充电开始前:清除该枪在 FRAM 中的全部 MV 映射行(释放 map 槽位;不擦 SPI Flash 池)
* @param[in] u8_gun_no 枪索引
* @return 0 成功;1 枪号非法;2 FRAM 写失败
* @see u8_ocpp_mv_offline_mv_save(映射满时按枪淘汰最旧一行)
*/
U8_T u8_ocpp_mv_offline_map_clear_gun(U8_T u8_gun_no);
/* 整池擦除、FRAM 读写默认块、池容量查询等为模块内部实现,无对外声明。 */
#ifdef __cplusplus
}
#endif
#endif /* OCPP_MV_OFFLINE_FLASH_IMPL_H */