9ceb218f80
Co-authored-by: Cursor <cursoragent@cursor.com>
196 lines
11 KiB
C
196 lines
11 KiB
C
/**
|
||
* @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:累计 Wh(u64/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; /**< SoC:0~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=前半区枪1,1=后半区枪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 */
|