# OCPP 功能模块说明(CCU601E_D) 本文档描述 **`CCU601E_D\app\plat_comm\ocpp`** 目录内各文件职责、主要数据结构及与其它模块的关系,便于维护与二次开发。全文基于工程源码归纳;协议细节以 **OCPP 1.6-j** 为准。 --- ## 1. 模块定位与编译开关 | 项目 | 说明 | |------|------| | **协议** | OCPP **1.6**,典型传输为 **WebSocket**(子协议 `ocpp1.6`)。 | | **宏开关** | 全局 **`BS_OCPP_EN`**(通常为 `Public_define.h` 等处):为 **0** 时本目录多数代码不参与编译。 | | **依赖** | **cJSON**(JSON 组帧/解析)、FreeRTOS 内存 **`pvPortMalloc` / `vPortFree`**(见 `BS_ocpp_ctrl.h` 中 `OCPP_MALLOC`)、平台任务 **`plat_comm`**(TCP/4G、充电订单接口)、计量/F **`meter_calculate`**、Flash 配置等。 | --- ## 2. 目录文件一览与职责 | 文件名 | 类型 | 职责摘要 | |--------|------|----------| | **BS_ocpp_ctrl.h** | 头文件 | OCPP 运行时控制结构体、定时器枚举、充电侧标志位枚举;对外 API:`v_ocpp_init_data`、`v_ocpp_tcp_connect`、`v_ocpp_logo_on`、`v_ocpp_recv_data`、`v_ocpp_send_ctrl`、`v_ocpp_private_logic`、`recv_mags` 等。 | | **BS_ocpp_ctrl.c** | 实现 | **核心控制器**:WebSocket 收发衔接、`send_mag` 统一发包、`ocpp_send_mag_ctrl` 周期/事件触发(Boot、Heartbeat、Status、MeterValues、Start/Stop、Authorize);平台下行解析(RemoteStart/Stop、ChangeConfiguration、GetConfiguration、Reset、Unlock、UpdateFirmware、SendLocalList、GetDiagnostics 等);**离线/未结算订单** `s8_unsettled_order_chaek`;各 Action 的 **CallResult** 处理函数(`*_res` / `*_unpack`)。**离线周期 MeterValues 的落盘与补发状态机**委托 **`BS_ocpp_mv_offline_bridge`**;**补发单条 PDU** 由 **`s32_ocpp_send_meter_values_replay`** 调 **`MeterValues_mag_offline_replay`**(实现位于桥接模块)。源码内用 **`#if 1 // …`** 做大段分区(见 **§4.0**)。 | | **BS_ocpp_mv_offline_bridge.h** | 头文件 | (**`BS_OCPP_EN`** 内)声明 **`MeterValues_mag_offline_replay`**、`v_ocpp_mv_offline_bridge_private_logic`、`v_ocpp_mv_offline_unsettled_abort`、`u8_ocpp_mv_offline_unsettled_step`;依赖 Flash 侧 **`ocpp_mv_offline_flash_impl.h`** 与 **`bs_ocpp_json_data.h`**。 | | **BS_ocpp_mv_offline_bridge.c** | 实现 | **离线 MeterValues 桥接**:TCP 未连接或已连接未登录时按周期把测量快照写入 SPI Flash 池(与 **`u8_get_ocpp_measurand_data`** 口径对齐);FRAM 映射键为 **订单开始充电时间**经 **`xDate2Seconds`** 的 32 位秒值 + 枪号;联网成功且未结单已取得平台 **transactionId** 后,在 **StopTransaction** 前按序 **`u8_ocpp_mv_offline_unsettled_step`** 补发;**`MeterValues_mag_offline_replay`** 按 **`u8_MeterValuesMeasurandNeed`** 从快照填 **SampledValue** 并 **`j_get_ocpp_json(OCPP_MeterValues, …)`**。**`v_ocpp_mv_offline_init`** 在 **`v_ocpp_init_data`** 中调用。详情见桥接文件头注释与 **`flash_file_mgr/ocpp_mv_offline_flash_impl.*`**。 | | **bs_ocpp_json_data.h** | 头文件 | **OCPP_CMD** 枚举(桩→平台 / 平台→桩)、各 PDU 的 **Request/Response** 结构体、`GetConfiguration`/`MeterValues` 等缓冲区长度宏;供组帧与解析共用。 | | **bs_ocpp_json_data.c** | 实现 | **字符串常量表**:命令名、`StatusNotification` 的 errorCode/statusCode、`StopTransaction.reason`、RemoteStart 应答等;**发送侧**:`BootNotification_json`、`StartTransaction_json`、`MeterValues_json` 等 **cJSON 组帧**;**接收侧**:`get_ocpp_data` / `ocpp_*_unpack` 解析平台下行 payload;**通用**:`j_get_ocpp_json`、`s_get_ocpp_cmd_str`、枚举转字符串等。 | | **bs_ocpp_str.h** | 头文件 | OCPP 协议中出现的 **字面量**(measurand、context、unit、`Accepted`/`Blocked`、以及标准 **Configuration Key** 名称宏 `KEY_*`),避免魔法字符串散落。 | | **bs_ocpp_configuration.h** | 头文件 | **连接器相位**枚举、**MeterValues 测量项**枚举 `OCPP_MEASURAND_ID`(顺序固定,与配置数组下标绑定);Flash 配置结构 **`S_OCPP_FLASH_CFG_DATA`** / **`OCPP_CONFIG`** 声明;测量项英文串表 **`OCPP_measurand_str`** 外部声明。 | | **bs_ocpp_configuration.c** | 实现 | **`s_ocpp_flash_cfg_data`**:默认与 Flash 持久化的 OCPP 配置(Heartbeat、MeterValues 采样项、`SupportedFeatureProfiles` 等);**GetConfiguration** / **ChangeConfiguration** 读写逻辑;与 **`spi_Flash`/`flash_external_data`** 协同落地配置;测量项字符串数组 **`OCPP_measurand_str`** 定义。 | | **bs_websocket.h** | 头文件 | WebSocket **帧类型**(文本/PING/PONG/断开);**`ws_send`**、**`websocket_build`**、**`ocpp_recv`** 声明;可选 **`OCPP_WS_HTTP_AUTHORIZATION_EN`**(HTTP Basic)。 | | **bs_websocket.c** | 实现 | **RFC6455** 握手(含 Sec-WebSocket-Accept、可选 Basic)、帧封装/解析;与 **`bs_connect_impl`** URL/TCP 衔接;收包后递交给 **`recv_mags`**(见 `BS_ocpp_ctrl.c`)。 | --- ## 3. 核心数据结构(`BS_ocpp_ctrl.h`) ### 3.1 `OCPP_SEND_TIMER` 周期性上报节拍:**BootNotification**、**Heartbeat**、各枪 **StatusNotification**、**MeterValues**、**StopTransaction** 定时、**Authorize** 定时等。具体周期值在运行时由配置或初始化写入 **`s_timerCnt[]`**。 ### 3.2 `OCPP_CHARGE_FLAG` 与充电流程协同的软件标志(落在 **`plat_comm_task`** 的枪数据中,通过 **`u8_charge_flag_ctrl`** 访问),例如: - **`OCPP_STATE_CHANGE_FLAG`**:连接器状态变化需上报; - **`OCPP_TRADE_UPLOAD_FLAG`**:订单结束待上送 **StopTransaction**; - **`OCPP_START_RESULT_FLAG`** / **`OCPP_STOP_RESULT_FLAG`**:启动/停止结果触发; - **`OCPP_VIN_START_FLAG`**:即插即充等场景的鉴权请求节拍。 ### 3.3 `OCPP_DATA_CTRL` OCPP 私有全局状态(指针 **`private_ocpp_data`**,初始化见 **`v_ocpp_init_data`**),主要包括: - **登录与链路**:`ulong_timer`、超时重连等; - **离线/未结订单**:`u8_unsettled_order_flag`、`s_unsettled_order_data`(动态分配的 **`S_LOG_DATA`**);补单流水线 **`u8_unsettled_auth_phase`**(`OCPP_UNSETTLED_AUTH_PHASE_E`:先 **Authorize** 再 **StartTransaction**)、**`u32_unsettled_last_auth_s` / `u32_unsettled_last_stx_s`**(**Authorize** 节流;**StartTransaction** 在本单鉴权通过后仅发一次,用非零时间戳标记已发,避免平台多笔 transaction); - **每枪控制**:`s_ctrl_flag[]`(MeterValues 召测、Trigger、鉴权计数等); - **定时器数组**:`s_timerCnt[OCPP_TIMERCNT_NUM]`; - **异步应答队列**:`ack_ctrl[]`(平台多条指令并发时的应答上下文)。 ### 3.4 `OCPP_DATA_SAVE` 待发 CALL 与应答匹配的 **`messageId`/`key`** 缓存(`save_json_cmd` / `get_json_cmd`),用于解析 **`CALLRESULT`** / **`CALLERROR`** 时找到对应枪号与命令类型。 --- ## 4. `BS_ocpp_ctrl.c` 功能分层说明 ### 4.0 源码分区(`#if 1` 标记) 为方便在单文件内浏览,`BS_ocpp_ctrl.c` 用大段 **`#if 1 // 注释`** 划分逻辑归属(**非**编译裁剪,恒为 1): | 分区注释 | 主要内容 | |----------|----------| | **`//公共`** | 随机数、待发 **messageId** 队列(`save_json_cmd` / `get_json_cmd`)、发送节拍累加(`v_ocpp_timer_add`、`u8_ocpp_timer_ctrl`)、**`v_ocpp_timestamp_to_str`**、应答块分配/释放、**`parse_ftp_url`** 与 FTP URL 自测、时间差工具函数;**供 FTP / 其它模块写 OCPP 侧“待发 Notify 状态”** 的 **`v_ocpp_set_diag_upload_notify_pending`、`v_ocpp_set_fw_notify_pending`、`v_ocpp_set_fw_reboot_after_installed`**(写入 **`s_bs_task_ctrl.ocpp_data`**,与 **`DiagnosticsStatusNotification_mag` / `FirmwareStatusNotification_mag`**、**`send_mag`** 配合)。**不再**在控制器内保留已迁出的 **unix 秒→ISO8601** 辅助(补发时间戳在桥接文件内实现)。 | | **`//数据获取`** | 枪/订单标志 **`u8_charge_flag_ctrl`**、桩状态、重启与交易上送标记、**`u8_get_ocpp_measurand_data`**、**`u8_get_ocpp_transaction_data`**、**GetConfiguration** 键填充、**StatusNotification** 变化检测与 Faulted 判定等。 | | **`//发送`** | 各 **`BootNotification_mag`、`*_mag`** 组 **`cJSON` body**(含 **MeterValues** 在线路径)、**GetConfiguration** 应答体、**`DiagnosticsStatusNotification_mag`、`FirmwareStatusNotification_mag`**、**`send_mag`**、**`s32_ocpp_send_meter_values_replay`**(**离线快照→JSON** 调用桥接层 **`MeterValues_mag_offline_replay`**)、**`ocpp_send_mag_ctrl`**;部分静态函数名含 **`_res`** 实为 **对外发送的 CallResult payload 组装**,仍属发送段。 | | **`//接收`** | 平台下行 **CALL** 解析(**`*_mag` unpack**、`ocpp_analysis_data`)、**CALLRESULT** 分发(**`*_res`**)、**`recv_mags`**。 | | **`未结订单与充电鉴权`** | **`u8_ocpp_is_pre_trade_no_pending`**、未结内存释放 **`v_safe_free_unsettled_order_data`**(内含 **`v_ocpp_mv_offline_unsettled_abort`**)、**`u8_unsettled_pretrade_pipeline`**、**`s8_unsettled_order_chaek`**、充电中鉴权检查 **`v_ocpp_private_charge_authentication_cheak`**。 | | **`//外部控制使用`** | **`v_ocpp_init_data` / `v_ocpp_init_data_ex`**(含 **`v_ocpp_mv_offline_init`**)、RX 超时与重连门槛 **`u8_ocpp_block_rx_timeout_relink`**、**`v_ocpp_tcp_connect`、`v_ocpp_logo_on`、`v_ocpp_recv_data`、`v_ocpp_send_ctrl`、`v_ocpp_private_logic`**。 | **内层** **`MeterValues_mag`** 中另有 **`#if 1 //根据枚举 MeterValues 测量项…`**,仅区分「按配置枚举上送」与「简化固定字段」两套实现,不单独对应上表大段。 ### 4.1 发送路径 1. 业务或定时器置位标志 → **`ocpp_send_mag_ctrl()`** 轮询; 2. **`send_mag(gunNo, cmd, ack_data, type)`**: - 构造 **[2, messageId, Action, payload]**(主动)或 **[3, …]**(应答); - `switch(cmd)` 调用各 **`*_mag`** 生成 **`cJSON` body**(实际多在 **`BS_ocpp_ctrl.c`** 内静态函数,如 `BootNotification_mag`、`StartTransaction_mag`,内部再调 **`bs_ocpp_json_data.c`** 的 json 辅助);**在线 MeterValues** 走 **`MeterValues_mag`**;**离线 Flash 中缓存的一条快照补发** 走 **`s32_ocpp_send_meter_values_replay`**,其 body 由 **`MeterValues_mag_offline_replay`**(**`BS_ocpp_mv_offline_bridge.c`**)生成,与在线共用 **`j_get_ocpp_json`** 与 **`u8_MeterValuesMeasurandNeed`**; - 文本输出经 **`ws_send`** 发到 CSMS。 **`type` 参数**:同一 Action 不同数据源时使用,例如 **StartTransaction / StopTransaction / Authorize** 的 **实时枪数据(0)** 与 **未结算内存订单 `s_unsettled_order_data`(1)**。 ### 4.2 接收路径 1. **`ocpp_recv()`**(`bs_websocket.c`)收到文本帧 → **`recv_mags()`**; 2. 解析 JSON:区分 **CALL / CALLRESULT / CALLERROR**; 3. **CALL**:根据 **Action** 字符串映射 **`OCPP_CMD`** → **`unpack`** 填充 **`OCPP_DATA_RECV_ACK`**,必要时 **`send_mag` 返回 CallResult**; 4. **CALLRESULT**:**`get_json_cmd`** 匹配 messageId → 调用对应 **`StartTransaction_res`、`Authorize_res`** 等,更新本地订单/流水号/鉴权状态。 ### 4.3 典型已实现的平台指令(下行) 包括但不限于:**RemoteStartTransaction**、**RemoteStopTransaction**、**UnlockConnector**、**Reset**、**ChangeAvailability**、**ChangeConfiguration**、**GetConfiguration**、**TriggerMessage**、**SendLocalList**、**GetLocalListVersion**、**ClearCache**、**UpdateFirmware**、**GetDiagnostics**(工程内可能对 diagnostics 使用自定义 Action 名,以源码为准)。 ### 4.4 离线 / 未结算订单(概要) - **`s8_unsettled_order_chaek()`**:联网且登录后周期性调用;从 **`s8_plat_get_unsettled_order`**(**`bs_public_impl.c`**)拉取 **Flash 未结列表**或 **EEPROM 掉电临时订单**,挂到 **`s_unsettled_order_data`**(一次一单)。 - **已有平台流水号**(**`u8_preTradeNo`** 有效:非空且非 **`0xFF`** 占位,判定见 **`u8_ocpp_is_pre_trade_no_pending`**):直接 **`StopTransaction(type=1)`** 上送结束侧订单信息(meterStop、timestamp、reason、transactionData 等),随后释放内存上下文。 - **尚无流水号**(空或 **`0xFF`** 占位):**`Authorize(type=1)`**(idTag 取订单 **`u8_user_id`**)→ 仅 **`idTagInfo.status == Accepted`** 后 **`StartTransaction(type=1)`** 申请 **transactionId** → **`StartTransaction_res`** 在桩空闲且未结副本存在时把 **transactionId** 写入 **`u8_preTradeNo`** → 下一周期走 **StopTransaction(type=1)**。**补充**:若本笔在离线阶段已通过 **`BS_ocpp_mv_offline_bridge`** 将周期 **MeterValues** 写入 Flash,则在 **已取得平台 `transactionId`** 后、发 **StopTransaction** 前,由 **`u8_ocpp_mv_offline_unsettled_step`** 按序号逐条补发(帧格式与在线 **`send_mag`/`MeterValues`** 一致),补完或映射无效后再结束事务。 - **拒绝与单次 StartTransaction**:**StartTransaction.conf** 对 Blocked/Invalid 等未结补单**立即** **`v_safe_free_unsettled_order_data`**(不重试 **StartTransaction**,避免平台侧多笔订单);**Authorize** 非 Accepted 同样丢弃内存。**Authorize** 带秒级节流(**`u32_unsettled_last_auth_s`**);鉴权通过后 **StartTransaction** 仅尝试发送一次(成功发出后 **`u32_unsettled_last_stx_s` 非零** 即不再发;链路 **`ws_send` 失败** 未置位时可下一周期再试一次发送)。 **详细分场景(Flash / EEPROM、与平台报文顺序)见第 10~12 节。** 详细边界条件见代码注释及同类需求文档。 --- ## 5. `bs_ocpp_json_data.c` 功能分层说明 | 类别 | 内容 | |------|------| | **命令表** | `ocpp_cmd_str[][]` 与 **`OCPP_CMD`** 枚举对齐,用于日志与解析。 | | **枚举串** | errorCode、status、reason、UnlockConnector 状态等,供 **`s_get_ocpp_enum_data_str`** 使用。 | | **桩 → 平台** | `*_json()`:把结构体填进 **cJSON**。 | | **平台 → 桩** | `ocpp_*_unpack()`:从 **cJSON** 填结构体,供业务判断。 | | **入口封装** | **`j_get_ocpp_json`**:按 cmd 选择对应 json 构造函数;**`get_ocpp_data`**:解析 CallResult。 | --- ## 6. `bs_ocpp_configuration.c` 功能说明 - 维护全局 **`s_ocpp_flash_cfg_data`**(含 **`OCPP_CONFIG`**):对标 **OCPP 标准配置键**,如 HeartbeatInterval、MeterValuesSampledData、AuthorizeRemoteTxRequests、StopTransactionOnInvalidId 等; - **ChangeConfiguration**:校验、写入 RAM + Flash;部分键需解析 CSV(测量项列表)、相位字符串等; - **GetConfiguration**:按 key 列表返回 **readonly/value**;未知 key 记入 **unknownKey**; - **`OCPP_measurand_str[]`**:与 **`OCPP_MEASURAND_ID`** 一一对应,供 MeterValues/StopTxn 数据项映射。 --- ## 7. `bs_websocket.c` 功能说明 - 完成 **TLS/TCP 之上的 WebSocket** 字节流处理(依工程 **`websocket_build`** 与连接模块集成方式而定); - **握手**:HTTP Upgrade → Sec-WebSocket-Accept(内置 SHA1/Base64 相关逻辑); - **心跳**:处理 **Ping/Pong**,业务层亦可配合应用层 Heartbeat; - **数据**:文本帧交给 **`recv_mags`**。 --- ## 8. `bs_ocpp_str.h` 功能说明 - **协议常量**:measurand、location、unit、`Sample.Periodic`、`Transaction.Begin/End` 等; - **授权/配置结果**:`Accepted`、`Blocked`、`Invalid` 等; - **配置键名**:`KEY_HEARTBEAT_INTERVAL`、`KEY_METER_VALUES_SAMPLED_DATA` 等(与 **`bs_ocpp_configuration`** 协同)。 避免与 **`bs_ocpp_json_data.h`** 中结构体字段混淆:**str.h** 侧重 **字面量与 Key 名**,**json_data.h** 侧重 **PDU 结构体与枚举**。 --- ## 9. 与其它目录的典型调用关系(示意) ```mermaid flowchart LR subgraph plat_comm_task T[v_ocpp_send_ctrl / v_ocpp_recv_data] end subgraph ocpp C[BS_ocpp_ctrl.c] M[BS_ocpp_mv_offline_bridge.c] J[bs_ocpp_json_data.c] W[bs_websocket.c] CFG[bs_ocpp_configuration.c] end subgraph impl P[bs_public_impl.c] end subgraph flash F[ocpp_mv_offline_flash_impl] end T --> C C --> M C --> J C --> W C --> CFG C --> P M --> F M --> J ``` - **`plat_comm_task`**:周期性调用 **`v_ocpp_private_logic`** / **`v_ocpp_send_ctrl`**,驱动 **`ocpp_send_mag_ctrl`**;未联网或未登录分支末尾可调用 **`v_ocpp_mv_offline_bridge_private_logic`** 做离线 MV 周期采样落盘; - **`bs_public_impl`**:枪状态、订单 **`S_LOG_DATA`**、未结算订单 **`s8_plat_get_unsettled_order`**、结算确认等;**`v_ftp_update_action`** 在 4G 下根据 **`u8_log_file_upload` / `u8_updata_flag`** 调度 **`ec200a_4g_ftp.c`** 中日志或固件 FTP 状态机; - **`meter_calculate`**:订单写 Flash、EEPROM 临时订单、未结算索引链表(见 **`unsettled_order_mng`**)。 ### 9.1 GetDiagnostics、4G FTP 与 PlatComm 调度(与 `4g_module` 衔接) **GetDiagnostics** 由 **`BS_ocpp_ctrl.c`** 解析平台下发的 **location**(FTP URL、账号等),置 **`s_bs_task_ctrl.ocpp_data.u8_log_file_upload`**,并在 **`GetDiagnostics_res`** 中准备文件名、调用 **`v_ftp_ctrl_init()`** 复位 FTP 步进。**须保证「先发出 CallResult / Uploading,再置 `u8_updata_flag`」**,否则与 **`EC200A_BLOCK_QISEND_DURING_4G_FTP`**、**`u8_bs_ftp_ocpp_strict_mux`** 组合时可能挡掉应答(详见 **`4g_module/4G模块AT指令配置功能说明.md`** 第四节)。 **4G + 非透传** 时,日志上传在 **`ec200a_4g_ftp.c`** 中已改为:**`QFOPEN` + `QFSEEK` + `QFWRITE`** 按 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`(默认 4096)** 分包写模组 UFS;**`QFWRITE` 在单周期内仅短等轮询**,未完成则下一 **FTP 半周期** 续传。**`plat_comm_task.c`** 在 FTP 活跃时对 4G 采用 **相邻 PlatComm 周期「仅联网 / 仅 FTP 一步」交替**,与上述分包叠加后,**FTP 传输期间 OCPP 仍可周期性完成 `QISEND`/`QIRD` 与 WebSocket 业务**。 **`v_ocpp_private_logic`**:不得在 **`u8_updata_flag==0`** 时无条件清除 **`u8_log_file_upload`**,否则会误入固件升级分支(日志出现 **`[FTP-UPG]`** 等异常);该约束与 **`4g_module/4G模块AT指令配置功能说明.md`** 第三节一致。 配置与排障(**`EC200A_FTP_*`**、**`EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS`** 等)以 **`ec200a_4g_cfg.h`** 与 **`4G模块AT指令配置功能说明.md`** 为准。 --- ## 10. 离线未结算订单处理流程(Flash 列表) 本节描述:**充电正常结束或结算链路中断后,订单已写入 Flash,且索引已进入 EEPROM「未结算列表」**,待 CSMS 上线后通过 OCPP 补单:**本地尚无有效 transactionId(空或 0xFF 占位)时,先 Authorize(idTag),再 StartTransaction 获取流水号并写入 `u8_preTradeNo`,再 StopTransaction 上送订单结束信息**;**若 `u8_preTradeNo` 已为有效流水号,则直接 StopTransaction**,不再重复鉴权与 StartTransaction。 ### 10.1 数据从哪里来(产生侧) | 环节 | 位置 | 说明 | |------|------|------| | 订单落盘 | `meter_calculate_impl.c` → **`v_meterlog_flash_save_log(1, gunNo)`** | 订单结束时 **`u32_chg_order_save_record`** 写 Flash,得到有效 **`orderIndex`(u32_index)**。 | | 进入未结队列 | 同上 | **`u8_add_unsettled_order(orderIndex)`**(`BSP/flash_file_mgr/unsettled_order_mng.c`)把该索引写入 **EEPROM 未结算管理区**,表示「尚未收到平台对本单的结算确认」。 | | 典型字段 | **`S_LOG_DATA`** | 含 **`u8_user_id`**、起止电量与时间、**`u8_preTradeNo`**(从未拿到 **transactionId** 时可能为空;离线刷卡等场景可能为 **`0xFF`** 首字节占位,**`u8_ocpp_is_pre_trade_no_pending`** 视为须先 **Authorize + StartTransaction**)、**`u8_gunNo`** 等。 | 充电过程中周期性刷新 EEPROM 临时区的逻辑见 **第 11 节**;正常完整结束订单后,会 **清除该枪 EEPROM 临时记录**(`v_clear_temp_chg_record_in_eeprom`),并依赖 **Flash 未结列表** 做后续上送。 ### 10.2 读单出口(`bs_public_impl.c`) **`S8_T s8_plat_get_unsettled_order(S_LOG_DATA *s_log, U8_T u8_type)`** 在 **已处理完掉电临时单** 之后,走 Flash 未结逻辑: 1. **`u16_get_unsettled_order_count()`** 为 0 则直接返回 0(无单)。 2. **`u8_type == 1`** 时只返回数量,不读内容。 3. 否则 **`v_get_unsettled_order_indexes`** 取 **队首** 索引 **`currentIndex`**。 4. **`v_read_chg_order_record(s_log, currentIndex)`** 从 Flash 读出完整 **`S_LOG_DATA`**。 5. **`u8_platform_settlement_confirm(currentIndex)`**:按当前实现,在 **判断 readResult 之前** 即从未结列表中移除该索引(若 Flash 读失败,存在「索引已删、数据未读出」风险,属实现细节,维护时需留意)。 返回值:**>0** 表示仍有未结条数(含本条);**0** 无单;**-1** 读单失败。 ### 10.3 OCPP 何时启动补单(`BS_ocpp_ctrl.c`) 前置条件(**`v_ocpp_private_logic`** 内 **已联网且已登录 CSMS**): - **`u8_unsettled_order_flag == 0`**(本开机周期尚未标记「未结检查完毕」); - **`ulong_timer - u32_timer > 10`**(与注释「约 30 秒」对应的节流,实际以 `ulong_timer` 单位为准),更新 **`u32_timer`** 后调用 **`s8_unsettled_order_chaek()`**。 断网或未登录时 **`u8_unsettled_order_flag` 被置 0**,不会跑未结上送;避免无链路发 OCPP。 ### 10.4 OCPP 状态机:`s8_unsettled_order_chaek` 内存载体:**`private_ocpp_data->s_unsettled_order_data`**(堆上 **`S_LOG_DATA`**,一次只处理一单)。分支判定统一使用 **`u8_ocpp_is_pre_trade_no_pending(u8_preTradeNo)`**(空串或首字节 **`0xFF`** 视为「尚无有效流水号」)。 **首次取单**(`s_unsettled_order_data == NULL`): 1. **`OCPP_MALLOC`** + **`memset`**,**`u8_unsettled_auth_phase = NONE`**,**`u32_unsettled_last_auth_s` / `u32_unsettled_last_stx_s` 清零**; 2. **`s8_plat_get_unsettled_order(..., 0)`** 填入内容; 3. **`u8_user_id` 为空** → **`v_safe_free_unsettled_order_data()`**,返回; 4. **尚无流水号**(`u8_ocpp_is_pre_trade_no_pending == 1`):调用 **`u8_unsettled_pretrade_pipeline`** - **`OCPP_UNSETTLED_AUTH_NONE`**:节流后 **`send_mag(gunIdx, OCPP_Authorize, NULL, 1)`**,**`Authorize_mag`** 从 **`s_unsettled_order_data->u8_user_id`** 取 idTag;成功后 **`u8_unsettled_auth_phase = WAIT`**; - **`WAIT`**:等待 **`Authorize_res`**;仅 **Accepted** 时置 **`OK`**,否则释放订单; - **`OK`**:本单在 **`u32_unsettled_last_stx_s == 0`** 时仅 **`send_mag(0, OCPP_StartTransaction, NULL, 1)` 一次**(成功后置非零时间戳,避免重复 **StartTransaction** 导致平台多笔订单);**`StartTransaction_mag`** 用未结副本组 **StartTransaction.req**(**`connectorId = u8_gunNo+1`**、历史 **meterStart / timestamp / idTag**)。 5. **已有流水号**(`pending == 0`):**`send_mag(0, OCPP_StopTransaction, NULL, 1)`** → **`v_safe_free_unsettled_order_data()`**。 **同一单未释放时**(内存中仍持有上一笔未结单): - 仍按 **步骤 4 / 5** 区分;**`u8_cnt`(静态)** 仅用于 **已有流水号** 时 **StopTransaction** 重试计数;无流水号路径由 **`u8_unsettled_pretrade_pipeline`** 管理(**StartTransaction** 每单最多成功发出一次)。 ### 10.5 平台应答:Authorize 与 StartTransaction - **`Authorize_res`**(**`u8_unsettled_auth_phase == WAIT`**):仅 **`idTagInfo.status == Accepted`** 时置 **`OCPP_UNSETTLED_AUTH_OK`**,否则 **`v_safe_free_unsettled_order_data()`**。解析失败时 **`WAIT` 回退为 `NONE`** 以便重试鉴权。 - **`StartTransaction_res`**:若 **`idTagInfo.status`** 为 **Blocked / Invalid / Expired / ConcurrentTx**,未结补单路径**不得**把 **`transactionId`** 写入 **`preTradeNo`**,并**立即**释放内存(**不重试 StartTransaction**)。 当 **桩空闲**(**`u8_get_pile_state(gunNo)==0`**)且 **`s_unsettled_order_data` 非空** 且非上述拒绝态:将 **`data.transactionId`** 写入 **`u8_preTradeNo`**,**`u8_unsettled_auth_phase = NONE`**,下一周期上送 **StopTransaction**。 注意:**StopTransaction** 主动发包 **`gunNo` 常为 0**,但 PDU 内 **connectorId / 计量** 依赖 **`s_unsettled_order_data->u8_gunNo`**;**Authorize** 主动发包使用 **订单枪号 `gunIdx`**,以便 **CALLRESULT** 与 **`ack_data->gunNo`** 一致。 ### 10.6 StopTransaction(type=1) **`StopTransaction_mag(..., type==1)`** 使用 **`s_unsettled_order_data`**:**transactionId**、**idTag**、结束时间、**meterStop**、**reason**(内部故障码映射到 **`StopTransactionRequest.reason`**,如 Local / PowerLoss 等)。 --- ## 11. 掉电(异常断电)订单处理流程(EEPROM 临时记录) 本节描述:**充电过程中异常断电**,来不及走完整「订单结束写 Flash + 未结索引」路径时,依赖 **每枪独立 EEPROM 槽位** 保存的 **最后一次充电快照**,上电后优先恢复并走 **同一套 OCPP 补单**。 ### 11.1 数据何时写入 EEPROM(充电中刷新) | 调用 | 说明 | |------|------| | **`v_meterlog_flash_save_log(0, gunNo)`** | **订单未结束**,周期性把当前 **`s_metering_ctrl.s_gun_log[gunNo]`** 写入 EEPROM: **`v_save_temp_chg_record_to_eeprom(..., TEMP_CHG_FLAG_UNSETTLED)`**(`meter_calculate_flash_impl.c`)。 | 每枪地址互不干扰(如枪 A / 枪 B 不同 EEPROM 基址)。 ### 11.2 正常结束订单时对 EEPROM 的处理 **`v_meterlog_flash_save_log(1, gunNo)`**(订单结束分支):写 Flash 订单、**加入未结索引链** 后,执行 **`v_clear_temp_chg_record_in_eeprom(gunNo)`**,避免「已正规入库」仍被当成掉电单重复上传。 ### 11.3 上电读单优先级(`s8_plat_get_unsettled_order` 前半段) 对 **`gunNo = 0 .. GUN_MAX_CNT-1`** 依次: 1. **`v_check_temp_chg_record_valid(gunNo)`**:标志为 **`TEMP_CHG_FLAG_UNSETTLED` 或 SETTLED 类有效值** 则认为有条目; 2. **`v_read_temp_chg_record_from_eeprom(gunNo, s_log, NULL)`** 读出 **`S_LOG_DATA`**; 3. 强制 **`s_log->e_stop_reason[0] = E_FAULT_13073`**(异常掉电),用于 **StopTransaction** 映射为 **`PowerLoss`** 等; 4. **`v_clear_temp_chg_record_in_eeprom(gunNo)`**:读完即清标志(下次不再重复读); 5. **return 1**,表示有一笔待 OCPP 处理的未结算数据。 因此:**掉电单总是先于 Flash 未结队列被取出**;若 EEPROM 与 Flash 同时存在数据,**优先消耗掉电副本**。 ### 11.4 与 OCPP 的衔接 掉电单读出后 **`S_LOG_DATA`** 同样挂在 **`s_unsettled_order_data`** 上,后续与 **第 10.4~10.6 节** 完全一致: - **无有效流水号** → **Authorize(type=1)** → **Accepted** 后 **StartTransaction(type=1)** → 应答写入 **`preTradeNo`** → **StopTransaction(type=1)**; - **读单时已有流水号**(少见)→ 直接 **StopTransaction(type=1)**; - 本实现 **StopTransaction** 发送后即 **`v_safe_free_unsettled_order_data()`**(与 **CALLRESULT** 无硬耦合)。 ### 11.5 流程示意图(合并视角) ```mermaid sequenceDiagram participant MC as meter_calculate participant EEPROM as EEPROM临时区 participant Flash as Flash订单+未结索引 participant IMPL as s8_plat_get_unsettled_order participant OCPP as s8_unsettled_order_chaek Note over MC,EEPROM: 充电中周期 MC->>EEPROM: v_save_temp_chg_record(UNSETTLED) Note over MC,Flash: 正常结束 MC->>Flash: 写订单 + u8_add_unsettled_order MC->>EEPROM: clear 临时记录 Note over EEPROM,OCPP: 上电/联网后 EEPROM-->>IMPL: 若有有效临时记录优先读出并 clear Flash-->>IMPL: 否则读未结队列首条 IMPL->>OCPP: 填充 s_unsettled_order_data OCPP->>OCPP: 无txId→Auth(1)→StartTx(1)→写preTradeNo→StopTx(1) ``` --- ## 12. 两类订单的差异小结与维护注意 | 对比项 | Flash 离线未结订单 | EEPROM 掉电临时订单 | |--------|-------------------|---------------------| | **典型成因** | 已写完订单但 CSMS 未确认结算 / **StopTransaction** 未成功 | 充电中断电,仅有最后一次周期快照 | | **持久化位置** | Flash 订单文件 + EEPROM **未结索引列表** | EEPROM **按枪临时块** | | **读取顺序** | 在掉电分支 **之后** | **优先**(for 循环枪号顺序) | | **停止原因** | 以 **`S_LOG_DATA` 内原有故障码** 映射 | 读出后强制 **`E_FAULT_13073`**(映射 **PowerLoss**) | | **OCPP 后续** | 相同:**Authorize(1) → StartTx(1) → 填 preTradeNo → StopTx(1)**(有流水号则仅 **StopTx(1)**) | 相同 | **实现层面已知耦合点**(便于排查问题): 1. **未结补单**:**`StopTransaction` / `StartTransaction`** 常用 **`send_mag(0, …)`**,PDU 内 **connectorId / 计量** 依赖 **`s_unsettled_order_data->u8_gunNo`**;**`Authorize(type=1)`** 使用 **`send_mag(gunIdx, …)`**(订单枪号),以便 **CALLRESULT** 与 **`ack_data->gunNo`** 一致。需确认 sampled 数据是否混枪。 2. **Flash 读单**后立即 **`u8_platform_settlement_confirm`**:若 Flash 读失败,可能导致索引已删。 3. **`StartTransaction_res`** 未结算分支已对 **Blocked/Invalid 等** 拒绝 **`preTradeNo`** 写入;**Authorize** 非 **Accepted** 会丢弃内存上下文。 4. **`StopTransaction` 成功后立即 `free`**,与 **`StopTransaction_res`** 无联动;若需严格「应答后再删单」,需架构层增强。 --- ## 13. 维护建议 1. **新增 OCPP Action**:在 **`OCPP_CMD`**、**`ocpp_cmd_str`**、**`send_mag` switch**、**接收分发**、**json 组帧/解析** 五处同步扩展。 2. **新增 Configuration Key**:在 **`bs_ocpp_str.h`**、`OCPP_CONFIG` 结构体、**Get/Change** 分支中一并添加,并处理 Flash 默认值与长度。 3. **测量项顺序**:**`OCPP_MEASURAND_ID` 顺序不可随意插入**,否则配置位图与 **`OCPP_measurand_str`** 错位。 4. **GetDiagnostics / 4G 日志 FTP**:与 **`plat_comm` / `4g_module`** 强相关;**CallResult 与 `u8_updata_flag` 顺序**、**`u8_log_file_upload` 勿误清**、**PlatComm 联网与 FTP 半周期交替**、**`QFWRITE`/`QFREAD` 分包** 等见 **第 9.1 节** 与 **`app/plat_comm/4g_module/4G模块AT指令配置功能说明.md`**。 5. **补单链路变更**:同步核对 **第 10~12 节** 与 **`bs_public_impl.c` / `meter_calculate_impl.c` / `unsettled_order_mng`** 的一致性。 6. **离线 MeterValues**:改动落盘键(**StartChargeTime** / 枪号)、FRAM 映射、快照字段或与在线 measurand 口径时,需同时核对 **`BS_ocpp_mv_offline_bridge.c`**、**`ocpp_mv_offline_flash_impl.*`** 与 **`u8_get_ocpp_measurand_data`**;释放未结单内存前须 **`v_ocpp_mv_offline_unsettled_abort`**(见 **`v_safe_free_unsettled_order_data`**)。 --- *文档版本:2026-05 起补充 **离线 MV 桥接** 与 **`BS_ocpp_ctrl.c` 分区说明**;与当前 CCU601E_D `app/plat_comm/ocpp` 目录一致,若后续增减文件请同步更新第二节表格。*