Initial commit: CCU621_M firmware project with BLE debug link support.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:28:36 +08:00
commit 9ceb218f80
1597 changed files with 724159 additions and 0 deletions
@@ -0,0 +1,363 @@
# 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 StopTransactiontype=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.410.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. **补单链路变更**:同步核对 **第 1012 节****`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` 目录一致,若后续增减文件请同步更新第二节表格。*