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,184 @@
# 4G 模块 AT 指令与配置功能说明(EC200A)
本目录实现 **Quectel EC200A** 与 MCU 的 AT 交互:**蜂窝注册 → PDP → TCP/TLS 业务**,以及 **FTP(S) 远程升级****GetDiagnostics 日志上传**。下文按 **AT 流程**、**透传 / 非透传**(含 **TCP 与 FTP 分 PDP**)、**FTP 升级与日志两套状态机**、**plat_comm / OCPP 衔接**、**配置宏与排障** 说明。
---
## 目录与文件速览
| 文件 | 作用 |
|------|------|
| `ec200a_4g_cfg.h` | 透传开关、TCP/FTP **分 PDP**、PlatComm FTP 与平台间隔、**QFREAD 分片**、**QFTPGET(COM) 分片收包**(与 QFREAD 同思路的轮询等待)、**QFWRITE 分片轮询**、**UFS 单次读写块 `EC200A_FTP_UFS_RW_CHUNK_BYTES`(默认 4096)**、FTP 期间是否暂缓 **QISEND**、日志块大小、日志收尾到 **QISEND** 前延时等 |
| `ec200a_4g.h` / `ec200a_4g.c` | 初始化状态机、UART 收发、`QIOPEN`/`QSSLOPEN`、非透传 **`QISEND`/`QIRD`**(明文 TCP)与 **`QSSLSEND`/`QSSLRECV`**`wss`/`QSSLOPEN`)、软重连等 |
| `ec200a_4g_ftp.h` / `ec200a_4g_ftp.c` | **`u8_ec200a_4g_ftp_update`**(固件升级)、**`u8_ec200a_4g_ftp_log_upload`**(诊断日志)、**`v_ftp_ctrl_init`** |
上层业务(如 OCPP WebSocket)经 **`bs_connect_impl.c`** 的 **`v_bs_send` / `u16_bs_recv`** 接入 4GFTP 调度由 **`bs_public_impl.c`** 的 **`v_ftp_update_action`** 在 PlatComm 中周期调用。OCPP 侧置位 **`u8_updata_flag`**、**`u8_log_file_upload`** 等与 **`BS_ocpp_ctrl.c`** 协同(见第四节)。
---
## 一、AT 指令流程(蜂窝初始化与建链)
### 1.1 前置:`v_4g_data_init`
应用解析配置里的服务器 URL(域名、端口、APN、`ws://`/`wss://`)填入 **`S_4G_CONFIG_PARA`**,再调用 **`v_4g_data_init`**,内部 **`s8_cat_reboot(0)`** 完成模组上电/复位类动作,随后周期性调用 **`s8_ec200a_4g_init`** 推进状态机。
### 1.2 主初始化路径(`s8_ec200a_4g_init`
状态枚举 **`E_EC200A_4G_INIT_STEP`**,典型顺序:命令模式 → SIM → CCID → CFUN → CGREG → **业务 PDP `QICSGP`/`QIACT``EC200A_TCP_PDP_CTX_ID`** → 可选 TLS 配置 → **`QIOPEN`/`QSSLOPEN`** → 完成。
非透传在打开 Socket 前会 **`s8_ec200a_qisde_apply()`**`AT+QISDE`),再 **`s8_ec200a_qiopen_accumulate`** 等。
### 1.3 建链与 access_mode
- **`EC200A_TCP_QIOPEN_ACCESS_MODE`**:透传为 **2**(等 `CONNECT`),缓冲模式为 **0**(轮询 `+QIOPEN`/`+QSSLOPEN` 错误码)。
- 业务 Socket 使用 **`EC200A_TCP_PDP_CTX_ID`**(默认 **1**),与 FTP 所用 PDP **必须不同**(见 `ec200a_4g_cfg.h` 编译期校验)。
### 1.4 超时与恢复
状态机末尾根据 **`u8_out_cnt`** 累加;过久会打日志、回退 **`STEP_SWCHTO_CMD_MODE`** 并可能 **`s8_cat_reboot`**。非透传下 TCP 断连可先做 **QICLOSE+QIOPEN 软重连**,连续失败后再全量 **`v_bs_4G_data_init`**(见 `ec200a_4g_cfg.h`**`EC200A_TCP_SOFT_REOPEN_FAIL_MAX`**)。
---
## 二、透传与非透传(缓冲模式)
### 2.1 配置关系(`ec200a_4g_cfg.h` 摘要)
| 宏 | 作用 |
|----|------|
| **`EC200A_TCP_USE_TRANSPARENT_MODE`** | **1** 透传;**0** 缓冲(`QISEND`/`QIRD` |
| **`EC200A_TCP_PDP_CTX_ID` / `EC200A_FTP_PDP_CTX_ID`** | 业务 TCP 与 FTP **分 PDP**(默认 1 / 2),**禁止相等** |
| **`EC200A_FTP_TO_PLATFORM_GAP_MS`** | 每轮 PlatComm 内 **`v_ftp_update_action` 一步之后**、平台收发循环前的间隔 (ms),减轻 FTP AT 与 **QISEND** 粘连 |
| **`EC200A_FTP_QFREAD_*`** | 非透传下 **`AT+QFREAD`**(日志读 UFS)及 **`AT+QFTPGET``COM:`** 固件分片:应答在单周期内 **短 `mSleep` + 收包累加** 直至 `\r\nOK`**QFTPGET** 侧等待轮次约为 **`EC200A_FTP_QFREAD_TOTAL_WAITS` 的 12 倍**(FTP 拉网侧数据较慢,见 `s16_qftpget_com_at_slice` |
| **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** | 模组 UFS 单次 **`AT+QFREAD` / `AT+QFWRITE`** 数据负载,同时也是 **固件 `AT+QFTPGET="…","COM:",off,len` 的单片长度**(默认 **4096 = 1024×4**);须为 Flash **`PACKET_SIZE`(1024)** 的整数倍(编译期校验)。固件下载每片经 **CONNECT 负载** 解析后,按 **`upgrade_write_packet`** 与 **`QFREAD` 读 UFS 写 Flash** 相同的 **flash_base + 1024 子包** 逻辑写入 |
| **`EC200A_FTP_QFWRITE_*`** | 非透传下 **`AT+QFWRITE`**CONNECT→发负载→`\r\nOK` 跨多轮 PlatComm **短等轮询**`QFWRITE_WAIT_MS` / `QFWRITE_WAITS_PER_PLAT_ROUND` / `QFWRITE_TOTAL_WAITS`),避免单周期长时间阻塞 UART |
| **`EC200A_FTP_LOG_CHUNK_SIZE` / `EC200A_FTP_LOG_QFWRITE_MAX_BYTES`** | 默认与 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** 对齐;日志从 FATFS 读块与单次 QFWRITE 负载一致,**一步一块**、包间回到 AT 态 |
| **`EC200A_BLOCK_QISEND_DURING_4G_FTP`** | **1**(默认):`u8_updata_flag` 且 4G 模组 FTP 时 **`v_bs_send`/`ws_send` 暂缓 QISEND**QFTPPUT 大文件期间避免与 AT 抢 UART;**0** 尝试穿插(仍可能 `no prompt >`)。**勿**仅用 `u8_log_file_upload` 判暂缓(recv 早于 CallResult 置位会挡应答)。 |
| **`EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS`** | 日志上传收尾(**`QFTPCLOSE`**、`v_ftp_ctrl_init`、排空 RX)后,首帧 OCPP **QISEND** 前短延时,减轻 **`break off=0`** |
| **`EC200A_FTP_LOG_DONE_REQUEST_BS_RENEW`** | 日志上传 **`STEP_FTP_INIT_OK`**:是否 **`v_set_bs_renew`**(默认 **0**,不重连 OCPP);置 **1** 时 FTP 后软重开 TCP 并 **重登** |
| **`EC200A_TCP_APP_RW_MAX`** / **`EC200A_QIRD_AT_READ_MAX`** / **`EC200A_QISEND_AT_LEN_MAX`** | 应用单次读写与 Quectel AT 上限(1500 / 1460)钳位 |
### 2.2 平台统一收发(`bs_connect_impl.c`
**`u8_netType == 0`4G** 时:`v_bs_send` → 透传 **`v_4g_send_data`** / 非透传 **`v_ec200a_tcp_app_send`**`u16_bs_recv` 同理。
非透传 **`s8_ec200a_qisend_once`**:先发 **`AT+QISEND=id,len`**,等 **`>`**,再发二进制负载,再等 **`SEND OK`**;发送前 **`ec200a_modem_uart_rx_drain`** 减轻粘连。详见源码注释与历史版本本文档附录(若需可从版本管理拷贝为 `4G模块AT指令配置功能说明_appendix.md`)。
### 2.3 TCP 与 FTP 的关系(重点)
| 维度 | 说明 |
|------|------|
| **PDP** | 业务 TCP 与 FTP 使用 **不同 context**FTP 侧 **`QIDEACT`/`QIACT`** 只作用 **`EC200A_FTP_PDP_CTX_ID`****不拆**业务 TCP 所在 PDP。 |
| **UART** | 物理 **一根** 串口;**`QISEND`/`QIRD`/`QFTP*`/`QFREAD`/`QFWRITE`** 均为 **分时串行**。 |
| **“并存”** | 模组协议栈内 TCP 与 FTP 会话可同时存在;MCU **交替发 AT**,单时刻 UART 上只有一种事务。 |
| **PlatComm 与联网穿插** | **`plat_comm_task.c`**4G FTP 活跃时 **相邻两周期交替**——一周期只做 **联网**`v_plat_comm_network_round`:双路 `recv` + 各平台 `send_ctrl`),下一周期只做 **FTP 一步**`v_plat_comm_ftp_round``recv`**`v_ftp_update_action`** → **`EC200A_FTP_TO_PLATFORM_GAP_MS`**)。配合 **QFTPGET(COM) / QFREAD / QFWRITE 分包 + 短等**FTP 传输期间 **OCPP 仍可周期性收发包**(非透传路径)。 |
---
## 三、FTP:固件升级与日志上传(两套路径)
**`u8_ec200a_4g_ftp_update`** 与 **`u8_ec200a_4g_ftp_log_upload`** 共用全局 **`e_ec200a_ftp_init_step`** 与同一套 **`STEP_FTP_*`** 枚举,但 **跳转序列不同**;进入任一路径前通常 **`v_ftp_ctrl_init()`** 将步进复位到 **`STEP_FTP_SWCHTO_CMD_MODE`**(或由业务在 **`GetDiagnostics_res`** 等里显式调用)。
**`v_ftp_update_action``bs_public_impl.c`** 分支逻辑:
1.**`s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 1`**(且编译开启 OCPP):走 **`u8_ec200a_4g_ftp_log_upload()`**(4G);网线且未实现日志 FTP 时会清标志并上报失败。
2. **否则**:走 **`u8_ec200a_4g_ftp_update()`**(4G 固件)或 LwIP 升级状态机。
因此 **`u8_log_file_upload` 必须与 `u8_updata_flag` 配合正确**:若误清 **`u8_log_file_upload`**,会在 **`u8_updata_flag` 仍为 1** 时误入 **固件升级** 分支,出现日志里的 **`[FTP-UPG]`** 与 **`RX pkt(0)/(3)`** 等异常(**`BS_ocpp_ctrl.c`** 中已去掉在 **`u8_updata_flag==0` 时无条件清 `u8_log_file_upload`** 的逻辑)。
### 3.1 固件远程升级(`u8_ec200a_4g_ftp_update`
- **入口**PlatComm 周期调用 **`v_ftp_update_action`** → **`u8_ec200a_4g_ftp_update()`**(当 **`u8_log_file_upload==0`** 且 **`t_ftp_update_info.u8_netType==0`**)。
- **日志标签****`[FTP-UPG] step=…`**(与日志上传的 **`ftp test …`** 区分)。
- **配置****`T_FTP_UPDATE_INFO t_ftp_update_info`**(URL、主机、端口、路径、**固件文件名**、账号等)。
**远端对象路径**:由 **`s32_ftp_fill_remote_get_object()`** 将 **`u8_path` + `u8_file_name`** 拼成 **`s_ftp_remote_get_obj`**(如 `/home/ftpuser/ftpfile/firmware/CCU601E.bin`),供 **`QFTPSIZE`** 与 **`QFTPGET`** 使用,**避免仅 CWD 到绝对路径失败时无法取文件**。
**`QFTPCWD`**:路径过长则跳过;先 **`AT+QFTPCWD="<path>"`**,失败再试 **去掉前导 `/`**;仍失败则 **打日志并返回成功步进(1**,后续依赖 **全路径 `QFTPSIZE` / `QFTPGET`**
**与源码一致的步骤顺序(摘要)**
| 顺序 | 枚举步 | 行为要点 |
|------|--------|----------|
| 1 | `STEP_FTP_SWCHTO_CMD_MODE` | **`s8_work_mode_cmd`**:退出透传、**`AT`** |
| 23 | `SET_CGREG` / `QUERY_CGREG` | PS 注册 |
| 45 | `SET_QIDEACT` / `SET_QIACT` | 仅 **FTP PDP**:去激活 → **`QICSGP`+`QIACT`** |
| 610 | `FTP_CFG_*` | **`QFTPCFG`**contextid、account、filetype、transmode、rsptimeout |
| 11 | `QFTPOPEN` | 登录 FTP 服务器 |
| 12 | `QFTPCWD` | 切换远程目录(失败时仍可继续,见上) |
| 13 | `GET_QFTPSIZE` | **`QFTPSIZE`** 对 **`s_ftp_remote_get_obj`** 取远程大小 → **`u32_file_size`**;成功则按 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** 计算 **`u16_packSize` / `u16_packNum`****`u16_packIndex=0`** |
| 14 | `QFDEL` | 清模组 **UFS** 旧文件(与日志/杂项共用空间,仍建议执行) |
| 15 | `GET_QFTPGET` | 循环 **`AT+QFTPGET="<remote>","COM:",offset,len`****非透传** 用 **`s16_qftpget_com_at_slice`**(与 **`s16_qfread_at_slice`** 同思路等 **OK**);**透传** 用 **`s16_4g_Send_Recv_Ftp_Get`**。首片前 **`v_upgrade_header_data(0)`****`parse_4g_ftp_response`** 取 CONNECT 与 **OK** 间二进制,按与 **`s8_ec200a_qfread`** 相同的 **`flash_base` + 1024 子包** 调 **`upgrade_write_packet`**。**返回 0** 表示尚有分片;**1** 表示全部写完 |
| 16 | `STEP_FTP_SAVE` / `INIT_OK` | **`v_upgrade_header_data(1)`** + **`v_sys_reboot()`** |
**已不再作为升级主路径****`QFLST``QFOPEN``QFSEEK``QFREAD`(从 UFS 整包下载后再读)**;源码中相关 **`case`** 仍保留,供其它场景或对照,**正常 OCPP 固件升级不会走入**。
**超时**:全局 **`u8_out_cnt`** 超过约 **3 分钟****`u8_updata_flag`**、打 **`FTP TO, exit upg`****`QUERY_CGREG`** 另有 **120 次** 强制跳转。
### 3.2 GetDiagnostics 日志上传(`u8_ec200a_4g_ftp_log_upload`
- **入口****`u8_log_file_upload==1`** 时由 **`v_ftp_update_action`** 调用。
- **日志标签****`ftp test <步号>`**。
- **典型流程**:命令模式 → **UFS `QFDEL`** → 本地日志信息(**`s8_get_log_info`**:按 **`EC200A_FTP_LOG_CHUNK_SIZE`** 等收紧 **`u16_packSize`**)→ **`QFOPEN`** 在 UFS 上覆盖打开日志文件 → **`STEP_FTP_FTP_WRITE_LOG_UFS`****`QFSEEK` + `QFWRITE`** 从 FATFS **按块(默认 4096** 写入模组 UFS**`QFWRITE` 在单周期内仅短等轮询**,未收齐 **CONNECT/OK****`s8_ec200a_write_log_data` 返回进行中**,下一 FTP 半周期续传(**不采用整段 `QFUPL` 透传写文件`**,避免长时间占 UART)→ **`QFLST`** →(与升级类似的)**CGREG / FTP PDP `QIDEACT`/`QIACT`** → **`QFTPCFG` 链** → **`QFTPOPEN`/`QFTPCWD`** → **`QFTPPUT`** 上传到远端 → **`AT+QFTPCLOSE`****`s8_ec200a_qftpclose`**)→ **`STEP_FTP_INIT_OK`** 等待若干周期 → 清 **`u8_updata_flag`**、**`u8_log_file_upload`**、**`v_ftp_ctrl_init`**,并在非透传下 **排空 RX + `EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS` 延时 + 再 drain**,减轻紧接着的 **Uploaded** / **StatusNotification** 出现 **`break off=0`**。
**`EC200A_FTP_LOG_CHUNK_SIZE` / `EC200A_FTP_LOG_QFWRITE_MAX_BYTES`**:默认与 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`** 一致;控制从 FATFS 单次读取与单次 **QFWRITE** 负载。注意 **读缓冲 + 过滤缓冲** 的 RAM 占用(过滤后长度 ≤ 当块读长)。
### 3.3 与「误走升级分支」的鉴别
| 现象 | 含义 |
|------|------|
| 日志 **`[FTP-UPG]`** + **`QFTPGET(COM)` / `step``GET_QFTPGET`** | 正在跑 **`u8_ec200a_4g_ftp_update`** |
| 日志 **`ftp test`** | 正在跑 **`u8_ec200a_4g_ftp_log_upload`** |
| GetDiagnostics 后出现 **`[FTP-UPG]`** 且 **`RX pkt(0)/(3)`** 卡进度 | 多为 **`u8_log_file_upload` 被清 0** 仍 **`u8_updata_flag==1`**,已按上节在 OCPP 侧修复 |
---
## 四、与 plat_comm、OCPP、互斥策略的衔接
### 4.1 `plat_comm_task.c`
- **`u8_ftp_transfer_active`**:任一路 **`u8_updata_flag!=0`**;若开启 OCPP,在 **4G****`u8_log_file_upload==1`** 时,即使 **`u8_updata_flag` 仍为 0**GetDiagnostics **CallResult 尚未置位 `updata_flag`** 的窗口),也视为 **FTP 活跃**,以便调度 **`v_ftp_update_action`** 跑日志状态机。
- **`ftp4g`****`u8_ftp_transfer_active && t_ftp_update_info.u8_netType==0`**。为真时:使用 **静态相位 `s_plat_comm_net_ftp_phase`** —— **奇偶半周期交替**:一半周期只跑 **`v_plat_comm_network_round`**(联网收发),另一半周期只跑 **`v_plat_comm_ftp_round`**(开头双路 **`v_bs_recv_data`** → **`v_ftp_update_action` 一步** → **`EC200A_FTP_TO_PLATFORM_GAP_MS`**)。这样 **FTP 与 OCPP 不在同一 PlatComm 周期内叠执行**,与 **`QFTPGET(COM)`/`QFREAD`/`QFWRITE` 分包 + 短等** 叠加后,**FTP 传输过程中仍可稳定进行联网数据收发**。
- **网线 FTP****`u8_ftp_transfer_active && netType!=0`** 时独占整周期 **`continue`**(与 4G 交替策略不同)。
### 4.2 `u8_bs_ftp_ocpp_strict_mux``plat_comm_task.h` / `v_plat_data_init`
- **默认 0**:不在 FTP 期间强行拦截 OCPP **`v_bs_send`**;大文件上传请依赖 **`EC200A_BLOCK_QISEND_DURING_4G_FTP`(默认已 1)** 暂缓 QISEND,否则易 **`no prompt >`**。若仍要业务帧与 FTP 硬穿插可 **`#define EC200A_BLOCK_QISEND_DURING_4G_FTP 0`** 并拉大 **`EC200A_FTP_TO_PLATFORM_GAP_MS`**,或运行时置 **`u8_bs_ftp_ocpp_strict_mux=1`**。
- **为 1****`bs_connect_impl.c`** 在 **模组 FTP 传输请求有效** 且目标为 OCPP 通道时 **return** 掉发送;**`bs_public_impl.c`** 在 OCPP **4G TX busy** 时暂缓 **`v_ftp_update_action`**。与 **GetDiagnostics****「先 CallResult / Uploading,再置 `u8_updata_flag`」** 顺序配合使用(见 **`BS_ocpp_ctrl.c`**)。
### 4.3 OCPP 要点(文件在 `ocpp/`,此处仅列衔接)
- **GetDiagnostics****`GetDiagnostics_mag`** 解析 URL 并置 **`u8_log_file_upload`****`GetDiagnostics_res`** 准备 **`fileName`**、**`v_ftp_ctrl_init`****不在组帧前**置 **`u8_updata_flag`**,避免与 strict mux 组合导致 **CallResult 发不出**。**`send_mag`** 在 **CallResult `ws_send` 成功** 后置 **`u8_diag_notify_pending=Uploading`****`ocpp_send_mag_ctrl`** 在 **Uploading****`send_mag` 之后** 再置 **`u8_updata_flag=1`**。与 **4G 分包 FTP、PlatComm 半周期交替** 的衔接说明见 **`ocpp/OCPP功能模块说明.md`** **第 9.1 节**
- **`v_ocpp_private_logic`**:不再在 **`u8_updata_flag==0`** 时无条件 **`u8_log_file_upload=0`**,避免误切 **固件升级** 分支。
### 4.4 透传模式下的 FTP
透传(**`EC200A_TCP_USE_TRANSPARENT_MODE==1`**)下,**日志写 UFS 的 `QFOPEN`/`QFSEEK`/`QFWRITE` 路径不参与编译**(相关步进会失败或超时退出);**固件升级** 仍使用 **`QFTPGET``COM:`** 分片 + **`s16_4g_Send_Recv_Ftp_Get`**,与缓冲模式相比 UART 占用特征不同,需单独测试。
---
## 五、配置与排障建议
| 问题倾向 | 建议 |
|-----------|------|
| **`4G QISEND: no prompt >` / `break off=0`** | 保持 **`EC200A_BLOCK_QISEND_DURING_4G_FTP=1`**(默认);增大 **`EC200A_FTP_TO_PLATFORM_GAP_MS`**;确认 **日志已走 `QFOPEN+QFSEEK+QFWRITE` 分包**(勿再依赖长时 **`QFUPL`** 透传);固件已走 **`QFTPGET(COM)`** 分片;可调 **`EC200A_FTP_UFS_RW_CHUNK_BYTES`**(须为 1024 倍数)、**`EC200A_FTP_QFREAD_*`**(同时影响 QFTPGET 片内等待上限);或 **`u8_bs_ftp_ocpp_strict_mux=1`** |
| **`QSSLOPEN` 成功但 `AT+QISEND` 立刻 ERROR** | **`wss` 须用 `AT+QSSLSEND` / `AT+QSSLRECV`**,不能对 SSL socket 发 **`QISEND`/`QIRD`**(固件 `ec200a_4g.c` 已按 `use_tls` 分支) |
| **`QFTPCWD` 失败但需升级** | 当前实现会在 CWD 失败后仍继续;若仍 **QFTPSIZE/QFTPGET** 失败,核对 **FTP 上实际路径****`s_ftp_remote_get_obj`** 拼接是否一致(含前导 `/` |
| **日志上传结束后首包 OCPP 失败** | 确认 **`QFTPCLOSE`**;调大 **`EC200A_FTP_LOG_DONE_TO_QISEND_DELAY_MS`**;若确认 TCP 已死且可接受重登,再设 **`EC200A_FTP_LOG_DONE_REQUEST_BS_RENEW=1`** |
| **GetDiagnostics 走成 `[FTP-UPG]`** | 确认 **`u8_log_file_upload`** 未被异常清除;**`v_ftp_update_action`** 是否进入 **`u8_log_file_upload` 分支** |
| **双 PDP** | 修改 context 时同时覆盖 **TCP/FTP** 两宏且保持 **不等** |
| **QIRD ERROR** | 检查 **`want`** 是否超 **1500**(已由宏钳位) |
---
## 六、自检清单(FTP 升级相关)
- [ ] **`EC200A_TCP_PDP_CTX_ID``EC200A_FTP_PDP_CTX_ID`**
- [ ] 固件升级日志为 **`[FTP-UPG]`****`QFTPSIZE`** 使用 **远端全路径****`QFTPGET(COM)`** 分片日志 **`pkt i/N`****全部片完成后** 直接进入 **`STEP_FTP_SAVE`**(不经 **UFS QFLST/QFREAD** 主路径)
- [ ] 日志上传为 **`ftp test`**,写 UFS 阶段为 **`QFOPEN` + 循环 `QFSEEK`/`QFWRITE`(块长默认 4096)**,上传结束有 **`QFTPCLOSE`** 与收尾 **drain + 延时**
- [ ] PlatComm**4G FTP** 时周期头 **recv + FTP + gap**,且 **每平台 send 后 recv**
- [ ] OCPP**CallResult → Uploading → `u8_updata_flag`** 顺序;**不误清 `u8_log_file_upload`**
---
*路径:`CCU601E_D/app/plat_comm/4g_module/4G模块AT指令配置功能说明.md`*
*若需保留旧版「2.5 QISEND/QIRD 字节级长说明」,可从版本管理历史中拷贝为 `4G模块AT指令配置功能说明_appendix.md`。*
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
/**
* @file bs_wh_gm5.h
* @brief WH-GM5 4G 模组 AT 驱动
*
* 负责模组初始化、APN/Socket 配置、信号查询及串口通信。
* 通信串口:USART6COM_PORT_BS)。
*/
#ifndef _BS_WH_GM5_H_
#define _BS_WH_GM5_H_
#include "publicdata/type.h"
#include "main.h"
#include "publicdata/publicdata.h"
#include "usart.h"
#include "plat_comm/plat_comm_task.h"
#define COM_PORT_BS E_USART_6 //4G 模组 WH-GM5 通信串口
typedef enum
{
STEP_SWCHTO_CMD_MODE = 0, //切换到指令模式
STEP_MODE_NET_ON, //设置工作模式为网络透传模式(默认net)
STEP_UAT_ON, //使能串口AT指令(默认ON)
STEP_UAT_SET_PACK_TIME,
STEP_SET_PWD, //设置指令密码(默认usr.cn)
STEP_SET_APN, //设置APN
STEP_SET_SDPEN, //设置套接字分发
STEP_SCKA_ON, //设置socketA使能(默认ON)
STEP_SET_SCKA_PARA, //设置socketA参数
STEP_SCKB_ON, //设置socketB使能(默认ON)
STEP_SCKB_OFF, //设置socketB关闭(OFF)
STEP_SET_SCKB_PARA, //设置socketB参数
STEP_HEART_OFF, //设置心跳包不使能
STEP_SAVE, //保存当前设置并重启
STEP_INIT_OK, //设置完成
}E_WH_GM5_INIT_STEP;
typedef struct
{
U8_T u8_Sim_Type; //APN类型(0:移动,1:联通,2:电信)
U8_T u8_socketB_en; //socketB使能 默认套接字分发功能使能 - 使能分发时,组帧解帧需要添加帧头;不使能直接透传模式,将数据上送平台,但无法区分多平台
U8_T *socketA_IP; //域名或者IP+端口号 例如 www.baidu.com:1234
U8_T *socketB_IP; //is NULL 则不存在socket
U8_T *apn_str;
U16_T socketA_port; //端口号
U16_T socketB_port;
U8_T use_tls; //是否启用模块内置 TLS0=TCP1=TLSwss
}S_4G_CONFIG_PARA;
typedef struct
{
U8_T u8_out_cnt; //超时计数
E_WH_GM5_INIT_STEP e_wh_gm5_init_step; //无线通信模块初始化步骤标志
U8_T u8_Sim_Card[NUM_20]; //SIM卡号
U8_T u8_4g_Version[NUM_20]; //4g版本
S_4G_CONFIG_PARA s_4g_para;
}S_4G_CTRL_DATA;
//结构体1 -- 用于内部逻辑控制和数据存储
//结构体2 -- 用于联网参数存储-获取
S8_T s8_UAT_check_signal_quality(void);
S8_T s8_check_number(U8_T *data); //查询SIM卡号
S8_T s8_check_socket_LinkState(U8_T u8_id);
S8_T s8_WH_GM5_init(void);
U8_T u8_UAT_CheckConnState(void); //通过串口AT指令(密码ieszt)查询模块网络连接状态
void v_wh_gm5_data_init(S_4G_CONFIG_PARA *s_4g_para);
void v_4g_uart_cache_clear(void);
U8_T v_GM5_get_sim(U8_T *data); /**< 获取 SIM 卡号,有数据返回 1 */
#endif
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
#ifndef EC200A_4G_H
#define EC200A_4G_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "plat_comm/4g_module/ec200a_4g_cfg.h"
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
STEP_SWCHTO_CMD_MODE = 0, //切换到指令模式
STEP_QUERY_SIM_STATE, //查询SIM卡状态
STEP_QUERY_CCID, //查询sim卡号
STEP_SET_CFUN, //设置功能模式
STEP_SET_CGREG, //设置PS域 网络注册状态
STEP_QUERY_CGREG, //查询PS域 网络注册状态
STEP_SET_QICSGP_APN, //设置TCP场景参数 设置APN
STEP_SET_CGQMIN, //设置服务质量参数(最低可接受的)
STEP_SET_CGQREQ, //设置服务质量参数(请求的)
STEP_QUERY_CSQ, //查询信号强度
STEP_SET_QIACT, //激活PDP场景
STEP_SET_SSL_CFG, //TLS 配置(QSSLCFG 等,仅在需要 TLS 时执行)
STEP_SET_SOCKET_AND_OPEN, //设置socket 参数并打开(TCP 或 TLS
STEP_SAVE, //保存当前设置并重启
STEP_INIT_OK, //设置完成
STEP_INIT_DEBUG, // 调试
}E_EC200A_4G_INIT_STEP;
typedef struct
{
U8_T u8_Sim_Type; //APN类型(0:移动,1:联通,2:电信)
U8_T u8_socketB_en; //socketB使能 默认套接字分发功能使能 - 使能分发时,组帧解帧需要添加帧头;不使能直接透传模式,将数据上送平台,但无法区分多平台
U8_T *socketA_IP; //域名或者IP+端口号 例如 www.baidu.com:1234
U8_T *socketB_IP; //is NULL 则不存在socket
U8_T *apn_str;
U16_T socketA_port; //端口号
U16_T socketB_port;
U8_T use_tls; //是否启用模块内置 TLS0=TCP1=TLSwss
}S_4G_CONFIG_PARA;
typedef struct
{
U8_T u8_out_cnt; //超时计数
U8_T u8_csq; //信号强度
E_EC200A_4G_INIT_STEP e_4g_init_step; //无线通信模块初始化步骤标志
U8_T u8_Sim_Card[NUM_20]; //SIM卡号
U8_T u8_4g_Version[NUM_20]; //4g版本
U32_T u32_outtime; //超时计数 单位秒
U8_T u8_tcp_socket_id; // 非透传:QISEND/QIRD 使用的 connectID(明文 TCP=0TLS 与 QSSLOPEN 的 clientID 一致)
S_4G_CONFIG_PARA s_4g_para;
}S_4G_CTRL_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
S8_T s8_ec200a_4g_init(void);
void v_4g_data_init(S_4G_CONFIG_PARA *s_4g_para);
void v_4g_send_data(U8_T u8_id, U8_T *u8_sendBuf , U16_T u16_len);
U16_T u16_4g_recv_data(U8_T *u8_recvBuf ,U16_T u16_len);
/** 排空模组 RX FIFO(丢弃);FTP / QISEND 等共用 UART 去粘连 */
void v_4g_uart_rx_discard_fifo(void);
#if !EC200A_TCP_USE_TRANSPARENT_MODE
/** 非透传:经 AT+QISEND 发送 TCP/TLS 负载(按 EC200A_TCP_APP_RW_MAX 分包;详见 ec200a_4g.c */
void v_ec200a_tcp_app_send(U8_T u8_id, U8_T *u8_sendBuf, U16_T u16_len);
/** 非透传:AT+QIRD 读模组 Socket 缓冲(单次至多 EC200A_TCP_APP_RW_MAX;大帧需上层循环) */
U16_T u16_ec200a_tcp_app_recv(U8_T *u8_recvBuf, U16_T u16_len);
/** 模组已在 STEP_INIT_OK 且 PDP 正常时,仅 AT+QICLOSE/QSSLCLOSE + QIOPEN/QSSLOPEN 重建业务 Socket(不拆 PDP */
S8_T s8_ec200a_tcp_socket_close(void);
S8_T s8_ec200a_tcp_socket_soft_reopen(void);
#endif
U8_T u8_ec200a_modem_tcp_stack_ok(void);
U8_T u8_get_sim_card(U8_T *u8_data ,U8_T buf_len);
U8_T u8_UAT_check_signal_quality(void);
void v_4g_log_data_send_data(U8_T *u8_sendBuf , U16_T u16_len);
S8_T s8_ec200a_qiact(void);
/** 按 context_id 配置/激活/去激活 PDP,供 FTP 与业务 TCP 分场景(见 ec200a_4g_cfg.h */
S8_T s8_ec200a_qicsgp_ctx(U8_T context_id);
S8_T s8_ec200a_qiact_ctx(U8_T context_id);
S8_T s8_ec200a_qideact_ctx(U8_T context_id);
#ifdef __cplusplus
}
#endif
#endif /* EC200A_4G_H */
+172
View File
@@ -0,0 +1,172 @@
#ifndef EC200A_4G_CFG_H
#define EC200A_4G_CFG_H
#include "usart.h"
/**
* @file ec200a_4g_cfg.h
* @brief EC200A 模组:串口选择、TCP/FTP/TLS 场景、FTP 轮询与 UFS 块长、非透传 TCP 分包与缓冲派生。
* 可在包含本头或 `ec200a_4g.h` 之前用 `#define 宏名 值` 覆盖下列默认值。
*/
/* ========== 模组 UART 与调试(`ec200a_4g.c` 发送/排空等共用) ========== */
/** 1:在 `v_4g_send_data` / `u16_4g_recv_data` 等路径打印调试信息;0:关闭。开启时 printf 须走调试口,勿与模组口 COM_PORT_BS 共用 TX。 */
#ifndef TEST_BS_PRINTF_FLAG
#define TEST_BS_PRINTF_FLAG (0)
#endif
/** 与 Quectel 模组通信的 USART 逻辑编号(见 `usart.h` 中 `E_USART_ID`,默认 UART12 接 4G)。 */
#ifndef COM_PORT_BS
#define COM_PORT_BS E_USART_6
#endif
/** `v_4g_send_data` 第一形参占位 ID;当前实现未区分多路,固定 0xFF 即可。 */
#ifndef ID_4G
#define ID_4G (0xFFU)
#endif
/* -------------------------------------------------------------------------- */
/* 1) 透传 / QIOPEN 访问模式 */
/* -------------------------------------------------------------------------- */
/** Socket 数据路径:1 = UART 透传数据态;0 = 缓冲/命令模式(AT+QISEND / AT+QIRD)。 */
#ifndef EC200A_TCP_USE_TRANSPARENT_MODE
#define EC200A_TCP_USE_TRANSPARENT_MODE (0)
#endif
/** AT+QIOPEN / AT+QSSLOPEN 最后一参「access_mode」:透传为 2,缓冲模式为 0(随 `EC200A_TCP_USE_TRANSPARENT_MODE` 自动选择)。 */
#if EC200A_TCP_USE_TRANSPARENT_MODE
#define EC200A_TCP_QIOPEN_ACCESS_MODE (2)
#else
#define EC200A_TCP_QIOPEN_ACCESS_MODE (0)
#endif
/* -------------------------------------------------------------------------- */
/* 2) PDP:业务 TCP vs FTPTLS 与业务 TCP 同 PDP */
/* -------------------------------------------------------------------------- */
/** 业务 TCPQIOPEN/QSSLOPEN、模组 QIACT)使用的 PDP context ID。 */
#ifndef EC200A_TCP_PDP_CTX_ID
#define EC200A_TCP_PDP_CTX_ID (1U)
#endif
/** FTPQFTPCFG、QICSGP、QIACT、QIDEACT)使用的 PDP context ID;须与 `EC200A_TCP_PDP_CTX_ID` 不同,避免 FTP 去激活时拆掉业务 TCP。 */
#ifndef EC200A_FTP_PDP_CTX_ID
#define EC200A_FTP_PDP_CTX_ID (2U)
#endif
/* 编译期约束:TCP 与 FTP 不得共用同一 PDP,否则 FTP 侧 QIDEACT/重配会拆掉业务 TCP。 */
#if (EC200A_TCP_PDP_CTX_ID) == (EC200A_FTP_PDP_CTX_ID)
#error EC200A_FTP_PDP_CTX_ID must not equal EC200A_TCP_PDP_CTX_ID
#endif
/** TLSQSSL*)使用的 PDP 与业务 TCP 相同,与 `EC200A_TCP_PDP_CTX_ID` 保持一致。 */
#define EC200A_SSL_PDP_CTX_ID EC200A_TCP_PDP_CTX_ID
/** AT+QSSLCFG 等命令中的 SSL context 编号(模组侧 ctx)。 */
#ifndef EC200A_SSL_CTX_ID
#define EC200A_SSL_CTX_ID (1)
#endif
/** AT+QSSLOPEN 使用的 clientID / 套接字索引(与明文 TCP 的 connectID 区分)。 */
#ifndef EC200A_SSL_CLIENT_ID
#define EC200A_SSL_CLIENT_ID (4)
#endif
/* -------------------------------------------------------------------------- */
/* 3) FTPAT+QFREAD / AT+QFWRITE 轮询(单次 sleep 与总等待次数) */
/* -------------------------------------------------------------------------- */
/** QFREAD/QFWRITE 收 UART 时,每轮循环内单次 `mSleep` 间隔(毫秒)。 */
#ifndef EC200A_FTP_UART_SLICE_WAIT_MS
#define EC200A_FTP_UART_SLICE_WAIT_MS (25U)
#endif
/** 单次 `s16_qfread_at_slice` 调用内,最多执行「sleep + 收包」的次数;超时仍未见应答则本次读失败。 */
#ifndef EC200A_FTP_QFREAD_TOTAL_WAITS
#define EC200A_FTP_QFREAD_TOTAL_WAITS (80U)
#endif
/** 单次 `s8_ec200a_qfwrite_log_bin` 调用内,等待 CONNECT/OK 等阶段的最多次数;超出则本次写失败。 */
#ifndef EC200A_FTP_QFWRITE_TOTAL_WAITS
#define EC200A_FTP_QFWRITE_TOTAL_WAITS (200U)
#endif
/* -------------------------------------------------------------------------- */
/* 4) GetDiagnostics / UFSQFUPL 与单次读写块长 */
/* -------------------------------------------------------------------------- */
/** `AT+QFUPL` 第三参数:UART 侧无数据输入超时(秒),手册范围 1~65535;分片发送时须大于单 PlatComm 周期可能间隔。 */
#ifndef EC200A_FTP_QFUPL_AT_TIMEOUT_SEC
#define EC200A_FTP_QFUPL_AT_TIMEOUT_SEC (120U)
#endif
/** QFUPL 数据态下,单次向模组 UART 写入负载的上限(字节);0 表示按整块发送的旧策略(由上层决定)。 */
#ifndef EC200A_FTP_QFUPL_UART_SLICE_MAX
#define EC200A_FTP_QFUPL_UART_SLICE_MAX (512U)
#endif
/** 模组 UFS 单次 `AT+QFREAD` / `AT+QFWRITE` 数据负载(字节)。FTP 升级 QFREAD 缓冲、日志 FAT 读块与 QFWRITE 上限均以此为界;须为 Flash `PACKET_SIZE` 的倍数且 ≤65535。 */
#ifndef EC200A_FTP_UFS_RW_CHUNK_BYTES
#define EC200A_FTP_UFS_RW_CHUNK_BYTES (4096U)
#endif
#if !EC200A_TCP_USE_TRANSPARENT_MODE
/* -------------------------------------------------------------------------- */
/* 5) 非透传 TCP:软重连、QISDE、应用分包、模组 AT 上限与接收环形区派生尺寸 */
/* -------------------------------------------------------------------------- */
/** 连续「仅 QICLOSE+QIOPEN 软重连」失败达到此次数后,才走全量 `v_bs_4G_data_init` 类驻网恢复。 */
#ifndef EC200A_TCP_SOFT_REOPEN_FAIL_MAX
#define EC200A_TCP_SOFT_REOPEN_FAIL_MAX (5U)
#endif
/** AT+QISDE1 将 QISEND 的 TCP 负载回显到模组 UART(调试用);0 关闭(初始化通常下发 AT+QISDE=0)。 */
#ifndef EC200A_QISDE_ECHO
#define EC200A_QISDE_ECHO (0U)
#endif
/** 应用层单次 TCP 读/写意向上限(字节);`ec200a_4g.c` 内会再钳位到 `EC200A_QIRD_AT_READ_MAX` / `EC200A_QISEND_AT_LEN_MAX`。 */
#ifndef EC200A_TCP_APP_RW_MAX
#define EC200A_TCP_APP_RW_MAX (2048U)
#endif
/** AT+QIRD 第二参数「欲读长度」上限(Quectel 文档常见上限 1500);超过模组可能直接 ERROR。 */
#ifndef EC200A_QIRD_AT_READ_MAX
#define EC200A_QIRD_AT_READ_MAX (1500U)
#endif
/** AT+QISEND 单次发送负载上限(字节,Quectel 常见上限 1460)。 */
#ifndef EC200A_QISEND_AT_LEN_MAX
#define EC200A_QISEND_AT_LEN_MAX (1460U)
#endif
/** 读到 +QIRD: 0(缓冲无数据)后额外延时(毫秒),减轻空轮询;0 表示不延时。 */
#ifndef EC200A_QIRD_EMPTY_POLL_DELAY_MS
#define EC200A_QIRD_EMPTY_POLL_DELAY_MS (0U)
#endif
/** 存放 AT+QIRD 应答及 +QIRD: 前缀等 URC 的缓冲上限:`EC200A_TCP_APP_RW_MAX` + 固定余量(字节)。 */
#define EC200A_QIRD_RSP_MAX ((EC200A_TCP_APP_RW_MAX) + 384U)
/** 非透传下 QIOPEN/QSSLOPEN 应答在单任务内拼接用的累计缓冲大小(字节)。 */
#define EC200A_QIOPEN_ACCUM_SZ (640U)
#if EC200A_QISDE_ECHO
/** QISDE=1 时:等待 `>` / SEND OK 的环形接收区容量(字节),需容纳回显的应用数据。 */
#define EC200A_QISEND_WAIT_RB_SZ ((EC200A_TCP_APP_RW_MAX) * 2U + 512U)
/** QISDE=1 时:`memmove` 收缩接收区时保留的尾部字节数,避免截断半条 URC。 */
#define EC200A_QISEND_RB_TAIL_KEEP ((EC200A_TCP_APP_RW_MAX) / 2U + 256U)
#else
/** QISDE=0 时:等待 QISEND 提示与应答的接收缓冲容量(字节)。 */
#define EC200A_QISEND_WAIT_RB_SZ (896U)
/** QISDE=0 时:接收区收缩时保留的尾部字节数(字节)。 */
#define EC200A_QISEND_RB_TAIL_KEEP (384U)
#endif
#endif /* !EC200A_TCP_USE_TRANSPARENT_MODE */
#endif /* EC200A_4G_CFG_H */
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
#ifndef EC200A_4G_FTP_H
#define EC200A_4G_FTP_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "publicdata/type.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
U8_T u8_ec200a_4g_ftp_update();
U8_T u8_ec200a_4g_ftp_log_upload();
void v_ftp_ctrl_init();
#ifdef __cplusplus
}
#endif
#endif /* EC200A_4G_FTP_H */
@@ -0,0 +1,365 @@
# IES 积成平台通信移植方案(CCU601_A → CCU601E_D
本文档基于 **`CCU601_A_20240601\app\platform`** 中积成智通(IESZT)协议实现,对照 **`CCU601E_D\app\plat_comm`** 现有联网框架(`plat_comm_task` + `bs_connect_impl` + `bs_public_impl`),给出在 E 工程中**新增 IES 平台连接**的分阶段实现方案。
---
## 0. 备份说明
已在本机创建工程目录备份(不含 `MDK-ARM\output` 编译产物,以缩短拷贝时间):
| 路径 |
|------|
| `d:\Desktop\NewProject\cursor\EuropeanStandard\CCU601E_D_backup_20260519` |
如需完整含编译输出的备份,可在资源管理器中自行复制整个 `CCU601E_D` 目录。
---
## 1. 源工程(CCU601_AIES 模块概览
### 1.1 文件与职责
| 文件 | 行级规模(约) | 职责 |
|------|----------------|------|
| **`BS_ies_ctrl.h`** | 协议常量 + `S_IES_CTRL_DATA` | 帧类型码 `NT_*`、数据对象、对外 API |
| **`BS_ies_ctrl.c`** | ~560 行 | 回调注册、TCP/4G 建链、登录、收包解帧、私有超时逻辑 |
| **`BS_ies_interface.h/c`** | ~1800+ 行 | **组帧/解帧**:登录、心跳、费率、实时数据、账单、远程启停、对时、FTP 升级、二维码等 |
| **`BS_Task.c/h`** | ~1150 行 | **平台任务总线**:周期发送、双枪调度、`bs_register_callback``Task_BS` 主循环 |
| **`FtpUpdate.c`** | FTP 升级 | 与 `NT_UPDATE_AUTO(0x90)` 配合(A 侧旧 FTP 流程) |
| **`BS_WH_GM5.c`** | 4G 模组 | A 工程 **WH-GM5** 透传/AT**E 工程为 EC200A,不可直接搬** |
### 1.2 协议特征(与 OCPP 差异)
积成协议为 **二进制定长头 + 变长数据域**,非 JSON/WebSocket。
```
帧起始 0x68 | 长度 2B | 序号 1B | 设备地址 16B ASCII | 加密 1B | 数据对象 1B | 帧类型 1B | 数据域 N | 校验 1B
```
- **协议版本**`NT_PTL_VERSION = 37`V3.5
- **帧类型码**:登录 `0x01/0x02`、心跳 `0x03/0x04`、充电实时 `0x11/0x13`、账单 `0x47/0x48`、远程启停 `0x44~0x46`、对时 `0x76`、计费 `0x7D/0x7E`、FTP `0x90` 等(见 `BS_ies_ctrl.h`
- **校验**:序号域至数据域末字节 **累加和**`u8_crc_checkSum`
- **粘包**`v_bs_unpack_handle_ieszt``0x68` 搜头、长度域算整帧长
### 1.3 A 侧任务模型
```mermaid
flowchart TB
subgraph Task_BS
TCP[v_bs_tcp_connect]
LOG[v_ies_logo_on / v_bs_register_req]
SEND[v_bs_send_ctrl]
RECV[v_ies_recv_data → unpack]
PRIV[v_ies_private_crel]
end
subgraph callbacks
IF[BS_ies_interface 组帧]
end
SEND -->|E_BS_CALLBACK_CMD| IF
IF -->|com_read / eth_send| LINK[4G WH_GM5 / 以太网 socket]
```
- **发送**`BS_Task.c``v_bs_send_ctrl(bs_flag)` 按周期/标志调用 `v_bs_callback(cmd, gun)`IES 在 `v_bs_ies_register_callback()` 中注册具体组帧函数。
- **接收**`v_ies_recv_data()``v_bs_unpack_handle_ieszt()``v_bs_rx_cmd_hanle()``u8_recvBuf[22]` 帧类型分发。
- **登录成功**`v_bs_register_ack()` 内置 `u8_register_flag = 1`,并置 `bs_ctrl_data[].u8_log_on_flag`(与 E 的 `u8_log_on_flag` 概念一致)。
---
## 2. 目标工程(CCU601E_D)现状
### 2.1 已有挂钩(未实现)
| 位置 | 内容 |
|------|------|
| **`public_define.h`** | `BS_IES_EN` 默认 **0** |
| **`plat_comm_task.c`** | `MasterFuncArray` 已预留 **`"ieszt"`** 槽位及 7 个回调函数名 |
| **`plat_comm_task.h`** | `#include "plat_comm/ieszt/bs_ies_ctrl.h"``S_BS_IES_CTRL_DATA s_ies_data` |
| **目录** | **`plat_comm/ieszt/` 尚不存在**(编译开启 `BS_IES_EN` 会缺文件) |
E 侧 **`MasterFuncPtl`** 比 A 多一项 **`v_bs_recv_unpack`**(OCPP 未用;IES 可将解帧放在 `v_ies_recv_data` 内,**`v_ies_recv_unpack` 可留空**)。
### 2.2 E 侧应复用的基础设施(禁止重复造轮子)
| 模块 | 用途 |
|------|------|
| **`plat_comm_task.c`** | `v_Plat_Comm` 主循环、FTP/联网分时、`v_set_bs_renew` |
| **`bs_connect_impl.c`** | **`u8_tcp_connect`**、**`v_bs_send`**、**`u16_bs_recv`**4G EC200A + 网口 LwIP/TLS |
| **`bs_public_impl.c`** | 枪状态、**`S_LOG_DATA`** 订单、启停 **`v_bs_charge_action_ctrl`**、未结/掉电 **`s8_plat_get_unsettled_order`** |
| **`meter_calculate`** | 计量订单、EEPROM 临时单、Flash 未结索引 |
| **`flowctrl`** | `u8_bs_get_flow_state`E 已有,替代 A 的 `e_get_flow_rt_state` |
| **`v_ftp_update_action`** | OCPP/平台 FTP 升级;IES `0x90` 需映射到 **`t_ftp_update_info`** + `u8_updata_flag` |
### 2.3 A → E 关键差异对照
| 维度 | CCU601_A | CCU601E_D |
|------|----------|-----------|
| RTOS / 任务 | `Task_BS` + `GetCurSecond` | `v_Plat_Comm` + `get_current_seconds` |
| 4G | WH-GM5、`com_read(COM_PORT_BS)` | **EC200A**`v_ec200a_tcp_app_send/recv` |
| 网口 | `eth_connect` / `CreatSocket` 直连 | **`lwip_tcp_connect`** + `v_bs_eth_close_socket` |
| 订单结构 | **`CHARGE_LOG_T`** | **`S_LOG_DATA`** |
| 双平台控制块 | **`bs_ctrl_data[MAX_BS_NUM]`** | **`s_bs_task_ctrl.s_bs_ctrl[]`** + 可选 **`s_ies_data`** |
| 平台协议 | SDK + IES 二选一或双开 | **OCPP** + 可选 **IES**`MAX_BS_NUM=2` |
| 日志 | `logMsg_bs` | **`Plat_Comm_LOG` / `MYLOG_MSG`** |
---
## 3. 目标架构(移植后)
```mermaid
flowchart TB
subgraph PlatComm
PC[v_Plat_Comm]
M[MasterFuncArray slot: ieszt]
end
subgraph ieszt
CTRL[bs_ies_ctrl.c]
IF[bs_ies_interface.c]
SEND[v_ies_send_ctrl]
end
subgraph impl
CONN[bs_connect_impl]
PUB[bs_public_impl]
end
PC --> M
M --> CTRL
SEND --> IF
IF --> CONN
CTRL --> PUB
CONN --> EC200A[4G EC200A]
CONN --> LWIP[LwIP TCP/TLS]
```
**原则**
1. **IES 只实现「积成二进制协议」**TCP/4G/网口/TLS **一律走 `bs_connect_impl`**
2. **业务数据只通过 `bs_public_impl` + `meter_calculate`** 读写,不在 IES 模块内直接碰 flow 内部结构。
3. **周期发送**可移植 A 的 `E_BS_CALLBACK_CMD` 模型,但计时器挂到 **`s_bs_task_ctrl.s_bs_ctrl[ies_id]`** 或独立 **`S_BS_IES_CTRL_DATA`**,由 **`v_ies_send_ctrl`** 在已登录分支执行(与 OCPP 的 `v_ocpp_send_ctrl` 同级)。
---
## 4. 建议目录与文件清单
**`CCU601E_D/app/plat_comm/ieszt/`**(运行时说明见 **`ieszt/IES积成平台功能说明.md`**,与 **`平台联网任务功能说明.md`** 联动)新建:
| 文件 | 来源 | 说明 |
|------|------|------|
| **`bs_ies_ctrl.h`** | 自 A `BS_ies_ctrl.h` 改编 | 帧类型码、`S_BS_IES_CTRL_DATA`(合并原 `S_IES_CTRL_DATA` + 必要定时器) |
| **`bs_ies_ctrl.c`** | 自 A `BS_ies_ctrl.c` 重构 | 去掉 WH_GM5/CreatSocket;改用 `u8_tcp_connect(id)``v_bs_send``u16_bs_recv` |
| **`bs_ies_interface.h`** | 自 A `BS_ies_interface.h` | 组帧/解帧 API |
| **`bs_ies_interface.c`** | 自 A `BS_ies_interface.c` | 大文件;发送改为 `v_bs_send`;桩号从 `u8_get_str_data(E_STR_PILE_NO)` 获取 |
| **`bs_ies_send.c`**(可选) | 自 A `BS_Task.c` 抽取 | `v_ies_send_ctrl``v_ies_register_callback`、双枪周期逻辑,减小单文件体积 |
| **`bs_ies_adapter.c/h`**(建议新建) | 新写 | **`S_LOG_DATA` ↔ 账单组帧字段**、停止原因 **E_FAULT_* → IESZT_REASON** |
**不迁入 E 工程(或仅参考)**
- `BS_WH_GM5.c/h` → 使用现有 **`ec200a_4g.c`**
- `BS_sdk_*` → 与 IES 无关
- A 版 `FtpUpdate.c` 中与 OCPP 重复部分 → 统一 **`v_ftp_update_action`** + `ec200a_4g_ftp` / `lwip_ftp_upgrade`
---
## 5. 接口映射(MasterFuncPtl 七件套)
| 回调 | 实现要点 |
|------|----------|
| **`v_ies_init_data(U8_T id)`** | `id` 写入 `s_ies_data.u8_bs_flag``v_ies_register_callback()`;清 `s_ies_data`**不要**调用 `v_bs_4G_data_init`(由 `u8_tcp_connect` 统一处理);帧序号 `u16_msgSerialNumber=0` |
| **`v_ies_tcp_connect(void)`** | 返回 **`u8_tcp_connect(s_ies_data.u8_bs_flag)`** 结果;删除 A 中 `v_WH_GM5_init`、独立 `CreatSocket` |
| **`v_ies_logo_on(void)`** | 周期发 **`v_bs_register_req()`**(约 20s);**`v_bs_register_ack`** 内置置位后返回 1;登录超时 6 次 → **`v_set_bs_renew(id)`** + 仅重置 IES 私有数据(**勿全量 `v_ocpp_init_data`** |
| **`v_ies_recv_data(void)`** | `u16_bs_recv(id, buf, len)`;若 `u8_updata_flag` 且 4G FTP 活跃则可直接 return(与 A 一致) |
| **`v_ies_recv_unpack(id, buf, len)`** | **建议空实现**;解帧已在 `v_ies_recv_data` 完成。若未来 UART 分包由外部注入,再迁入 `v_bs_unpack_handle_ieszt` |
| **`v_ies_send_ctrl(void)`** | 移植 A **`v_bs_send_ctrl(ies_flag)`**:登录后心跳、费率、空闲/充电实时、表底、告警、账单、即插即充、离线单等 |
| **`v_ies_private_logic(U8_T id)`** | 心跳无应答计数、登录态维护、**`v_cheak_state_change` 类逻辑**(改调 E 的 `v_bs_log_txFlag_*`)、离线订单 **`s8_plat_get_unsettled_order` 路径**(可选与 OCPP 互斥) |
---
## 6. 数据与业务适配层(`bs_ies_adapter`
### 6.1 订单 / 账单(`NT_BILL_UPLOAD 0x47`
A`v_get_log_data` / `v_gunTmpChgRcd_rd` + `CHARGE_LOG_T``v_bs_bill_pack`
E 建议:
```c
S_LOG_DATA log;
u8_plat_get_real_order(&log, gun) s8_plat_get_unsettled_order(&log, 0);
v_ies_bill_pack_from_log(gun, &log);
```
- **`u8_preTradeNo`**:IES 账单字段与 A 一致处直接 `memcpy`;无平台流水号时用设备流水号或占位规则与运营确认。
- **停充原因**`log.e_stop_reason[0]`**`v_ies_map_stop_reason_to_ieszt()`** 转为 `IESZT_REASON_E`A 已在 `BS_Task.c` 有大表,可迁到 adapter)。
### 6.2 远程启停(`0x44` / `0x46`
A`v_bs_rmt_start/stop` 内调 flow/UI。
E
```c
S_BS_CHAR_CTRL ctrl;
memset(&ctrl, 0, sizeof(ctrl));
ctrl.u16_ctrl_res = E_NORMAL_1002; /* 或映射平台原因 */
v_bs_charge_action_ctrl(gun, E_CHAR_CTRL_STOP, &ctrl);
```
启机需组 **`S_CHAR_LOG_START_DATA`** 后 `E_CHAR_CTRL_START`,与 OCPP RemoteStart 路径对齐,避免双协议冲突(见 §8)。
### 6.3 对时(`NT_CLK_SYNC_REQ 0x76`
A:写 RTC。
E**`v_bs_set_time(&Comm_Time)`**`bs_public_impl` 已有)。
### 6.4 计费模型(`0x05/0x06/0x7D/0x7E`
A:写费率 Flash/共享区。
E:对接 **`meter_calculate` / 费率存储**`u32_meterlog_get_sys_data(E_METERLOG_GET_DATA_FEE_*)` 或现有配置接口),需与产品确认 IES 费率 PDU 与 E 侧 96 时段模型是否一致。
### 6.5 实时数据(`0x11` / `0x13`
字段来源改为:
- 电压电流 SOC`u32_bs_get_sys_data` / `u64_bs_get_sys_data`
- 枪状态:`u8_bs_get_flow_state``E_BS_GET_SYS_DATA_GUN_STATE`
- 电量:`E_BS_GET_SYS_DATA_MEMTER_ELECT`
### 6.6 FTP 升级(`NT_UPDATE_AUTO 0x90`
解析平台下发的 FTP 参数 → 填充 **`t_ftp_update_info`** → 置对应槽位 **`u8_updata_flag=1`** → 由现有 **`v_ftp_update_action`** 执行(4G/网口分支已有)。
---
## 7. 配置与编译集成
### 7.1 宏与工程配置
```c
/* public_define.h */
#define BS_IES_EN (1) /* 使能积成平台 */
#define BS_OCPP_EN (1) /* 可与 OCPP 双平台:槽位 0=OCPP1=IES,由 PLAT_EN 决定 */
```
- Keil / CMake:将 **`plat_comm/ieszt/*.c`** 加入 **PlatComm** 分组。
- 确认 **`plat_comm_task.h`** 中 `S_BS_IES_CTRL_DATA``bs_ies_ctrl.h` 定义一致(或 typedef 别名)。
### 7.2 系统参数(与 A 对齐项)
| 配置项 | A 来源 | E 建议 |
|--------|--------|--------|
| 主站 IP/端口 | `u8_remote_ip` / `u16_remote_port` | 继续用 **`t_sys_fix_cfg`**IES **不用** WebSocket URL,可用 **纯 `tcp://ip:port`** 或单独 **IES 服务器配置项**(若与 OCPP 域名不同,需在配置表增加字段,避免与 `u8_serverA_domain_name` 混用) |
| 桩号 16 字节 | `u8_pile_serial_num` | `u8_get_str_data(E_STR_PILE_NO)` |
| 平台使能 | `bs_type[]` 初始化 | `E_BS_GET_SYS_DATA_PLAT_EN` per 槽位 |
| 联网方式 | `e_network_type` | `E_BS_GET_SYS_DATA_NET_TYPE` |
### 7.3 `MasterFuncArray` 注册示例
与现有 `plat_comm_task.c` 一致,确保 **`BS_IES_EN`** 时槽位与 **`PLAT_EN`** 配置对应,例如:
- 槽位 0OCPP`BS_OCPP_EN`
- 槽位 1IES`BS_IES_EN`
双平台同时使能时,**两路独立 `u8_TCP_connect_flag` / `u8_log_on_flag`**,互不覆盖。
---
## 8. 双平台共存策略(OCPP + IES
| 风险 | 策略 |
|------|------|
| 同一枪被 OCPP RemoteStart 与 IES `0x44` 同时控制 | 产品层约定:**仅一个平台为主**;或 `PLAT_EN` 互斥。代码层在 `v_bs_rmt_start` 中判断 **`u8_bs_get_flow_state==0`** 且 OCPP 未在充。 |
| 离线订单双路上送 | E 未结索引统一 **`unsettled_order_mng`**IES 掉电单走 **`s8_plat_get_unsettled_order`** 时与 OCPP **互斥处理**(同一单不两协议各 Stop 一次)。 |
| 4G UART 占用 | OCPP 与 IES **共用 `bs_connect_impl`** 时,**不能两槽位同时 `u8_netType==0` 各建一条 Socket**,除非运营确认为双连接;通常 **一机一路 4G 一个 TCP****双平台只选其一或网口+4G 分流**。 |
| FTP 与 IES 收包 | 保持 **`plat_comm_task` FTP/联网分时**IES **`v_ies_recv_data`** 在 FTP 周期不解析 IES 帧(与 A 相同 guard)。 |
---
## 9. 分阶段实施计划
### 阶段 1:骨架可编译(1~2 天)
- [ ] 创建 `plat_comm/ieszt/`,迁入 **`bs_ies_ctrl.h`**、精简 **`bs_ies_ctrl.c`**`init/tcp_connect/logo_on/recv/private` 空壳 + `u8_tcp_connect`
- [ ] **`BS_IES_EN=1`** 编译通过;`v_ies_recv_unpack` 空实现
- [ ] 槽位 **`bs_enable=1`** 时能进 `v_Plat_Comm` 建链日志
### 阶段 2:链路 + 登录心跳(2~3 天)
- [ ] 迁入 **`bs_ies_interface.c`** 中:`u8_bs_pack_frm_head``v_bs_register_req/ack``v_bs_heart/ack``u8_crc_checkSum`
- [ ] 收包粘包 **`v_bs_unpack_handle_ieszt`**
- [ ] 实机验证:**Login 0x02**、**Heartbeat 0x04**、平台对时(若有)
### 阶段 3:运营关键帧(3~5 天)
- [ ] **`v_bs_rtd_free` / `v_bs_rtd_chging`** + `v_ies_send_ctrl` 周期表(对齐 A:心跳 20s、充电 30s、空闲 180s 等)
- [ ] **`v_bs_bill_pack`** + adapter 从 **`S_LOG_DATA`**
- [ ] **`v_bs_rmt_start/stop`** → `v_bs_charge_action_ctrl`
- [ ] **`v_bs_bill_upload_ack`** → `v_bs_platform_settlement_confirm` / 清 EEPROM 临时单
### 阶段 4:增强与对齐(按需)
- [ ] 计费模型 `0x7D/0x7E`、二维码 `0x08/0x0A`、故障 `0x15`、表底 `0x31`
- [ ] FTP `0x90``t_ftp_update_info`
- [ ] 掉电未结:与 **`meter_calculate` EEPROM 恢复**、**`s8_plat_get_unsettled_order`** 联调
- [ ] 双枪 `u8_bs_twoGnu_send` 逻辑迁入
### 阶段 5:测试与文档
- [ ] 用例:4G / 网线、单枪/双枪、远程启停、断网重连、充电中离线、掉电上电补单
- [ ] 更新 **`平台联网任务功能说明.md`** 增加 IES 章节
---
## 10. 接收发送伪代码(E 风格)
```c
void v_ies_recv_data(void)
{
U8_T id = s_ies_data.u8_bs_flag;
U8_T buf[512];
U16_T n;
if (s_bs_task_ctrl.s_bs_ctrl[id].u8_updata_flag != 0U
&& s_bs_task_ctrl.s_bs_ctrl[id].u8_netType == 0U) {
return;
}
n = u16_bs_recv(id, buf, sizeof(buf));
if (n > 0U) {
v_bs_unpack_handle_ieszt(buf, (S16_T)n);
}
}
static void v_ies_send_frame(U8_T *frm, U16_T len)
{
v_bs_send(s_ies_data.u8_bs_flag, frm, len);
}
```
---
## 11. 测试验收标准(摘要)
| 编号 | 项 | 通过标准 |
|------|-----|----------|
| T1 | TCP | `PlatComm` 打印 `tcp connect`,平台侧可见连接 |
| T2 | 登录 | 收到 `0x02``u8_log_on_flag=1`,桩号与配置一致 |
| T3 | 心跳 | 周期 `0x03`,平台 `0x04`,断链后 `v_set_bs_renew` 可恢复 |
| T4 | 充电实时 | 充电中 `0x11`、空闲 `0x13` 周期上送,数据与电表一致 |
| T5 | 账单 | 停充后 `0x47`,平台 `0x48` 后本地未结清除 |
| T6 | 远程停 | 平台 `0x46` 后枪停止,原因码正确 |
| T7 | 掉电 | 有 txId 的订单恢复后账单 **transactionId 非 0**(与 OCPP 掉电修复一致) |
---
## 12. 关联文档
| 文档 | 路径 |
|------|------|
| E 联网任务总览 | `plat_comm/平台联网任务功能说明.md` |
| OCPP 模块 | `plat_comm/ocpp/OCPP功能模块说明.md` |
| 4G EC200A | `plat_comm/4g_module/4G模块AT指令配置功能说明.md` |
| 源 IES 协议头 | `CCU601_A_20240601/app/platform/BS_ies_ctrl.h` |
---
*文档版本:2026-05-19;随 `plat_comm/ieszt` 落地情况更新勾选清单。*
+14
View File
@@ -0,0 +1,14 @@
# gwsdk — 国网平台 SDK(预留)
**当前未参与 CCU621 编译。**
- `public_define.h``BS_GWSDK_EN = 0`
- `GD32H759.uvprojx` 未添加本目录下 `.c` 文件
- 依赖第三方头:`infra_defs.h``protocol.h` 等(PCU606 侧 SDK 包)
启用前需:
1. 将 SDK 库与头文件路径加入 Keil Include
2.`bs_gwsdk_ctrl.c``bs_gwsdk_interface.c` 加入工程
3.`gwsdk_interface.c``ccu_comm/ccu_interface.h` 改为 `plat_comm/impl/bs_local_data.h`
4. 设置 `BS_GWSDK_EN=1`,并与 `BS_IES_EN` 互斥或按产品配置二选一
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
#ifndef BS_GWSDK_CTRL_H
#define BS_GWSDK_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/type.h"
#if (BS_GWSDK_EN)
/*------ Exported macro(宏定义) -------------------------*/
#define GWSDK_TIMERCNT_NUM (3 + 5*GUN_MAX_CNT) //计时计数 -每一个需主动上送的报文总数
/*------ Exported struct(结构体定义) --------------------*/
typedef enum{
SDK_TIMER_REQUSET = 0, //校时
SDK_TIMER_FEE_REQ, //费率请求
SDK_TIMER_DC_DEV_INFO, //直流设备属性上报
SDK_TIMER_REAL_WORK_GUN, //工作实时状态 数量跟随枪数量 - 包含工作状态和空闲状态两种
SDK_TIMER_TRADE_INFO = SDK_TIMER_REAL_WORK_GUN + GUN_MAX_CNT, //订单信息上送 数量跟随枪数量
SDK_TIMER_PROPERTY_BMS = SDK_TIMER_TRADE_INFO + GUN_MAX_CNT, //BMS 属性周期上送
SDK_TIMER_METER_INFO = SDK_TIMER_PROPERTY_BMS + GUN_MAX_CNT, //电表低值上送
SDK_TIMER_FAULT_INFO = SDK_TIMER_METER_INFO + GUN_MAX_CNT, //故障信息上报
}SDK_SEND_TIMER;
typedef struct
{
U8_T u8_bs_flag; //处于双平台架构中的哪个 用于映射控制结构体的地址
S8_T gw_connet_flag; //联网标识
U8_T u8_bs_state_type; //维护状态
U8_T u8_bs_reboot_flag; //平台重启标志 - 要求收到重启命令时,需回复后再重启
U8_T u8_bs_gun2_notup_cnt; //2枪订单未上传计时
U8_T txFlag_synTime; //是否已校时标识 0否 1是
U8_T txFlag_askFeeMode; //请求费率
U8_T txFlag_firwareInfo; //固件信息上送标志
U8_T txFlag_versInfo; //版本信息上送标志
U8_T txFlag_cfgInfoUpdate; //配置信息更新请求
U8_T txFlag_devMduInfoEvt; //设备组件信息上报
U8_T u8_Meter_ack[2]; //电表召测标志
U8_T u8_sim_id[20]; //sim卡号
U16_T u16_work_info_period; //工作实时状态 上送周期
U16_T u16_free_info_period; //空闲实时状态 上送周期
U16_T ChargeCnt; //充电序号计次
U16_T OperateCnt; //操作序号计次 生成订单流水号时使用
struct
{
U8_T u8_real_state_cnt; //充电实时状态 上送计数
U16_T u16_charge_cnt; //充电次数
U32_T u32_charge_time; //充电时间
}s_gwsdk_gun[GUN_MAX_CNT];
struct
{
U16_T u16_timerCnt; //周期发送计数
U16_T u16_timeNUM ; //计数周期值 单位秒
}s_timerCnt[GWSDK_TIMERCNT_NUM];
}S_GWSDK_CTRL_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_sdk_init_data(U8_T bs_flag);
U8_T v_sdk_tcp_connect(void);
U8_T v_sdk_logo_on(void);
void v_sdk_recv_data(void);
void v_sdk_send_ctrl(void);
void v_sdk_private_crel(U8_T u8_flag);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_GWSDK_CTRL_H */
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
#ifndef BS_GWSDK_INTERFACE_H
#define BS_GWSDK_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#if (BS_GWSDK_EN)
#include "infra_defs.h"
#include "protocol_data_def.h"
#include "interface.h"
#include "protocol.h"
/*------ Exported macro(宏定义) -------------------------*/
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void gw_get_firmware_data(evs_event_firmware_info *fireware_info ); //获取固件信息
void gw_get_ver_data(evs_event_ver_info *ver_info); //获取版本信息
void gw_get_devInfo_data(evs_event_devmdu_info* devInfo_data); //获取设备组件信息
void gw_get_alarm_data(evs_event_alarm *event_alarm ,U8_T u8_gunNo); //获取故障告警信息
void gw_getPilePropertyData(evs_property_dcPile* dcProperty); //获取直流桩设备属性
void gw_get_gunlink_data(evs_event_pile_stutus_change *gunlink_state,U8_T u8_gunNo); //获取枪连接状态
void gw_get_stopResult_info(U8_T gunNo,evs_event_stopCharge* event_stopResult); //枪停止结果上送
void gw_get_startResult_info(U8_T gunNo,evs_event_startResult* event_startResult) ;//枪启动结果上送
void gw_get_DC_meter_data(evs_property_meter* meterData,U8_T u8_gunNo); //获取直流电表底值数据
void gw_get_dc_BMS_Data(evs_property_BMS* BMSData ,U8_T u8_gunNo); //获取直流桩BMS属性;
void gw_get_log_info(U8_T flag ,U8_T gunNo,evs_event_tradeInfo* tradeInfo_data);
void gw_get_Auth_Data(evs_event_startCharge* AuthData ,U8_T u8_gunNo); //获取即插即充所需数据
void gw_get_dc_nonWork(evs_property_dc_nonWork* gunData,U8_T u8_gunNo); //获取直流非充电中状态数据
void gw_get_dc_work(evs_property_dc_work* gunData,U8_T u8_gunNo); //获取直流充电中状态数据
U8_T u8_feeModle_save(evs_service_issue_feeModel *param);
U8_T u8_gwsdk_start_charge_ctrl(evs_service_startCharge *param);
U8_T u8_gwsdk_stop_chaege_ctrl(evs_service_stopCharge *param);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_GWSDK_INTERFACE_H */
@@ -0,0 +1,200 @@
# IES 积成平台功能说明(CCU601E_D / plat_comm/ieszt
本文档描述 **积成智通 IESZT** 二进制协议在 E 工程中的落地方案、与 **PlatComm 联网任务** 的挂接关系,以及 **`S_BS_IES_CTRL_DATA`** 各字段含义。
上层任务状态机、双平台槽位、FTP 分时等见 **[《平台联网任务功能说明.md》](../平台联网任务功能说明.md)**。
---
## 1. 模块定位
| 项目 | 说明 |
|------|------|
| **目录** | `app/plat_comm/ieszt/` |
| **编译宏** | **`BS_IES_EN`**`public_define.h` |
| **PlatComm 槽位** | `MasterFuncArray` 名称 **`"ieszt"`**`plat_comm_task.c`);与 OCPP 并存时通常为槽位 **0**OCPP 为槽位 **1**(以 `PLAT_EN` 配置为准) |
| **全局数据** | **`s_bs_task_ctrl.s_ies_data`**`plat_comm_task.h`,类型 **`S_BS_IES_CTRL_DATA`** |
| **传输层** | 与 OCPP 共用 **`bs_connect_impl`**`u8_tcp_connect` / `v_bs_send` / `u16_bs_recv`**明文 TCP**,无 WebSocket |
| **当前阶段** | **登录/心跳** + **校时** + **费率** + **二维码 0x08/0x0A** |
---
## 2. 源文件与功能分区
**`BS_ocpp_ctrl.c`** 相同,`.c` 内用 **`#if 1 //公共` / `#if 1 //发送` / `#if 1 //接收` / `#if 1 //外部控制使用`** 折叠分区(便于 Keil 大纲跳转与整段屏蔽调试)。
| 文件 | `#if 1` 分区 | 内容 |
|------|----------------|------|
| **`bs_ies_ctrl.c`** | 公共 | `v_ies_timer_add``u8_ies_timer_ctrl`、定时器初值、`v_ies_reset_ctx``v_ies_on_login_ok` |
| | 发送 | `v_ies_logo_on``v_ies_send_ctrl` |
| | 接收 | `v_ies_recv_data``v_ies_recv_unpack` |
| | 外部控制使用 | `v_ies_init_data``v_ies_tcp_connect``v_ies_private_logic` |
| **`impl/bs_public_impl.c`** | — | **`u8_get_str_data` / `u8_set_str_data`**、**`u32_bs_get_sys_data` / `u8_set_sys_data`**(含 `E_STR_QR_DISPLAY``E_BS_GET_SYS_DATA_UNIX_TIMESTAMP` |
| **`bs_ies_interface.c`** | 公共 | 组帧头、CRC、`v_ies_finish_frame_and_send` |
| | 发送 | `v_ies_register_req``v_ies_heart_req``v_ies_fee_req``u8_ies_send_ack` |
| | 接收 | 登录/心跳/校时/费率/二维码;`v_ies_unpack_handle`(经 `bs_public_impl` 读写系统) |
| **`bs_ies_ctrl.h`** | — | 协议常量、`IES_SEND_TIMER``S_BS_IES_CTRL_DATA`、对外 API |
| **`bs_ies_interface.h`** | — | 报文层对外声明 |
移植路线图(阶段 3+)见 **`../IES积成平台移植方案.md`**。
---
## 3. 与 PlatComm 的挂接(对照联网任务文档 §4)
### 3.1 七回调映射
| PlatComm 回调 | IES 实现 | 说明 |
|---------------|----------|------|
| **`v_bs_data_init`** | `v_ies_init_data` | 清零 **`s_ies_data`**,写入 **`u8_bs_flag`** |
| **`v_bs_tcp_connect`** | `v_ies_tcp_connect` | 建链前清 **`u8_register_flag`**;内部 **`u8_tcp_connect(id)`** |
| **`v_bs_logon_connect`** | `v_ies_logo_on` | 周期发 **0x01****`u8_register_flag==1`** 时返回 1,置槽位 **`u8_log_on_flag`** |
| **`v_bs_recv_data`** | `v_ies_recv_data` | **`u16_bs_recv`** → **`v_ies_unpack_handle`** |
| **`v_bs_recv_unpack`** | `v_ies_recv_unpack` | 空实现(4G 外部分包预留) |
| **`v_bs_send_ctrl`** | `v_ies_send_ctrl` | 已登录后周期发 **0x03**、**0x05**(未获费率时) |
| **`v_bs_private_logic`** | `v_ies_private_logic` | **`u8_heart_outTime_cnt`** 超限 → **`v_set_bs_renew`** |
调度顺序与 **`v_plat_comm_network_round`** 一致,见 **[平台联网任务功能说明.md §4.3](../平台联网任务功能说明.md)**。
### 3.2 与 `S_BS_CTRL_DATA` 的分工
| 数据 | 位置 | 用途 |
|------|------|------|
| TCP/登录标志 | **`s_bs_ctrl[i].u8_TCP_connect_flag`** / **`u8_log_on_flag`** | PlatComm 通用状态机 |
| IES 协议态 | **`s_ies_data`** | 登录应答、心跳计数、**`s_timerCnt[]`** 周期定时、序列号等 |
| 槽位号 | **`s_ies_data.u8_bs_flag`** | 与 **`i`**、`E_STR_PILE_NO` 第三参数、`v_bs_send(id)` 一致 |
---
## 4. 运行态结构 `S_BS_IES_CTRL_DATA`
所有 IES 私有计时与计数集中在 **`s_bs_task_ctrl.s_ies_data`**`v_ies_reset_ctx` / `memset` 时一并清零)。
| 字段 | 类型 | 含义 |
|------|------|------|
| **`u8_bs_flag`** | U8 | PlatComm 槽位 **0/1**;对应 **`E_BS_GET_SYS_DATA_PLAT_EN`** 与桩编号 **`E_STR_PILE_NO`** 的 `u8_dataPara` |
| **`u8_register_out_cnt`** | U8 | 已发 **0x01**、仍未收到 **0x02** 的累计次数;超过 **`BS_IES_LOGIN_RETRY_MAX`** 触发 **`v_set_bs_renew`** |
| **`u8_heart_outTime_cnt`** | U8 | 已发 **0x03**、仍未收到 **0x04** 的连续次数;由 **`v_ies_heart_req`** 递增、**`v_ies_heart_ack`** 清零 |
| **`u8_register_flag`** | U8 | **1** = 平台登录成功(**0x02** 且结果字节为 0);为 1 时 **`v_ies_logo_on`** 返回 1 |
| **`u8_syn_time_flag`** | U8 | **1** = 已完成平台校时(登录成功或收到 **0x76** |
| **`u8_ask_fee_flag`** | U8 | **1** = 已获取/下发费率,不再周期发 **0x05** |
| **`u16_msgSerialNumber`** | U16 | 自发帧序列号(组帧头时 `s16_serialNum<=0` 自增) |
| **`ulong_restart_timer`** | ULONG | 最近一次 TCP 建链成功时刻(`get_current_seconds` |
| **`s_timerCnt[]`** | 数组 | 与 OCPP **`OCPP_DATA_CTRL.s_timerCnt`** 同结构:`u16_timerCnt` 每秒 +1`u16_timeNUM` 为周期(秒) |
| **`ack_ctrl[]`** | 指针数组 | 平台下行需应答帧的异步队列(**`IES_DATA_RECV_ACK`**,同 OCPP **`ack_ctrl`**);`v_ies_send_ctrl` 每轮上送一条 |
### 4.1 `IES_SEND_TIMER` 与默认周期(`v_ies_init_timer_period`
| 枚举 | 默认 `u16_timeNUM`(秒) | 说明 |
|------|-------------------------|------|
| **`IES_TIMER_LOGIN`** | `BS_IES_LOGIN_INTERVAL_S`20 | `v_ies_logo_on` 发 0x01 |
| **`IES_TIMER_HEART`** | `BS_IES_HEART_INTERVAL_S`20 | `v_ies_send_ctrl` 发 0x03 |
| **`IES_TIMER_FEE_MODULE`** | 30 | 登录后周期发 **0x05** 直至 **0x06****0x7D** 写入费率 |
| **`IES_TIMER_FREE_DATA_GUN1/2`** | 180 | 空闲实时 0x13 |
| **`IES_TIMER_CHG_DATA_GUN1/2`** | 30 | 充电实时 0x15 |
| **`IES_TIMER_METER_GUN1/2`** | 3600 | 电表 |
| **`IES_TIMER_TRADE_GUN1`** | 10 | 账单 |
内部接口(`bs_ies_ctrl.c`,与 OCPP 同名语义):**`v_ies_timer_add()`** 每秒递增;**`u8_ies_timer_ctrl(cmd, GET_FLAG/CLEAR_FALG)`** 查询到时或清零。
---
## 5. 登录与心跳流程
```text
[TCP 未连接]
v_ies_tcp_connect → u8_tcp_connect
(清 u8_register_flagCLEAR_FALG LOGIN/HEART 定时器)
[TCP 已连接,未登录]
v_ies_logo_on → v_ies_timer_add
IES_TIMER_LOGIN 到时 → v_ies_register_req
收到 0x02 且 [BODY]==0 → v_ies_on_login_ok(含首包心跳定时到点)
超时次数 > BS_IES_LOGIN_RETRY_MAX → v_set_bs_renew + v_ies_reset_ctx
[已登录,u8_log_on_flag==1]
v_ies_send_ctrl → v_ies_timer_add
IES_TIMER_HEART 到时 → v_ies_heart_req
u8_ask_fee_flag==0 且 IES_TIMER_FEE_MODULE 到时 → v_ies_fee_req
v_ies_recv_data → 0x04 → v_ies_heart_ack
→ 0x76 → v_ies_set_clk_syncv_bs_set_timeack 挂入 ack_ctrl
→ 0x7D → 解析费率 → v_bs_save_fee_data0x7E 挂入 ack_ctrl
v_ies_send_ctrl → ack_ctrl 非空 → u8_ies_send_ack → v_ies_free_ack_data
v_ies_private_logic → v_ies_timer_add
u8_heart_outTime_cnt > BS_IES_HEART_MISS_MAX → v_set_bs_renew
```
---
## 6. 0x68 帧与已实现命令
帧域下标见 **`bs_ies_ctrl.h`** 中 **`IES_FRM_OFF_*`**(与 A 工程 `u8_recvBuf[21/22/23]` 一致)。
| 类型码 | 方向 | 函数 |
|--------|------|------|
| **0x01** | 上行 | `v_ies_register_req` |
| **0x02** | 下行 | `v_ies_register_ack` |
| **0x03** | 上行 | `v_ies_heart_req` |
| **0x04** | 下行 | `v_ies_heart_ack` |
| **0x05** | 上行 | `v_ies_fee_req` |
| **0x06** | 下行 | `v_ies_fee_module_ack``v_bs_save_fee_data` |
| **0x07** | 上行 | `u8_ies_send_ack` 二维码下发应答 |
| **0x08** | 下行 | `v_ies_qr_dis``QRDisplay[]` + Flash |
| **0x09** | 上行 | `u8_ies_send_ack` 二维码召唤应答 |
| **0x0A** | 下行 | `v_ies_qr_call` |
| **0x75** | 上行 | `v_ies_clk_sync_ack` |
| **0x76** | 下行 | `v_ies_set_clk_sync``v_bs_set_time` |
| **0x7D** | 下行 | `v_ies_fee_cfg_req` |
| **0x7E** | 上行 | `v_ies_fee_cfg_ack` |
| 其它 | — | `v_ies_rx_cmd_handle` 打日志占位 |
---
## 7. 配置与联调要点
| 配置项 | 接口 | 说明 |
|--------|------|------|
| 平台使能 | **`E_BS_GET_SYS_DATA_PLAT_EN`** | 槽位 0/1 与 **`MasterFuncArray`** 下标一致 |
| 联网方式 | **`E_BS_GET_SYS_DATA_NET_TYPE`** | 0=4G1=网口;IES 与 OCPP 共用同一套 connect |
| 桩编号 | **`E_STR_PILE_NO`** + **`u8_bs_flag`** | 双平台时平台 A/B 各一套桩号 |
| 枪数 / 故障 | **`E_BS_GET_SYS_DATA_GUN_NUM`**、**`NOW_FAULT_ALARM_NUM`** | 登录帧、心跳帧组帧 |
**双平台 + 单 4G TCP**:产品与 **`PLAT_EN`** 需约定主平台;详见移植方案「风险」章节。
**FTP 升级中**`v_ies_recv_data`**`u8_updata_flag!=0` 且 4G** 时跳过收包(与 OCPP 策略一致,见联网任务文档 §4.4)。
---
## 8. 日志
| 宏 | 定义位置 | 说明 |
|----|----------|------|
| **`IES_PRINT_FLAG`** | `plat_comm_task.h` | 值 **0xFE**,与 **`OCPP_PRINT_FLAG`(0xFF)** 同类:不受 `u8_task_log_flag` 门控时默认输出 |
| **`IES_PRINTF_LOG`** | `bs_ies_ctrl.h` | `MYLOG_MSG(IES_PRINT_FLAG, …)`;**ieszt 目录内请用此宏,勿用 `Plat_Comm_LOG`** |
| 日志 | 含义 |
|------|------|
| `IES: init slot=` | `v_ies_init_data` |
| `IES: login req pile=` | 发出 0x01 |
| `IES: login ok` / `login fail` | 0x02 解析结果 |
| `IES: heartbeat tx cnt=` | 发出 0x03(含未应答计数) |
| `IES: heartbeat ack` | 收到 0x04 |
| `IES: clk sync` / `clk sync ack` | 0x76 校时 / 0x75 应答 |
| `IES: fee module req` / `fee model saved` | 0x05 请求 / 0x06·0x7D 落盘 |
| `IES: login timeout, renew` / `heartbeat miss, renew` | 触发 **`v_set_bs_renew`** |
---
## 9. 关联文档
| 文档 | 内容 |
|------|------|
| **[平台联网任务功能说明.md](../平台联网任务功能说明.md)** | PlatComm 任务、双槽状态机、FTP 分时、**`v_set_bs_renew`** |
| **[IES积成平台移植方案.md](../IES积成平台移植方案.md)** | A→E 分阶段移植清单 |
| **`4g_module/4G模块AT指令配置功能说明.md`** | 4G TCP/FTP 与 PlatComm 分时 |
| **`impl/bs_public_impl.h`** | **`E_STR_*`** / **`E_BS_GET_SYS_DATA_*`** 枚举 |
---
*文档版本:与 `plat_comm/ieszt` 源码同步;结构体字段变更时请同步更新 §4。*
+43
View File
@@ -0,0 +1,43 @@
# ieszt — 积成 IES 运营平台协议
规约依据:积成充电桩网络通信协议(V3.5 / V3.7 / V3.8,由 `BS_IES_TYPE` 选择)
## 编译开关
`public_define.h` 中设置:
```c
#define BS_IES_EN (1) // 使能积成 IES 运营协议(CCU621 默认)
```
协议版本在 `bs_ies_ctrl.h` 中配置:
```c
#define BS_IES_TYPE (2) // 1=山高3.82=积成3.7(默认),3=积成3.5
```
可与 `BS_IESYW_EN` 并存:平台 A 走 IES 运营(本目录),平台 B 走远程运维(`iesyw/`),共用 4G 模组 socketA/B 分发。
## 文件
| 文件 | 说明 |
| --- | --- |
| `bs_ies_ctrl.c/h` | 任务回调、登录/心跳、周期上送调度、接收解帧 |
| `bs_ies_interface.c/h` | 帧格式(0x68 起始)、帧类型码、组包/解包 |
## 主要帧类型
| 类型 | 说明 |
| --- | --- |
| 0x01/0x02 | 登录/应答 |
| 0x03/0x04 | 心跳/应答 |
| 0x05/0x06 | 计费模型请求/应答 |
| 0x07~0x0A | 二维码下发/召唤 |
| 0x11/0x13 | 充电中/非充电实时数据 |
| 0x15 | 故障告警 |
| 0x31 | 表底值上送 |
| 0x41~0x48 | 启停/账单结算 |
| 0x76 | 对时 |
| 0x7D/0x7E | 费率相关 |
详细说明见 [plat_comm模块说明.md](../plat_comm模块说明.md)。
+709
View File
@@ -0,0 +1,709 @@
#include "plat_comm/ieszt/bs_ies_ctrl.h"
#include "plat_comm/ieszt/bs_ies_interface.h"
#include "plat_comm/impl/bs_connect_impl.h"
#include "plat_comm/impl/bs_public_impl.h"
#if WH_GM5_EN
#include "plat_comm/4g_module/bs_wh_gm5.h"
#endif
#if (BS_IES_EN)
#define BS_TIME_1S 10 //1秒 发送周期1秒计数
S_BS_IES_CTRL_DATA *private_ies_data;
#endif
#if (BS_IES_EN) //内部控制函数
void v_ies_send(U8_T *u8_sendBuf, U32_T sendLen)
{
u8_sendBuf[sendLen] = u16_crc_checksum(&u8_sendBuf[3], sendLen-3); //和校验
sendLen ++;
//更新帧长度域
u8_sendBuf[1] = (sendLen-4)>>8;
u8_sendBuf[2] = sendLen-4;
v_bs_send(private_ies_data->id,u8_sendBuf,sendLen);
}
//发送周期计数累加 1秒加一
static void v_ies_timer_add()
{
U8_T i;
static ULONG time_old = 0;
ULONG time_ing = get_current_seconds(); //获取当前秒数
if(time_old != time_ing)
{
time_old = time_ing;
for (i = 0; i < IES_TIMERCNT_NUM; i++)
{
if(private_ies_data->s_timerCnt[i].u16_timerCnt < private_ies_data->s_timerCnt[i].u16_timeNUM)
{
private_ies_data->s_timerCnt[i].u16_timerCnt ++;
}
}
}
}
//获取发送周期计数到时标志
static U8_T u8_ies_timer_ctrl(IES_SEND_TIMER cmd ,FLAG_CTLR flag)
{
U8_T ret = 0;
if(flag == CLEAR_FALG) //清空对应计时计数
{
private_ies_data->s_timerCnt[(U8_T)cmd].u16_timerCnt = 0;
ret = 1;
}
else if(private_ies_data->s_timerCnt[(U8_T)cmd].u16_timeNUM != 0 &&
private_ies_data->s_timerCnt[(U8_T)cmd].u16_timerCnt >= private_ies_data->s_timerCnt[(U8_T)cmd].u16_timeNUM)
{
private_ies_data->s_timerCnt[(U8_T)cmd].u16_timerCnt = 0;
ret = 1;
}
return ret;
}
#endif
#if (BS_IES_EN) //发送相关
/**
* ************************************************************************
* @brief - 故障信息变化检查
*
*
* @return U8_T - 0未变化 1变化
*
* @author jiankalka
* @date 2025-09-20
* ************************************************************************
*/
static U8_T u8_cheak_fault()
{
static U8_T u8_old_fault_num=0;
U8_T u8_fault_num,ret = 0;
u8_fault_num = u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM,0) +
u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM,1); //获取故障 告警数量
if(u8_fault_num != u8_old_fault_num)
{
u8_old_fault_num = u8_fault_num;
ret = 1;
}
return ret;
}
/**
* ************************************************************************
* @brief - 抢连接变化检查
*
* @param[in] u8_gunNo - 抢号 0-N
*
* @return U8_T - 0未变化 1已经变化
*
* @author jiankalka
* @date 2025-09-20
* ************************************************************************
*/
static U8_T u8_cheak_gunlink(U8_T u8_gunNo)
{
static U8_T u8_old_gunLink[GUN_MAX_CNT] = {0};
U8_T u8_gunLink = u8_ies_get_gunLink_state(u8_gunNo); //获取枪连接状态 0未连接 1已连接
//u8_gunLink = (u8_gunLink == 0 ? 0 : 1);
if(u8_gunLink != u8_old_gunLink[u8_gunNo])
{
//printf("%d枪变位,原状态:%d,现状态:%d\n",u8_gunNo,u8_old_gunLink[u8_gunNo],u8_gunLink);
u8_old_gunLink[u8_gunNo] = u8_gunLink;
return 1;
}
return 0;
}
/**
* ************************************************************************
* @brief - 抢状态变化检查
*
* @param[in] u8_gunNo - 抢号 0-N
*
* @return U8_T - 0未变化 1已经变化
*
* @author jiankalka
* @date 2025-09-20
* ************************************************************************
*/
static U8_T u8_cheak_gunState(U8_T u8_gunNo)
{
static U8_T u8_old_gunState[GUN_MAX_CNT] = {0};
U8_T u8_gunState = u8_ies_get_gun_state(u8_gunNo); //获取枪状态 0未连接 1已连接
//u8_gunLink = (u8_gunLink == 0 ? 0 : 1);
if(u8_gunState != u8_old_gunState[u8_gunNo])
{
//printf("%d枪变位,原状态:%d,现状态:%d\n",u8_gunNo,u8_old_gunState[u8_gunNo],u8_gunState);
u8_old_gunState[u8_gunNo] = u8_gunState;
return 1;
}
return 0;
}
//发送控制
static void v_ies_send_mag_ctrl()
{
S_BS_CTRL_DATA *s_bs_ctrl = &s_bs_task_ctrl.s_bs_ctrl[private_ies_data->id]; //获取控制数据
U8_T i,u8_data; //临时变量 使用前注意赋值
U8_T u8_gunCnt = s_bs_task_ctrl.u8_gunCnt;
U8_T u8_get_flow_state[GUN_MAX_CNT];
static U8_T fee_flag = 0; //连接平台后直接请求一次计费模型标志
//检查心跳周期是否到位
if(u8_ies_timer_ctrl(IES_TIMER_Heartbeat,GET_FLAG) == 1)
{
v_ies_heart();
return;
}
//电表底值上送
for(i=0;i<u8_gunCnt;i++)
{
if(u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_MeterValues_GUN + i), GET_FLAG) == 1)
{
v_ies_meter_data(i);
return;
}
}
//主动上送
//桩主动请求充电
for(i=0;i<u8_gunCnt;i++)
{
if(private_ies_data->s_ctrl_flag[i].u8_charge_req == 1)
{
v_ies_start_req(i);
return;
}
}
//请求费率
if(fee_flag == 0 ||(private_ies_data->txFlag_askFeeMode == 0 &&
u8_ies_timer_ctrl(IES_TIMER_FEE_REQ,GET_FLAG) == 1))
{
v_ies_fee_req();
fee_flag = 1;
return;
}
//订单上送
for(i=0; i<u8_gunCnt; i++)
{
u8_data = 0;
if(s_bs_ctrl->s_gundata[i].txFlag_tradUpload == 1)
{
if( s_bs_ctrl->s_gundata[i].u8_tradUpload_cnt < 6) //充电中 只发6次
u8_data = 1;
}
else
s_bs_ctrl->s_gundata[i].u8_tradUpload_cnt = 0;
if(u8_data == 1 && u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_TRADE_INFO + i), GET_FLAG) == 1)
{
v_ies_bill_pack(i);
s_bs_ctrl->s_gundata[i].u8_tradUpload_cnt ++;
return;
}
}
//故障状态上送-有故障变化时上送,无故障变化时,3分钟上送一次
u8_data = u8_cheak_fault();
if(u8_data == 1 || u8_ies_timer_ctrl(IES_TIMER_FAULT_GUN ,GET_FLAG) == 1)
{
u8_ies_timer_ctrl(IES_TIMER_FAULT_GUN ,CLEAR_FALG); //清空计数
v_ies_fault_V35(i);
return;
}
//充电/空闲实时信息上报
for(i=0; i<u8_gunCnt; i++)
{
u8_get_flow_state[i] = u8_bs_get_flow_state(i); //获取终端状态 0 空闲 1开始充电 2充电中
if(u8_get_flow_state[i] == 2) //充电中
{
//当状态由变化时 前3帧按10秒发送--u8_real_state_cnt 0x00-0x0F 充电中计数 0x10-0x1F 非充电中计数
private_ies_data->s_ies_gun[i].u8_real_state_cnt &= 0x0F;
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 3)
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = 10; //前3帧 10秒周期上送
}
else
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = private_ies_data->u16_work_info_period; //上送周期等于充电数据上送周期设置值
}
if(u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_REAL_WORK_GUN + i), GET_FLAG) == 1)
{
// printf("%d枪,充电中,u8_real_state_cnt=%02x\n",i,private_ies_data->s_ies_gun[i].u8_real_state_cnt);
v_ies_rtd_chging(i);
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 3)
{
private_ies_data->s_ies_gun[i].u8_real_state_cnt ++;
}
return;
}
}
else
{
//当状态由变化时 前3帧按10秒发送--u8_real_state_cnt 0x00-0x0F 充电中计数 0x10-0x1F 非充电中计数
private_ies_data->s_ies_gun[i].u8_real_state_cnt &= 0xF0; //清除低位计数值
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 0x30)
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = 3; //前3帧 3秒周期上送
}
else
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = private_ies_data->u16_free_info_period; //上送周期等于充电数据上送周期设置值
}
if(u8_ies_timer_ctrl((IES_SEND_TIMER)(IES_TIMER_REAL_WORK_GUN + i), GET_FLAG) == 1 //|| u8_cheak_gunlink(i)
|| u8_cheak_gunState(i)) //延时到时间上送、枪状态改变上送
{
//printf("%d枪,非充电中,u8_real_state_cnt=%02x\n",i,private_ies_data->s_ies_gun[i].u8_real_state_cnt);
v_ies_rtd_free(i);
if(private_ies_data->s_ies_gun[i].u8_real_state_cnt < 0x30)
{
private_ies_data->s_ies_gun[i].u8_real_state_cnt += 0x10;
}
return;
}
}
}
}
#endif
#if (BS_IES_EN) //接收
static void v_bs_rx_cmd_hanle(U8_T *u8_recvBuf) //具体报文解帧
{
U8_T u8_cmd = u8_recvBuf[22];
switch(u8_cmd)
{
case NT_LOGIN_ACK: //0x02 //登录注册应答
{
if(private_ies_data->u8_register_flag == 0)
{
v_ies_register_ack(u8_recvBuf);
}
break;
}
case NT_TICK_ACK: //0x04 //心跳应答
{
v_ies_heart_ack();
break;
}
case NT_FEE_MODULE_ACK: //0x06 //计费模型应答
{
v_ies_fee_ack(u8_recvBuf);
break;
}
case NT_MDU_DATA_ACK: //0x17 //充电桩整流模块数据应答
{
break;
}
case NT_START_CHG_ACK: //0x42 //平台确认桩主动申请启动充电
{
v_ies_rx_start(u8_recvBuf);
break;
}
case NT_REMOTE_START_REQ: //0x44 //平台远程控制启机
{
if(private_ies_data->txFlag_askFeeMode == 1) //未收到计费模型,不启动充电
{
Plat_Comm_LOG("private_ies_data->txFlag_askFeeMode=%d\n",private_ies_data->txFlag_askFeeMode);
v_ies_rmt_start(u8_recvBuf);
}
break;
}
case NT_REMOTE_STOP_REQ: //0x46 //平台远程停机
{
v_ies_rmt_stop(u8_recvBuf);
break;
}
case NT_BILL_UPLOAD_ACK: //0x48 //平台结算账单确认
{
v_ies_bill_upload_ack(u8_recvBuf);
break;
}
case NT_USERACNTINFO_ACK: //0x4A //平台回复查询用户账单信息(未用)
{
//v_bs_user_acntInfo_ack(u8_recvBuf);
break;
}
case NT_CARD_BILL_UPLOAD_ACK: //0x4C //平台刷卡结算订单应答
{
//v_bs_bill_card_ack(u8_recvBuf);
break;
}
case NT_WORK_PARA_CFG_REQ: //0x72 //平台设置工作参数
{
//v_bs_set_wkPara(u8_recvBuf);
break;
}
case NT_WORK_PARA_QUERY_REQ: //0x74 //平台查询工作参数
{
//v_bs_query_wkPara(u8_recvBuf);
break;
}
case NT_CLK_SYNC_REQ: //0x76 //平台对时
{
v_ies_set_clk_sync(u8_recvBuf);
break;
}
case NT_REMOTE_OPRT_REQ: //0x78 //平台远程操作
{
//v_bs_rmt_oprt(u8_recvBuf);
break;
}
case NT_FEE_CFG_REQ: //0x7D //平台设置计费模型
{
v_ies_set_fee(u8_recvBuf);
break;
}
case NT_UPDATE_AUTO: //0x90 //平台下发FTP升级命令
{
//v_ies_upgrade_data_auto(u8_recvBuf);
break;
}
case NT_QR_REQ_CALL: //0x0A 平台召唤二维码
{
v_ies_qr_call(u8_recvBuf);
break;
}
case NT_QR_REQ_DIS: //0x08 平台下发二维码
{
Plat_Comm_LOG("Receive platform QR code command\n");
// printf("收到平台下发二维码命令\n");
// for(int i=0;i < 100;i++)
// {
// printf("%.2x ",u8_recvBuf[i]);
// }
// printf("\n");
v_ies_qr_dis(u8_recvBuf);
break;
}
default:
break;
}
}
// static void v_bs_unpack_handle_ieszt(U8_T *u8_recvBuf, U16_T u16_rcvLen) //解帧函数
// {
// U8_T u8_crc;
// U16_T u16_rcvByteNum = 0; //解粘包指针
// U16_T u16_oneFrmLen = 0; //一完整帧理论总长度
// U16_T u16_dataLen = 0; //数据域长度
// U8_T upackBuf[BS_RECV_BUF_LEN] = {0}; //待解析帧缓存
// while(u16_rcvLen >= 23) //帧头长度=23
// {
// if(u8_recvBuf[u16_rcvByteNum] != NT_HEADER) //找帧头
// {
// u16_rcvByteNum++;
// u16_rcvLen--;
// continue;
// }
// u16_dataLen = (U16_T)((u8_recvBuf[u16_rcvByteNum+1]<<8) + u8_recvBuf[u16_rcvByteNum+2]);
// if(u16_dataLen >= 256)
// return;
// u16_oneFrmLen = u16_dataLen + 4;
// if(u16_oneFrmLen <= u16_rcvLen) //构成完整帧
// {
// memcpy(&upackBuf[0], &u8_recvBuf[u16_rcvByteNum], u16_oneFrmLen);
// //和校验
// u8_crc = u16_crc_checksum(&upackBuf[3], u16_oneFrmLen-4);
// if(u8_crc == upackBuf[u16_oneFrmLen-1])
// {
// v_bs_rx_cmd_hanle(upackBuf); //和校验通过,解析数据
// }
// u16_rcvByteNum += u16_oneFrmLen;
// u16_rcvLen -= u16_oneFrmLen;
// }
// else
// return;
// }
// }
static void v_bs_unpack_handle_ieszt(U8_T *u8_recvBuf, U16_T u16_rcvLen) //解帧函数
{
U8_T u8_crc;
U16_T u16_rcvByteNum = 0; //解粘包指针
U16_T u16_oneFrmLen = 0; //一完整帧理论总长度
U16_T u16_dataLen = 0; //数据域长度
U8_T upackBuf[BS_RECV_BUF_LEN] = {0}; //待解析帧缓存
static U8_T u8_save_buf[BS_RECV_BUF_LEN*2] = {0};
static U16_T u16_save_len = 0;
// 检查缓冲区是否溢出
if(u16_rcvLen + u16_save_len >= BS_RECV_BUF_LEN*2)
{
// 移除前四分之一数据以腾出空间
U16_T remove_len = u16_save_len / 4; // 计算要移除的长度
U16_T remain_len = u16_save_len - remove_len; // 剩余数据长度
if(remain_len > 0) {
// 将后面的数据向前移动,覆盖前四分之一
memmove(u8_save_buf, u8_save_buf + remove_len, remain_len);
}
u16_save_len = remain_len; // 更新保存的数据长度
}
// 将接收到的数据添加到缓存区中
if(u16_rcvLen > 0) {
memcpy(&u8_save_buf[u16_save_len], u8_recvBuf, u16_rcvLen);
u16_save_len += u16_rcvLen;
}
// 使用静态缓冲区数据进行解析
U16_T process_len = u16_save_len; // 当前要处理的数据长度
u16_rcvByteNum = 0; // 重置解析指针
while(process_len >= 23) //帧头长度=23
{
if(u8_save_buf[u16_rcvByteNum] != NT_HEADER) //找帧头
{
u16_rcvByteNum++;
process_len--;
continue;
}
u16_dataLen = (U16_T)((u8_save_buf[u16_rcvByteNum+1]<<8) + u8_save_buf[u16_rcvByteNum+2]);
if(u16_dataLen >= 256)
{
u16_rcvByteNum++;
process_len--;
continue; // 数据长度异常,跳过这个帧头
}
u16_oneFrmLen = u16_dataLen + 4;
if(u16_oneFrmLen <= process_len) //构成完整帧
{
memcpy(&upackBuf[0], &u8_save_buf[u16_rcvByteNum], u16_oneFrmLen);
//和校验
u8_crc = u16_crc_checksum(&upackBuf[3], u16_oneFrmLen-4);
if(u8_crc == upackBuf[u16_oneFrmLen-1])
{
v_bs_rx_cmd_hanle(upackBuf); //和校验通过,解析数据
}
u16_rcvByteNum += u16_oneFrmLen;
process_len -= u16_oneFrmLen;
}
else
break; // 数据不完整,等待下次接收更多数据
}
// 将处理完的数据从静态缓冲区中移除
if(u16_rcvByteNum > 0) // 有处理完的数据需要移除
{
// 计算剩余未处理数据的长度
U16_T remaining_len = u16_save_len - u16_rcvByteNum;
if(remaining_len > 0)
{
// 将未处理的数据向前移动,覆盖已处理的数据
memmove(u8_save_buf, u8_save_buf + u16_rcvByteNum, remaining_len);
}
// 更新静态缓冲区的数据长度
u16_save_len = remaining_len;
// 清空移动后空出的部分(可选,为了调试清晰)
if(remaining_len < BS_RECV_BUF_LEN*2) {
memset(u8_save_buf + remaining_len, 0, BS_RECV_BUF_LEN*2 - remaining_len);
}
}
}
#endif
#if (BS_IES_EN) //外部控制使用
/****************************************************
*函数名称: v_ies_init_data()
*功能说明: 程序运行前 参数初始化
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
void v_ies_init_data(U8_T bs_flag)
{
U8_T i;
private_ies_data = &s_bs_task_ctrl.s_ies_data; //指针数据初始化
memset(private_ies_data,0,sizeof(S_BS_IES_CTRL_DATA));
private_ies_data->id = bs_flag; //平台位置
// if (bs_flag == 0U) {
// s_bs_task_ctrl.s_bs_ctrl[bs_flag].u8_netType =
// (u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NET_TYPE,bs_flag) == 1U) ? 0U : 1U;
// } else {
// s_bs_task_ctrl.s_bs_ctrl[bs_flag].u8_netType =
// (u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NET_TYPE,bs_flag) == 1U) ? 0U : 1U;
// }
s_bs_task_ctrl.s_bs_ctrl[bs_flag].fd = -1;
s_bs_task_ctrl.s_bs_ctrl[bs_flag].ulong_restart_timer = get_current_seconds();
if(s_bs_task_ctrl.s_bs_ctrl[bs_flag].u8_netType == 0) //4G
{
v_bs_4G_data_init();
}
//周期发送控制初始化
private_ies_data->u16_work_info_period = 30; //工作实时状态 上送周期
private_ies_data->u16_free_info_period = 120; //空闲实时状态 上送周期
//周期发送控制初始化
private_ies_data->s_timerCnt[IES_TIMER_Register].u16_timeNUM = 20; //秒 登录帧上送周期
private_ies_data->s_timerCnt[IES_TIMER_Register].u16_timerCnt = 20; //确保立马执行登录帧
private_ies_data->s_timerCnt[IES_TIMER_Heartbeat].u16_timeNUM = 15; //秒 心跳帧上送周期
private_ies_data->s_timerCnt[IES_TIMER_FEE_REQ].u16_timeNUM = 60; //秒 费率请求
private_ies_data->s_timerCnt[IES_TIMER_FAULT_GUN].u16_timeNUM = 60*3; //秒 故障信息上报
for ( i = 0; i < GUN_MAX_CNT; i++)
{
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timeNUM = 30; //秒 工作实时状态
private_ies_data->s_timerCnt[IES_TIMER_REAL_WORK_GUN + i].u16_timerCnt = 0xFFFF;
private_ies_data->s_timerCnt[IES_TIMER_TRADE_INFO + i].u16_timeNUM = 30; //秒 订单信息上送
private_ies_data->s_timerCnt[IES_TIMER_MeterValues_GUN + i].u16_timeNUM = 60*30; //秒 电表低值上送
}
}
/****************************************************
*函数名称: v_ies_tcp_connect()
*功能说明: TCP连接功能实现 - 配置4G模块
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
U8_T v_ies_tcp_connect(void)
{
return u8_tcp_connect(private_ies_data->id);
}
/****************************************************
*函数名称: v_ies_logo_on()
*功能说明: 平台桩信息登录 -
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
U8_T v_ies_logo_on(void)
{
v_ies_timer_add(); //计时计数+1
//检查登录周期是否到位
if(u8_ies_timer_ctrl(IES_TIMER_Register,GET_FLAG) == 1)
{
v_ies_register_req(); //登录帧
Plat_Comm_LOG("Send login platform packet, u8_register_flag:%d\n",private_ies_data->u8_register_flag);
if(private_ies_data->u8_register_out_cnt++ > 6) //两分钟登录帧未连接成功
{
v_set_bs_renew(private_ies_data->id); //设置主函数 联网重连
v_ies_init_data(private_ies_data->id); //设置 子函数参数重置 重走初始化过程
Plat_Comm_LOG("Login frame send timeout\n");
}
}
if(private_ies_data->u8_register_flag == 1)
{
private_ies_data->u8_register_out_cnt = 0;
return 1;
}
else
return 0;
}
/****************************************************
*函数名称: v_ies_recv_data()
*功能说明: 数据接收处理 - 控制任务周期调用 使用网口或者4G非分包功能时使用,需内部实现从串口获取数据
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ies_recv_data(void)
{
S16_T s16_recvLen = 0;
U8_T u8_recvBuf[BS_RECV_BUF_LEN] = {0};
s16_recvLen = u16_bs_recv(private_ies_data->id,u8_recvBuf,BS_RECV_BUF_LEN);
if(s16_recvLen > 0)
{
v_bs_unpack_handle_ieszt(&u8_recvBuf[0], s16_recvLen);
}
}
/**
* ************************************************************************
* @brief - 控制任务接收解析完数据后调用 4G 模块多包接收时使用-当4G使用多socket数据接收时,串口数据获取和分包功能由外部实现,函数传入参数为本平台的接收数据
*
* @param[in] id - 平台地址
* @param[in] recvBuf - 接收数据
* @param[in] u16_len - 接收数据长度
*
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
void v_ies_recv_unpack(U8_T id, U8_T *recvBuf,U16_T u16_len)
{
v_bs_unpack_handle_ieszt(recvBuf,u16_len);
}
/****************************************************
*函数名称: v_ies_send_ctrl()
*功能说明: 发送控制函数 -
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ies_send_ctrl(void)
{
v_ies_timer_add(); //计时计数+1
v_ies_send_mag_ctrl(); //发送控制
}
/****************************************************
*函数名称: v_ies_private_logic()
*功能说明: 私有协议逻辑控制 -
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ies_private_logic(U8_T bs_flag)
{
if(private_ies_data->u8_heart_outTime_cnt > 3) //发送三次心跳 未回复 则进行重连
{
v_set_bs_renew(private_ies_data->id); //设置主函数 联网重连
v_ies_init_data(private_ies_data->id); //设置 子函数参数重置 重走初始化过程
//轻质化群充测试过程中发现与平台连接超时后不重连
//检查发现iotx_linkkit_ctx_t结构体中is_opened,is_connected未复位,原因未知,为确保现场使用,此地暂时强制复位
//v_connected_resset();
Plat_Comm_LOG("Timeout reconnect\n");
}
}
#endif
+100
View File
@@ -0,0 +1,100 @@
/**
* @file bs_ies_ctrl.h
* @brief 积成 IES 平台协议控制层
*
* 定义 IES 协议私有数据结构、周期上送计时器及任务回调声明。
* 协议版本由 BS_IES_TYPE 选择(默认积成 3.7)。
*/
#ifndef BS_IES_CTRL_H
#define BS_IES_CTRL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#if (BS_IES_EN)
/*------ Exported macro(宏定义) -------------------------*/
#define IES_TIMERCNT_NUM (4 + 3*GUN_MAX_CNT) //计时计数
#define IES_ACK_STRUCT_NUM 5 //应答结构体数量
/*************************************
* 宏名称: BS_IES_TYPE
* 宏说明: 积成平台协议版本
* 设置说明:未定义:报错
* 1 山高平台 3.8 2积成 3.7 3积成 3.5
*
**************************************/
#define BS_IES_TYPE 2
/*------ Exported struct(结构体定义) --------------------*/
typedef enum{
IES_TIMER_Register = 0, //登录帧 0
IES_TIMER_Heartbeat, //心跳 1
IES_TIMER_FEE_REQ, //费率请求 2
IES_TIMER_FAULT_GUN, //充电故障告警上送3
IES_TIMER_REAL_WORK_GUN, //工作实时状态 数量跟随枪数量 - 包含工作状态和空闲状态两种 4
//IES_TIMER_FAULT_GUN = IES_TIMER_REAL_WORK_GUN+GUN_MAX_CNT, //充电故障告警上送, 4+20 24
IES_TIMER_MeterValues_GUN = IES_TIMER_REAL_WORK_GUN+GUN_MAX_CNT, //表底值上送, 25+20 45
IES_TIMER_TRADE_INFO = IES_TIMER_MeterValues_GUN+GUN_MAX_CNT, //订单信息上送 数量跟随枪数量 46+20 66
}IES_SEND_TIMER;
typedef struct
{
U8_T id; //平台位置 平台1还是平台2
U8_T u8_register_out_cnt; //登录帧超时计数
U8_T u8_heart_outTime_cnt; //心跳超时计时
U8_T u8_register_flag; //是否已注册/是否联网.0否1是
U8_T txFlag_askFeeMode; //请求费率
U8_T u8_time_flag; //校时标志
U8_T u8_heart_outTime; //心跳超时计时--
U16_T u16_msgSerialNumber; //帧序号
U16_T u16_work_info_period; //工作实时状态 上送周期
U16_T u16_free_info_period; //空闲实时状态 上送周期
struct
{
U8_T u8_MeterValues_type; //数据上送标志 0无动作 1空闲数据上送 2充电中数据上送 3交易开始数据上送 4交易停止数据上送
U8_T u8_charge_req; //充电请求标志 0 不请求充电 1 请求充电
}s_ctrl_flag[GUN_MAX_CNT]; //数据控制标志
struct
{
U8_T u8_real_state_cnt; //充电实时状态 上送计数
U16_T u16_charge_cnt; //充电次数
U32_T u32_charge_time; //充电时间
}s_ies_gun[GUN_MAX_CNT];
struct
{
U16_T u16_timerCnt; //周期发送计数
U16_T u16_timeNUM ; //计数周期值
}s_timerCnt[IES_TIMERCNT_NUM];
}S_BS_IES_CTRL_DATA;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_ies_init_data(U8_T bs_flag);
U8_T v_ies_tcp_connect(void);
U8_T v_ies_logo_on(void);
void v_ies_recv_data(void);
void v_ies_send_ctrl(void);
void v_ies_private_logic(U8_T bs_flag);
void v_ies_recv_unpack(U8_T id, U8_T *recvBuf,U16_T u16_len);
void v_ies_send(U8_T *u8_sendBuf, U32_T sendLen);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_IES_CTRL_H */
File diff suppressed because it is too large Load Diff
+336
View File
@@ -0,0 +1,336 @@
/**
* @file bs_ies_interface.h
* @brief 积成 IES 平台协议报文层
*
* 帧格式、帧类型码定义及组包/解包函数声明。
* 帧头 0x68,设备地址 16 字节 ASCII 桩编号。
*/
#ifndef BS_IES_INTERFACE_H
#define BS_IES_INTERFACE_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#if (BS_IES_EN)
/*------ Exported macro(宏定义) -------------------------*/
/************积成通信协议帧结构*********************
* 帧起始域 1字节 0x68
* 帧长度域 2字节 从序列号域到帧数据域,高字节在前,低字节在后
* 序列号域 1字节 数据包发送顺序号,从0开始,应答帧则同询问包
* 设备地址 16字节 ASCII
* 加密方式 1字节 0不加密,1AES加密,登录帧和确认帧加密方式为0
* 数据对象 1字节 高2位:0直流1交流2交直流;低6位标识枪号,0代表充电机本体
* 帧类型码 1字节
* 帧数据域 N字节
* 校验和 1字节 序列号域到帧数据域
**************************************************/
#define NT_PTL_VERSION 37 //通信协议版本V3.5
#define NT_HEADER 0x68 //帧头
//数据对象
#if BS_TYPE_JIMO_EN //即墨平台使能,则桩类型为液冷充电桩。否则为直流桩
#define NT_DC_PILE 3 //直流桩 //#define NT_AC_PILE 1 //交流桩 //直流桩 0积成平台 3即墨平台
#else
#define NT_DC_PILE 0 //直流桩 //#define NT_AC_PILE 1 //交流桩 //直流桩 0积成平台 3即墨平台
#endif
#define NT_0B_MASK_H 0xC0 //高2位掩码
#define NT_0B_MASK_L 0x3F //低6位掩码
#define NT_ID_PILE 0 //充电桩本体
#define NT_ID_GUN1 1 //1号充电枪
#define NT_ID_GUN2 2 //2号充电枪
/************帧类型码宏定义************/
//登录心跳帧类型码数据定义
#define NT_LOGIN_REQ 0x01 //登录请求
#define NT_LOGIN_ACK 0x02 //登录应答
#define NT_TICK_REQ 0x03 //心跳请求
#define NT_TICK_ACK 0x04 //心跳应答
#define NT_FEE_MODULE_REQ 0x05 //计费模型请求
#define NT_FEE_MODULE_ACK 0x06 //计费模型应答
#define NT_QR_REQ_DIS_ACK 0x07 //二维码下发应答
#define NT_QR_REQ_DIS 0x08 //二维码下发
#define NT_QR_REQ_CALL_ACK 0x09 //二维码召唤应答
#define NT_QR_REQ_CALL 0x0A //二维码召唤帧
//实时数据帧类型码数据定义
#define NT_GUN_DATA_CHGING 0x11 //直流充电枪充电中实时数据
#define NT_GUN_DATA_FREE 0x13 //直流充电枪非充电中实时数据
#define NT_FAULT_DATA 0x15 //直流充电桩故障告警数据上传
#define NT_MDU_DATA_REQ 0x16 //运营平台整流模块数据读取 rectifier module整流模块
#define NT_MDU_DATA_ACK 0x17 //充电桩整流模块数据应答
//表底值上送
#define NET_METER_DATA 0x31 //表底值上送
//运营交互帧类型码数据定义
#define NT_START_CHG_REQ 0x41 //充电桩主动申请启动充电
#define NT_START_CHG_ACK 0x42 //平台确认启动充电
#define NT_REMOTE_START_ACK 0x43 //充电桩回复远程启机
#define NT_REMOTE_START_REQ 0x44 //平台远程控制启机
#define NT_REMOTE_STOP_ACK 0x45 //充电桩远程停机命令回复
#define NT_REMOTE_STOP_REQ 0x46 //平台远程停机
#define NT_BILL_UPLOAD 0x47 //充电结算帐单上传
#define NT_BILL_UPLOAD_ACK 0x48 //平台结算账单确认
#define NT_USERACNTINFO_REQ 0x49 //充电桩主动申请查询用户账户信息(未用)
#define NT_USERACNTINFO_ACK 0x4A //平台回复查询用户账单信息(未用)
#define NT_CARD_BILL_UPLOAD 0x4B //充电桩刷卡结算订单上传
#define NT_CARD_BILL_UPLOAD_ACK 0x4C //平台刷卡结算订单应答
//运营平台设置帧类型码数据定义
#define NT_WORK_PARA_CFG_ACK 0x71 //充电桩应答工作参数设置
#define NT_WORK_PARA_CFG_REQ 0x72 //平台设置工作参数
#define NT_WORK_PARA_QUERY_ACK 0x73 //充电桩应答工作参数查询
#define NT_WORK_PARA_QUERY_REQ 0x74 //平台查询工作参数
#define NT_CLK_SYNC_ACK 0x75 //充电桩应答平台对时
#define NT_CLK_SYNC_REQ 0x76 //平台对时
#define NT_REMOTE_OPRT_ACK 0x77 //充电桩应答平台远程操作
#define NT_REMOTE_OPRT_REQ 0x78 //平台远程操作
#define NT_LCD_SHOW_ACK 0x79 //充电桩应答平台触发液晶屏显示(用户电话)
#define NT_LCD_SHOW_REQ 0x7A //平台触发液晶屏显示内容
#define NT_UNLOCK_CARD_REQ 0x7B //充电桩请求解锁卡
#define NT_UNLOCK_CARD_ACK 0x7C //平台应答解锁卡请求
#define NT_FEE_CFG_REQ 0x7D //平台设置计费模型
#define NT_FEE_CFG_ACK 0x7E //充电桩应答模型设置
//远程升级数据定义
#define NT_UPGRADE_RQ 0x80 //平台发送远程升级指令
#define NT_UPGRADE_DATA_REQ 0x81 //桩向平台请求文件内容(多次执行,直至文件传输完毕)
#define NT_UPGRADE_DATA_ACK 0x82 //平台应答文件内容请求
#define NT_UPDATE_AUTO 0x90 //平台FTP升级请求
#define NT_UPDATE_AUTO_ACK 0x93 //充电桩主动上传FTP文件传输结果
/*------ Exported struct(结构体定义) --------------------*/
/*------ Exported variables(变量定义) -------------------*/
typedef enum /* 积成智通后台通信协议-订单上送-启机失败/停机原因 */
{
IESZT_NORMAL=0, //0正常
IESZT_STARTING_FAULT, //1启动中有报警
IESZT_INSU_FAIL, //2绝缘检测失败
IESZT_DY_SWCH_FAIL, //3控制辅源电压检测切换失败
IESZT_DY_FAIL, //4投辅源失败
IESZT_STARTING_BATVOLT_FAIL = 6, //6启动中,检测电池电压失败
IESZT_KM_ON_FAIL, //7合直流输出开关失败
IESZT_MDU_STAT_FAIL, //8模块启动失败
IESZT_CHGING_FAULT, //9充电中有报警
IESZT_CHGING_GUN_UNCNCT, //10充电中,检测到枪未连接到车上
IESZT_CHGING_DY_OFF, //11充电中未检测到辅源
IESZT_CHGING_BATVOLT_FAIL, //12充电中未检测到电池电压
IESZT_CARD, //13刷卡停机
IESZT_NO_BALANCE, //14余额不足
IESZT_BMS_ABNMAL, //15充电中BMS异常停机
IESZT_BMS_FULL, //16充电中BMS充满停机
IESZT_STARTING_UNDERPOWER, //17启动中,辅助桩功率不足
IESZT_BMS_DMD_VOLT_OVER, //18充电中,BMS需求电压越限
IESZT_CURR_OVER, //19充电中,过流
IESZT_GUN_TEMP_OVER, //20充电中,枪温过温
IESZT_BMS_TO, //21 BMS通信超时
IESZT_BMS_COMM_INTRPUT= 24, //24 BMS通信中断
IESZT_LH_KM_OFF, //25 充电中,联合开关未合
IESZT_LL_KM_ON, //26 充电中,边界联络开关未分
IESZT_APP_STOP, //27 APP停止充电
IESZT_K1K2_OFF, //28 K1/K2闭合失败
IESZT_LOCK_GUN_FAIL = 30, //30 锁枪失败
IESZT_OUT_VOLT_LOW = 33, //33 启机时,输出电压不达标
IESZT_INSU_TO, //34 绝缘检测超时
IESZT_BSM_STOP, //35 bsm终止
IESZT_TO= 37, //37 超时重试
IESZT_RELIEF_ON = 42, //42 启机过程中泄放继电器非法合
IESZT_MONEY_OK, //43 达到设定金额
IESZT_TIME_OK, //44 达到设定时长
IESZT_POWER_OK, //45 达到设定电量
IESZT_URGENCY = 50, //50 急停
IESZT_ACS, //51 门禁
IESZT_WATER, //52 水浸
IESZT_FS, //53 防雷
IESZT_OVER_TEMP, //54 环温过温
IESZT_UNDER_TEMP, //55 环温过低
IESZT_CARdREADER_COMM, //56 读卡器通信故障
IESZT_LCD_COMM, //57 LCD通信故障
IESZT_KM_ERRACT, //58 母联直流接触器拒动/误动
IESZT_KM_ADHESION, //59 母联直流接触器粘连
IESZT_WHM_COMM, //60 电表通信故障
IESZT_FUSE, //61 直流熔断器故障
IESZT_GUN_KM_ADHESION, //62 #号枪对应直流输出接触器黏连故障 - 空闲,应断开,但检测到为闭合
IESZT_MDU_COMM_DOWN, //63 所有模块通信中断
IESZT_PRE_CHGOT, //64 充电机预充超时
IESZT_bms_BHM_OT, //65 BHM报文接收超时
IESZT_BMS_BRM_OT, //66 BRM报文接收超时
IESZT_BMS_BRM_ERR, //67 BRM报文数据异常 - 报文数据长度错误,版本协议错误
IESZT_BMS_BCP_OT, //68 BCP报文接收超时
IESZT_BMS_BCP_ERR, //69 BCP报文数据异常 - 报文数据长度错误,电池当前电压越限
IESZT_BMS_BRO_00_OT, //70 BRO报文(0x00)接收超时
IESZT_BMS_BRO_AA_OT, //71 BRO报文(0xAA)接收超时
IESZT_BMS_BCS_OT, //72 BCS报文接收超时
IESZT_BMS_VOLTPARA_MISMTCH, //73 电池最高允许充电电压小于充电机最小输出电压
IESZT_BATV_OVER10, //74 绝缘检测前直流输出接触器外侧电压>=10V
IESZT_BATV_BMSV_OVER5, //75 启动充电前直流输出接触器外侧电压与通信报文电池电压相差》=-5%
IESZT_OUTV_BATV_1TO10, //76 预充电时输出电压不在电池电压-(1V~10V)范围内
IESZT_VIN_ERR, //77 vin清单使能,当前车vin不在清单内
IESZT_CHGCABINET_COMM_DOWN, //78 充电柜通信中断
IESZT_BMS_BCL_OT, //79 电池充电需求报文BCL接收超时
IESZT_BMS_BAT_BCS_OT, //80 电池充电总状态报文BCS接收超时
IESZT_BMS_BSM_OT, //81 动力蓄电池状态信息BSM接收超时
IESZT_BMS_BSM_OVERU, //82 BSM - 单体动力蓄电池电压过高
IESZT_BMS_BSM_BELOWU, //83 BSM - 单体动力蓄电池电压过低
IESZT_BMS_BSM_OVERSOC, //84 BSM - 整车动力蓄电池荷电状态SOC过高
IESZT_BMS_BSM_BELOWSOC, //85 BSM - 整车动力蓄电池荷电状态SOC过低
IESZT_BMS_BSM_OVERI, //86 BSM - 动力蓄电池充电过电流
IESZT_BMS_BSM_OVERT, //87 BSM - 动力蓄电池温度过高
IESZT_BMS_BSM_INSUERR, //88 BSM - 动力蓄电池绝缘状态异常
IESZT_BMS_BSM_CONNERR, //89 BSM - 动力蓄电池组输出连接器连接状态异常
IESZT_BMS_BSM_FORBIT, //90 BSM - 禁止充电
IESZT_BMS_BST_OT, //91 BST报文接收超时
IESZT_BMS_BST_U_OK, //92 BST - 达到总电压设定值
IESZT_BMS_BST_SNGUOK, //93 BST - 达到单体电压设定值
IESZT_BMS_BST_INSUERR, //94 BST - 绝缘故障
IESZT_BMS_BST_CONN_OVERT, //95 BST - 输出连接器过温
IESZT_BMS_BST_CELL_OVERT, //96 BST - BMS元件过温/输出连接器过温
IESZT_BMS_BST_CONN_ERR, //97 BST - 充电连接器故障
IESZT_BMS_BST_BAT_OVERT, //98 BST - 电池组温度过高
IESZT_BMS_BST_HVRELAY_ERR, //99 BST - 高压继电器故障
IESZT_BMS_BST_2U_ERR, //100 BST - 检测点2电压检测故障
IESZT_BMS_BST_ELSE, //101 BST - 其他故障
IESZT_BMS_BST_OVERI, //102 BST - 电流超过需求值
IESZT_BMS_BST_U_ABNORML, //103 BST - 电压异常
IESZT_BMS_BEM, //104 BEM - 错误报文
IESZT_OUTPUT_VOLT_OVER, //105 直流输出过压
IESZT_OUTPUT_VOLT_LOW, //106 直流输出不达标
IESZT_GUNLOCK_ERR, //107 枪锁状态错误
IESZT_SMOKE_ERR, //108 烟感告警
} IESZT_REASON_E;
typedef enum //星星充电-订单上送-启机失败/停机原因
{
STAR_PILE_STOP = 0, //0 桩主动停止
STAR_URGENCY, //1 急停按键被按下
STAR_WHM_COMM, //2 电表通信故障
STAR_CARdREADER_COMM, //3 读卡器通信故障
STAR_PE_FAULT, //4 接地故障
STAR_MDU_COMM_DOWN, //5 电源模块通信故障
STAR_ALL_MDU_COMM_DOWN, //6 电源模块全部通信故障
STAR_OVER_TEMP, //7 过温故障
STAR_ELE_LEAKAGE, //8 漏电故障
STAR_METER_DATA_ABNOEMAL, //9 电表数据异常停止充电
SATR_ACS, //10 舱门打开停止充电
STAR_URGENCY_PRESS, //11 急停按键被按下(直流专用)
STAR_FCI = 17, //17 FCI故障-部分模块通讯故障(用作开关模块故障)
STAR_INVALID_CURR, //18 无有效电流停止(交流充满,车载故障等)
STAR_LOW_BLANCE, //19 余额不足停止
STAR_GUNLINK_INTERUPTED, //20 充电连接器断开停止
STAR_CARD_STOP, //21 刷卡停止
STAR_BMS_STOP, //22 BMS正常停止(直流充满)
STAR_APP_STOP, //23 手机APP或者微信停止
STAR_SCREEN_STOP, //24 触控屏手动停止
STAR_BMS_CONN_ERR, //25 BMS通信故障
STAR_DC_ELECT_SHORT, //26 充电机输出短路
STAR_MDU_HARD_ERR, //27 模块硬件故障
STAR_OUTPUT_OVER_CURR, //28 充电机输出过流
STAR_PILE_OVER_TEMP, //29 充电机过温保护
STAR_OUTPUT_OVER_VOL, //30 充电机输出过压
STAR_INPUT_OVER_VOL, //31 充电机输入过压
STAR_INPUT_LOW_VOL, //32 充电机输入欠压
STAR_BLEED_ERR, //33 泄放电阻放电故障(未能在规定时间电压降至60V以下)
STAR_BEM_ALARM, //34 BEM报警
STAR_INSUAL_ERR, //35 充电机绝缘检测故障
STAR_GUNLINK_ERR, //36 充电枪未正确连接
STAR_BATT_REVERSE, //37 电池反接故障
STAR_BATT_ABNORMAL, //38 电池异常
STAR_BSD_OVERTIME, //39 BSD超时
STAR_BST_OVERTIME, //40 BST超时
STAR_BCL_OVERTIME, //41 BCL超时
STAR_BRO_OVERTIME, //42 BRO超时
STAR_BCP_OVERTIME, //43 BCP超时
STAR_BRM_OVERTIME, //44 BRM超时
STAR_BMS_OTHER_ERR, //45 BMS其他故障
STAR_BMS_VOL_ABNORMAL, //46 BMS检测电压异常
STAR_BMS_CURR_OVER, //47 BMS检测电流过大
STAR_BMS_BATT_OVER_TEMP, //48 BMS电池组过温
STAR_BMS_COMPOENT_OVER_TEMP,//49 BMS元件过温
STAR_BMS_CONNECTOR_OVER_TEMP,//50 BMS输出连接器过温故障
STAR_BMS_INSUAL_ERR, //51 Bms绝缘故障
STAR_CAR_STOP, //52 车载停止
STAR_SWITCH_CHARGE_WAIT, //53 切换充等待中(用作避雷器故障)
STAR_PLAT_CONN_ERR, //54 与后台通信故障
STAR_PLAT_STOP, //55 后台停止
STAR_TIME_OK, //56 达到设置充电时长停机
STAR_POWER_OK, //57 达到设置充电电量停机
STAR_MONEY_OK, //58 达到设置充电金额停止
STAR_PILE_TERMAL_CONN_ERR, //59 主副板通信故障
STAR_POWER_OUTAGE_STOP, //60 掉电停止
STAR_K3K4_STICK, //61 辅助电源粘连
STAR_K1K2_STICK, //62 输出继电器粘连
STAR_BATTV_OVER10, //63 (启动前)继电器外侧电压大于10v停止充电
STAR_PILE_BHM_MISMATCH, //64 桩输出能力不匹配(BHM)停止充电
STAR_NEED_OVER_MAXVOL=66, //66 需求电压或充电电压大于最高允许充电电压停止充电
STAR_BRO_MAJOR_FAILURE, //67 BRO重大故障停止充电
STAR_PERCHARGE_VOL_MISMATCH,//68 预充电压不匹配停止充电
STAR_SMOKE_ERR, //69 烟雾报警(停止用)
STAR_CAR_MATCH_FAILED, //70 车辆匹配失败
STAR_SOC_REACH_THRESHOLD, //71 SOC达到阀值
STAR_CARD_UNREGISTERED, //100 卡号未注册
STAR_TIME_FORMAT_ABNORMAL, //101 时间格式异常
}STAR_REASON_E;
/*------ Exported function prototypes (函数声明) -------*/
void v_ies_register_req(void);
void v_ies_heart(void);
void v_ies_fee_req(void);
void v_ies_rtd_free(U8_T gunNo); //空闲实时数据 0x13, 变位上送,120s
void v_ies_rtd_chging(U8_T gunNo); //充电中实时数据 0x11 变化加间隔上送,30s
void v_ies_meter_data(U8_T u8_gunNo); //表底值 0x31,30分钟上送一次
void v_ies_fault_V35(U8_T u8_gunNo); //故障告警数据 0x15, V3.5协议故障不分枪
void v_ies_bill_pack(U8_T u8_gunNo); //组上送账单
void v_ies_start_req(U8_T u8_gunNo); //主动启动充电请求 0x41
void v_ies_set_clk_sync_ack(void);
void v_ies_set_fee_ack(void);
void v_ies_upgrade_data_auto_ack(void);
void v_ies_rmt_start_ack(U8_T u8_gunNo, U8_T serialNum ,U8_T flag); //远程启动应答 0x43, 应答帧序号跟询问帧需保持一致
void v_ies_rmt_stop_ack(U8_T u8_gunNo, U8_T serialNum); //远程停机应答 0x45
void v_ies_rmt_oprt_ack(U8_T *u8_opr_cmd, U8_T opr_result); //远程操作应答 0x77
void v_ies_heart_ack(void);
void v_ies_register_ack(U8_T *u8_recvBuf); //登陆帧应答
void v_ies_fee_ack(U8_T *u8_recvBuf); //计费模型应答帧
void v_ies_set_clk_sync(U8_T *u8_recvBuf); //对时
void v_ies_set_fee(U8_T *u8_recvBuf); //计费模型设置
void v_ies_upgrade_data_auto(U8_T *u8_recvBuf); //平台应答文件内容请求
void v_ies_start_req(U8_T gunNo); //主动启动充电请求 0x41
void v_ies_rx_start(U8_T *u8_recvBuf); //运营确认启动 0x42
void v_ies_bill_upload_ack(U8_T *u8_recvBuf); //结算账单应答
void v_ies_rmt_start(U8_T *u8_recvBuf); //远程启动
void v_ies_rmt_stop(U8_T *u8_recvBuf); //远程停机
void v_ies_rmt_oprt(U8_T *u8_recvBuf) ; //远程操作
void v_ies_qr_call(U8_T *u8_recvBuf) ; //二维码召唤
void v_ies_qr_dis(U8_T *u8_recvBuf) ; //二维码下发
U8_T u8_ies_get_gunLink_state(U8_T u8_gunNo);
U8_T u8_ies_get_gun_state(U8_T u8_gunNo);
#endif
#ifdef __cplusplus
}
#endif
#endif /* BS_IES_INTERFACE_H */
+472
View File
@@ -0,0 +1,472 @@
/**
* @file bs_connect_impl.c
* @brief 平台链路层实现:TCP/4G 连接、收发、4G 双 socket 分发
*
* - 联网参数从 t_sys_fix_cfg.s_net_cfg 读取
* - 4G 模式经 WH-GM5 模组,支持 socketA/B 分发
* - 以太网分支(u8_netType!=0)预留待补全
*/
#if EC200A_4G_EN
#include "plat_comm/4g_module/ec200a_4g.h"
#include "plat_comm/4g_module/ec200a_4g_cfg.h"
#endif
#if WH_GM5_EN
#include "plat_comm/4g_module/bs_wh_gm5.h"
#endif
#include "publicdata/publicdata.h"
#include "plat_comm/plat_comm_task.h"
#include "plat_comm/lwip_module/lwip_module.h"
#define BS_PRINTF_EN 1 //平台收发数据打印
ws_url_t url_info;
/**
* @brief 网口 TCP:关闭指定平台通道 socket
*/
void v_bs_eth_close_socket(U8_T u8_id)
{
if (s_bs_task_ctrl.s_bs_ctrl[u8_id].fd >= 0) {
lwip_tcp_close((int)s_bs_task_ctrl.s_bs_ctrl[u8_id].fd);
s_bs_task_ctrl.s_bs_ctrl[u8_id].fd = (S32_T)-1;
s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_TCP_connect_flag = 0;
}
}
#if 1 //联网参数配置
/**
* ************************************************************************
* @brief - 4G模块联网参数获取
*
* @param[in] s_4g_para - 4G联网参数结构体
*
*
* @author jiankalka
* @date 2025-09-01
* ************************************************************************
*/
#if WH_GM5_EN
void v_get_4G_config_para(S_4G_CONFIG_PARA *s_4g_para)
{
SYS_FIX_CFG_T *t_sys_fix_cfg = &g_t_share_data.t_sys_fix_cfg;
memset(s_4g_para , 0 ,sizeof(S_4G_CONFIG_PARA));
//s_4g_para->socketA_IP = testIP;
s_4g_para->socketA_IP = t_sys_fix_cfg->s_net_cfg.u8_serverA_domain_name; //
s_4g_para->socketB_IP = t_sys_fix_cfg->s_net_cfg.u8_serverB_domain_name;
s_4g_para->u8_Sim_Type = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_sim_anp_type;
s_4g_para->u8_socketB_en = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_platB_en;
}
#endif /* WH_GM5_EN */
/**
* ************************************************************************
* @brief - 4G模块参数初始化
*
*
*
* @author jiankalka
* @date 2025-09-19
* ************************************************************************
*/
void v_bs_4G_data_init(void)
{
#if EC200A_4G_EN
{
S_4G_CONFIG_PARA s_4g_para;
SYS_FIX_CFG_T *t_sys_fix_cfg = &g_t_share_data.t_sys_fix_cfg;
memset(&s_4g_para, 0, sizeof(S_4G_CONFIG_PARA));
s_4g_para.socketA_IP = t_sys_fix_cfg->s_net_cfg.u8_serverA_domain_name;
s_4g_para.socketB_IP = t_sys_fix_cfg->s_net_cfg.u8_serverB_domain_name;
s_4g_para.u8_Sim_Type = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_sim_anp_type;
s_4g_para.u8_socketB_en = t_sys_fix_cfg->s_net_cfg.u_net_comm_para.bits.bit_platB_en;
v_4g_data_init(&s_4g_para);
}
#endif /* EC200A_4G_EN */
#if WH_GM5_EN
{
S_4G_CONFIG_PARA s_4g_para;
v_get_4G_config_para(&s_4g_para);
v_wh_gm5_data_init(&s_4g_para);
}
#endif
}
/**
* ************************************************************************
* @brief - TCP 联网测试函数
*
*
* @return U8_T - 0未连接TCP 1已连接
*
* @author jiankalka
* @date 2025-08-29
* ************************************************************************
*/
U8_T u8_tcp_connect(U8_T u8_id)
{
U8_T u8_ret = 0;
if(s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_netType == 0) //4G联网
{
#if WH_GM5_EN
// 无线模块WH_GM5初始化
if(s8_WH_GM5_init() == 0) //如果4g配置成功
{
if(s8_check_socket_LinkState(u8_id) >= 0) //查询网路连接状态
{
mSleep(100);
v_4g_uart_cache_clear(); //4G模块配置完毕 清除串口缓存区中的数据
return 1;
}
}
if(u8_ret == 0 && u32_safe_seconds_since(s_bs_task_ctrl.s_bs_ctrl[u8_id].ulong_restart_timer) > NUM_120) //120分钟 没连上 则重新连接
{
v_bs_4G_data_init(); //4G模块控制数据 初始化
s_bs_task_ctrl.s_bs_ctrl[u8_id].ulong_restart_timer = get_current_seconds();
}
#endif
#if EC200A_4G_EN
if(s8_ec200a_4g_init() == 0)
{
u8_ret = 1;
}
#endif
}
else //网线连接
{
}
return u8_ret;
}
#endif
#if WH_GM5_EN // WH-GM5 4G收发接口
/**
* ************************************************************************
* @brief - 4G模块多端口数据接收、用于多socket分端口接收、需要传入两平台接收处理函数接口
*
* @param[in] socketA_unPack - socketA 接收处理函数,不存在时赋值为NULL
* @param[in] socketB_unPack - socketB 接收处理函数,不存在时赋值为NULL
*
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
void v_4g_wh5_data_divide_recv(RecvUnpackHandler socketA_unPack, RecvUnpackHandler socketB_unPack)
{
// 静态缓冲区用于保存残包
static U8_T u8_recv_saveBuf[BS_RECV_BUF_LEN];
static U16_T u16_recv_saveLen = 0;
// 1. 数据合并:将新数据与静态缓冲区的残包合并
U16_T u16_totalLen = 0; // 总数据长度(静态缓冲区+新数据)
U16_T u16_rcvByteNum = 0; // 当前解析位置
U16_T u16_oneFrmLen = 0; // 完整帧长度
U16_T u16_dataLen = 0; // 数据域长度
U8_T frameType = 0; // 帧类型
U8_T flags = 0; // 标志位
U8_T* payloadStart = NULL; // 有效载荷起始位置
U16_T payloadLen = 0; // 有效载荷长度
U8_T u8_recvBuf[BS_RECV_BUF_LEN] = {0}; //接收数据缓存区
// 0. 获取串口数据
U16_T u16_recvLen = u16_usart_recv(COM_PORT_BS, u8_recvBuf, BS_RECV_BUF_LEN);
// 如果没有接收到数据,直接返回
if(u16_recvLen == 0) {
return;
}
// 计算新数据需要复制的长度(避免缓冲区溢出)
U16_T actualCopyLen = u16_recvLen;
if (u16_recv_saveLen + u16_recvLen > BS_RECV_BUF_LEN) {
// 缓冲区溢出处理:保留新数据,丢弃最旧的数据
U16_T overflowBytes = (u16_recv_saveLen + u16_recvLen) - BS_RECV_BUF_LEN;
// 移动现有数据,为新数据腾出空间
memmove(u8_recv_saveBuf, &u8_recv_saveBuf[overflowBytes],
u16_recv_saveLen - overflowBytes);
u16_recv_saveLen -= overflowBytes;
// 更新实际可复制长度
actualCopyLen = BS_RECV_BUF_LEN - u16_recv_saveLen;
}
// 追加新数据到静态缓冲区末尾
memcpy(u8_recv_saveBuf + u16_recv_saveLen, u8_recvBuf, actualCopyLen);
u16_recv_saveLen += actualCopyLen;
// 设置总数据长度
u16_totalLen = u16_recv_saveLen;
// 2. 主解析循环 - 处理完整帧
while (u16_totalLen >= 8)
{
// 帧头检查
if (u8_recv_saveBuf[u16_rcvByteNum] != 0xAA ||
u8_recv_saveBuf[u16_rcvByteNum+1] != 0xFD ||
u8_recv_saveBuf[u16_rcvByteNum+2] != 0x55)
{
// 帧头不匹配,移动搜索位置
u16_rcvByteNum++;
u16_totalLen--;
continue;
}
// 获取数据长度(大端格式)
u16_dataLen = (U16_T)((u8_recv_saveBuf[u16_rcvByteNum+3] << 8) |
u8_recv_saveBuf[u16_rcvByteNum+4]);
// 计算完整帧长度 = 头部(3) + 长度字段(2) + 元数据(3) + 数据长度
u16_oneFrmLen = 6 + u16_dataLen;
// 检查帧完整性
if (u16_oneFrmLen > u16_totalLen) {
// 残包处理:帧不完整,等待更多数据
break;
}
// 提取关键字段
frameType = u8_recv_saveBuf[u16_rcvByteNum + 5]; // 类型字段(0x61/0x62)
flags = u8_recv_saveBuf[u16_rcvByteNum + 6]; // 标志位
// 安全获取有效载荷(跳过帧头、长度字段和类型字段)
payloadStart = &u8_recv_saveBuf[u16_rcvByteNum + 7];
payloadLen = u16_dataLen - 2; // 减去类型和标志位
// 确保有效载`` 荷在缓冲区范围内
if ((payloadStart + payloadLen) > (u8_recv_saveBuf + BS_RECV_BUF_LEN)) {
// 缓冲区越界错误处理:重置缓冲区
u16_recv_saveLen = 0;
return;
}
// 处理网络返回数据
if ((flags & 0x80) == 0) {
if (frameType == 0x61 && socketA_unPack != NULL) {
// SOCKETA数据
socketA_unPack(0, payloadStart, payloadLen);
}
else if (frameType == 0x62 && socketB_unPack != NULL) {
// SOCKETB数据
socketB_unPack(1, payloadStart, payloadLen);
}
//测试打印
#if BS_PRINTF_EN
printf("--------------plat[%d] recv\r\n",frameType - 0x60);
for (int i = 0; i < u16_recvLen; i++)
{
printf("%02x ",u8_recvBuf[i]);
}
printf("\n---------------\r\n");
#endif
}
// 移动位置指针(处理完整帧)
u16_rcvByteNum += u16_oneFrmLen;
u16_totalLen -= u16_oneFrmLen;
}
// 3. 残包处理:保存剩余数据到静态缓冲区
if (u16_totalLen > 0) {
// 移动剩余数据到静态缓冲区开头
if (u16_rcvByteNum > 0) {
memmove(u8_recv_saveBuf, &u8_recv_saveBuf[u16_rcvByteNum], u16_totalLen);
}
u16_recv_saveLen = u16_totalLen;
} else {
// 没有剩余数据,重置静态缓冲区
u16_recv_saveLen = 0;
}
return;
}
/**
* ************************************************************************
* @brief - 4G 模块发送数据接口
*
* @param[in] u8_id - 发送接口ID 0代表不使能分发功能,直接透传发送 1代表像socketA发送数据 2代表像socketB发送数据
* @param[in] u8_sendBuf - 发送数据数组指针
* @param[in] u16_len - 发送数据长度
*
*
* @author jiankalka
* @date 2025-09-01
* ************************************************************************
*/
void v_4g_wh5_data_send(U8_T u8_id,U8_T *u8_sendBuf,U16_T u16_len)
{
U16_T len,u16_checkSum;
U8_T send_buf[10]={0};
if(u8_id == 0) //不使用套接字分发 直接发送数据
u16_usart_send(COM_PORT_BS, u8_sendBuf, u16_len);
else
{
//套接字分发组帧头
send_buf[0] = 0x55;
send_buf[1] = 0xFD;
send_buf[2] = 0xAA;
len = u16_len + 2;
send_buf[3] = (len >> 8) & 0xFF;
send_buf[4] = len & 0xFF;
send_buf[5] = 0x60 + u8_id; //SOCKETA为0x61,SOCKETB为0x62
send_buf[6] = 0;
//发送帧头
u16_usart_send(COM_PORT_BS, send_buf, 7);
//发送数据
u16_usart_send(COM_PORT_BS, u8_sendBuf, u16_len);
//计算校验合 发送数据+SOCKET序号
u16_checkSum = u16_crc_checksum(u8_sendBuf, u16_len);
u16_checkSum = u16_checkSum + send_buf[5];
send_buf[0] = u16_checkSum&0xFF;
u16_usart_send(COM_PORT_BS, send_buf, 1);
}
}
/**
* ************************************************************************
* @brief - 4G模块数据接收 接收原始数据 不进行分包解析
*
* @param[in] u8_recvBuf - 接收数据缓存数组
* @param[in] u16_len - 接受数组长度
*
* @return U16_T -
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
U16_T u16_4g_wh5_data_recv(U8_T *u8_recvBuf,U16_T u16_len)
{
U16_T u16_recvLen;
memset(u8_recvBuf , 0 , u16_len);
u16_recvLen = u16_usart_recv(COM_PORT_BS, u8_recvBuf, u16_len);
return u16_recvLen;
}
#endif
#if 1 //公共s收发函数接口
/**
* ************************************************************************
* @brief - 公共发送接口
*
* @param[in] u8_id - 平台位置 0-1
* @param[in] u8_sendBuf - 发送数据
* @param[in] u16_len - 发送数据长度
*
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
void v_bs_send(U8_T u8_id,U8_T *u8_sendBuf,U16_T u16_len)
{
if(s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_netType == 0) //4G联网
{
#if WH_GM5_EN
if(s_bs_task_ctrl.u8_4g_dispense_en == 1) u8_id+=1; //分发使能 序号加一
v_4g_wh5_data_send(u8_id,u8_sendBuf,u16_len);
#endif
#if EC200A_4G_EN
if(s_bs_task_ctrl.u8_4g_dispense_en == 1) u8_id+=1;
#if EC200A_TCP_USE_TRANSPARENT_MODE
v_4g_send_data(u8_id,u8_sendBuf,u16_len);
#else
v_ec200a_tcp_app_send(u8_id,u8_sendBuf,u16_len);
#endif
#endif
}
else
{
}
mSleep(Plat_Send_Sleep);
s_bs_task_ctrl.u8_sendCnt++;
#if BS_PRINTF_EN
printf("\n--------------bs send\r\n");
for (int i = 0; i < u16_len; i++)
{
printf("%c",u8_sendBuf[i]);
}
printf("\r\n");
for (int i = 0; i < u16_len; i++)
{
printf("%02x ",u8_sendBuf[i]);
}
printf("\n---------------\r\n");
#endif
}
/**
* ************************************************************************
* @brief - 公共接收函数
*
* @param[in] u8_id - 平台位置 0-1
* @param[in] u8_recvBuf - 接收数据缓存地址
* @param[in] u16_len - 最大接收长度
*
* @return U16_T -
*
* @author jiankalka
* @date 2025-09-02
* ************************************************************************
*/
U16_T u16_bs_recv(U8_T u8_id,U8_T *u8_recvBuf,U16_T u16_len)
{
U16_T u16_recvLen = 0;
if(s_bs_task_ctrl.s_bs_ctrl[u8_id].u8_netType == 0) //4G联网
{
#if WH_GM5_EN
u16_recvLen = u16_usart_recv(COM_PORT_BS, u8_recvBuf, u16_len);
#endif
#if EC200A_4G_EN
u16_recvLen = u16_ec200a_tcp_app_recv(u8_recvBuf, u16_len);
#endif
}
else //以太网
{
// if(bs_task_ctrl_data.s_bs_ctrl[id].fd >= 0)
// u16_recvLen = eth_recv((int)bs_task_ctrl_data.s_bs_ctrl[id].fd, (char *)recvBuf, BS_RECV_BUF_LEN, 1);
}
#if BS_PRINTF_EN
if(u16_recvLen == 0) return u16_recvLen;
printf("\n--------------bs recv\r\n");
for (int i = 0; i < u16_recvLen; i++)
{
printf("%c",u8_recvBuf[i]);
}
printf("\r\n");
for (int i = 0; i < u16_recvLen; i++)
{
printf("%02x ",u8_recvBuf[i]);
}
printf("\n---------------\r\n");
#endif
return u16_recvLen;
}
#endif
+53
View File
@@ -0,0 +1,53 @@
#ifndef BS_CONNECT_IMPL_H
#define BS_CONNECT_IMPL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/publicdata.h"
#include "plat_comm/plat_comm_task.h"
#if EC200A_4G_EN
#include "plat_comm/4g_module/ec200a_4g.h"
#endif
#if WH_GM5_EN
#include "plat_comm/4g_module/bs_wh_gm5.h"
#endif
/*------ Exported macro(宏定义) -------------------------*/
/** 4G 套接字分发接收回调:id=平台序号,buf/len=有效载荷 */
typedef void (*RecvUnpackHandler)(U8_T id, U8_T *buf, U16_T len);
/*------ Exported struct(结构体定义) --------------------*/
typedef struct {
char host[NUM_64];
int port;
char path[NUM_64];
char apn_str[NUM_32];
int is_secure; /* 0=ws, 1=wss */
} ws_url_t;
/*------ Exported variables(变量定义) -------------------*/
/*------ Exported function prototypes (函数声明) -------*/
void v_bs_4G_data_init(void);
U8_T u8_tcp_connect(U8_T u8_id);
/** 网口:关闭指定通道 socket 并清零 fd/u8_TCP_connect_flag,重连前必须调用,避免 socket 泄漏导致“再连也连不上” */
void v_bs_eth_close_socket(U8_T u8_id);
void v_bs_send(U8_T u8_id,U8_T *u8_sendBuf,U16_T u16_len);
U16_T u16_bs_recv(U8_T u8_id,U8_T *u8_recvBuf,U16_T u16_len);
void v_4g_wh5_data_divide_recv(RecvUnpackHandler socketA_unPack, RecvUnpackHandler socketB_unPack);
int parse_websocket_url_simple(const char* url, ws_url_t* result) ;
#ifdef __cplusplus
}
#endif
#endif /* BS_CONNECT_IMPL_H */
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
#ifndef BS_PUBLIC_IMPL_H
#define BS_PUBLIC_IMPL_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "publicdata/publicdata.h"
#include "fault_cheak/fault_interface.h"
#include "app_rtc/app_rtc.h"
#include "meter_calculate/meter_calculate_impl.h"
#include "flowctrl/flow_interface.h"
/*------ Exported macro(宏定义) -------------------------*/
#define BS_BUF_SIZE_16 16
#define BS_BUF_SIZE_32 32
#define BS_BUF_SIZE_64 64
#define BS_BUF_SIZE_128 128
#define BS_BUF_SIZE_256 256
/*------ Exported struct(结构体定义) --------------------*/
typedef enum
{
GUN_NULL = 0, //无
GUN_1 = 0, //1枪
GUN_2, //2枪
}E_GUN_NO;
typedef enum{
GET_FLAG=0, //获取数据
SET_FALG, //设置标志
CLEAR_FALG, //清空标志
}FLAG_CTLR;
typedef union {
U8_T u8_data;
S8_T s8_data;
U16_T u16_data;
S16_T s16_data;
U32_T u32_data;
F32_T f32_data;
U64_T u64_data;
}TYPE_DATA;
typedef enum
{
//字符串数据类型 0x7000--0x8000
E_STR_TYPE = 0,
E_STR_PILE_NO, //桩编号-16
E_STR_SOFTWARE_VERSION, //软件版本号
E_STR_HARDWARE_VERSION, //硬件版本号
E_STR_LOG_preTradeNo, //订单流水号 type=0,平台流水号;type=1,设备流水号
E_STR_LOG_user_id, //订单用户标识
E_STR_LOG_vin, //VIN 车辆信息
E_STR_SDK_PARA_product_key, //国网sdk 产品密钥
E_STR_SDK_PARA_device_name, //国网sdk 资产吗
E_STR_SDK_PARA_device_secret, //国网sdk 设备秘钥
E_STR_SDK_PARA_device_reg_code, //国网sdk 注册码
E_STR_SDK_PARA_device_uid, //国网sdk 出厂编码
E_STR_SDK_PARA_firmware_version, //国网sdk 固件版本信息
E_STR_SDK_SIM_ID, //sim卡号
E_STR_METER_ADDR, //获取电表号 0直流电表 1交流电表
E_STR_FEE_ID, //计费模型ID
E_STR_QR_DISPLAY, //枪二维码 u8_gunNo=0/1 对应枪A/B
}E_GET_SYS_STR;
typedef struct
{
U8_T u8_init_flag; //初始化标志
U8_T u8_netType; //网络类型 04G 1网线
U8_T u8_update_server_addr[32]; //升级服务器IP
U16_T u16_update_server_port; //升级服务器端口号
U8_T u8_user[20]; //用户名,字符串,不足尾部补0
U8_T u8_pwd[20]; //密码,字符串,不足尾部补0
U8_T u8_path[64]; //路径名,字符串,不足尾部补0
U8_T u8_file_name[32]; //文件名,字符串,不足尾部补0
U8_T u8_bs_ack; //平台回复报文
U8_T u8_update_result; //升级结果
U32_T u32_file_size; //文件大小
U16_T u16_packSize; //单包数据大小
U16_T u16_packNum; //包数量
U16_T u16_packIndex; //获取包索引号
U32_T u32_file_fd; //文件句柄 EC200A 4G模块使用
}T_FTP_UPDATE_INFO;
/***************************
变量功能:定义变量类型枚举,内部调用时使用统一函数接口获取不同的数据内容
*********************************/
typedef enum
{
//桩信息
E_BS_GET_SYS_DATA_GUN_NUM = 0, //枪数量
E_BS_GET_SYS_DATA_NET_TYPE, //联网类型 0:4G 1:网线
E_BS_GET_SYS_DATA_PLAT_EN, //平台使能 u8_gunNo=0平台A 1平台B0不使能 1使能
E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM, //type=0当前故障数量/type=1当前告警数量
E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_DATA, //获取故障内容 参数 0-N 获取故障位置数量
E_BS_GET_SYS_DATA_4G_signal_quality, //4G信号强度
E_BS_GET_SYS_DATA_RATED_POWER, //额定功率 单位w
E_BS_GET_SYS_DATA_PILE_PUT_PARA, //充电桩输出参数 单位0.1V/0.1A 0最大电压 1最小电压 2最大电流 3最小电流
//枪信息
E_BS_GET_SYS_DATA_GUN_FLOW_STATE, //终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
E_BS_GET_SYS_DATA_GUN_STATE, //枪状态信息 type=0,枪连接状态;
//type=1,电子锁状态;
//type=2,枪空座归位状态;
//type=3,正母线直流输出接触器状态
//type=4,负母线直流输出接触器状态
E_BS_GET_SYS_DATA_GUN_TEMP_POS, //充电枪正极温度 单位0.1度 0-100最小为0
E_BS_GET_SYS_DATA_GUN_TEMP_NEG, //充电枪负极温度 单位0.1度 0-100最小为0
E_BS_GET_SYS_DATA_MEMTER_ELECT, //电表电量 0.001kWh
E_BS_GET_SYS_DATA_PILE_TEMP, //环境温度 精确到小数点后1位,-50℃偏差
E_BS_GET_SYS_DATA_METER_CUR, //电表电流 单位0.1A
E_BS_GET_SYS_DATA_METER_VOL, //电表电压 单位0.1V
//订单信息
E_BS_GET_SYS_DATA_LOG_ENERGY, //订单电量 0.001kWh 三位小数精度 type=0,已充电量;type=1,电表起始值,type=2,电表终止值
E_BS_GET_SYS_DATA_LOG_STOP_REASON, //充电订单停止原因
E_BS_GET_SYS_DATA_LOG_charge_flag, //订单充电标志 0未充电 1已经充电
E_BS_GET_SYS_DATA_SOC_DATA, //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
E_BS_GET_SYS_DATA_LOG_CHARGE_TIME, //充电时间 min
//bms信息
E_BS_GET_SYS_DATA_BMS_CELL_MAX_VOL, //最高单体动力蓄电池电压
E_BS_GET_SYS_DATA_BMS_NEED_VOL, //电压需求 单位0.1V
E_BS_GET_SYS_DATA_BMS_NEED_CUR, //电流需求 单位0.1A
E_BS_GET_SYS_DATA_BMS_CHARGE_VOL, //BMS充电电压测量值
E_BS_GET_SYS_DATA_BMS_CHARGE_CUR, //BMS充电电流测量值
E_BS_GET_SYS_DATA_BMS_BATT_CHARGE_MAX_VOL, //最高允许充电总电压
E_BS_GET_SYS_DATA_BMS_CELL_CHARGE_MAX_VOL, //单体动力蓄电池最高允许充电电压
E_BS_GET_SYS_DATA_BMS_BATT_CHARGE_MAX_CUR, //最高允许充电电流
E_BS_GET_SYS_DATA_BMS_BATT_RATED_VOL, //整车动力蓄电池系统额定总电压
E_BS_GET_SYS_DATA_BMS_BATT_VOL, //整车动力蓄当前电池总电压
E_BS_GET_SYS_DATA_BMS_BATT_RATED_C10, //整车动力蓄电池系统额定容量
E_BS_GET_SYS_DATA_BMS_BATT_RATED_ENERY, //动力蓄电池标称总能量
E_BS_GET_SYS_DATA_BMS_BATT_ID, //电池编号信息 0最高单体动力蓄电池电压所在编号 1最高动力蓄电池温度检测点编号 2最低动力蓄电池温度检测点编号
E_BS_GET_SYS_DATA_BMS_TEMP, //单位 0.1度0-100最小为0 0 电池最高温度 1最低温度
E_BS_GET_SYS_DATA_BMS_REMAIN_TIME, //剩余充电时间 min
E_BS_GET_SYS_DATA_BMS_BATT_MAX_TEMP, //最高动力蓄电池温度
E_BS_GET_SYS_DATA_BMS_BATT_MIN_TEMP, //最低动力蓄电池温度
E_BS_GET_SYS_DATA_BMS_BATT_ALLOW_MAX_TEMP, //最高允许动力蓄电池温度
E_BS_GET_SYS_DATA_UNIX_TIMESTAMP, //当前 RTC Unix 时间戳(秒)
} E_GET_SYS_DATA;
typedef struct
{
U16_T u16_ctrl_res; //控制原因 启动时为启动原因 停止时为停止原因;启动原因0 无动作 1离线卡 2在线卡 3后台启动(tcu/积成)\扫码启动 4vin启动 5调试启动
E_CHRG_LIMIT_MOD e_start_mode; //启动充电限制方式 0 不做限制的充电(默认) 1 限制金额 2 限制电量 electricity 3 限制SOC 4 限制充电时长 5 限制功率
U16_T u16_limit; //限制参数 默认精度 0.01
U32_T u32_balance_begin; //启动充电前金额 0.0001元
USER_TYPE_E e_user_type; //用户类型
U8_T u8_user_id[NUM_32]; //用户标识符
U8_T u8_tradeNo[EVS_MAX_TRADE_LEN]; //设备流水号
U8_T u8_preTradeNo[EVS_MAX_TRADE_LEN]; //平台订单流水号
}S_BS_CHAR_CTRL;
typedef enum
{
E_CHAR_CTRL_START = 0, //启动充电
E_CHAR_CHANGE_LOG, //修改数据
E_CHAR_CTRL_STOP, //停止充电
}E_CTRL_TYPE;
/*------ Exported variables(变量定义) -------------------*/
extern T_FTP_UPDATE_INFO t_ftp_update_info;
/*------ Exported function prototypes (函数声明) -------*/
U8_T u8_get_str_data(U8_T u8_gunNo,E_GET_SYS_STR e_dataType,U8_T u8_dataPara,char *u8_getData,U8_T u8_len);
/** 写字符串类系统参数;u8_len 为有效字节数(帧体可非 '\0' 结尾);返回 1 成功 0 失败 */
U8_T u8_set_str_data(U8_T u8_gunNo, E_GET_SYS_STR e_dataType, U8_T u8_dataPara,
const char *pu8_data, U8_T u8_len);
U32_T u32_bs_get_sys_data(U8_T u8_gunNo,E_GET_SYS_DATA e_datatype,U8_T u8_dataPara);
/** 写数值类系统参数;返回 1 成功 0 失败(如 E_BS_GET_SYS_DATA_UNIX_TIMESTAMP 校时) */
U8_T u8_set_sys_data(U8_T u8_gunNo, E_GET_SYS_DATA e_datatype, U32_T u32_val, U8_T u8_dataPara);
U64_T u64_bs_get_sys_data(U8_T u8_gunNo,E_GET_SYS_DATA e_datatype);
U8_T u8_bs_get_flow_state(U8_T u8_gunNo);
void v_bs_set_time(Comm_Time *currtime);
void v_bs_save_fee_data(U8_T u8_flag, S_FEE_MODEL *s_fee_model);
void v_bs_log_txFlag_tradUpload(U8_T u8_gunNo);
void v_bs_log_txFlag_autoStart(U8_T u8_gunNo); //设置充电鉴权上送标志;
void v_bs_charge_action_ctrl(U8_T u8_gunNo,E_CTRL_TYPE e_type, S_BS_CHAR_CTRL *s_ctrl_data);
void v_remote_gunLock_ctrl(U8_T u8_gunNo,U8_T u8_type);
void v_remote_reboot(U8_T type) ;
void v_cheak_state_change(void);
void v_ftp_update_action(void);
U8_T u8_bs_get_txFlag_autoRwsult(U8_T u8_gunNo) ; //对外获取鉴权结果
U8_T u8_bs_get_log_on_flag();
U8_T u8_card_info_add(char *card_id);
void v_card_info_clear();
void v_bs_get_gun_log_data(U8_T u8_gunNo , S_LOG_DATA *s_log);
void v_bs_gwsdk_set_para(U8_T u8_type, S_SDK_DATA *s_sdk_data);
void v_bs_logUploadOk_updateInfo(U16_T u16_cmplte_bill_index);
void v_bs_get_fee_part_data(U8_T u8_gunNo,U8_T flag,S_LOG_DATA *t_log,U32_T *u32_part_energy,F32_T *f32_rate,F32_T *f32_sevice);
S8_T s8_plat_get_unsettled_order( S_LOG_DATA *s_log,U8_T u8_type);
void v_bs_platform_settlement_confirm(U8_T u8_gunNo);
void v_ocpp_offline_local_authentication_check(U8_T u8_gunNo);
U8_T u8_plat_get_real_order(S_LOG_DATA *s_log,U8_T u8_gunNo);
void v_get_ocpp_cfg_info(char *mag , U16_T u16_len);
U8_T u8_set_ocpp_cfg_info(const char *key,const char *data);
void v_ocpp_get_real_log_file_name(char *fileName,int len);
U8_T u8_bs_get_update_flag();
U8_T u8_bs_get_gun_disable_flag(U8_T u8_gunNo);
void v_ocpp_set_gun_flow_real_state_init(U8_T u8_gunNo);
/** 设置二维码更新标志(CCU621 暂未实现) */
void v_ccu_set_qrcode_flag(U8_T u8_gunNo, U8_T flag);
#ifdef __cplusplus
}
#endif
#endif /* BS_PUBLIC_IMPL_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
/**
* @file lwip_ftp_upgrade.h
* @brief 基于 LwIP socket 的 FTP 固件升级状态机(网线远程升级)
*
* 说明:
* - 参考 ec200a_4g_ftp.c 的“状态机 + 周期调用”风格;
* - 通过 LwIP socket 实现 FTP 控制/数据连接,按 1024 字节分包写入升级区域;
* - 每次调用只推进一小步,不阻塞 v_Plat_Comm 线程。
*/
#ifndef LWIP_FTP_UPGRADE_H
#define LWIP_FTP_UPGRADE_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 初始化 LwIP FTP 升级状态机
*
* 说明:
* - 直接读取全局 t_ftp_update_info 中的 FTP 参数(server/port/user/pass/path/file_name);
* - 清空内部静态状态结构体,关闭残留 socket,准备进入 CONNECT 步骤。
*/
void lwip_ftp_upgrade_init_from_tftp(void);
/**
* @brief 周期推进 LwIP FTP 升级状态机(单步)
*
* 返回值:
* - 0:升级进行中(下次调用继续推进);
* - 1:升级成功完成(文件全部下载且升级头标记为 NEED_UP);
* - <0:升级失败(FTP/网络/Flash 错误等,具体原因在日志中打印)。
*/
int lwip_ftp_upgrade_step(void);
#ifdef __cplusplus
}
#endif
#endif /* LWIP_FTP_UPGRADE_H */
@@ -0,0 +1,215 @@
# LwIP FTP 升级功能说明(最终版)
## 1. 设计目标
-**不依赖 4G 模块 AT 指令** 的情况下,通过 **以太网 (LwIP socket)** 实现 FTP 固件下载。
- 复用已有的 `flash_upgrade.h` 升级区管理:
- `upgrade_erase_program_area()` 擦除升级区域;
- `upgrade_write_packet()``PACKET_SIZE`1024 字节)分包写入;
- `upgrade_success_update_header()` 更新升级头(`UPGRADE_FLAG_IDLE / UPGRADE_FLAG_NEED_UP`)。
- 与 4G FTP 升级整体思路保持一致,但实现形式为:
**非阻塞状态机 + 周期调用 `v_ftp_update_action()`**
## 2. 模块文件结构
位于 `app/plat_comm/lwip_module/`
- `lwip_module.[ch]`
- 对 LwIP socket 做简单封装:`lwip_resolve_host / lwip_set_nonblock / lwip_tcp_send / lwip_tcp_recv / lwip_tcp_close` 等。
- `lwip_ftp_upgrade.[ch]`
- **网线 FTP 程序远程升级的完整状态机实现**,仅保留这一对文件:
- 控制连接(21 端口);
- `USER/PASS` 登录;
- `TYPE I` 二进制模式;
- `CWD`(可选目录切换);
- `SIZE` 查询文件大小;
- `PASV` 被动模式解析数据通道;
- `RETR` 下载文件并按 1024 字节分包写 Flash
- `QUIT` 结束会话并写入 NEED_UP 升级头。
- 对外只暴露两个接口:
- `void lwip_ftp_upgrade_init_from_tftp(void);`
- `int lwip_ftp_upgrade_step(void);`
> 说明:旧版的 `lwip_ftp_fw_upgrade_api.[ch]`、`lwip_ftp_upgrade_flash_adapter.[ch]`、`lwip_ftp_fw_upgrade_sm.[ch]` 等文件已删除,不再使用。
## 3. 参数来源与触发方式
### 3.1 参数结构:`T_FTP_UPDATE_INFO`
`bs_public_impl.h` 中定义,关键字段如下:
- `u8_update_server_addr`FTP 服务器域名 / IP
- `u16_update_server_port`FTP 端口(通常 21);
- `u8_user / u8_pwd`:用户名、密码;
- `u8_path`:远程目录(可为空);
- `u8_file_name`:文件名;
- `u8_netType`:网络类型(`0=4G``1=网线`);
- `u32_file_size`:文件大小(由 FTP SIZE 结果回填);
- `u8_update_result`:升级结果(0=成功,1=失败)。
这些字段由 OCPP `UpdateFirmware` 命令解析函数 `parse_ftp_url()` 填充。
### 3.2 触发流程
1. OCPP 平台下发 `UpdateFirmware`URL 形如:
`ftp://user:pass@host:port/path/file.bin`
→ 解析到全局 `t_ftp_update_info`
2. 上层把对应平台的 `u8_updata_flag` 置位。
3. `plat_comm_task.c` 中的 `v_Plat_Comm()` 周期调用 `v_ftp_update_action()`
4. `v_ftp_update_action()` 内部根据:
- `s_bs_task_ctrl.ocpp_data.u8_log_file_upload``1=日志上传,0=程序升级`
- `t_ftp_update_info.u8_netType``0=4G`(走 4G 模块),`1=网线`(走 LwIP FTP)。
## 4. 程序升级流程(网线 / LwIP)
当满足:
- `s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 0`(程序升级);
- `t_ftp_update_info.u8_netType != 0`(网线/LwIP);
`v_ftp_update_action()` 中流程为:
```c
if (t_ftp_update_info.u8_init_flag == 0)
{
if (t_ftp_update_info.u8_netType == 0) {
v_ftp_ctrl_init(); // 4G 模块初始化
} else {
lwip_ftp_upgrade_init_from_tftp(); // 网线 FTP 状态机初始化
}
t_ftp_update_info.u8_init_flag = 1;
}
if (t_ftp_update_info.u8_netType == 0) {
u8_ec200a_4g_ftp_update(); // 4G FTP 升级
} else {
int ret = lwip_ftp_upgrade_step(); // LwIP FTP 升级状态机单步
if (ret == 1) {
// 升级成功
s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0;
s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0;
t_ftp_update_info.u8_update_result = 0;
} else if (ret < 0) {
// 升级失败
s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag = 0;
s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag = 0;
t_ftp_update_info.u8_update_result = 1;
}
}
```
### 4.1 状态机步骤(`E_LWIP_FTP_STEP`
`lwip_ftp_upgrade.c` 内部定义的状态机大致流程:
1. `LWIP_FTP_STEP_CONNECT`
- 建立 FTP 控制连接(21 端口),并设置为非阻塞。
2. `LWIP_FTP_STEP_WAIT_WELCOME`
- 等待 `220` 欢迎语。
3. `LWIP_FTP_STEP_SEND_USER / WAIT_USER`
- 发送 `USER xxx`,等待响应;
- 若返回 `331`,继续发送 `PASS`;否则直接进入 `TYPE`
4. `LWIP_FTP_STEP_SEND_PASS / WAIT_PASS`
- 发送 `PASS xxx`,等待登录结果。
5. `LWIP_FTP_STEP_SEND_TYPE / WAIT_TYPE`
- 发送 `TYPE I`,切换为二进制模式。
6. `LWIP_FTP_STEP_SEND_CWD / WAIT_CWD`(可选)
- 如果 `u8_path` 非空,发送 `CWD <path>` 切目录。
7. `LWIP_FTP_STEP_SEND_SIZE / WAIT_SIZE`
- 发送 `SIZE <file>`,并从 `213` 响应中解析文件大小;
- 回填到 `s_ctx.file_size``t_ftp_update_info.u32_file_size`
8. `LWIP_FTP_STEP_PREPARE_FLASH`
- 调用 `upgrade_erase_program_area()` 擦除升级区;
- 构造 `upgrade_header_t`,写入 `UPGRADE_FLAG_IDLE / file_size / packet_count` 等信息。
9. `LWIP_FTP_STEP_SEND_PASV / WAIT_PASV`
- 发送 `PASV`,解析 `227` 响应得到数据通道 IP/端口。
10. `LWIP_FTP_STEP_CONNECT_DATA`
-`data_ip / data_port` 建立数据连接 socket。
11. `LWIP_FTP_STEP_SEND_RETR / WAIT_RETR`
- 发送 `RETR <file>`,等待 `150/125` 等开始传输响应。
12. `LWIP_FTP_STEP_RECV_DATA`
- 循环接收数据,按 1024 字节分包写入 Flash(见 4.2)。
13. `LWIP_FTP_STEP_SEND_QUIT`
- 发送 `QUIT`,关闭控制连接;
- 写入 `UPGRADE_FLAG_NEED_UP` 头部,标记升级就绪。
14. `LWIP_FTP_STEP_DONE / FAIL`
- `DONE`:整个流程成功,`lwip_ftp_upgrade_step()` 返回 `1`
- `FAIL`:任意步骤出错,返回 `<0`
### 4.2 分包写 Flash 与进度打印
- 内部使用一个 **动态申请的** 1024 字节缓存 `packet_buf`(不是全局大数组):
-`lwip_ftp_upgrade_init_from_tftp()` 中第一次调用 `pvPortMalloc(PACKET_SIZE)`(FreeRTOS 堆),后续重复使用;
- 失败会直接打印 `"pvPortMalloc packet_buf fail"` 并进入 `FAIL`
- 数据通道每次 `lwip_tcp_recv()` 得到的数据都会写入 `packet_buf`
- 每累计满 1024 字节调用一次 `upgrade_write_packet(packet_index, packet_buf, len)`
- 处理最后不足一包的数据时,也会单独调用一次。
- **接收进度打印**
- 每次收到数据后,都会根据 `downloaded / file_size` 计算百分比:
```c
Plat_Comm_LOG("lwip ftp: recv %lu/%lu (%u%%)\r\n", ...);
```
- 方便在串口日志中实时观察下载进度。
### 4.3 控制通道响应等待(最长 1 秒)
- 所有 FTP 控制命令的响应(`220 / 331 / 230 / 213 / 227 / 150 ...`)统一通过:
```c
static int ftp_read_reply_block(int sock, uint32_t timeout_ms,
int *out_code, char *out_msg, size_t out_msg_sz);
```
- 关键特性:
- **内部使用局部缓冲区**
- `char rx_buf[FTP_CTRL_RX_BUF_SIZE];`
- `char line[FTP_LINE_MAX];`
- 不再在全局结构体中定义大数组。
- 每次调用最长阻塞 `timeout_ms` 毫秒(本工程中统一传入 `1000`,即 1 秒):
- 在 1 秒内尽可能收满一条完整响应行;
- 超时、对端关闭或错误均返回非 0,状态机进入 `FAIL`。
- 满足需求:
- **“发送命令后可最长等待 1 秒接收数据,这样可以保证大部分交互在一个周期内完成”**。
## 5. 日志上传(4G / 预留 LwIP
- 当 `s_bs_task_ctrl.ocpp_data.u8_log_file_upload == 1` 时,`v_ftp_update_action()` 走“日志上传”分支:
- 当前仅在 `t_ftp_update_info.u8_netType == 0`4G)时调用 `u8_ec200a_4g_ftp_log_upload()`
- 网线场景下会打印提示,尚未提供 LwIP 日志上传实现。
后续如需支持 **LwIP FTP 日志上传**,建议在 `lwip_ftp_upgrade.[ch]` 基础上扩展 `STOR/APPE` 等命令。
## 6. 注意事项
- **非阻塞总框架 + 局部 1 秒等待**:
- 整体采用状态机 + 周期调用的方式集成在 `v_Plat_Comm` 中,不新建线程;
- 单条 FTP 命令的响应等待时间 **最多 1 秒**,保证大部分交互能在一次 `v_ftp_update_action()` 周期内完成。
- **内存使用**
- 不再在全局结构体中定义大数组:
- 控制通道解析使用的缓冲区全部为函数内局部变量;
- 数据分包缓冲 `packet_buf[PACKET_SIZE]` 采用 FreeRTOS `pvPortMalloc` 动态申请,仅 1KB。
- **文件大小依赖 SIZE**
- 必须保证 FTP 服务器支持 `SIZE` 命令,否则无法写入正确的 `file_size`,升级头将不可靠。
- **错误处理与重试**
- 任意步骤出错,`lwip_ftp_upgrade_step()` 返回 `<0`
- `v_ftp_update_action()` 会清除 `u8_updata_flag` 并设置 `u8_update_result = 1`
- 上层可根据日志与结果决定是否重新触发升级。
## 7. 与 4G FTP 的对比
- **4G FTP**
- 文件:`ec200a_4g_ftp.c`
- 通过 AT 命令驱动 4G 模块内置 FTP 客户端;
- 状态机在 `u8_ec200a_4g_ftp_update()` 内实现。
- **LwIP FTP(网线)**
- 文件:`lwip_module.c` + `lwip_ftp_upgrade.c`
- 使用 LwIP socket 直接实现 FTP 协议;
- 与 4G 共用 `flash_upgrade` 升级区与头部结构;
- 集成方式完全嵌入 `v_ftp_update_action()`,不影响 `v_Plat_Comm` 线程的主循环。
+146
View File
@@ -0,0 +1,146 @@
#include "plat_comm/lwip_module/lwip_module.h"
#include "lwip/sockets.h" /* LwIP socket APIsocket/connect/send/recv/close */
#include "lwip/netdb.h" /* 域名解析 lwip_gethostbyname */
#include <string.h>
#include <errno.h>
/**
* ************************************************************************
* @brief 将主机名或 IP 字符串解析为 in_addr
* @param[in] host - 服务器地址,可为 "192.168.1.1" 或 "www.xxx.com"
* @param[out] out_addr - 解析得到的 IPv4 地址
* @return 0 成功,-1 失败(非 IP 且 DNS 解析失败)
* ************************************************************************
*/
int lwip_resolve_host(const char *host, struct in_addr *out_addr)
{
if (host == NULL || out_addr == NULL)
return -1;
/* 先按 IP 字符串解析(如 "134.17.16.109" */
unsigned long addr = inet_addr(host);
if (addr != INADDR_NONE) {
out_addr->s_addr = addr;
return 0;
}
/* 非 IP 则按域名解析(依赖 LwIP DNS,仅 LWIP_DNS=1 时 netdb.h 才声明 lwip_gethostbyname */
#if LWIP_DNS
{
struct hostent *he = lwip_gethostbyname(host);
if (he != NULL && he->h_addr_list[0] != NULL) {
memcpy(&out_addr->s_addr, he->h_addr_list[0], (size_t)he->h_length);
return 0;
}
}
#endif
return -1;
}
/**
* ************************************************************************
* @brief 设置 socket 为非阻塞模式
* @param[in] sock - socket 描述符
* @return 0 成功,-1 失败
* ************************************************************************
*/
int lwip_set_nonblock(int sock)
{
int flags = lwip_fcntl(sock, F_GETFL, 0);
if (flags >= 0) {
if (lwip_fcntl(sock, F_SETFL, flags | O_NONBLOCK) >= 0)
return 0;
}
return -1;
}
/**
* ************************************************************************
* @brief 创建 TCP socket 并连接到指定地址
* @param[in] host - 服务器地址(IP 或域名)
* @param[in] port - 服务器端口
* @return >=0 socket 描述符,<0 失败
* ************************************************************************
*/
int lwip_tcp_connect(const char *host, uint16_t port)
{
struct sockaddr_in server_addr;
struct in_addr ip_addr;
int sock;
if (host == NULL || host[0] == '\0' || port == 0)
return -1;
/* 创建 TCP 流式 socket */
sock = lwip_socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
return -1;
/* 解析服务器 hostIP 或域名)为 in_addr */
if (lwip_resolve_host(host, &ip_addr) != 0) {
lwip_close(sock);
return -1;
}
/* 填充服务器地址并发起连接 */
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = ip_addr.s_addr;
server_addr.sin_port = htons(port);
if (lwip_connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
lwip_close(sock);
return -1;
}
/* 设为非阻塞,避免 recv 阻塞任务 */
if (lwip_set_nonblock(sock) != 0) {
lwip_close(sock);
return -1;
}
return sock;
}
/**
* ************************************************************************
* @brief 关闭 socket
* @param[in] sock - socket 描述符
* ************************************************************************
*/
void lwip_tcp_close(int sock)
{
if (sock >= 0)
lwip_close(sock);
}
/**
* ************************************************************************
* @brief 发送数据
* @param[in] sock - socket 描述符
* @param[in] buf - 发送缓冲区
* @param[in] len - 发送长度
* @return >0 实际发送字节数,<=0 失败或对端关闭
* ************************************************************************
*/
int lwip_tcp_send(int sock, const void *buf, size_t len)
{
if (sock < 0 || buf == NULL || len == 0)
return -1;
return lwip_send(sock, buf, len, 0);
}
/**
* ************************************************************************
* @brief 接收数据(非阻塞)
* @param[in] sock - socket 描述符
* @param[out] buf - 接收缓冲区
* @param[in] len - 缓冲区长度
* @return >0 实际接收字节数,0 对端关闭,<0 错误(errno 需检查)
* ************************************************************************
*/
int lwip_tcp_recv(int sock, void *buf, size_t len)
{
if (sock < 0 || buf == NULL || len == 0)
return -1;
return lwip_recv(sock, buf, len, 0);
}
+82
View File
@@ -0,0 +1,82 @@
#ifndef LWIP_MODULE_H
#define LWIP_MODULE_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include <stdint.h>
#include <stddef.h> /* size_t */
#include "lwip/sockets.h" /* LwIP socket API 和结构体定义 */
#include "lwip/inet.h" /* LwIP inet_addr 和 INADDR_NONE 等宏 */
/* LwIP socket 相关宏定义(部分环境在 lwip/sockets.h 中未定义时在此兜底) */
#ifndef F_GETFL
#define F_GETFL 3 /* fcntl 获取文件描述符标志 */
#endif
#ifndef F_SETFL
#define F_SETFL 4 /* fcntl 设置文件描述符标志 */
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK 0x0004 /* 非阻塞模式,recv 无数据时立即返回不阻塞任务 */
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK 11 /* 非阻塞 recv 无数据时的 errno */
#endif
#ifndef EAGAIN
#define EAGAIN 11 /* 同上,与 EWOULDBLOCK 等价 */
#endif
/**
* @brief 将主机名或 IP 字符串解析为 in_addr
* @param[in] host 服务器地址,可为 "192.168.1.1" 或 "www.xxx.com"
* @param[out] out_addr 解析得到的 IPv4 地址
* @return 0 成功,-1 失败(非 IP 且 DNS 解析失败)
*/
int lwip_resolve_host(const char *host, struct in_addr *out_addr);
/**
* @brief 创建 TCP socket 并连接到指定地址
* @param[in] host 服务器地址(IP 或域名)
* @param[in] port 服务器端口
* @return >=0 socket 描述符,<0 失败
*/
int lwip_tcp_connect(const char *host, uint16_t port);
/**
* @brief 关闭 socket
* @param[in] sock socket 描述符
*/
void lwip_tcp_close(int sock);
/**
* @brief 发送数据
* @param[in] sock socket 描述符
* @param[in] buf 发送缓冲区
* @param[in] len 发送长度
* @return >0 实际发送字节数,<=0 失败或对端关闭
*/
int lwip_tcp_send(int sock, const void *buf, size_t len);
/**
* @brief 接收数据(非阻塞)
* @param[in] sock socket 描述符
* @param[out] buf 接收缓冲区
* @param[in] len 缓冲区长度
* @return >0 实际接收字节数,0 对端关闭,<0 错误(errno 需检查)
*/
int lwip_tcp_recv(int sock, void *buf, size_t len);
/**
* @brief 设置 socket 为非阻塞模式
* @param[in] sock socket 描述符
* @return 0 成功,-1 失败
*/
int lwip_set_nonblock(int sock);
#ifdef __cplusplus
}
#endif
#endif /* LWIP_MODULE_H */
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
#ifndef _BS_OCPP_CTRL_H_
#define _BS_OCPP_CTRL_H_
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#if BS_OCPP_EN //OCPP平台使能
#include "bs_ocpp_json_data.h"
#include "meter_calculate/meter_calculate_impl.h"
#include "publicdata/publicdata.h"
#include "main.h"
#include "flash_file_mgr/ocpp_mv_offline_flash_impl.h"
#define OCPP_TIMERCNT_NUM 12 //ocpp 计时计数
#define OCPP_ACK_STRUCT_NUM 5 //ocpp 应答结构体数量
#define OCPP_MALLOC pvPortMalloc
#define OCPP_FREE vPortFree
typedef enum{
OCPP_TIMER_BootNotification = 0, //登录帧
OCPP_TIMER_Heartbeat, //心跳帧
OCPP_TIMER_StatusNotification_GUNA, //A枪 状态上传
OCPP_TIMER_StatusNotification_GUNB, //B枪 状态上传
OCPP_TIMER_MeterValues_GUNA, //A枪电表数据上传
OCPP_TIMER_MeterValues_GUNB, //B枪电表数据上传
OCPP_TIMER_StopTransaction_GUNA, //A枪停止充电数据上传
OCPP_TIMER_StopTransaction_GUNB, //B枪停止充电数据上传
OCPP_TIMER_Authorize_GUNA, //A枪鉴权查询上传
OCPP_TIMER_Authorize_GUNB, //B枪鉴权查询上传
}OCPP_SEND_TIMER;
typedef enum{
OCPP_STATE_CHANGE_FLAG = 0, //状态变位标志
OCPP_TRADE_UPLOAD_FLAG, //交易订单上传标志
OCPP_START_RESULT_FLAG, //启动结果上送标志
OCPP_STOP_RESULT_FLAG, //停止结果上送标志
OCPP_VIN_START_FLAG, //VIN鉴权请求上送标志
OCPP_VIN_RESULT, //VIN鉴权结果
OCPP_BILL_INDEX, //上送订单索引号
}OCPP_CHARGE_FLAG;
/** DiagnosticsStatusNotification 待上送状态:外部/FTP 仅赋值;ocpp_send_mag_ctrl 监听非 0 则发送并清 0 */
typedef enum {
OCPP_DIAG_NOTIFY_PENDING_NONE = 0,
OCPP_DIAG_NOTIFY_PENDING_UPLOADING,
OCPP_DIAG_NOTIFY_PENDING_UPLOADED,
OCPP_DIAG_NOTIFY_PENDING_UPLOAD_FAILED,
} OCPP_DIAG_NOTIFY_PENDING_E;
/** FirmwareStatusNotification 待上送状态:与 Diagnostics 同套路(u8_*_notify_pending + ocpp_send_mag_ctrl */
typedef enum {
OCPP_FW_NOTIFY_PENDING_NONE = 0,
OCPP_FW_NOTIFY_PENDING_DOWNLOADING,
OCPP_FW_NOTIFY_PENDING_DOWNLOADED,
OCPP_FW_NOTIFY_PENDING_INSTALLING,
OCPP_FW_NOTIFY_PENDING_INSTALLED,
OCPP_FW_NOTIFY_PENDING_DOWNLOAD_FAILED,
OCPP_FW_NOTIFY_PENDING_INSTALLATION_FAILED,
} OCPP_FW_NOTIFY_PENDING_E;
typedef struct
{
U8_T flag; //存储标志
U8_T check_count; //查询次数
U8_T gunNo; //抢号
U16_T u16_cnt; /* 存储计数(回绕安全:v_clear_save_cmd 基于 max-min 差值检测回绕)*/
OCPP_CMD cmd; //命令值
char key[64]; //随机数密钥
}OCPP_DATA_SAVE;
typedef struct
{
OCPP_CMD cmd; //命令值
U8_T gunNo; //枪号
U8_T type; //应答数据类型 不同帧自定义
U8_T timer_cnt; //计时计数 用于超时判定
char ack_key[64]; //回复相同秘钥
char ack_data[32]; //应答数据内容 不同帧自定义
}OCPP_DATA_RECV_ACK;
/** 未结补单:先 Authorize 再 StartTransaction 的鉴权阶段 */
typedef enum {
OCPP_UNSETTLED_AUTH_NONE = 0,
OCPP_UNSETTLED_AUTH_WAIT,
OCPP_UNSETTLED_AUTH_OK
} OCPP_UNSETTLED_AUTH_PHASE_E;
typedef struct
{
U8_T id; //平台id 0-1
U8_T u8_register_flag; //登录标志
U8_T u8_ping_ack; //ping包回复标志
U8_T u8_unsettled_order_flag; //离线订单上送检查
U8_T u8_reboot_flag; //重启标志 0无动作 1平台下发重启 2本地重启
U8_T u8_log_file_upload; //日志文件上传标志
U8_T u8_diag_notify_pending; //见 OCPP_DIAG_NOTIFY_PENDING_E,仅赋值触发;发送在 ocpp_send_mag_ctrl
U8_T u8_fw_notify_pending; //见 OCPP_FW_NOTIFY_PENDING_E,与 u8_diag_notify_pending 并列、同发送机制
U8_T u8_fw_reboot_pending; //Installed 已成功上送后再 v_sys_reboot(避免未上报即复位)
U8_T u8_fw_installing_fsn_sent; //FSN(Installing) 已 sm>0 发出;STEP_FTP_SAVE 写头/复位前须为 1
S_LOG_DATA *s_unsettled_order_data;
U8_T u8_unsettled_auth_phase; // 未结补单:Authorize→StartTransaction 流水线阶段,取值见 OCPP_UNSETTLED_AUTH_PHASE_E
U32_T u32_unsettled_last_auth_s; // 未结补单:上次发出 Authorize 的秒时间戳(get_current_seconds),用于发送节流
U32_T u32_unsettled_last_stx_s; // 未结补单:上次发出 StartTransaction 的秒时间戳(get_current_seconds),用于发送节流
struct
{
U8_T u8_MeterValues_type; //数据上送标志 0无动作 1空闲数据上送 2充电中数据上送 3交易开始数据上送 4交易停止数据上送
//平台召测数据 "BootNotification","DiagnosticsStatusNotification","FirmwareStatusNotification","Heartbeat","MeterValues","StatusNotification"
U8_T u8_get_Trigger_BootNotification;
U8_T u8_get_Trigger_DiagnosticsStatusNotification;
U8_T u8_get_Trigger_FirmwareStatusNotification;
U8_T u8_get_Trigger_Heartbeat;
U8_T u8_get_Trigger_MeterValues;
U8_T u8_get_Trigger_StatusNotification;
U8_T u8_aut_cnt; //鉴权次数
U8_T u8_StatusNotification_send_cnt; //状态上传次数(保留,周期上送已取消)
U8_T u8_need_status_after_login; // BootNotification Accepted/Pending 后每枪需上送一次 StatusNotification
U8_T s_u8_mv_ws_fail_snap_req; /** MeterValues ws 发送失败时置位,下一周期强制写一条离线 MV */
int s_ocpp_live_stop_tx_id; /** 在线已上送 StopTransaction 的 transactionId(按枪),-1 表示无 */
}s_ctrl_flag[GUN_MAX_CNT]; //数据控制标志
struct
{
U16_T u16_timerCnt; //周期发送计数
U16_T u16_timeNUM ; //计数周期值
}s_timerCnt[OCPP_TIMERCNT_NUM];
U16_T u16_ocpp_ping_timer; //ping包周期计数
U32_T u32_timer; //计时计数
ULONG ulong_timer; //接收计时
OCPP_DATA_RECV_ACK *ack_ctrl[OCPP_ACK_STRUCT_NUM];
}OCPP_DATA_CTRL;
int recv_mags(char* recv_mag);
/****************************************************
*函数名称: v_ocpp_init_data()
*功能说明: OCPP 应用层全量初始化:`v_ocpp_mv_offline_init`、清 OCPP_DATA_CTRL、Flash 配置与定时器、再走联网恢复(含 4G)
*参数说明:
* 上电由平台主任务调用;TCP 长时间连不上时亦调用本函数;diag/fw 等 pending 随 memset 清零
****************************************************/
void v_ocpp_init_data(U8_T bs_flag);
/** 仅联网恢复:释放应答、可选 v_bs_4G_data_init、fd=-1、ulong_restart_timer;不执行 memset(OCPP_DATA_CTRL) */
void v_ocpp_init_data_ex(U8_T bs_flag, U8_T reset_4g_modem);
/****************************************************
*函数名称: v_ocpp_tcp_connect()
*功能说明: TCP连接功能实现 - 配置4G模块
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
U8_T v_ocpp_tcp_connect();
/****************************************************
*函数名称: v_ocpp_logo_on()
*功能说明: 平台桩信息登录 -
*参数说明:
* 输入参数:
* 输出参数: 0失败 1成功
****************************************************/
U8_T v_ocpp_logo_on();
/****************************************************
*函数名称: v_ocpp_recv_data()
*功能说明: 数据接收处理 -
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ocpp_recv_data();
/****************************************************
*函数名称: v_ocpp_send_ctrl()
*功能说明: sdk私有协议逻辑控制 -
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ocpp_send_ctrl();
/****************************************************
*函数名称: v_ocpp_private_logic()
*功能说明: sdk私有协议逻辑控制 -
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ocpp_private_logic(U8_T bs_flag);
/** 仅置 u8_diag_notify_pendingUploaded / UploadFailed);实际上送由 ocpp_send_mag_ctrl 监听该变量完成 */
void v_ocpp_set_diag_upload_notify_pending(U8_T pending);
/** 置 u8_fw_notify_pending;合法值见 OCPP_FW_NOTIFY_PENDING_E;实际上送由 ocpp_send_mag_ctrl */
void v_ocpp_set_fw_notify_pending(U8_T pending);
/** FSN(Installing) 是否已由 ocpp_send_mag_ctrl 成功发出(供 ec200a STEP_FTP_SAVE 等待) */
U8_T u8_ocpp_fw_installing_fsn_sent(void);
void v_ocpp_clr_fw_installing_fsn_sent(void);
/** Installed 上送成功后需复位:置位,由 ocpp_send_mag_ctrl 在 FSN(Installed) 发出后调用 v_sys_reboot(旧流程;FTP 升级现改 EEPROM+先复位) */
void v_ocpp_set_fw_reboot_after_installed(U8_T pending);
/** FTP 写升级头后:FRAM 置位,复位;BootNotification Accepted/Pending 后再发 FSN Installed */
void v_ocpp_fw_eeprom_set_installed_after_boot(void);
/** 在线会话已上送 StopTransaction(如 Remote)后登记 txId,避免 EEPROM 掉电单重复 StopTx PowerLoss */
void v_ocpp_mark_live_stop_tx_done(U8_T gun_idx, int transaction_id);
/** 未结/掉电补单是否应跳过(同枪同 txId 已在线停充上送) */
U8_T u8_ocpp_skip_unsettled_stop_for_live_tx(S_LOG_DATA *log);
/** 未结单与当前枪正在进行的在线充电为同一会话(勿 StopTx / 勿抢 session MV 补发) */
U8_T u8_ocpp_unsettled_is_live_charging_session(S_LOG_DATA *log);
/** 未结订单:preTradeNo 是否仍为占位/空(须先 Authorize→StartTransaction */
U8_T u8_ocpp_is_pre_trade_no_pending(const U8_T *p);
/** 补发一条离线缓存的 MeterValues(与 send_mag 主动帧格式一致) */
int32_t s32_ocpp_send_meter_values_replay(int gunNo, int transactionId, const S_OCPP_MV_OFFLINE_MV *mv);
#endif
#endif
@@ -0,0 +1,764 @@
/**
* @file BS_ocpp_mv_offline_bridge.c
* @brief OCPP 离线 MeterValues 桥接实现:落盘(未连接/未登录)+ 未结订单联网后顺序补发
*
* @details **产品需求(明确)**\n
* 1. **离线周期存储**:TCP 未连接或已连接但未登录 CSMS 时,充电过程中按配置周期将当前测量快照写入 SPI Flash 池,\n
* 并在 FRAM 建立「本笔充电」与序号区间的映射。\n
* 2. **离线订单上送**:未结订单在联网并完成 **Authorize → StartTransaction** 取得平台 **transactionId(流水号)** 后,\n
* 在发送 `StopTransaction` 之前,须检查 Flash 中是否存有 **本次充电** 的离线 MeterValues。\n
* 3. **如何认定「本次充电」的 MV**:落盘与补发使用 **同一规则** —— 以订单日志中的 **开始充电时间** `StartChargeTime``Comm_Time`\n
* 经 `xDate2Seconds` 得到 **32 位**开始充电秒 `u32_charge_start_sec`,并与 **枪号** 一起在 FRAM 映射中查找(落盘 API 第二参为兼容保留,恒传 0)。\n
* 未结内存中的 `S_LOG_DATA` 与离线采样时 `v_meterlog_put_char_log_data` 取到的订单为同一会话时,**开始时间一致**则键一致,\n
* `u8_ocpp_mv_offline_map_get` 命中的区间即为本笔充电的 MV,按序号逐条补发后再 `StopTransaction`。\n
*
* @details **实现划分**\n
* 1. **键空间**:见上;离线时设备流水号常为空,故不以设备单号做键。\n
* 2. **负载内容**`S_OCPP_MV_OFFLINE_MV` 与 `BS_ocpp_ctrl.c` 中 `u8_get_ocpp_measurand_data` 口径对齐,补发由 `s32_ocpp_send_meter_values_replay` 组 JSON。\n
* 3. **模块上下文**:文件内单一静态结构体 `s_ocpp_mv_bridge_ctx` 统一管理「每枪落盘节流时间戳、未结补发状态机」。\n
* `v_ocpp_mv_offline_init` 在 **`v_ocpp_init_data`**`BS_ocpp_ctrl.c`)中调用以装载 FRAM 管理块;`v_ocpp_mv_offline_bridge_private_logic` 不触发 Flash 层 init。\n
* 释放未结订单内存前须 `v_ocpp_mv_offline_unsettled_abort()`,避免状态机与已释放 `S_LOG_DATA` 不一致。\n
* 4. **两类时间源**`get_current_seconds()` 为 **上电起单调秒**`public_func.c`),只宜做间隔/节流;**OCPP 报文与 Flash 内 `u32_timestamp_unix`** 须用 **`GetCurrentTime` + `xDate2Seconds`**,与在线 `MeterValues_mag` 及 `StartChargeTime` 映射键同一套日历 epoch。
*/
#include "BS_ocpp_mv_offline_bridge.h"
#if BS_OCPP_EN
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "BS_ocpp_ctrl.h"
#include "publicdata/public_define.h"
#include "publicdata/publicdata.h"
#include "plat_comm/impl/bs_public_impl.h"
#include "flash_file_mgr/ocpp_mv_offline_flash_impl.h"
#include "plat_comm/plat_comm_task.h"
#include "BS_ocpp_configuration.h"
#include "app_rtc/app_rtc.h"
#include "bs_ocpp_json_data.h"
#define OCPP_OFFLINE_LOG(fmt, ...) \
MYLOG_MSG(OCPP_PRINT_FLAG,fmt, ##__VA_ARGS__) //任务打印
extern int32_t s32_ocpp_send_meter_values_replay(int gunNo, int transactionId,
const S_OCPP_MV_OFFLINE_MV *mv);
/* 与 BS_ocpp_ctrl.c 中 `v_ocpp_timestamp_to_str` 口径一致(离线 MV 时间戳由 unix 秒经本文件内 helper 转换) */
static void v_mv_offline_timestamp_to_str(Comm_Time *ctTime, char *time_str)
{
Comm_Time time;
if (ctTime == NULL) {
GetCurrentTime(&time);
ctTime = &time;
}
#if (OCPP_TIMESTAMP_UTC_OUTPUT)
else {
memcpy(&time, ctTime, sizeof(Comm_Time));
ctTime = &time;
}
v_adjust_time_with_timezone((Comm_Time *)ctTime, -8, 0);
(void)snprintf(time_str, (size_t)OCPP_DATE_LEN, "%04u-%02u-%02uT%02u:%02u:%02uZ",
(unsigned)ctTime->iYear, (unsigned)ctTime->ucMonth, (unsigned)ctTime->ucDay,
(unsigned)ctTime->ucHour, (unsigned)ctTime->ucMin, (unsigned)ctTime->ucSec);
#else
(void)snprintf(time_str, (size_t)OCPP_DATE_LEN, "%04u-%02u-%02uT%02u:%02u:%02u+08:00",
(unsigned)ctTime->iYear, (unsigned)ctTime->ucMonth, (unsigned)ctTime->ucDay,
(unsigned)ctTime->ucHour, (unsigned)ctTime->ucMin, (unsigned)ctTime->ucSec);
#endif
}
static void v_mv_offline_timestamp_unix_to_str(U32_T unix_sec, char *time_str)
{
Comm_Time ct;
xSeconds2Date((unsigned long)unix_sec, &ct);
v_mv_offline_timestamp_to_str(&ct, time_str);
}
/** 将离线 32B 快照填到单个 SampledValue(与 `u8_get_ocpp_measurand_data` 口径一致) */
static U8_T u8_offline_mv_to_sampled(SampledValue *sv, OCPP_MEASURAND_ID e_type, const S_OCPP_MV_OFFLINE_MV *mv)
{
char data_str[24] = {0};
if (sv == NULL || mv == NULL) {
return 0u;
}
switch (e_type) {
case ENUM_EnergyActiveImportRegister:
(void)snprintf(data_str, sizeof(data_str), "%lu", (unsigned long)mv->u32_meter_import_wh);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_EnergyActiveImportRegister, strlen(OCPP_STR_EnergyActiveImportRegister));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_Wh, strlen(OCPP_STR_Wh));
return 1u;
case ENUM_EnergyActiveImportInterval:
(void)snprintf(data_str, sizeof(data_str), "%lu", (unsigned long)mv->u32_energy_interval_wh);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_EnergyActiveImportInterval, strlen(OCPP_STR_EnergyActiveImportInterval));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_Wh, strlen(OCPP_STR_Wh));
return 1u;
case ENUM_PowerActiveImport:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_power_active_w);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_PowerActiveImport, strlen(OCPP_STR_PowerActiveImport));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_W, strlen(OCPP_STR_W));
return 1u;
case ENUM_PowerOffered:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_power_offered_01kw * 100.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_PowerOffered, strlen(OCPP_STR_PowerOffered));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_W, strlen(OCPP_STR_W));
return 1u;
case ENUM_CurrentImport:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_current_import_a_x100 / 100.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_CurrentImport, strlen(OCPP_STR_CurrentImport));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_A, strlen(OCPP_STR_A));
return 1u;
case ENUM_CurrentOffered:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_current_offered_a_x100 / 100.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_CurrentOffered, strlen(OCPP_STR_CurrentOffered));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_A, strlen(OCPP_STR_A));
return 1u;
case ENUM_Voltage:
(void)snprintf(data_str, sizeof(data_str), "%.3f", (F32_T)mv->u16_voltage_v_x10 / 10.0f);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_Voltage, strlen(OCPP_STR_Voltage));
memcpy(sv->location, OCPP_STR_Outlet, strlen(OCPP_STR_Outlet));
memcpy(sv->unit, OCPP_STR_V, strlen(OCPP_STR_V));
return 1u;
case ENUM_SoC:
(void)snprintf(data_str, sizeof(data_str), "%u", (unsigned)mv->u8_soc);
memcpy(sv->value, data_str, strlen(data_str));
memcpy(sv->format, OCPP_STR_Raw, strlen(OCPP_STR_Raw));
memcpy(sv->measurand, OCPP_STR_SOC, strlen(OCPP_STR_SOC));
memcpy(sv->location, OCPP_STR_EV, strlen(OCPP_STR_EV));
memcpy(sv->unit, OCPP_STR_Percent, strlen(OCPP_STR_Percent));
if (mv->u8_soc_data_type == 1u) {
memcpy(sv->context, OCPP_STR_TransactionBegin, strlen(OCPP_STR_TransactionBegin));
} else if (mv->u8_soc_data_type == 2u) {
memcpy(sv->context, OCPP_STR_TransactionEnd, strlen(OCPP_STR_TransactionEnd));
} else {
memcpy(sv->context, OCPP_STR_SamplePeriodic, strlen(OCPP_STR_SamplePeriodic));
}
return 1u;
default:
return 0u;
}
}
cJSON *MeterValues_mag_offline_replay(int gunNo, int transactionId, const S_OCPP_MV_OFFLINE_MV *mv)
{
MeterValuesRequest json_data;
char time_str[30] = {0};
U8_T i;
U8_T u8_data_num = 0;
if (mv == NULL) {
return NULL;
}
(void)memset(&json_data, 0, sizeof(json_data));
json_data.connectorId = gunNo + 1;
json_data.transactionId = transactionId;
v_mv_offline_timestamp_unix_to_str(mv->u32_timestamp_unix, time_str);
(void)memcpy(json_data.meterValue[0].timestamp, time_str, strlen(time_str));
for (i = 0; i < (U8_T)ENUM_measurand_Num; i++) {
if ((s_ocpp_flash_cfg_data.s_ocpp_config_data.u8_MeterValuesMeasurandNeed[i] & E_CFG_MVMN_MeterValuesSampledData)
== E_CFG_MVMN_MeterValuesSampledData) {
if (u8_offline_mv_to_sampled(&json_data.meterValue[0].sampledValue[u8_data_num], (OCPP_MEASURAND_ID)i, mv) != 0u) {
if (++u8_data_num >= MeterValuesRequest_SV_NUM) {
break;
}
}
}
}
if (u8_data_num == 0u) {
return NULL;
}
return j_get_ocpp_json(OCPP_MeterValues, &json_data);
}
/**
* @brief 离线 MV 桥接模块级 RAM 上下文(本文件内静态单例)
* @details 集中存放:\n
* - `last_mv_save_sec[]`:每枪上次**成功**落盘的单调秒,用于 `MeterValueSampleInterval` 节流;\n
* - `unsettled`:未结订单补发 MV 时的状态机(与 `s_unsettled_order_data` 生命周期独立,释放订单前须 abort)。
*/
/** 离线 MV 顺序补发状态(未结补单 / 在线会话内补发共用) */
typedef struct {
U8_T active; /**< 非 0:正处于某笔单的补发区间 `[seq_next, seq_end]` 内 */
U8_T gun; /**< 当前补发枪号 */
U32_T u32_charge_start_sec; /**< `xDate2Seconds(StartChargeTime)`,与 FRAM `u32_tx_sn_lo` 一致 */
int i32_txid; /**< 本笔 OCPP transactionId(会话补发在读订单失败时沿用) */
U32_T seq_start;
U32_T seq_next;
U32_T seq_end;
} S_OCPP_MV_REPLAY_CTX;
typedef struct {
U32_T last_mv_save_sec[GUN_MAX_CNT]; /**< 每枪上次成功落盘时的 `get_current_seconds()`(单调秒),仅用于采样周期节流 */
S_OCPP_MV_REPLAY_CTX unsettled; /**< 未结订单 StopTx 前补发 */
S_OCPP_MV_REPLAY_CTX session; /**< 在线起充、中途离线再上线:用已有 transactionId 补发 */
} S_OCPP_MV_OFFLINE_BRIDGE_CTX;
/** 模块唯一静态上下文;静态存储期整体零初始化 */
static S_OCPP_MV_OFFLINE_BRIDGE_CTX s_ocpp_mv_bridge_ctx;
/** 每枪当前是否已为本笔充电完成起充准备(flow 离开充电后清零) */
static U8_T s_u8_mv_charge_session_armed[GUN_MAX_CNT];
/** 上一周期 BMS flow,用于停充边沿补写一条 MV */
static U8_T s_u8_mv_prev_bs_flow[GUN_MAX_CNT];
/** map full 日志节流(单调秒) */
static U32_T s_u32_mv_map_full_log_sec[GUN_MAX_CNT];
/**
* @fn v_ocpp_mv_charge_start_sec_from_time
* @brief 用订单「开始充电时间」得到与 FRAM 映射一致的 **单路** 32 位键(`xDate2Seconds`
* @param[in] ct 指向 `S_LOG_DATA.StartChargeTime` 的日历时间(与未结订单 `log` 中字段同源)
* @param[out] out_sec 成功时为 `xDate2Seconds(ct)`;无效日历或未写入时为 0
* @details 双枪在同一秒启动时 `*out_sec` 可能相同,依赖 `S_OCPP_MV_OFFLINE_MAP_ROW.u8_gun_no` 区分;\n
* 调用 `u8_ocpp_mv_offline_mv_save` / `map_get` 时第二参固定传 0(与 FRAM 行 `u32_tx_sn_hi` 约定一致,非 64 位拆分)。\n
* 对 `ct` 做简单范围检查(年月日非全零、月 1–12、日 1–31),避免未刷新的日志参与映射。
*/
static void v_ocpp_mv_charge_start_sec_from_time(const Comm_Time *ct, U32_T *out_sec)
{
unsigned int sec;
if (out_sec != NULL) {
*out_sec = 0u;
}
if (ct == NULL || out_sec == NULL) {
return;
}
if (ct->iYear == 0u && ct->ucMonth == 0u && ct->ucDay == 0u) {
return;
}
if (ct->ucMonth == 0u || ct->ucMonth > 12u) {
return;
}
if (ct->ucDay == 0u || ct->ucDay > 31u) {
return;
}
sec = xDate2Seconds((Comm_Time *)ct);
*out_sec = (U32_T)sec;
}
/**
* @fn u8_ocpp_mv_fill_snapshot
* @brief 从实时系统数据填充一条待写入 Flash 的 `S_OCPP_MV_OFFLINE_MV` 快照
* @param[in] gun 枪索引 `0..GUN_MAX_CNT-1`,与 `u8_ocpp_mv_offline_mv_save` 的 `u8_gun_no` 一致
* @param[out] out 输出快照;成功时魔数、版本、时间戳及测量字段均写入
* @return 1 成功;0 参数非法或枪号越界
* @details 各字段数据源与 `u8_get_ocpp_measurand_data` 对齐说明:\n
* - `u32_timestamp_unix``GetCurrentTime` + `xDate2Seconds`,与在线 `MeterValues_mag` 的日历时间同源,供补发时 `v_mv_offline_timestamp_unix_to_str` 组 ISO8601**勿用** `get_current_seconds()`,其为上电起算单调秒)。\n
* - `u32_meter_import_wh`:电表累计 Wh`E_BS_GET_SYS_DATA_MEMTER_ELECT`/10 截断 U32\n
* - `u32_energy_interval_wh`:本事务段增量 Wh(当前累计 `E_BS_GET_SYS_DATA_LOG_ENERGY` type=1\n
* - `u16_voltage_v_x10` / 电流:`METER_VOL`、`METER_CUR`(电流存 0.01A:原 0.1A×10\n
* - `u16_power_active_w`:由本快照已写入的 `u16_voltage_v_x10`0.1V)与 `u16_current_import_a_x100`0.01A)整数估算:`W = vol×cur / 1000`,饱和 65535\n
* - `u16_power_offered_01kw` / `u16_current_offered_a_x100`:桩额定功率 0.1kW 单位、桩最大输出电流\n
* - SOC`E_BS_GET_SYS_DATA_SOC_DATA` 当前值,`u8_soc_data_type` 固定 0(周期采样)
*/
static U8_T u8_ocpp_mv_fill_snapshot(U8_T gun, S_OCPP_MV_OFFLINE_MV *out)
{
U64_T u64_wh; /**< 电表累计能量 Wh(64 位中间量,避免乘除溢出) */
U32_T start_wh; /**< 事务起始电表累计 Wh,来自日志能量 type=1 */
U64_T delta; /**< 事务段内增量 Wh = 当前累计 − 起始 */
Comm_Time now_ct; /**< 当前 RTC 日历(东八区),与 `BS_ocpp_ctrl.c` 中 `v_ocpp_timestamp_to_str(NULL,…)` 一致 */
if (out == NULL || gun >= GUN_MAX_CNT) {
return 0u;
}
(void)memset(out, 0, sizeof(*out)); //清空快照
out->u32_magic = OCPP_MV_OFFLINE_FLASH_MAGIC; //魔数
out->u16_version = (U16_T)OCPP_MV_OFFLINE_FLASH_VER; //版本
GetCurrentTime(&now_ct);
out->u32_timestamp_unix = (U32_T)xDate2Seconds(&now_ct); /* 日历 epoch 秒,非 get_current_seconds 上电秒 */
u64_wh = u64_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_MEMTER_ELECT) / 10u; //电表累计能量 Wh
out->u32_meter_import_wh = (U32_T)((u64_wh > 0xFFFFFFFFULL) ? 0xFFFFFFFFu : u64_wh); //电表累计能量 Wh 截断
start_wh = u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_LOG_ENERGY, 1u); //事务起始电表累计 Wh
delta = (out->u32_meter_import_wh > start_wh) ? ((U64_T)out->u32_meter_import_wh - (U64_T)start_wh) : 0u; //事务段内增量 Wh
out->u32_energy_interval_wh = (U32_T)((delta > 0xFFFFFFFFULL) ? 0xFFFFFFFFu : delta); //事务段内增量 Wh 截断
out->u16_voltage_v_x10 = u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_METER_VOL, 0u); //输出电压 0.1V
out->u16_current_import_a_x100 = (U16_T)(u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_METER_CUR, 0u) * 10u); //输出电流 0.01A
{
U32_T p_w; /**< `W = (0.1V×值)×(0.01A×值) = u16_voltage_v_x10 * u16_current_import_a_x100 / 1000` */
p_w = (U32_T)out->u16_voltage_v_x10 * (U32_T)out->u16_current_import_a_x100 / 1000u;
if (p_w > 65535u) {
p_w = 65535u;
}
out->u16_power_active_w = (U16_T)p_w;
}
out->u16_power_offered_01kw = (U16_T)u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_RATED_POWER, 0u); //额定功率 0.1kW
out->u16_current_offered_a_x100 = (U16_T)u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_PILE_PUT_PARA, 1u); //最大输出电流 0.01A
out->u8_soc = (U8_T)u32_bs_get_sys_data(gun, E_BS_GET_SYS_DATA_SOC_DATA, 0u); //SOC%
out->u8_soc_data_type = 0u; //SOC 上下文:0 当前 / 1 Transaction.Begin / 2 Transaction.End
out->u8_connector_id = (U8_T)(gun + 1u); //连接器号
out->u8_evse_id = gun; //EVSE/枪逻辑号
return 1u;
}
/**
* @fn v_ocpp_mv_offline_bridge_private_logic
* @brief 参见头文件声明;按枪轮询落盘离线 MV
*/
static U8_T u8_mv_pre_trade_no_pending(const U8_T *p)
{
if (p == NULL || p[0] == '\0' || p[0] == (U8_T)0xFF) {
return 1u;
}
return 0u;
}
/**
* @brief 将当前枪订单快照写入 Flash(不检查 flow,供停充边沿补一条)
* @return 1 成功;0 跳过或失败
*/
static U8_T u8_ocpp_mv_offline_persist_snapshot(U8_T gun, U32_T *seq_out)
{
S_LOG_DATA s_log;
U32_T charge_start_sec;
S_OCPP_MV_OFFLINE_MV mv;
U32_T seq_local;
U8_T sav_ret;
if (gun >= (U8_T)GUN_MAX_CNT) {
return 0u;
}
if (seq_out == NULL) {
seq_out = &seq_local;
}
(void)memset(&s_log, 0, sizeof(s_log));
v_meterlog_put_char_log_data(gun, &s_log);
v_ocpp_mv_charge_start_sec_from_time(&s_log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_ocpp_mv_fill_snapshot(gun, &mv) == 0u) {
return 0u;
}
sav_ret = u8_ocpp_mv_offline_mv_save(gun, charge_start_sec, 0u, &mv, seq_out);
if (sav_ret == 0u) {
s_ocpp_mv_bridge_ctx.last_mv_save_sec[gun] = (U32_T)get_current_seconds();
return 1u;
}
return 0u;
}
void v_ocpp_mv_offline_on_charge_start(U8_T gun)
{
if (gun >= (U8_T)GUN_MAX_CNT) {
return;
}
if (s_ocpp_mv_bridge_ctx.session.active != 0u
&& s_ocpp_mv_bridge_ctx.session.gun == gun) {
(void)memset(&s_ocpp_mv_bridge_ctx.session, 0, sizeof(s_ocpp_mv_bridge_ctx.session));
}
/* 勿清 FRAM 映射:同枪连续多笔离线充(A→B→A)须各自保留 StartChargeTime 键,上线后逐笔补 MV */
s_ocpp_mv_bridge_ctx.last_mv_save_sec[gun] = 0u;
s_u32_mv_map_full_log_sec[gun] = 0u;
s_u8_mv_charge_session_armed[gun] = 1u;
OCPP_OFFLINE_LOG("MV offline: charge start gun=%u (keep pending maps)\r\n", (unsigned)gun);
}
void v_ocpp_mv_offline_bridge_private_logic(U8_T bs_id)
{
U8_T i; /**< 枪循环变量 `0..GUN_MAX_CNT-1` */
U16_T interval_s; /**< 采样周期(秒),来自 OCPP 配置 `MeterValueSampleInterval` */
U32_T now; /**< `get_current_seconds()`:上电起单调秒,仅用于与 `MeterValueSampleInterval` 比较节流 */
(void)bs_id;
interval_s = (U16_T)60; /* 配置为 0 或未加载时的保守默认,避免除零或过频写 Flash */
if (s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval != 0u) {
interval_s = s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval;
}
now = (U32_T)get_current_seconds();
for (i = 0u; i < (U8_T)GUN_MAX_CNT; i++) {
S_LOG_DATA s_log; /**< 当前枪订单日志快照,含 `StartChargeTime` */
U32_T charge_start_sec; /**< `xDate2Seconds(StartChargeTime)`,与 FRAM `u32_tx_sn_lo` 一致 */
S_OCPP_MV_OFFLINE_MV mv; /**< 单条待写入 Flash 的 32B 负载 + 帧头由底层封装 */
U32_T seq_out; /**< 本笔分配到的该枪单调序号(调试用,此处未回传上层) */
U8_T sav_ret; /**< `u8_ocpp_mv_offline_mv_save` 返回值:0 成功,2 映射满,3 Flash,4 FRAM5 序号不连续等 */
{
U8_T bs_flow = u8_bs_get_flow_state(i);
/* 停充边沿:补写结束时刻 MV,避免最后一采样周期未落盘 */
if (s_u8_mv_prev_bs_flow[i] == 2u && bs_flow != 2u) {
U32_T seq_end = 0u;
if (u8_ocpp_mv_offline_persist_snapshot(i, &seq_end) != 0u) {
OCPP_OFFLINE_LOG("MV offline: stop snap gun=%u seq=%lu\r\n",
(unsigned)i, (unsigned long)seq_end);
}
}
s_u8_mv_prev_bs_flow[i] = bs_flow;
if (bs_flow != 2u) { //枪未充电 跳过
s_u8_mv_charge_session_armed[i] = 0u;
continue;
}
}
if (s_u8_mv_charge_session_armed[i] == 0u) {
U32_T resume_key = 0u;
U32_T resume_seq = 0u;
U16_T resume_cnt = 0u;
(void)memset(&s_log, 0, sizeof(s_log));
v_meterlog_put_char_log_data(i, &s_log);
v_ocpp_mv_charge_start_sec_from_time(&s_log.StartChargeTime, &resume_key);
if (resume_key != 0u
&& u8_ocpp_mv_offline_map_get(i, resume_key, 0u, &resume_seq, &resume_cnt) == 0u
&& resume_cnt > 0u) {
/* 链路抖动/TCP 重连:同一笔充电勿清 map,避免补发从 seq 头反复上送 */
s_u8_mv_charge_session_armed[i] = 1u;
OCPP_OFFLINE_LOG("MV offline: gun=%u link resume, keep map (%u条) key=0x%08lX\r\n",
(unsigned)i, (unsigned)resume_cnt, (unsigned long)resume_key);
} else {
v_ocpp_mv_offline_on_charge_start(i);
}
}
if (interval_s != 0u && s_ocpp_mv_bridge_ctx.last_mv_save_sec[i] != 0u
&& (now - s_ocpp_mv_bridge_ctx.last_mv_save_sec[i]) < (U32_T)interval_s) { //时间间隔未到 跳过
continue;
}
(void)memset(&s_log, 0, sizeof(s_log));
v_meterlog_put_char_log_data(i, &s_log); //获取订单日志
v_ocpp_mv_charge_start_sec_from_time(&s_log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) { /* 开始充电时间无效或未写入,无法建立稳定键 */
continue;
}
if (u8_ocpp_mv_fill_snapshot(i, &mv) == 0u) { //填充快照失败 跳过
continue;
}
sav_ret = u8_ocpp_mv_offline_mv_save(i, charge_start_sec, 0u, &mv, &seq_out);
if (sav_ret == 0u) {
s_ocpp_mv_bridge_ctx.last_mv_save_sec[i] = now;
OCPP_OFFLINE_LOG("MV offline: write ok gun=%u seq=%lu key_sec=0x%08lX unix_ts=%lu Wh=%lu Ival=%lu\r\n",
(unsigned)i,
(unsigned long)seq_out,
(unsigned long)charge_start_sec,
(unsigned long)mv.u32_timestamp_unix,
(unsigned long)mv.u32_meter_import_wh,
(unsigned long)mv.u32_energy_interval_wh);
} else if (sav_ret == 2u) {
U32_T now_log = (U32_T)get_current_seconds();
if (s_u32_mv_map_full_log_sec[i] == 0u
|| (now_log - s_u32_mv_map_full_log_sec[i]) >= 30u) {
s_u32_mv_map_full_log_sec[i] = now_log;
OCPP_OFFLINE_LOG("MV offline: map full gun=%u (FRAM rows=%u)\r\n",
(unsigned)i, (unsigned)OCPP_MV_OFFLINE_MAP_ROWS);
}
} else if (sav_ret != 5u) {
OCPP_OFFLINE_LOG("MV offline: save err=%u gun=%u\r\n", (unsigned)sav_ret, (unsigned)i);
}
}
}
void v_ocpp_mv_offline_snap_gun_now(U8_T gun)
{
U32_T seq_out = 0u;
if (gun >= (U8_T)GUN_MAX_CNT) {
return;
}
if (u8_bs_get_flow_state(gun) != 2u) {
return;
}
if (s_u8_mv_charge_session_armed[gun] == 0u) {
v_ocpp_mv_offline_on_charge_start(gun);
}
if (u8_ocpp_mv_offline_persist_snapshot(gun, &seq_out) != 0u) {
OCPP_OFFLINE_LOG("MV offline: ws-fail snap gun=%u seq=%lu\r\n",
(unsigned)gun, (unsigned long)seq_out);
}
}
/**
* @fn v_ocpp_mv_offline_unsettled_abort
* @brief 参见头文件;将 `s_ocpp_mv_bridge_ctx.unsettled` 整块清零
*/
void v_ocpp_mv_offline_unsettled_abort(void)
{
(void)memset(&s_ocpp_mv_bridge_ctx.unsettled, 0, sizeof(s_ocpp_mv_bridge_ctx.unsettled));
}
U8_T u8_ocpp_mv_offline_map_pending_for_log(const S_LOG_DATA *log)
{
U32_T charge_start_sec;
U32_T seq_start;
U16_T count;
U8_T gun;
if (log == NULL) {
return 0u;
}
gun = log->u8_gunNo;
if (gun >= (U8_T)GUN_MAX_CNT) {
gun = 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log->StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_ocpp_mv_offline_map_get(gun, charge_start_sec, 0u, &seq_start, &count) != 0u
|| count == 0u) {
return 0u;
}
return 1u;
}
void v_ocpp_mv_offline_session_replay_abort(void)
{
(void)memset(&s_ocpp_mv_bridge_ctx.session, 0, sizeof(s_ocpp_mv_bridge_ctx.session));
}
/**
* @brief 单步推进指定补发上下文,最多发送一条 MeterValues
* @return 1 本周期已发或需重试;0 无映射/已全部完成
*/
static U8_T u8_ocpp_mv_replay_step_ctx(S_OCPP_MV_REPLAY_CTX *rp, U8_T gun, U32_T charge_start_sec, int txid,
const char *tag)
{
U32_T seq_start;
U16_T count;
S_OCPP_MV_OFFLINE_MV mv;
if (rp == NULL || charge_start_sec == 0u || txid < 0) {
return 0u;
}
if (rp->active == 0u) {
if (u8_ocpp_mv_offline_map_get(gun, charge_start_sec, 0u, &seq_start, &count) != 0u || count == 0u) {
return 0u;
}
rp->active = 1u;
rp->gun = gun;
rp->u32_charge_start_sec = charge_start_sec;
rp->i32_txid = txid;
rp->seq_start = seq_start;
rp->seq_next = seq_start;
rp->seq_end = seq_start + (U32_T)count - 1u;
OCPP_OFFLINE_LOG("MV offline: %s queue gun=%u txId=%d key_sec=0x%08lX seq %lu-%lu (%u条)\r\n",
tag, (unsigned)gun, txid,
(unsigned long)charge_start_sec,
(unsigned long)seq_start,
(unsigned long)rp->seq_end,
(unsigned)count);
} else if (rp->gun != gun || rp->u32_charge_start_sec != charge_start_sec) {
(void)memset(rp, 0, sizeof(*rp));
return 0u;
}
if (rp->seq_next > rp->seq_end) {
(void)u8_ocpp_mv_offline_map_remove(rp->gun, rp->u32_charge_start_sec, 0u);
(void)memset(rp, 0, sizeof(*rp));
return 0u;
}
if (u8_ocpp_mv_offline_mv_read_by_seq(rp->gun, rp->seq_next, &mv) != 0u) {
OCPP_OFFLINE_LOG("MV offline: %s read fail gun=%u seq=%lu slot=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)rp->seq_next,
(unsigned long)(rp->seq_next % (U32_T)OCPP_MV_OFFLINE_POOL_MAX_PER_GUN));
rp->seq_next++;
return 1u;
}
if (u8_ocpp_mv_offline_mv_is_valid(&mv) == 0u) {
OCPP_OFFLINE_LOG("MV offline: %s read skip invalid gun=%u seq=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)rp->seq_next);
rp->seq_next++;
return 1u;
}
if (s32_ocpp_send_meter_values_replay((int)rp->gun, txid, &mv) <= 0) {
OCPP_OFFLINE_LOG("MV offline: %s send fail gun=%u seq=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)rp->seq_next);
return 1u;
}
{
U32_T total = rp->seq_end - rp->seq_start + 1u;
U32_T done;
rp->seq_next++;
done = rp->seq_next - rp->seq_start;
OCPP_OFFLINE_LOG("MV offline: %s send ok gun=%u %lu/%lu seq=%lu ts=%lu Wh=%lu\r\n",
tag, (unsigned)rp->gun,
(unsigned long)done, (unsigned long)total,
(unsigned long)(rp->seq_next - 1u),
(unsigned long)mv.u32_timestamp_unix,
(unsigned long)mv.u32_meter_import_wh);
if (rp->seq_next > rp->seq_end) {
OCPP_OFFLINE_LOG("MV offline: %s replay done gun=%u total=%lu\r\n",
tag, (unsigned)rp->gun, (unsigned long)total);
(void)u8_ocpp_mv_offline_map_remove(rp->gun, rp->u32_charge_start_sec, 0u);
(void)memset(rp, 0, sizeof(*rp));
return 0u;
}
}
return 1u;
}
U8_T u8_ocpp_mv_offline_session_replay_pending(U8_T gun)
{
S_LOG_DATA log;
U32_T charge_start_sec;
U32_T seq_start;
U16_T count;
if (gun >= (U8_T)GUN_MAX_CNT) {
return 0u;
}
if (s_ocpp_mv_bridge_ctx.session.active != 0u) {
return (gun == s_ocpp_mv_bridge_ctx.session.gun) ? 1u : 0u;
}
if (s_ocpp_mv_bridge_ctx.unsettled.active != 0u) {
return 0u;
}
(void)memset(&log, 0, sizeof(log));
if (u8_plat_get_real_order(&log, gun) == 0u) {
return 0u;
}
if (u8_mv_pre_trade_no_pending(log.u8_preTradeNo) != 0u) {
return 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_ocpp_mv_offline_map_get(gun, charge_start_sec, 0u, &seq_start, &count) != 0u || count == 0u) {
return 0u;
}
return 1u;
}
U8_T u8_ocpp_mv_offline_session_replay_step(U8_T gun)
{
S_LOG_DATA log;
U32_T charge_start_sec;
U8_T flow;
int txid;
if (gun >= (U8_T)GUN_MAX_CNT) {
return 0u;
}
if (s_ocpp_mv_bridge_ctx.unsettled.active != 0u) {
return 0u;
}
/* 已在补发:仅在本枪推进;其它枪轮询项直接跳过,避免 gun0 空订单 pending 占满发送队列(session_0013 */
if (s_ocpp_mv_bridge_ctx.session.active != 0u) {
if (gun != s_ocpp_mv_bridge_ctx.session.gun) {
return 0u;
}
txid = s_ocpp_mv_bridge_ctx.session.i32_txid;
(void)memset(&log, 0, sizeof(log));
if (u8_plat_get_real_order(&log, gun) != 0u) {
int live_tx = atoi((const char *)log.u8_preTradeNo);
if (live_tx > 0) {
s_ocpp_mv_bridge_ctx.session.i32_txid = live_tx;
txid = live_tx;
}
}
return u8_ocpp_mv_replay_step_ctx(&s_ocpp_mv_bridge_ctx.session,
s_ocpp_mv_bridge_ctx.session.gun,
s_ocpp_mv_bridge_ctx.session.u32_charge_start_sec,
txid, "session");
}
(void)memset(&log, 0, sizeof(log));
if (u8_plat_get_real_order(&log, gun) == 0u) {
return 0u;
}
if (u8_mv_pre_trade_no_pending(log.u8_preTradeNo) != 0u) {
return 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log.StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
txid = atoi((const char *)log.u8_preTradeNo);
flow = u8_bs_get_flow_state(gun);
if (flow != 2u && flow != 9u && flow != 10u) {
return 0u;
}
return u8_ocpp_mv_replay_step_ctx(&s_ocpp_mv_bridge_ctx.session, gun, charge_start_sec, txid, "session");
}
/**
* @fn u8_ocpp_mv_offline_unsettled_step
* @brief 参见头文件;单步推进补发状态机并最多发送一条 WS MeterValues
*/
U8_T u8_ocpp_mv_offline_unsettled_step(S_LOG_DATA *log)
{
U32_T charge_start_sec;
U8_T gun;
int txid;
if (log == NULL) {
return 0u;
}
if (u8_ocpp_unsettled_is_live_charging_session(log) != 0u) {
return 0u;
}
gun = log->u8_gunNo;
if (gun >= GUN_MAX_CNT) {
gun = 0u;
}
v_ocpp_mv_charge_start_sec_from_time(&log->StartChargeTime, &charge_start_sec);
if (charge_start_sec == 0u) {
return 0u;
}
if (u8_mv_pre_trade_no_pending(log->u8_preTradeNo) != 0u) {
return 0u;
}
txid = atoi((const char *)log->u8_preTradeNo);
if (txid < 0) {
return 0u;
}
if (u8_ocpp_mv_offline_map_pending_for_log(log) == 0u) {
OCPP_OFFLINE_LOG("MV offline: unsettled no map gun=%u key_sec=0x%08lX txId=%d\r\n",
(unsigned)gun, (unsigned long)charge_start_sec, txid);
return 0u;
}
if (s_ocpp_mv_bridge_ctx.session.active != 0u
&& s_ocpp_mv_bridge_ctx.session.gun == gun) {
/* 同枪在线会话 MV 补发中:勿 abort session 走未结 StopTxsession_0012 */
if (u8_ocpp_unsettled_is_live_charging_session(log) != 0u) {
return 0u;
}
v_ocpp_mv_offline_session_replay_abort();
}
return u8_ocpp_mv_replay_step_ctx(&s_ocpp_mv_bridge_ctx.unsettled, gun, charge_start_sec, txid, "unsettled");
}
#endif /* BS_OCPP_EN */
@@ -0,0 +1,92 @@
/**
* @file BS_ocpp_mv_offline_bridge.h
* @brief OCPP 离线 MeterValues 桥接层声明:连接应用逻辑与 `ocpp_mv_offline_flash_impl`SPI Flash + FRAM
*
* @details
* - **落盘**:TCP 未连接或已连接但未登录 CSMS 时,在充电过程中周期性把测量快照写入 Flash;FRAM 映射键为 **订单开始充电时间** `StartChargeTime` 经 `xDate2Seconds` 得到的 **32 位秒值**(写入 Flash 模块的 `u32_tx_sn_lo`;第二参恒 0,与 FRAM 行 `u32_tx_sn_hi` 约定一致,非 64 位拆分),并与枪号共同区分会话(离线时设备流水号常为空,故不用设备流水号做键)。\n
* - **FRAM/SPI 管理块**:由 `BS_ocpp_ctrl.c` 的 **`v_ocpp_init_data`** 调用 `v_ocpp_mv_offline_init()` 从 EEPROM 装载;每次全量 OCPP 初始化会再次执行(与 FRAM 内容一致,可幂等重入)。\n
* - **补发(未结)**:未结算订单在联网且已取得平台 `transactionId` 后,由 `u8_ocpp_mv_offline_unsettled_step` 按序逐条补发 MeterValues\n
* 全部完成后再由 `BS_ocpp_ctrl` 发送 `StopTransaction`。\n
* - **补发(在线会话)**:Remote 在线起充 → 充电中离线 → 再上线且 **同一 transactionId** 仍有效时,由 `u8_ocpp_mv_offline_session_replay_step` 补发离线窗口内落盘的 MV,\n
* 再发在线周期 MV / `StopTransaction`。\n
* - 本头文件仅在 `BS_OCPP_EN` 打开时提供 API,避免无 OCPP 工程误链。
*/
#ifndef BS_OCPP_MV_OFFLINE_BRIDGE_H
#define BS_OCPP_MV_OFFLINE_BRIDGE_H
#include "publicdata/type.h"
#include "meter_calculate/meter_calculate_impl.h"
#if BS_OCPP_EN
#include "flash_file_mgr/ocpp_mv_offline_flash_impl.h"
#include "bs_ocpp_json_data.h"
/**
* @fn MeterValues_mag_offline_replay
* @brief 由离线快照组 MeterValues PDU(补发;与在线 `MeterValues_mag` 共用 `j_get_ocpp_json` / 测量项配置)
*/
cJSON *MeterValues_mag_offline_replay(int gunNo, int transactionId, const S_OCPP_MV_OFFLINE_MV *mv);
/**
* @fn v_ocpp_mv_offline_bridge_private_logic
* @brief 未联网或未登录周期逻辑:充电中按配置间隔采样并写入离线 MV 池
* @param[in] bs_id 平台/任务侧 OCPP 实例 id,与 `v_ocpp_private_logic(U8_T bs_flag)` 入参一致(当前实现内未使用,保留接口扩展)
* @details
* - 调用方:`v_ocpp_private_logic` 在 **TCP 未连接** 或 **已连接未登录** 分支末尾调用。\n
* - 每枪独立节流:间隔取自 `s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval`(为 0 时用默认 60s)。\n
* - 仅当 `u8_bs_get_flow_state(枪)==2`(充电中)且当前枪订单日志中 **开始充电时间** `StartChargeTime` 有效时落盘;\n
* 键为 `xDate2Seconds(StartChargeTime)` 的 **单路 U32**`u8_ocpp_mv_offline_mv_save` 第一参;第二参恒 0)。\n
* @note 与在线 `send_mag(..., OCPP_MeterValues, ...)` 互斥由上层网络状态保证;双枪各自写入 Flash 半区(见 `ocpp_mv_offline_flash_impl.h`)。
*/
void v_ocpp_mv_offline_bridge_private_logic(U8_T bs_id);
/** 在线 MeterValues 发送失败时立即落盘一条离线 MV(忽略采样间隔节流) */
void v_ocpp_mv_offline_snap_gun_now(U8_T gun);
/** 该枪进入新一笔充电会话时调用:清 FRAM 映射槽、复位落盘节流 */
void v_ocpp_mv_offline_on_charge_start(U8_T gun);
/**
* @fn v_ocpp_mv_offline_unsettled_abort
* @brief 强制中止「未结订单离线 MV 补发」状态机
* @details
* - 在 `v_safe_free_unsettled_order_data` 释放未结订单内存前调用,避免静态序号与已释放的 `S_LOG_DATA` 不一致。\n
* - 不清 Flash、不删 FRAM 映射行(映射删除仅在补发完成或读失败跳过路径中处理)。
*/
void v_ocpp_mv_offline_unsettled_abort(void);
/** 未结订单在 Flash 中是否有待补发的离线 MeterValues(按 StartChargeTime 映射键) */
U8_T u8_ocpp_mv_offline_map_pending_for_log(const S_LOG_DATA *log);
/**
* @fn u8_ocpp_mv_offline_unsettled_step
* @brief 未结补单:在已有平台 transactionId 的前提下,尝试发送下一条离线缓存的 MeterValues
* @param[in] log 当前内存中的未结算订单副本指针(与 `private_ocpp_data->s_unsettled_order_data` 相同语义)
* @return
* - **1**:本周期已发出一条 MV、或读/校验失败已跳过序号、或 WebSocket 发送失败需下周期重试同一序号;\n
* 调用方 **不得** 发送 `StopTransaction`**不得** `free` 该订单。\n
* - **0**Flash/FRAM 中无本单映射、或已全部补发完毕并已 `map_remove`、或参数/键不合法;\n
* 调用方可继续发送 `StopTransaction` 并释放订单(与现有未结流程一致)。
* @details
* - 映射键:与落盘相同,由 `log->StartChargeTime` 经 `xDate2Seconds` 得到同一 **U32**;与 `u8_ocpp_mv_offline_mv_save` 写入 `u32_tx_sn_lo` 时一致。\n
* - JSON 中 `transactionId`:使用 `atoi(log->u8_preTradeNo)`**含 0**);仅 0xFF/空占位时不补发。\n
* - 每调用最多 **1 条** WS 报文,避免单次周期阻塞过长。
*/
U8_T u8_ocpp_mv_offline_unsettled_step(S_LOG_DATA *log);
/** 中止「在线会话内」离线 MV 补发状态机(新一笔充电开始时会自动调用) */
void v_ocpp_mv_offline_session_replay_abort(void);
/** 当前枪是否存在待补发的离线 MV(已有 txId,FRAM 映射非空) */
U8_T u8_ocpp_mv_offline_session_replay_pending(U8_T gun);
/**
* @brief 在线起充后会话内补发:用当前订单 `preTradeNo` 作 transactionId,逐条上送 Flash 中本笔充电的 MV
* @return 1 本周期已发或需重试;0 无待补发或已完成(调用方可继续 StopTx / 周期 MV
*/
U8_T u8_ocpp_mv_offline_session_replay_step(U8_T gun);
#endif /* BS_OCPP_EN */
#endif /* BS_OCPP_MV_OFFLINE_BRIDGE_H */
@@ -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` 目录一致,若后续增减文件请同步更新第二节表格。*
File diff suppressed because it is too large Load Diff
+395
View File
@@ -0,0 +1,395 @@
#ifndef _BS_OCPP_CONFIG_H_
#define _BS_OCPP_CONFIG_H_
#include "bs_ocpp_ctrl.h"
#if BS_OCPP_EN //配置参数 分可选、必选
//枚举 每个连接器相对于其电表(若无电表则相对于电网连接)的相序
typedef enum
{
ENUM_NotApplicable, // 不适用(单相交流/直流充电桩,非三相场景)
ENUM_Unknown, // 未知(设备未检测到相序,需人工排查接线)
ENUM_RST, // 标准参考相序(L1-R、L2-S、L3-T,默认推荐配置)
ENUM_RTS, // 反向参考相序(L1-R、L2-T、L3-S,相序反转需修正)
ENUM_SRT, // 反向240度旋转(L1-S、L2-R、L3-T,特殊场景接线)
ENUM_STR, // 标准120度旋转(L1-S、L2-T、L3-R,特殊场景接线)
ENUM_TRS, // 标准240度旋转(L1-T、L2-R、L3-S,特殊场景接线)
ENUM_TSR // 反向120度旋转(L1-T、L2-S、L3-R,特殊场景接线)
} OCPP_CONNECTOR_PHASE;
//枚举MeterValues测量项 顺序固定!不可变更! (定义顺序关联了配置参数结构体里相关数组的下标)
typedef enum
{
// ------------------- 能量累计值(Register- 文档7.31核心能量类 -------------------
ENUM_EnergyActiveExportRegister, // 有功电能输出累计值(Wh)- 设备向电网输出的总有功电能累计
ENUM_EnergyActiveImportRegister, // 有功电能输入累计值(Wh)- 电网向设备输入的总有功电能累计
ENUM_EnergyReactiveExportRegister, // 无功电能输出累计值(VARh)- 设备向电网输出的总无功电能累计
ENUM_EnergyReactiveImportRegister, // 无功电能输入累计值(VARh)- 电网向设备输入的总无功电能累计
// ------------------- 能量时段值(Interval- 文档7.31时段能量类 -------------------
ENUM_EnergyActiveExportInterval, // 有功电能输出时段值(Wh)- 单个时钟对齐时段内输出的有功电能
ENUM_EnergyActiveImportInterval, // 有功电能输入时段值(Wh)- 单个时钟对齐时段内输入的有功电能
ENUM_EnergyReactiveExportInterval, // 无功电能输出时段值(VARh)- 单个时钟对齐时段内输出的无功电能
ENUM_EnergyReactiveImportInterval, // 无功电能输入时段值(VARh)- 单个时钟对齐时段内输入的无功电能
// ------------------- 功率类 - 文档7.31功率计量类 -------------------
ENUM_PowerActiveExport, // 有功功率输出(W)- 设备向电网输出的实时有功功率
ENUM_PowerActiveImport, // 有功功率输入(W)- 电网向设备输入的实时有功功率
ENUM_PowerOffered, // 提供的功率(W)- 充电桩可向EV提供的最大可用有功功率
ENUM_PowerReactiveExport, // 无功功率输出(VAR)- 设备向电网输出的实时无功功率
ENUM_PowerReactiveImport, // 无功功率输入(VAR)- 电网向设备输入的实时无功功率
ENUM_PowerFactor, // 功率因数(无单位,0~1)- 实时有功功率与视在功率的比值
// ------------------- 电流类 - 文档7.31电流计量类 -------------------
ENUM_CurrentImport, // 输入电流(A)- 电网向设备输入的实时电流
ENUM_CurrentExport, // 输出电流(A)- 设备向电网输出的实时电流
ENUM_CurrentOffered, // 提供的电流(A)- 充电桩可向EV提供的最大可用电流
// ------------------- 电气参数类 - 文档7.31基础电气参数 -------------------
ENUM_Voltage, // 电压(V)- 充电桩输出端的实时线电压
ENUM_Frequency, // 电网频率(Hz)- 接入电网的实时频率(50/60Hz)
// ------------------- 物理/状态量类 - 文档7.31非电气计量项 -------------------
ENUM_Temperature, // 温度(℃)- 充电桩/EV电池的实时温度(文档标注为℃/℉,默认℃)
ENUM_SoC, // 电池剩余电量(%- EV电池的State of Charge0~100%
ENUM_RPM, // 转速(转/分钟)- 冷却风扇/泵等设备的实时转速
// 统计最大数量标志,用于定义配置参数中数组空间的大小
ENUM_measurand_Num,
} OCPP_MEASURAND_ID;
/*
//MeterValues测量项结构体
typedef struct
{
OCPP_MEASURAND_ID enum_measurandID; //枚举变量
const char* str_data; //对应字符串内容
}OCPP_MEASURAND_INFO;
*/
/*
// MeterValues测量项 结构体数组(枚举值 ↔ 标准字符串 映射表,只读不可修改。顺序对应枚举顺序,固定不可变更)
const OCPP_MEASURAND_INFO OCPP_measurand_info[] =
*/
// MeterValues测量项 串数组(标准字符串 只读不可修改。顺序对应枚举顺序,固定不可变更)
extern const char *OCPP_measurand_str[];
// 配置参数 结构体 前半部分必选 后半部分可选
typedef struct
{
// ------------------- 必选boolean类型(U8_T -------------------
U8_T u8_AuthorizeRemoteTxRequests; // 远程启动交易是否需要预授权
U8_T u8_LocalAuthorizeOffline; // 离线时是否允许本地授权启动交易
U8_T u8_LocalPreAuthorize; // 在线时是否无需等待中央系统授权即可本地启动交易
U8_T u8_StopTransactionOnEVSideDisconnect; // EV端断开时是否自动停止交易
U8_T u8_StopTransactionOnInvalidId; // 收到无效授权状态时是否停止当前交易
U8_T u8_UnlockConnectorOnEVSideDisconnect; // EV端断开时是否解锁充电点侧连接器
U8_T u8_LocalAuthListEnabled; // 是否启用本地授权列表
// ------------------- 必选integer类型(U16_T -------------------
U16_T u16_ClockAlignedDataInterval; // 时钟对齐数据间隔(秒)
U16_T u16_ConnectionTimeOut; // 准备状态超时自动取消交易时间(秒)
U16_T u16_GetConfigurationMaxKeys; // 单个GetConfiguration请求最大键数量
U16_T u16_HeartbeatInterval; // 心跳间隔(秒)
U16_T u16_MeterValueSampleInterval; // 计量数据采样间隔(秒)
U16_T u16_NumberOfConnectors; // 充电点物理连接器数量
U16_T u16_ResetRetries; // 充电点复位失败后重试次数
U16_T u16_TransactionMessageAttempts; // 交易消息提交失败后重试次数
U16_T u16_TransactionMessageRetryInterval; // 交易消息重试间隔(秒)
U16_T u16_LocalAuthListMaxLength; // 本地授权列表最大存储条目数
U16_T u16_SendLocalListMaxLength; // 单个SendLocalList请求最大发送条目数
U16_T u16_ChargeProfileMaxStackLevel; // 充电配置最大堆叠层级
U16_T u16_ChargingScheduleMaxPeriods; // 单个充电计划最大时段数
U16_T u16_MaxChargingProfilesInstalled; // 同时可安装的充电配置最大数量
// ------------------- 必选CSL类型 -------------------------------
OCPP_CONNECTOR_PHASE enum_ConnectorPhaseRotation[3]; //各枪电表的连接相序
/*合并 MeterValuesAlignedData、
MeterValuesSampledData、
StopTxnAlignedData、
StopTxnSampledData
MeterValues测量项在以上数据单元是否需要
bit 0 MeterValuesAlignedData是否需要
bit 1 MeterValuesSampledData是否需要
bit 2 StopTxnAlignedData是否需要
bit 3 StopTxnSampledData是否需要
*/
U8_T u8_MeterValuesMeasurandNeed[ENUM_measurand_Num];
/*支持的功能配置文件列表
bit 0 :Core(核心) 是否支持
bit 1 FirmwareManagement(固件管理) 是否支持
bit 2 LocalAuthListManagement(本地授权列表管理) 是否支持
bit 3 Reservation(预约) 是否支持
bit 4 SmartCharging(智能充电) 是否支持
bit 5 RemoteTrigger(远程触发) 是否支持
*/
U8_T u8_SupportedFeatureProfiles;
/*充电调度支持的物理量列表
bit 0 'Current'(电流) 是否支持
bit 1 'Power'(功率) 是否支持
*/
U8_T ChargingScheduleAllowedChargingRateUnit;
// ------------------- 可选boolean类型(U8_T0=false,非0=true -------------------
U8_T u8_AllowOfflineTxForUnknownId; // 是否允许未知ID用户离线启动交易
U8_T u8_AuthorizationCacheEnabled; // 是否启用本地授权缓存
U8_T u8_ReserveConnectorZeroSupported; // 是否支持对0号连接器进行预约
U8_T u8_ConnectorSwitch3to1PhaseSupported; // 是否支持交易期间三相切单相充电
// ------------------- 可选integer类型(U16_T,单位见注释) -------------------
U16_T u16_BlinkRepeat; // 充电点指示灯闪烁次数
U16_T u16_ConnectorPhaseRotationMaxLength; // 相位旋转配置最大条目数(补充:可选核心项)
U16_T u16_LightIntensity; // 充电点灯光亮度(百分比)
U16_T u16_MaxEnergyOnInvalidId; // 无效ID用户最大允许充电能量(Wh)
U16_T u16_MeterValuesAlignedDataMaxLength; // 时钟对齐计量项列表最大长度(可选)
U16_T u16_MeterValuesSampledDataMaxLength; // 采样计量项列表最大长度(可选)
U16_T u16_MinimumStatusDuration; // 状态稳定后发送通知的最短持续时间(秒)
U16_T u16_StopTxnAlignedDataMaxLength; // 交易停止时对齐计量项最大长度(可选)
U16_T u16_StopTxnSampledDataMaxLength; // 交易停止时采样计量项最大长度(可选)
U16_T u16_SupportedFeatureProfilesMaxLength; // 支持功能配置文件列表最大长度(可选)
U16_T u16_WebSocketPingInterval; // WebSocket Ping间隔(秒,0表示禁用)
//------------------私有配置------------------------
char QRCodeConnector1[NUM_64];
char QRCodeConnector2[NUM_64];
} OCPP_CONFIG;
typedef struct
{
S8_T s8_ChangeAvailability[GUN_MAX_CNT]; //更改枪可用性 <0 不可用 >=0 可用 ||>0时可扩充枪预约状态
int card_ver; //卡号版本
char chargePointVendor[OCPP_CiString20]; //供应商信息 可改
}S_OCPP_CHARGE_CFG_DATA;
typedef struct
{
Comm_Time time;
OCPP_CONFIG s_ocpp_config_data; //数据内容
S_OCPP_CHARGE_CFG_DATA s_ocpp_charge_data; //充电相关数据
}S_OCPP_FLASH_CFG_DATA;
extern S_OCPP_FLASH_CFG_DATA s_ocpp_flash_cfg_data;
#endif
#if BS_OCPP_EN //OCPP可配置参数
/*
U8 timeNUm
1、将数据类型定义枚举表
2、创建枚举和字符串的映射结构体; 包含枚举变量、支持标志、数据类型、对应字符串内容、
3、编写根据接收字符串查找映射表对应枚举的函数
4、根据枚举码 使用switch 将模块搭建出来
typedef struct
{
1、枚举
2、支持标志 U8_T
3\数据 数字/字符
4、对应字符串内容 char*;
}METERV_MAG;
const METERV_MAG met[] =
{
{1,“a },
{2.},
}
0 1
U8_T u8_action(type =0 get 。1 set ,meiju ,char *v)
meiju eMMC_HIGH_VOLTAGE_RANGE(const char *mag )
{
}
*/
//保存unknown的Key串
extern char *unknown_tmpbuf[GetConfiguration_NUM];
//OCPP可配置参数枚举
typedef enum
{
ENUM_NULL = 0,
//Core Profile
ENUM_ALLOW_OFFLINE_TX_FOR_UNKNOWN_ID, /* 可选 允许未知用户ID的离线交易 (布尔: 0/1) */
ENUM_AUTHORIZATION_CACHE_ENABLED, /* 可选 启用本地授权缓存 (布尔: 0/1) */
ENUM_AUTHORIZE_REMOTE_TX_REQUESTS, /* 必选 允许远程启动充电事务 (布尔: 0/1) */
ENUM_BLINK_REPEAT, /* 可选 充电桩指示灯闪烁次数 (整数: 次数) */
ENUM_CLOCK_ALIGNED_DATA_INTERVAL, /* 必选 时钟对齐的数据上报间隔 (整数: 秒) */
ENUM_CONNECTION_TIME_OUT, /* 必选 TCP连接超时时间 (整数: 秒) */
ENUM_CONNECTOR_PHASE_ROTATION, /* 必选 连接器相位旋转配置 (字符串: L1/L2/L3顺序) */
ENUM_CONNECTOR_PHASE_ROTATION_MAX_LENGTH, /* 可选 相位旋转配置最大长度 (整数: 字节数) */
ENUM_GET_CONFIGURATION_MAX_KEYS, /* 必选 GetConfiguration请求最大返回Key数 (整数: 数量) */
ENUM_HEARTBEAT_INTERVAL, /* 必选 心跳间隔 (整数: 秒) */
ENUM_LIGHT_INTENSITY, /* 可选 充电桩灯光亮度 (整数: 百分比) */
ENUM_LOCAL_AUTHORIZE_OFFLINE, /* 必选 本地离线授权模式 (布尔: 0/1) */
ENUM_LOCAL_PRE_AUTHORIZE, /* 必选 本地预授权模式 (布尔: 0/1) */
ENUM_MAX_ENERGY_ON_INVALID_ID, /* 可选 无效ID用户的最大允许充电能量 (整数: Wh) */
ENUM_METER_VALUES_ALIGNED_DATA, /* 必选 对齐数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
ENUM_METER_VALUES_ALIGNED_DATA_MAX_LENGTH, /* 可选 对齐数据列表最大长度 (整数: 字符数) */
ENUM_METER_VALUES_SAMPLED_DATA, /* 必选 采样数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
ENUM_METER_VALUES_SAMPLED_DATA_MAX_LENGTH, /* 可选 采样数据列表最大长度 (整数: 字符数) */
ENUM_METER_VALUE_SAMPLE_INTERVAL, /* 必选 采样间隔 (整数: 秒) */
ENUM_MINIMUM_STATUS_DURATION, /* 可选 状态最小持续时间 (整数: 秒) */
ENUM_NUMBER_OF_CONNECTORS, /* 必选 充电桩连接器数量 (整数: 数量) */
ENUM_RESET_RETRIES, /* 必选 复位重试次数 (整数: 次数) */
ENUM_STOP_TRANSACTION_ON_EV_SIDE_DISCONNECT, /* 必选 EV端断开时停止事务 (布尔: 0/1) */
ENUM_STOP_TRANSACTION_ON_INVALID_ID, /* 必选 无效ID时停止事务 (布尔: 0/1) */
ENUM_STOP_TXN_ALIGNED_DATA, /* 必选 事务停止时的对齐数据项 (字符串: 逗号分隔的measurand值) */
ENUM_STOP_TXN_ALIGNED_DATA_MAX_LENGTH, /* 可选 停止事务对齐数据最大长度 (整数: 字符数) */
ENUM_STOP_TXN_SAMPLED_DATA, /* 必选 停止事务时的采样数据项 (字符串: 逗号分隔的measurand值) */
ENUM_STOP_TXN_SAMPLED_DATA_MAX_LENGTH, /* 可选 停止事务采样数据最大长度 (整数: 字符数) */
ENUM_SUPPORTED_FEATURE_PROFILES, /* 必选 支持的功能配置文件 (字符串: 逗号分隔的FeatureProfile值) */
ENUM_SUPPORTED_FEATURE_PROFILES_MAX_LENGTH, /* 可选 功能配置文件列表最大长度 (整数: 字符数) */
ENUM_TRANSACTION_MESSAGE_ATTEMPTS, /* 必选 事务消息重试次数 (整数: 次数) */
ENUM_TRANSACTION_MESSAGE_RETRY_INTERVAL, /* 必选 事务消息重试间隔 (整数: 秒) */
ENUM_UNLOCK_CONNECTOR_ON_EV_SIDE_DISCONNECT, /* 必选 EV端断开时解锁连接器 (布尔: 0/1) */
ENUM_WEB_SOCKET_PING_INTERVAL, /* 可选 WebSocket Ping间隔 (整数: 秒) */
//Local Auth List Management Profile
ENUM_LOCAL_AUTH_LIST_ENABLED, /* 必选 启用本地授权列表 (布尔: 0/1) */
ENUM_LOCAL_AUTH_LIST_MAX_LENGTH, /* 必选 本地授权列表的最大条目数 (整数) */
ENUM_SEND_LOCAL_LIST_MAX_LENGTH, /* 必选 发送本地列表的最大条目数 (整数) */
//Reservation Profile
ENUM_RESERVE_CONNECTOR_ZERO_SUPPORTED, /* 可选 是否支持预留连接器0 (布尔: 0/1) */
//Smart Charging Profile
ENUM_CHARGE_PROFILE_MAX_STACK_LEVEL, /* 必选 充电配置最大堆叠层级 (整数) */
ENUM_CHARGING_SCHEDULE_ALLOWED_RATE_UNIT, /* 必选 充电速率允许单位 (字符串: "A"或"W") */
ENUM_CHARGING_SCHEDULE_MAX_PERIODS, /* 必选 充电计划最大时段数 (整数) */
ENUM_CONNECTOR_SWITCH_3TO1_PHASE_SUPPORTED, /* 可选 是否支持三相切单相充电 (布尔: 0/1) */
ENUM_MAX_CHARGING_PROFILES_INSTALLED, /* 必选 最大可安装充电配置数 (整数) */
//私有配置
ENUM_QRCODE_TEXT_CONNECTOR1, /* 枪1二维码 */
ENUM_QRCODE_TEXT_CONNECTOR2, /* 枪2二维码 */
//num-end OCPP配置参数的总数量,作为在根据字符串寻找对应枚举变量时的上限;未来添加新变量时需在此位置前添加;
ENUM_OCPP_CONFIG,
}OCPP_CONFIG_KEY_ID;
//OCPP可配置参数是否支持
typedef enum
{
ENUM_SUPPORT_NOT = 0,
ENUM_SUPPORT_YES,
}OCPP_CONFIG_SUPPORT;
//OCPP可配置参数数据类型
typedef enum
{
ENUM_TYPE_INT = 0,
ENUM_TYPE_CSL,
}OCPP_CONFIG_TYPE;
//OCPP修改配置参数返回操作结果
typedef enum
{
ENUM_Rejected = 0,
ENUM_Accepted,
ENUM_Required,
ENUM_NotSupported,
}OCPP_SETCONFIG_RET;
//OCPP获取配置参数返回只读结果
typedef enum
{
ENUM_RnW = 0,
ENUM_R_Only,
}OCPP_GETCONFIG_RET;
//OCPP可配置参数结构体
typedef struct
{
OCPP_CONFIG_KEY_ID u8_keyID; //1,枚举变量
OCPP_CONFIG_SUPPORT u8_support; //2,支持标志 0:不支持,1:支持。
OCPP_CONFIG_TYPE u8_type; //3,数据类型 1:integer2:CSL。
const char* str_data; //4、对应字符串内容 char*;
}OCPP_CONFIG_INFO;
//OCPP可配置参数操作类型
typedef enum
{
ENUM_TYPE_GET = 0,
ENUM_TYPE_SET,
}OCPP_CONFIG_OPERATE;
//OCPP可配置参数结构体数组
extern const OCPP_CONFIG_INFO OCPP_config_info[];
/*******************************************************************************
* 名称: u8_find_OCPP_configID
* 功能: 根据字符串查找对应OCPP配置参数的枚举变量
* 参数: *mag OCPP配置参数的键的字符串
* 返回: 返回该键在OCPP_CONFIG_KEY_ID的对应枚举量,若返回ENUM_NULL则未找到该键
* 说明:
******************************************************************************/
OCPP_CONFIG_KEY_ID u8_find_OCPP_configID(const char *mag);
/*******************************************************************************
* 名称: find_OCPP_configMag
* 功能: 根据配置参数的枚举号 找对应字符串 并赋值
* 参数: keyID 配置参数的枚举号
* *mag 要赋值串的指针头
* u16_len 被赋值的key串最大长度
* 返回: -
* 说明:
******************************************************************************/
void find_OCPP_configMag(OCPP_CONFIG_KEY_ID keyID, char *mag, U16_T u16_len);
/*******************************************************************************
* 名称: u8_check_OCPP_configSupport
* 功能: 根据配置参数的枚举号 找是否支持
* 参数: keyID 配置参数的枚举号
* 返回: 是否支持标志
* 说明:
******************************************************************************/
OCPP_CONFIG_SUPPORT u8_check_OCPP_configSupport(OCPP_CONFIG_KEY_ID keyID);
/*******************************************************************************
* 名称: u8_OCPP_config_operate
* 功能: 对OCPP配置参数进行读或写
* 参数: operate_type 操作类型,ENUM_TYPE_GET--获取参数,ENUM_TYPE_GET--设置参数
* *key_mag 配置参数键的字符串
* *value_mag 配置参数值的字符串
* u16_len 配置参数值的字符串的最大长度
* 返回: 参数修改时:0-Rejected 1-Accepted 2-RebootRequired 3-NotSupported
* 参数获取时 0-readonly-false 1-readonly-ture
* 说明:
******************************************************************************/
U8_T u8_OCPP_config_operate(OCPP_CONFIG_OPERATE operate_type, const char *key_mag, char *value_mag , U16_T u16_len);
/****************************************************
*函数名称: v_ocpp_init_config_data()
*功能说明: 程序运行前 配置参数初始化
*参数说明:
* 输入参数:
* 输出参数:
****************************************************/
void v_ocpp_init_config_data();
void v_flash_save_ocpp_cfg_data(void);
#endif
#endif
File diff suppressed because it is too large Load Diff
+528
View File
@@ -0,0 +1,528 @@
#ifndef _BS_OCPP_JSON_DATA_H_
#define _BS_OCPP_JSON_DATA_H_
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#include "bs_ocpp_str.h"
#if BS_OCPP_EN //OCPP平台使能
#include "cJSON.h"
#include <string.h>
#define OCPP_LOGMSG_DEBUG_EN 1 //打印标志
#define OCPP_PRINTF_DEBUG_EN 1 //printf打印标志
#define MeterValuesRequest_MV_NUM 1 //电表数据组数量
#define MeterValuesRequest_SV_NUM 8 //数据内容数量
#define GetConfiguration_NUM 20 //平台获取数据最大数量
#define StopTransactionRequest_TD_NUM 1 //停止充电 数据组数量
#define StopTransactionRequest_SV_NUM 5 //停止充电 数据内容数量
#define E_CFG_MVMN_MeterValuesAlignedData (0x01)
#define E_CFG_MVMN_MeterValuesSampledData (0x02)
#define E_CFG_MVMN_StopTxnAlignedData (0x04)
#define E_CFG_MVMN_StopTxnSampledData (0x08)
#define OCPP_DATE_LEN (30) //OCPP时间格式 数据长度 ISO 8601 格式。
#define OCPP_CiString20 (20+1)
#define OCPP_CiString30 (30+1)
#define OCPP_CiString25 (25+1)
#define OCPP_CiString50 (50+1)
#define OCPP_CiString100 (100+1)
#define OCPP_CiString255 (255+1)
#define OCPP_CiString300 (300+1)
#define OCPP_CiString500 (500+1)
typedef enum{
OCPP_NULL = 0,
OCPP_BootNotification, //引导通知 桩->平台
OCPP_StatusNotification, //状态通知 桩->平台
OCPP_Heartbeat, //心跳 桩->平台
OCPP_Authorize, //授权 桩->平台
OCPP_MeterValues, //采样信息上传 桩->平台
OCPP_StartTransaction, //开始交易 桩->平台
OCPP_StopTransaction, //停止交易 桩->平台
OCPP_RemoteStartTransaction,//平台下发启动充电 平台->桩
OCPP_RemoteStopTransaction, //平台下发停止充电 平台->桩
OCPP_UnlockConnector, //解枪锁 平台->桩
OCPP_UpdateFirmware, //固件升级 平台->桩
OCPP_Reset, //重启 平台->桩
OCPP_CancelReservation, //取消已经预定的充电桩 平台->桩
OCPP_ChangeAvailability, //设置枪可用状态 平台->桩
OCPP_ChangeConfiguration, //更改参数配置 平台->桩
OCPP_TriggerMessage, //平台下发数据召测 平台->桩
OCPP_GetConfiguration, //平台获取参数配置 平台->桩
OCPP_ClearCache, //清楚桩内缓存 一般为白名单 平台->桩
OCPP_SendLocalList, //平台下发本地缓存列表
OCPP_GetLocalListVersion, //获取卡号版本
OCPP_GetDiagnostics, //获取诊断日志文件
OCPP_DiagnosticsStatusNotification, /* 诊断日志上传状态 桩->平台 */
OCPP_FirmwareStatusNotification, /* 固件升级状态 桩->平台 */
}OCPP_CMD;
typedef enum{
OCPP_StatusNotification_errorCode = 0, //StatusNotification errorCode
OCPP_StatusNotification_statusCode,
OCPP_RemoteStartTransactionResponse_statusCode,
OCPP_RemoteStopTransactionResponse_statusCode,
OCPP_StopTransactionRequest_reason,
OCPP_UnlockConnectorResponse_stateCode,
OCPP_DiagnosticsStatusNotification_status, /* 与 OCPP_DIAG_NOTIFY_PENDING_E 下标对齐:0 Idle … */
OCPP_FirmwareStatusNotification_status, /* 与 OCPP_FW_NOTIFY_PENDING_E 下标对齐:0 Idle … */
}OCPP_ENUM_DATA_TYPE;
//状态错误信息
typedef enum{
OCPP_ERRORCODE_ConnectorLockFailure, //连接器锁定故障
OCPP_ERRORCODE_EVCommunicationError, //EV通讯故障
OCPP_ERRORCODE_GroundFailure, //接地故障
OCPP_ERRORCODE_HighTemperature, //高温
OCPP_ERRORCODE_InternalError, //内部错误
OCPP_ERRORCODE_LocalListConflict, //本地列表冲突
OCPP_ERRORCODE_NoError, //无错误
OCPP_ERRORCODE_OtherError, //其他错误
OCPP_ERRORCODE_OverCurrentFailure, //过电流故障
OCPP_ERRORCODE_PowerMeterFailure, //功率表故障
OCPP_ERRORCODE_PowerSwitchFailure, //电源开关故障
OCPP_ERRORCODE_ReaderFailure, //读卡器故障
OCPP_ERRORCODE_ResetFailure, //重置失败
OCPP_ERRORCODE_UnderVoltage, //电压过低
OCPP_ERRORCODE_OverVoltage, //电压过高
OCPP_ERRORCODE_WeakSignal, //信号弱
}OCPP_STATUSNOTIFICATION_ERRORCODE;
//状态信息
typedef enum{
OCPP_STATUSCODE_Available, //空闲
OCPP_STATUSCODE_Preparing, //准备
OCPP_STATUSCODE_Charging, //充电
OCPP_STATUSCODE_SuspendedEVSE, //暂停EVSE
OCPP_STATUSCODE_SuspendedEV, //暂停EV
OCPP_STATUSCODE_Finishing, //完成
OCPP_STATUSCODE_Reserved, //预留
OCPP_STATUSCODE_Unavailable, //不空闲
OCPP_STATUSCODE_Faulted, //故障
}OCPP_STATUSNOTIFICATION_STATUSCODE;
typedef struct
{
//必选
char chargePointVendor[OCPP_CiString20]; //"type": "string","maxLength": 20
char chargePointModel[OCPP_CiString20]; //"type": "string","maxLength": 20
//可选
char chargePointSerialNumber[OCPP_CiString25]; //"type": "string","maxLength": 25
char chargeBoxSerialNumber[OCPP_CiString25]; //"type": "string","maxLength": 25
char firmwareVersion[OCPP_CiString20]; //"type": "string","maxLength": 50
char iccid[OCPP_CiString30]; //"type": "string","maxLength": 20
char imsi[OCPP_CiString30]; //"type": "string","maxLength": 20
char meterType[OCPP_CiString25]; //"type": "string","maxLength": 25
char meterSerialNumber[OCPP_CiString25]; //"type": "string","maxLength": 25
}BootNotificationRequest;
typedef struct
{
//必选
char status[OCPP_CiString20]; //"type": "string","enum": ["Accepted","Pending","Rejected"]
char currentTime[OCPP_DATE_LEN]; //"type": "string","format": "date-time" 2022-09-09T16:40:00+04:00
int interval; //"type": "integer"
//可选
}BootNotificationResponse;
typedef struct
{
//必选
int connectorId; //连接器id
char errorCode[OCPP_CiString30]; //枚举 ocpp_errorCode 当前的错误状态代码。应当表示充电桩或连接器当前的错误情况。
/*"ConnectorLockFailure", 充电枪锁止机构故障
"EVCommunicationError", 与电动车通信失败
"GroundFailure", 接地故障
"HighTemperature", 设备温度过高
"InternalError", 内部错误
"LocalListConflict", 本地授权列表冲突
"NoError", 无错误
"OtherError", 其他未分类错误
"OverCurrentFailure", 过流故障
"PowerMeterFailure", 电表故障
"PowerSwitchFailure", 电源开关故障
"ReaderFailure", 读卡器故障
"ResetFailure", 复位失败
"UnderVoltage", 欠压故障
"OverVoltage", 过压故障
"WeakSignal" 信号弱*/
char status[OCPP_CiString20]; //枚举 statusCode 连接器的当前状态。
/*"Available", 可用
"Preparing", 准备中 插枪
"Charging", 充电中
"SuspendedEVSE", 充电桩主动暂停
"SuspendedEV", 电动车请求暂停
"Finishing", 结束中
"Reserved", 已预约
"Unavailable", 不可用
"Faulted" 故障*/
//可选
char info[OCPP_CiString30]; //与错误相关的其他自由格式的信息
char timestamp[OCPP_DATE_LEN]; //日期时间 状态变化发生的时间戳
char vendorId[OCPP_CiString50]; //制造商标识。
char vendorErrorCode[OCPP_CiString50]; //制造商特定的错误代码。
}StatusNotificationRequest;
typedef struct
{
char value[OCPP_CiString20]; //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
char context[OCPP_CiString20]; //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)
/*"Interruption.Begin", 中断开始 标识充电过程被主动中断的起始时刻(如用户手动停止、系统故障触发)。
"Interruption.End", 中断结束 标识中断状态恢复的时间点(如故障修复后重新启动充电)。
"Sample.Clock", 时钟同步采样 按固定时钟时间(如整点)触发的数据采样,与时间严格对齐。
"Sample.Periodic", 定期采样 按固定间隔(如每15分钟)触发的数据采样,与起始时间无关。
"Transaction.Begin", 交易开始 标识充电交易的起始时刻(如用户刷卡启动充电)。
"Transaction.End", 交易结束 标识充电交易的终止时刻(如用户拔枪或余额耗尽)。
"Trigger", 触发器事件 表示由外部信号(如远程控制命令)触发的动作,需结合具体 triggerMessage 使用。
"Other" 其他事件
*/
char format[OCPP_CiString20]; //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。/*"Raw" 明文,"SignedData" 签名加密*/
char measurand[OCPP_CiString50]; //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register (导入的有效能量)。
/*"Energy.Active.Export.Register", 累计放电电量(电表读数)Wh 或 kWh
"Energy.Active.Import.Register", 累计充电电量(电表读数)Wh 或 kWh
"Energy.Reactive.Export.Register", 无功电能导出累计值 (单位: kvarh/varh)
"Energy.Reactive.Import.Register", 无功电能导入累计值 (单位: kvarh/varh)
"Energy.Active.Export.Interval", 时间间隔内有功电能导出 (单位: kWh/Wh)
"Energy.Active.Import.Interval", 时间间隔内有功电能导入 (单位: kWh/Wh)
"Energy.Reactive.Export.Interval", 时间间隔内无功电能导出 (单位: kvarh/varh)
"Energy.Reactive.Import.Interval", 时间间隔内无功电能导入 (单位: kvarh/varh)
"Power.Active.Export", 瞬时值 有功功率(放电功率)W
"Power.Active.Import", 瞬时值 有功功率(充电功率)W
"Power.Offered", 充电桩可提供的最大功率 (单位: W/kW)
"Power.Reactive.Export", 瞬时无功功率导出 (单位: var/kvar)
"Power.Reactive.Import", 瞬时无功功率导入 (单位: var/kvar)
"Power.Factor", 功率因数 (无单位, 范围: -1.0~1.0)
"Current.Import", 输入电流(充电电流)
"Current.Export", 输出电流(放电电流)
"Current.Offered", 充电桩可提供的最大电流 (单位: A)
"Voltage", 电压(相电压或线电压)
"Frequency", 电网频率
"Temperature", 温度(如充电桩内部温度)
"SoC", 电池电量状态(State of Charge
"RPM" 电机转速 (单位: RPM)*/
char phase[OCPP_CiString20]; //可选 可选字段,表示电力相位,例如 L1, L2, L3。
/*"L1",
"L2",
"L3",
"N",
"L1-N",
"L2-N",
"L3-N",
"L1-L2",
"L2-L3",
"L3-L1"*/
char location[OCPP_CiString20]; //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
/*"Cable",电缆
"EV",车辆
"Inlet",入口
"Outlet",出口
"Body"整桩 */
char unit[OCPP_CiString20]; //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
/*"Wh",
"kWh",
"varh",
"kvarh",
"W",
"kW",
"VA",
"kVA",
"var",
"kvar",
"A",
"V",
"K",
"Celcius",
"Celsius", 摄氏度
"Fahrenheit", 华氏度
"Percent" 百分比*/
}SampledValue;
typedef struct
{
int connectorId; //必须 表示充电连接器的唯一标识符。
int transactionId; //可选 当前充电事务的唯一标识符。这是可选字段,只有在当前有正在进行的充电事务时才有值。
struct
{
char timestamp[OCPP_DATE_LEN]; //必须 记录取样数据的时间,ISO 8601 格式。
SampledValue sampledValue[MeterValuesRequest_SV_NUM]; //必须 最大同时上送3种数据 代表一个或多个取样值,每个取样值包含以下字段:
}meterValue[MeterValuesRequest_MV_NUM]; //必须 这是一个数组,包含一组计量值。每个计量值包含一个时间戳和对应的一组取样值。
}MeterValuesRequest;
typedef struct
{
int connectorId; //可选 指示发起交易的连接器ID,即充电桩的具体插口编号。
char idTag[OCPP_CiString20]; //必选 标识电动汽车用户或车主的唯一ID标识,用于识别用户。。
struct
{
int chargingProfileId; //必选 充电配置的唯一标识符
int transactionId; //可选 关联的事务ID,用于将此配置与特定充电事务关联
int stackLevel; //必选 配置优先级,数值越高优先级越高
char chargingProfilePurpose[OCPP_CiString25]; //必选 定义配置的用途
/* "ChargePointMaxProfile", 充电桩最大功率配置
"TxDefaultProfile", 默认事务配置
"TxProfile" 特定事务配置*/
char chargingProfileKind[OCPP_CiString20]; //必选 定义配置的时间类型
/* "Absolute", 绝对时间配置
"Recurring", 循环配置
"Relative" 相对时间配置*/
char recurrencyKind[OCPP_CiString20]; //可选 仅当chargingProfileKind为"Recurring"时有效
/* "Daily",每天
"Weekly" 每周*/
char validFrom[OCPP_DATE_LEN]; //可选 "date-time"配置生效的开始时间。如果不存在,则配置文件在充电桩收到后立即有效。当ChargingProfilePurpose是TxProfile时,请勿使用。
char validTo[OCPP_DATE_LEN]; //可选 "date-time"配置失效的结束时间。如果不存在,则该配置文件在被另一个 配置文件替换之前一直有效。当ChargingProfilePurpose是TxProfile时,请勿使用。
struct
{
int duration; //可选 持续时间
char startSchedule[OCPP_DATE_LEN]; //可选 "date-time" 充电计划开始时间
char chargingRateUnit[OCPP_CiString20]; //必选 充电速率单位
/* "A",
"W"*/
struct
{
int startPeriod; //必选 时段开始时间(相对于充电计划开始时间的秒数)
float limit; //必选 该时段的最大充电速率
int numberPhases; //可选 使用的相数(1-3)
}chargingSchedulePeriod[3]; //必选 充电计划时段数组
float minChargingRate; //可选 最小充电速率
}chargingSchedule; //必选 充电计划配置
}chargingProfile; //可选 充电配置参数,用于指定充电行为。
}RemoteStartTransactionRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted"接受,"Rejected"拒绝
}RemoteStartTransactionResponse;
typedef struct
{
int transactionId; //当前充电事务的唯一标识符。
}RemoteStopTransactionRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted"接受,"Rejected"拒绝
}RemoteStopTransactionResponse;
typedef struct
{
int connectorId; //必选 指示发起交易的连接器ID,即充电桩的具体插口编号。
int meterStart; //必选 交易开始时的电表读数,这通常是充电开始时的初始能量值。 Wh
int reservationId; //可选 如果交易基于事先的预定进行,此字段包含预定的ID。
char idTag[OCPP_CiString20]; //必选 标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
char timestamp[OCPP_DATE_LEN]; //必选 "date-time" 交易开始的时间戳,表示交易开始的准确时间。
}StartTransactionRequest;
typedef struct
{
int transactionId; //必选 中央系统生成的唯一交易ID,用于后续的交易管理和追踪。
struct
{
char status[OCPP_CiString20]; //必选 指示idTag的当前状态,例如是否被接受、是否过期等。Accepted、Blocked、Expired、Invalid、ConcurrentTx
char expiryDate[OCPP_DATE_LEN]; //可选 idTag有效期的截止日期
char parentIdTag[OCPP_CiString20]; //可选 如果存在关联账户,这是父idTag。
}idTagInfo; //必选 包含对idTag的验证和状态信息,其中包括:
}StartTransactionResponse;
typedef struct
{
int meterStop; //必选 交易结束时的电表读数,这通常是充电结束时的最终能量值 Wh
int transactionId; //必选 要停止的充电事务的唯一标识符
char idTag[OCPP_CiString20]; //可选 标识电动汽车用户或车主的唯一ID标识。
char timestamp[OCPP_DATE_LEN]; //必选 交易结束的时间戳,表示交易停止的准确时间
char reason[OCPP_CiString20]; //可选 指示停止充电的原因,这可以包括:正常结束、紧急停止等情况。 Local、Remote、EmergencyStop、EVDisconnected、HardReset、SoftReset、deAuthorized、Expired、EVConnectTimeout、Other
struct
{
char timestamp[OCPP_DATE_LEN]; //必选
SampledValue sampledValue[StopTransactionRequest_SV_NUM]; //必选
}transactionData[StopTransactionRequest_TD_NUM]; //可选 包含与充电交易相关的更详细的数据,如中途的数据采集点
}StopTransactionRequest;
typedef struct
{
struct
{
char status[OCPP_CiString20]; //必选 指示idTag的当前状态,例如是否被接受、是否过期等。Accepted、Blocked、Expired、Invalid、ConcurrentTx
char expiryDate[OCPP_DATE_LEN]; //可选 idTag有效期的截止日期。
char parentIdTag[OCPP_CiString20]; //可选 果存在关联账户,这是父idTag。
}idTagInfo;
}StopTransactionResponse;
typedef struct
{
int connectorId;
}UnlockConnectorRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Unlocked","UnlockFailed","NotSupported"
}UnlockConnectorResponse;
typedef struct
{
int retries; //可选
int retryInterval; //可选
char retrieveDate[OCPP_DATE_LEN]; //必选
char location[OCPP_CiString255]; //必选
}UpdateFirmwareRequest;
typedef struct
{
int test;
}UpdateFirmwareResponse;
typedef struct
{
char type[OCPP_CiString20]; // "Hard","Soft"
}ResetRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected"
}ResetResponse;
typedef struct
{
char idTag[OCPP_CiString20];
}AuthorizeRequest;
typedef struct
{
struct
{
char expiryDate[OCPP_DATE_LEN]; //可选
char parentIdTag[OCPP_CiString20]; //可选
char status[OCPP_CiString20]; //必选
/*"Accepted", 请求已被接受
"Blocked", 请求被拒绝(因策略限制)
"Expired", 授权凭证已过期
"Invalid", 凭证无效或格式错误
"ConcurrentTx" 存在并发事务(同一车辆/用户已在充电)*/
}idTagInfo;
}AuthorizeResponse;
typedef struct
{
int reservationId;
}CancelReservationRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected"
}CancelReservationResponse;
typedef struct
{
int connectorId;
char type[OCPP_CiString20]; //"Inoperative","Operative"
}ChangeAvailabilityRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected",Scheduled"
}ChangeAvailabilityResponse;
typedef struct
{
char key[OCPP_CiString50];
char value[OCPP_CiString300]; //
}ChangeConfigurationRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected","RebootRequired","NotSupported"
}ChangeConfigurationResponse;
typedef struct
{
int connectorId; //可选
char requestedMessage[OCPP_CiString50]; //必选 "BootNotification","DiagnosticsStatusNotification","FirmwareStatusNotification","Heartbeat","MeterValues","StatusNotification"
}TriggerMessageRequest;
typedef struct
{
char status[OCPP_CiString20]; //"Accepted","Rejected","NotImplemented"
}TriggerMessageResponse;
typedef struct
{
char key[GetConfiguration_NUM][OCPP_CiString50]; //array
}GetConfigurationRequest;
typedef struct
{
char unknownKey[GetConfiguration_NUM][OCPP_CiString50]; //array
struct
{
int readonly; //必选
char key[OCPP_CiString50]; //必选
char value[OCPP_CiString100]; //可选
}configurationKey[GetConfiguration_NUM];
}GetConfigurationResponse;
typedef struct
{
int res;
}ClearCacheRequest;
typedef struct
{
char status[OCPP_CiString20];
}ClearCacheResponse;
typedef struct
{
char idTag[OCPP_CiString30];
struct
{
char expiryDate[OCPP_DATE_LEN];
char parentIdTag[OCPP_CiString20];
char status[OCPP_CiString20];
}idTagInfo;
}LocalAuthorizationList;
typedef struct
{
int listVersion;
char updateType[OCPP_CiString30];
int List_num;
LocalAuthorizationList *list;
}SendLocalListRequest;
typedef struct
{
char status[OCPP_CiString20];
}SendLocalListResponse;
typedef struct
{
int listVersion;
}GetLocalListVersionResponse;
typedef struct
{
int retries; //可选
int retryInterval; //可选
char location[OCPP_CiString255]; //必须
char startTime[OCPP_DATE_LEN]; //可选
char stopTime[OCPP_DATE_LEN]; //可选
}GetDiagnosticsRequest;
typedef struct
{
char fileName[OCPP_CiString50];
}GetDiagnosticsResponse;
typedef struct {
char status[OCPP_CiString20];
} DiagnosticsStatusNotificationRequest;
/** 与 DiagnosticsStatusNotification 载荷相同,仅 Action 名不同 */
typedef DiagnosticsStatusNotificationRequest FirmwareStatusNotificationRequest;
cJSON* j_get_ocpp_json(OCPP_CMD cmd , void *data);
int get_ocpp_data(OCPP_CMD cmd , void *data ,cJSON *root);
OCPP_CMD e_get_recv_json_cmd(char* cmd_str);
const char *s_get_ocpp_cmd_str(OCPP_CMD cmd);
const char *s_get_ocpp_enum_data_str(OCPP_ENUM_DATA_TYPE type ,int cmd);
#endif
#endif
+150
View File
@@ -0,0 +1,150 @@
#ifndef _BS_OCPP_STR_H_
#define _BS_OCPP_STR_H_
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#if BS_OCPP_EN //OCPP平台使能
#define OCPP_STR_SamplePeriodic "Sample.Periodic" //周期上送 Periodic delivery
#define OCPP_STR_TransactionBegin "Transaction.Begin" //交易开始
#define OCPP_STR_TransactionEnd "Transaction.End" //交易停止
#define STR_TRUE "TRUE"
#define STR_true "true"
#define STR_1 "1"
#define STR_FALSE "FALSE"
#define STR_false "false"
#define STR_0 "0"
/*
#define OCPP_STR_EnergyActiveImportRegister "Energy.Active.Import.Register" //累计充电电量 单位kWh
#define OCPP_STR_PowerActiveImport "Power.Active.Import" //充电功率 单位W
#define OCPP_STR_CurrentImport "Current.Import" //充电电流 单位A
#define OCPP_STR_Voltage "Voltage" //电压 单位V
#define OCPP_STR_SOC "SoC" //电池电量状态(State of Charge
#define OCPP_STR_Temperature "Temperature" //温度(桩内)
*/
#define OCPP_STR_EnergyActiveExportRegister "Energy.Active.Export.Register" // 有功电能输出累计值(Wh
#define OCPP_STR_EnergyActiveImportRegister "Energy.Active.Import.Register" // 有功电能输入累计值(Wh
#define OCPP_STR_EnergyReactiveExportRegister "Energy.Reactive.Export.Register" // 无功电能输出累计值(VARh
#define OCPP_STR_EnergyReactiveImportRegister "Energy.Reactive.Import.Register" // 无功电能输入累计值(VARh
#define OCPP_STR_EnergyActiveExportInterval "Energy.Active.Export.Interval" // 有功电能输出时段值(Wh
#define OCPP_STR_EnergyActiveImportInterval "Energy.Active.Import.Interval" // 有功电能输入时段值(Wh
#define OCPP_STR_EnergyReactiveExportInterval "Energy.Reactive.Export.Interval" // 无功电能输出时段值(VARh
#define OCPP_STR_EnergyReactiveImportInterval "Energy.Reactive.Import.Interval" // 无功电能输入时段值(VARh
#define OCPP_STR_PowerActiveExport "Power.Active.Export" // 有功功率输出(W
#define OCPP_STR_PowerActiveImport "Power.Active.Import" // 有功功率输入(W
#define OCPP_STR_PowerOffered "Power.Offered" // 充电桩可提供的最大有功功率(W)
#define OCPP_STR_PowerReactiveExport "Power.Reactive.Export" // 无功功率输出(VAR
#define OCPP_STR_PowerReactiveImport "Power.Reactive.Import" // 无功功率输入(VAR
#define OCPP_STR_PowerFactor "Power.Factor" // 功率因数(无单位,0~1
#define OCPP_STR_CurrentImport "Current.Import" // 输入电流(A
#define OCPP_STR_CurrentExport "Current.Export" // 输出电流(A
#define OCPP_STR_CurrentOffered "Current.Offered" // 充电桩可提供的最大电流(A
#define OCPP_STR_Voltage "Voltage" // 实时线电压(V
#define OCPP_STR_Frequency "Frequency" // 电网实时频率(Hz
#define OCPP_STR_Temperature "Temperature" // 设备/电池实时温度(℃)
#define OCPP_STR_SOC "SoC" // 电池剩余电量(%
#define OCPP_STR_RPM "RPM" // 风扇转速(转/分钟)
#define OCPP_STR_CurrentRequest "Current.Request" // 请求电流
#define OCPP_STR_VoltageRequest "Voltage.Request" // 请求电压值
#define OCPP_STR_Raw "Raw" //明文(不加密)
#define OCPP_STR_Body "Body" //整桩
#define OCPP_STR_EV "EV" //整桩
#define OCPP_STR_Outlet "Outlet" //出口
#define OCPP_STR_L1 "L1" //
#define OCPP_STR_L2 "L2" //
#define OCPP_STR_L3 "L3" //
#define OCPP_STR_Wh "Wh"
#define OCPP_STR_kWh "kWh" //
#define OCPP_STR_W "W" //
#define OCPP_STR_V "V" //
#define OCPP_STR_A "A" //
#define OCPP_STR_Celsius "Celsius" // 摄氏度
#define OCPP_STR_Percent "Percent" //百分比*/
#define OCPP_STR_Accepted "Accepted" //接受
#define OCPP_STR_Rejected "Rejected" //拒绝
#define OCPP_STR_RebootRequired "RebootRequired" //需要重启
#define OCPP_STR_NotSupported "NotSupported" //不支持
#define OCPP_STR_Blocked "Blocked" //阻塞
#define OCPP_STR_Expired "Expired" //过期
#define OCPP_STR_Invalid "Invalid" //无效
#define OCPP_STR_ConcurrentTx "ConcurrentTx" //并发事务
// --------------------------------------------------------
// 核心配置参数(数据类型与功能说明)
// --------------------------------------------------------
#define KEY_ALLOW_OFFLINE_TX_FOR_UNKNOWN_ID "AllowOfflineTxForUnknownId" /* 可选 允许未知用户ID的离线交易 (布尔: 0/1) */
#define KEY_AUTHORIZATION_CACHE_ENABLED "AuthorizationCacheEnabled" /* 可选 启用本地授权缓存 (布尔: 0/1) */
#define KEY_AUTHORIZE_REMOTE_TX_REQUESTS "AuthorizeRemoteTxRequests" /* 必选 允许远程启动充电事务 (布尔: 0/1) */
#define KEY_BLINK_REPEAT "BlinkRepeat" /* 可选 充电桩指示灯闪烁次数 (整数: 次数) */
#define KEY_CLOCK_ALIGNED_DATA_INTERVAL "ClockAlignedDataInterval" /* 必选 时钟对齐的数据上报间隔 (整数: 秒) */
#define KEY_CONNECTION_TIMEOUT "ConnectionTimeOut" /* 必选 TCP连接超时时间 (整数: 秒) */
#define KEY_CONNECTOR_PHASE_ROTATION "ConnectorPhaseRotation" /* 必选 连接器相位旋转配置 (字符串: L1/L2/L3顺序) */
#define KEY_CONNECTOR_PHASE_ROTATION_MAX_LENGTH "ConnectorPhaseRotationMaxLength" /* 可选 相位旋转配置最大长度 (整数: 字节数) */
#define KEY_GET_CONFIGURATION_MAX_KEYS "GetConfigurationMaxKeys" /* 必选 GetConfiguration请求最大返回Key数 (整数: 数量) */
#define KEY_HEARTBEAT_INTERVAL "HeartbeatInterval" /* 必选 心跳间隔 (整数: 秒) */
#define KEY_LIGHT_INTENSITY "LightIntensity" /* 可选 充电桩灯光亮度 (整数: 百分比) */
#define KEY_LOCAL_AUTHORIZE_OFFLINE "LocalAuthorizeOffline" /* 必选 本地离线授权模式 (布尔: 0/1) */
#define KEY_LOCAL_PRE_AUTHORIZE "LocalPreAuthorize" /* 必选 本地预授权模式 (布尔: 0/1) */
#define KEY_MAX_ENERGY_ON_INVALID_ID "MaxEnergyOnInvalidId" /* 可选 无效ID用户的最大允许充电能量 (整数: Wh) */
#define KEY_METER_VALUES_ALIGNED_DATA "MeterValuesAlignedData" /* 必选 对齐数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
#define KEY_METER_VALUES_ALIGNED_DATA_MAX_LENGTH "MeterValuesAlignedDataMaxLength" /* 可选 对齐数据列表最大长度 (整数: 字符数) */
#define KEY_METER_VALUES_SAMPLED_DATA "MeterValuesSampledData" /* 必选 采样数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
#define KEY_METER_VALUES_SAMPLED_DATA_MAX_LENGTH "MeterValuesSampledDataMaxLength" /* 可选 采样数据列表最大长度 (整数: 字符数) */
#define KEY_METER_VALUE_SAMPLE_INTERVAL "MeterValueSampleInterval" /* 必选 采样间隔 (整数: 秒) */
#define KEY_MINIMUM_STATUS_DURATION "MinimumStatusDuration" /* 可选 状态最小持续时间 (整数: 秒) */
#define KEY_NUMBER_OF_CONNECTORS "NumberOfConnectors" /* 必选 充电桩连接器数量 (整数: 数量) */
#define KEY_RESET_RETRIES "ResetRetries" /* 必选 复位重试次数 (整数: 次数) */
#define KEY_STOP_TXN_ON_EV_SIDE_DISCONNECT "StopTransactionOnEVSideDisconnect" /* 必选 EV端断开时停止事务 (布尔: 0/1) */
#define KEY_STOP_TXN_ON_INVALID_ID "StopTransactionOnInvalidId" /* 必选 无效ID时停止事务 (布尔: 0/1) */
#define KEY_STOP_TXN_ALIGNED_DATA "StopTxnAlignedData" /* 必选 事务停止时的对齐数据项 (字符串: 逗号分隔的measurand值) */
#define KEY_STOP_TXN_ALIGNED_DATA_MAX_LENGTH "StopTxnAlignedDataMaxLength" /* 可选 停止事务对齐数据最大长度 (整数: 字符数) */
#define KEY_STOP_TXN_SAMPLED_DATA "StopTxnSampledData" /* 必选 停止事务时的采样数据项 (字符串: 逗号分隔的measurand值) */
#define KEY_STOP_TXN_SAMPLED_DATA_MAX_LENGTH "StopTxnSampledDataMaxLength" /* 可选 停止事务采样数据最大长度 (整数: 字符数) */
#define KEY_SUPPORTED_FEATURE_PROFILES "SupportedFeatureProfiles" /* 必选 支持的功能配置文件 (字符串: 逗号分隔的FeatureProfile值) */
#define KEY_SUPPORTED_FEATURE_PROFILES_MAX_LENGTH "SupportedFeatureProfilesMaxLength" /* 可选 功能配置文件列表最大长度 (整数: 字符数) */
#define KEY_TRANSACTION_MESSAGE_ATTEMPTS "TransactionMessageAttempts" /* 必选 事务消息重试次数 (整数: 次数) */
#define KEY_TRANSACTION_MESSAGE_RETRY_INTERVAL "TransactionMessageRetryInterval" /* 必选 事务消息重试间隔 (整数: 秒) */
#define KEY_UNLOCK_CONNECTOR_ON_EV_DISCONNECT "UnlockConnectorOnEVSideDisconnect" /* 必选 EV端断开时解锁连接器 (布尔: 0/1) */
#define KEY_WEBSOCKET_PING_INTERVAL "WebSocketPingInterval" /* 可选 WebSocket Ping间隔 (整数: 秒) */
// --------------------------------------------------------
// 本地授权与充电配置管理
// --------------------------------------------------------
#define KEY_LOCAL_AUTH_LIST_ENABLED "LocalAuthListEnabled" /* 必选 启用本地授权列表 (布尔: 0/1) */
#define KEY_LOCAL_AUTH_LIST_MAX_LENGTH "LocalAuthListMaxLength" /* 必选 本地授权列表的最大条目数 (整数) */
#define KEY_SEND_LOCAL_LIST_MAX_LENGTH "SendLocalListMaxLength" /* 必选 发送本地列表的最大条目数 (整数) */
// --------------------------------------------------------
// 连接器与充电行为控制
// --------------------------------------------------------
#define KEY_RESERVE_CONNECTOR_ZERO_SUPPORTED "ReserveConnectorZeroSupported" /* 可选 是否支持预留连接器0 (布尔: 0/1) */
// --------------------------------------------------------
// 充电计划与配置限制
// --------------------------------------------------------
#define KEY_CHARGE_PROFILE_MAX_STACK_LEVEL "ChargeProfileMaxStackLevel" /* 必选 充电配置最大堆叠层级 (整数) */
#define KEY_CHARGING_SCHEDULE_ALLOWED_RATE_UNIT "ChargingScheduleAllowedChargingRateUnit" /* 必选 充电速率允许单位 (字符串: "A"或"W") */
#define KEY_CHARGING_SCHEDULE_MAX_PERIODS "ChargingScheduleMaxPeriods" /* 必选 充电计划最大时段数 (整数) */
#define KEY_CONNECTOR_SWITCH_3TO1_PHASE_SUPPORTED "ConnectorSwitch3to1PhaseSupported" /* 可选 是否支持三相切单相充电 (布尔: 0/1) */
#define KEY_MAX_CHARGING_PROFILES_INSTALLED "MaxChargingProfilesInstalled" /* 必选 最大可安装充电配置数 (整数) */
// --------------------------------------------------------
// 私有配置
// --------------------------------------------------------
#define KEY_QRCODE_TEXT_CONNECTOR1 "QRCodeTextOfConnector-1" /* 枪1二维码 */
#define KEY_QRCODE_TEXT_CONNECTOR2 "QRCodeTextOfConnector-2" /* 枪2二维码 */
#endif
#endif
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
#ifndef _BS_WEBSOCKET_H_
#define _BS_WEBSOCKET_H_
#include "publicdata/type.h"
#include "publicdata/public_define.h"
#include <string.h>
#include <stdio.h>
#if BS_OCPP_EN //OCPP平台使能
#define OCPP_WEBSOCKET_EN 1
/** WebSocket HTTP 握手是否携带 Authorization: Basic(桩号:密钥);0=不携带,适用于不要求 HTTP Basic 的平台 */
#ifndef OCPP_WS_HTTP_AUTHORIZATION_EN
#define OCPP_WS_HTTP_AUTHORIZATION_EN 1
#endif
/** GET 路径是否在桩号后加尾斜杠;PLN 等为 /wsspkludev/CPID(默认 0,桩号后无 / */
#ifndef OCPP_WS_GET_PATH_TRAILING_SLASH
#define OCPP_WS_GET_PATH_TRAILING_SLASH 0
#endif
typedef enum
{
WDT_NULL = 0, // 非标准数据包
WDT_MINDATA, // 0x0:中间数据包
WDT_TXTDATA, // 0x1txt类型数据包
WDT_BINDATA, // 0x2bin类型数据包
WDT_DISCONN, // 0x8:断开连接类型数据包 收到后需手动 close(fd)
WDT_PING, // 0x8ping类型数据包 ws_recv 函数内自动回复pong
WDT_PONG, // 0xApong类型数据包
} Ws_DataType;
int websocket_build();
void ocpp_recv();
int ws_send(void* buff, int buffLen, bool mask, Ws_DataType type);
#endif
#endif
+287
View File
@@ -0,0 +1,287 @@
#include "plat_comm/plat_comm_task.h"
#include "plat_comm/impl/bs_connect_impl.h"
#include "plat_comm/impl/bs_public_impl.h"
#if EC200A_4G_EN
#include "plat_comm/4g_module/ec200a_4g_cfg.h"
#endif
#include "app_init/app_init.h"
#include "app_init/task_lock.h"
#include "publicdata/publicdata.h"
#include "wdt_task/wdt_task.h"
#define PLATCOMM_TASK_ID (TASK_ID_PlatComm)
#define PLATCOMM_SLEEP_TIME_MS() (MY_GET_SLEEP_TIME(PLATCOMM_TASK_ID))
#define PLATCOMM_LOCK_INIT() (u8_task_lock_ctrl(PLATCOMM_TASK_ID, TASK_LOCK_OP_INIT))
#define PLATCOMM_LOCK_TAKE() (u8_task_lock_ctrl(PLATCOMM_TASK_ID, TASK_LOCK_OP_TAKE))
#define PLATCOMM_LOCK_GIVE() (u8_task_lock_ctrl(PLATCOMM_TASK_ID, TASK_LOCK_OP_GIVE))
#define PLATCOMM_FEED_DOG() (v_wdt_setFlag(PLATCOMM_TASK_ID))
#define PLATCOMM_LOG(fmt, ...) MYLOG_MSG(PLATCOMM_TASK_ID, (fmt), ##__VA_ARGS__)
BS_TASK_CTRL_DATA s_bs_task_ctrl;
typedef struct
{
const char *Master;
void (*v_bs_data_init)(U8_T); //数据初始化
U8_T (*v_bs_tcp_connect)(void); //tcp建立
U8_T (*v_bs_logon_connect)(void); //平台登录
void (*v_bs_recv_data)(void); //平台接收数据
void (*v_bs_recv_data_divide)(U8_T,U8_T*,U16_T); //平台接收数据
void (*v_bs_send_ctrl)(void); //平台发送逻辑控制
void (*v_bs_private_logic)(U8_T); //私有逻辑执行函数
} MasterFuncPtl;
const MasterFuncPtl MasterFuncArray[MAX_BS_NUM]= //最大2个平台
{
// 字符名称 数据初始化 TCP连接 登录连接 接收解析函数 发送控制函数 私有逻辑执行函数
#if (BS_IES_EN)
/* 积成 IESZTbs_ies_ctrl=状态机,bs_ies_interface=0x68 组解帧;阶段1 登录+心跳 */
{"ieszt", v_ies_init_data, v_ies_tcp_connect, v_ies_logo_on,v_ies_recv_data,v_ies_recv_unpack,v_ies_send_ctrl,v_ies_private_logic},
#endif
#if (BS_GWSDK_EN)
{"gwsdk", v_sdk_init_data, v_sdk_tcp_connect, v_sdk_logo_on,v_sdk_recv_data,NULL,v_sdk_send_ctrl,v_sdk_private_crel}, //0积成平台
#endif
#if (BS_OCPP_EN) //OCPP平台使能
{"ocpp", v_ocpp_init_data, v_ocpp_tcp_connect, v_ocpp_logo_on,v_ocpp_recv_data,NULL,v_ocpp_send_ctrl,v_ocpp_private_logic}, //1 云快充平台
#endif
#if ((BS_IES_EN + BS_GWSDK_EN + BS_OCPP_EN) < MAX_BS_NUM)
{"null", NULL, NULL, NULL, NULL, NULL, NULL, NULL},
#endif
};
/****************************************************************************系统集成改动部分-开始******************************************************/
#if 1 //公共接口 外部调用函数
/****************************************************
*函数名称: v_set_bs_renew()
*功能说明: 设置联网状态 重新连接
*参数说明:
* 输入参数: U8_T u8_bs 平台类型
* 输出参数:
****************************************************/
void v_set_bs_renew(U8_T u8_bs)
{
/* 网口:必须先关闭 socket,否则只清 fd 会导致 LwIP 侧 socket 泄漏,重连几次后 connect 失败(再连也连不上) */
if (s_bs_task_ctrl.s_bs_ctrl[u8_bs].u8_netType != 0) {
v_bs_eth_close_socket(u8_bs); /* 内部已置 fd=-1、u8_TCP_connect_flag=0 */
} else {
s_bs_task_ctrl.s_bs_ctrl[u8_bs].u8_TCP_connect_flag = 0;
s_bs_task_ctrl.s_bs_ctrl[u8_bs].fd = (S32_T)-1;
s_bs_task_ctrl.s_bs_ctrl[u8_bs].u8_4g_soft_tcp_reconnect = 1;
s_bs_task_ctrl.s_bs_ctrl[u8_bs].u8_4g_tcp_soft_fail_streak = 0;
}
s_bs_task_ctrl.s_bs_ctrl[u8_bs].u8_log_on_flag = 0;
/* 勿在此处调用 v_bs_data_init:原先会触发 v_bs_4G_data_init 整网重驻网。
* 断连后仅关 Socket + QIOPEN 由 u8_tcp_connect / v_ocpp_init_data_ex(...,0) 完成(不清 OCPP_DATA_CTRL)。
* 上电首次 v_ocpp_init_data`v_ocpp_mv_offline_init` + 运行态 RAM 重置 + v_ocpp_init_data_ex(...,1)
* TCP 长时间失败在 BS_ocpp_ctrl 内调用 `v_ocpp_init_data`(与上电同路径)。 */
}
#endif
void v_plat_data_init(void)
{
memset(&s_bs_task_ctrl , 0, sizeof(s_bs_task_ctrl));
s_bs_task_ctrl.u8_gunCnt = u32_bs_get_sys_data(0, E_BS_GET_SYS_DATA_GUN_NUM, 0);
/* 通过 u32_bs_get_sys_data 获取平台使能:0 平台A1 平台B */
s_bs_task_ctrl.s_bs_ctrl[0].bs_enable = (U8_T)u32_bs_get_sys_data(0, E_BS_GET_SYS_DATA_PLAT_EN, 0);
s_bs_task_ctrl.s_bs_ctrl[1].bs_enable = (U8_T)u32_bs_get_sys_data(1, E_BS_GET_SYS_DATA_PLAT_EN, 0);
if(s_bs_task_ctrl.s_bs_ctrl[0].bs_enable &&
s_bs_task_ctrl.s_bs_ctrl[1].bs_enable)
s_bs_task_ctrl.u8_4g_dispense_en = 1; //socketA和socketB使能 4g 数据分发使能
/* 通过 u32_bs_get_sys_data 获取联网类型:0 4G1 网线 */
s_bs_task_ctrl.s_bs_ctrl[0].u8_netType = u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_NET_TYPE,0);
s_bs_task_ctrl.s_bs_ctrl[1].u8_netType = u32_bs_get_sys_data(1,E_BS_GET_SYS_DATA_NET_TYPE,0);
}
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
/** 是否有 FTP/远程升级在跑(含 OCPP 日志上传:CallResult 前 u8_updata_flag 仍为 0 的窗口) */
static U8_T u8_plat_comm_ftp_transfer_active(void)
{
U8_T active =
(U8_T)((s_bs_task_ctrl.s_bs_ctrl[0].u8_updata_flag != 0U
|| s_bs_task_ctrl.s_bs_ctrl[1].u8_updata_flag != 0U) ? 1U : 0U);
#if BS_OCPP_EN
if (active == 0U
&& t_ftp_update_info.u8_netType == 0U
&& s_bs_task_ctrl.ocpp_data.u8_log_file_upload != 0U) {
active = 1U;
}
#endif
return active;
}
/** 各平台一轮:建链 / 登录 / send / recv / private_logic(与 FTP 步进分离,占满「联网」段) */
static void v_plat_comm_network_round(void)
{
U8_T i;
for (i = 0; i < MAX_BS_NUM; i++) {
if (s_bs_task_ctrl.s_bs_ctrl[i].bs_enable == 0) {
continue;
}
if (s_bs_task_ctrl.s_bs_ctrl[i].u8_TCP_connect_flag == 0) {
if (MasterFuncArray[i].v_bs_tcp_connect != NULL) {
s_bs_task_ctrl.s_bs_ctrl[i].u8_TCP_connect_flag =
MasterFuncArray[i].v_bs_tcp_connect();
}
if (s_bs_task_ctrl.s_bs_ctrl[i].u8_TCP_connect_flag) {
Plat_Comm_LOG("[%d] tcp connect\n", i);
}
} else {
if (s_bs_task_ctrl.s_bs_ctrl[i].u8_log_on_flag == 0) {
if (MasterFuncArray[i].v_bs_logon_connect != NULL) {
s_bs_task_ctrl.s_bs_ctrl[i].u8_log_on_flag =
MasterFuncArray[i].v_bs_logon_connect();
}
if (s_bs_task_ctrl.s_bs_ctrl[i].u8_log_on_flag) {
Plat_Comm_LOG("[%d] logOn is ok\n", i);
}
} else {
if (MasterFuncArray[i].v_bs_send_ctrl != NULL) {
MasterFuncArray[i].v_bs_send_ctrl();
}
}
}
if (MasterFuncArray[i].v_bs_recv_data != NULL) {
MasterFuncArray[i].v_bs_recv_data();
}
if (MasterFuncArray[i].v_bs_private_logic != NULL) {
MasterFuncArray[i].v_bs_private_logic(i);
}
}
}
/** FTP 单步;4Gt_ftp_update_info.u8_netType==0)时随后双路 recv,便于收 +QFTP* 等尾 URC */
static void v_plat_comm_ftp_round(void)
{
U8_T i;
v_ftp_update_action();
if (t_ftp_update_info.u8_netType != 0U) {
return;
}
for (i = 0; i < MAX_BS_NUM; i++) {
if (s_bs_task_ctrl.s_bs_ctrl[i].bs_enable == 0) {
continue;
}
if (MasterFuncArray[i].v_bs_recv_data != NULL) {
MasterFuncArray[i].v_bs_recv_data();
}
}
}
#endif
void v_plat_comm_task(void *arg)
{
uint16_t sleep_time_ms;
uint32_t last_send_time;
uint32_t current_time;
static uint8_t s_plat_comm_net_ftp_phase = 0U;
(void)arg;
(void)PLATCOMM_LOCK_INIT();
sleep_time_ms = PLATCOMM_SLEEP_TIME_MS();
last_send_time = get_current_seconds();
v_plat_data_init();
if(s_bs_task_ctrl.s_bs_ctrl[0].bs_enable != 0
&& MasterFuncArray[0].v_bs_data_init != NULL)
MasterFuncArray[0].v_bs_data_init(0);
if(s_bs_task_ctrl.s_bs_ctrl[1].bs_enable != 0
&& MasterFuncArray[1].v_bs_data_init != NULL)
MasterFuncArray[1].v_bs_data_init(1);
for (;;) {
PLATCOMM_FEED_DOG();
current_time = get_current_seconds();
if ((TASK_RUN_PRINT_TIME != 0U) && (u32_safe_seconds_since(last_send_time) >= TASK_RUN_PRINT_TIME)) {
if (PLATCOMM_LOCK_TAKE() == 1U) {
PLATCOMM_LOG("plat comm task is runing, sleep time is %ums", sleep_time_ms);
(void)PLATCOMM_LOCK_GIVE();
}
last_send_time = current_time;
}
#if 0 //屏蔽联网测试
mSleep(u16_leep_time);
// if(MasterFuncArray[0].v_bs_private_logic != NULL) //私有逻辑执行函数 例如超时计数等,注意判断网路连接状态
// MasterFuncArray[0].v_bs_private_logic(0);
continue;
#endif
#if SYSTEM_TYPE == SYSTEM_TYPE_CCU601E_D
/* FTP 活跃时:相邻两个 Plat_Comm 周期各只做一端——周期 A 仅联网、周期 B 仅 FTP,互不叠在同一周期内。
* 无 FTP 时:每周期只跑联网,并把相位复位为 0(下次进入 FTP 仍先联网)。4G FTP 步内为 `v_ftp_update_action` 后再双路 recv。 */
{
U8_T const ftp_active = u8_plat_comm_ftp_transfer_active();
if (ftp_active == 0U) {
s_plat_comm_net_ftp_phase = 0U;
v_plat_comm_network_round();
} else if (s_plat_comm_net_ftp_phase == 0U) {
v_plat_comm_network_round();
s_plat_comm_net_ftp_phase = 1U;
} else {
v_plat_comm_ftp_round();
s_plat_comm_net_ftp_phase = 0U;
}
}
#else
/* 遍历平台 A/B */
for (U8_T i = 0U; i < 2U; i++) {
if (s_bs_task_ctrl.s_bs_ctrl[i].bs_enable == 0U) {
continue;
}
/* 状态机:未建链 → 建链;已建链未登录 → 登录;已登录 → 周期发送 */
if (s_bs_task_ctrl.s_bs_ctrl[i].u8_TCP_connect_flag == 0U) {
if (MasterFuncArray[i].v_bs_tcp_connect != NULL) {
s_bs_task_ctrl.s_bs_ctrl[i].u8_TCP_connect_flag = MasterFuncArray[i].v_bs_tcp_connect();
}
} else if (s_bs_task_ctrl.s_bs_ctrl[i].u8_log_on_flag == 0U) {
if (MasterFuncArray[i].v_bs_logon_connect != NULL) {
s_bs_task_ctrl.s_bs_ctrl[i].u8_log_on_flag = MasterFuncArray[i].v_bs_logon_connect();
}
} else if (MasterFuncArray[i].v_bs_send_ctrl != NULL) {
MasterFuncArray[i].v_bs_send_ctrl();
}
/* 接收:4G 双 socket 分发模式 或 单平台透传模式 */
if (s_bs_task_ctrl.u8_4g_dispense_en != 0U) {
v_4g_wh5_data_divide_recv(MasterFuncArray[0].v_bs_recv_data_divide, MasterFuncArray[1].v_bs_recv_data_divide);
} else if (MasterFuncArray[i].v_bs_recv_data != NULL) {
MasterFuncArray[i].v_bs_recv_data();
}
/* 协议私有逻辑:状态变位检测、超时处理等 */
if (MasterFuncArray[i].v_bs_private_logic != NULL) {
MasterFuncArray[i].v_bs_private_logic(i);
}
}
#endif
/* 动态 sleep:扣除本周期发送占用的时间,保证整体周期稳定 */
if (s_bs_task_ctrl.u8_sendCnt * Plat_Send_Sleep < sleep_time_ms) {
mSleep(sleep_time_ms - s_bs_task_ctrl.u8_sendCnt * Plat_Send_Sleep);
} else {
mSleep(sleep_time_ms);
}
s_bs_task_ctrl.u8_sendCnt = 0U;
}
}
+103
View File
@@ -0,0 +1,103 @@
#ifndef PLAT_COMM_TASK_H
#define PLAT_COMM_TASK_H
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
extern "C" {
#endif
/*------ Exported includes(头文件) ----------------------*/
#include "main.h"
#include "../mylog/mylog.h"
#include "plat_comm/impl/bs_public_impl.h"
#include "plat_comm/impl/bs_connect_impl.h"
#include "tls/bs_tls_mbed.h"
/*------ Exported macro(宏定义) -------------------------*/
#define Plat_Comm_LOG(fmt, ...) \
MYLOG_MSG(TASK_ID_PlatComm,fmt, ##__VA_ARGS__) // PlatComm 任务通用打印
#define OCPP_PRINT_FLAG MYLOG_ID_FORCE_OCPP /**< OCPP 强制打印,见 OCPP_PRINTF_LOG */
#define IES_PRINT_FLAG MYLOG_ID_FORCE_IES /**< IES 强制打印,见 IES_PRINTF_LOG */
#if (BS_IES_EN)
#include "plat_comm/ieszt/bs_ies_ctrl.h"
#endif
#if (BS_GWSDK_EN)
#include "plat_comm/gwsdk/bs_gwsdk_ctrl.h"
#endif
#if BS_OCPP_EN //OCPP平台使能
#include "plat_comm/ocpp/BS_ocpp_ctrl.h"
#endif
#define Plat_Send_Sleep (50) //发送延时时间
/*------ Exported struct(结构体定义) --------------------*/
typedef struct
{
U8_T bs_enable; //使能标志
U8_T u8_TCP_connect_flag; //TCP连接建立标志
/** 4Gu8_netType==0):v_set_bs_renew 后置位;仅关模组 Socket 再 QIOPEN,不整网重驻网 */
U8_T u8_4g_soft_tcp_reconnect;
U8_T u8_4g_tcp_soft_fail_streak;
U8_T u8_log_on_flag; //登录标志
U8_T u8_netType; //联网方式 04G 1网线
U8_T u8_updata_flag; //远程升级标志 0未升级 1:ftp升级 2:sdk ota升级
//联网及外部交互控制数据 放置在最外层
struct
{
U8_T u8_pile_state_change_flag; //枪状态变位标志 1说明状态变化 0未变化
U8_T txFlag_tradUpload; //交易记录上送标识
U8_T online_flag; //重新上电标识
U8_T txFlag_startResult; //启动结果上送标识
U8_T txFlag_stopResult; //停止结果上送标识
U8_T txFlag_autoStart; //即插即充鉴权发送标志
U8_T txFlag_autoRwsult; //即插即充鉴权结果 0无意义 1鉴权成功 2鉴权失败
U8_T u8_bms_sendCnt; //bms属性发送次数
U8_T u8_tradUpload_cnt; //订单发送次数
U16_T u16_cmplte_bill_index; //上送订单索引号
}s_gundata[GUN_MAX_CNT];
S32_T fd; //以太网连接专用
ULONG ulong_restart_timer; //超时时间
#if (BS_TLS_MBEDTLS_EN)
U8_T is_secure; //当前 TCP 是否走 TLS0=明文 1=TLS
BS_TLS_CTX tls_ctx; //TLS 上下文,仅在网口+wss 时使用
#endif
}S_BS_CTRL_DATA;
typedef struct
{
U8_T u8_gunCnt; //枪数量
U8_T u8_4g_dispense_en; //4G模块串口分发使能标志
U8_T u8_sendCnt; //发送数量
S_BS_CTRL_DATA s_bs_ctrl[MAX_BS_NUM];
#if (BS_IES_EN)
S_BS_IES_CTRL_DATA s_ies_data;
#endif
#if (BS_GWSDK_EN)
S_GWSDK_CTRL_DATA s_gwsdk_ctrl;
#endif
#if (BS_OCPP_EN) //OCPP平台使能
OCPP_DATA_CTRL ocpp_data;
#endif
}BS_TASK_CTRL_DATA;
/*------ Exported variables(变量定义) -------------------*/
extern BS_TASK_CTRL_DATA s_bs_task_ctrl;
/*------ Exported function prototypes (函数声明) -------*/
void v_set_bs_renew(U8_T u8_bs);
void v_plat_comm_task(void *arg);
#ifdef __cplusplus
}
#endif
#endif /* PLAT_COMM_TASK_H */
+42
View File
@@ -0,0 +1,42 @@
# plat_comm 模块说明
> 路径:`app/plat_comm/`
> 返回主说明:[README](../../README.md)
## 1. 模块职责
- 平台通信任务模板实现;
- 负责平台对接业务的任务框架与周期调度;
- 为后续平台协议接入预留统一入口。
## 2. 关键文件
- `plat_comm_task.c`
- `plat_comm_task.h`
## 3. 功能说明
- 提供标准任务模板(初始化、循环、延时、喂狗);
- 预留平台上报/下发命令处理位置;
-`app_init` 任务表和任务锁体系保持一致。
## 4. 调度与对外接口
该模块由 FreeRTOS 任务 `PlatComm` 周期运行(单位:ms),周期来自 `app/app_init/app_init.c`
- `PlatComm``100ms`(任务入口:`v_plat_comm_task(void *arg)`
对外接口(来自 `plat_comm_task.h`):
- `void v_Plat_Comm_init(void)`:平台模块初始化
- `void v_plat_comm_task(void *arg)`:平台通信任务入口
- `U8_T u8_plat_comm_get_gun_num(void)`:获取枪数量(供通信与业务对齐)
## 5. 维护建议
- 平台协议接入时建议按“通信层/业务层”拆分实现;
- 大报文处理建议增加缓存长度和超时保护。
## 6. 关联文档
- 主索引:[README](../../README.md)
@@ -0,0 +1,348 @@
# 平台联网任务功能说明(CCU601E_D / plat_comm
本文档描述 **`CCU601E_D\app\plat_comm`** 目录内**平台联网任务**的整体架构、状态机、链路层与业务层分工,以及与计量、Flash、OCPP 的衔接。内容基于当前工程源码归纳;各子模块细节见文末**关联文档**。
---
## 1. 模块定位
| 项目 | 说明 |
|------|------|
| **任务** | FreeRTOS 任务 **`TASK_ID_PlatComm`**,入口 **`v_Plat_Comm`**,周期约 **100ms**(由任务睡眠配置决定)。 |
| **职责** | 多平台(最多 **2 路**)的 **TCP 建链 → 平台登录 → 周期收发 → 私有逻辑**;与 **FTP 远程升级 / 诊断日志上传** 分时调度;对上层屏蔽 **4G / 网线** 差异。 |
| **协议栈** | 业务协议由 **`MasterFuncArray`** 注册(当前工程默认 **OCPP 1.6 + WebSocket**);传输由 **`bs_connect_impl`** 统一 **发送/接收**。 |
| **全局控制块** | **`BS_TASK_CTRL_DATA s_bs_task_ctrl`**`plat_comm_task.c` / `plat_comm_task.h`)。 |
---
## 2. 编译开关与平台槽位
定义见 **`app/publicdata/public_define.h`**(摘要):
| 宏 | 典型值 | 含义 |
|----|--------|------|
| **`MAX_BS_NUM`** | 2 | 同时支持的平台通道数(槽位 0 / 1)。 |
| **`BS_OCPP_EN`** | 1 | OCPP 协议栈参与编译。 |
| **`BS_IES_EN`** | 1 | 积成平台(`plat_comm/ieszt/`,登录+心跳);详见 **[ieszt/IES积成平台功能说明.md](ieszt/IES积成平台功能说明.md)**。 |
| **`BS_GWSDK_EN`** | 0 | 国网 SDK 平台(`gwsdk/`,本仓库目录可能未包含)。 |
| **`BS_TLS_MBEDTLS_EN`** | 视工程 | 网口 **wss://** 时 MCU 侧 **mbedTLS** 封装。 |
| **`LWIP_FTP_UPGRADE_ENABLE`** | 视工程 | 网线 **LwIP FTP** 固件下载状态机。 |
运行时使能与联网方式**不只看宏**,还读系统配置:
- **`E_BS_GET_SYS_DATA_PLAT_EN`**`u8_gunNo` = 0 → 平台 A,= 1 → 平台 B;0 关闭、1 使能。
- **`E_BS_GET_SYS_DATA_NET_TYPE`**0 = **4GEC200A**1 = **网线(LwIP**
初始化在 **`v_plat_data_init()`**`plat_comm_task.c`)中写入各槽位 **`bs_enable`**、**`u8_netType`**。
---
## 3. 目录结构一览
```
plat_comm/
├── plat_comm_task.c / .h # 联网任务主循环、平台函数表、重连入口
├── 平台联网任务功能说明.md # 本文档
├── impl/
│ ├── bs_connect_impl.c / .h # TCP/TLS 建链、统一 v_bs_send / u16_bs_recv
│ └── bs_public_impl.c / .h # 桩枪数据桥接、未结订单、FTP 调度、OCPP 配置读写
├── ocpp/ # OCPP 1.6(详见 ocpp/OCPP功能模块说明.md
├── ieszt/ # 积成 IESZT(详见 ieszt/IES积成平台功能说明.md
├── 4g_module/ # EC200A AT、TCP、FTP(详见 4G模块AT指令配置功能说明.md)
└── lwip_module/ # 网口 socket、LwIP FTP 升级(详见 lwip_ftp功能说明.md
```
---
## 4. 联网任务主状态机(`v_Plat_Comm`
### 4.1 平台函数表 `MasterFuncArray`
每个使能槽位绑定一组回调(**`plat_comm_task.c`**):
| 回调 | OCPP 实现 | 作用 |
|------|-----------|------|
| **`v_bs_data_init`** | `v_ocpp_init_data` | 上电/全量复位:OCPP 上下文、离线 MV FRAM、定时器等。 |
| **`v_bs_tcp_connect`** | `v_ocpp_tcp_connect` | **TCP + WebSocket 握手**(内部先 `u8_tcp_connect`)。 |
| **`v_bs_logon_connect`** | `v_ocpp_logo_on` | **BootNotification** 登录,成功置 **`u8_log_on_flag`**。 |
| **`v_bs_recv_data`** | `v_ocpp_recv_data``ocpp_recv` | 收包并解析 JSONCALL / CALLRESULT)。 |
| **`v_bs_send_ctrl`** | `v_ocpp_send_ctrl``ocpp_send_mag_ctrl` | 周期/事件驱动主动上送(Heartbeat、MV、StopTx 等)。 |
| **`v_bs_private_logic`** | `v_ocpp_private_logic` | 超时重连、未结订单、离线 MV 落盘、鉴权检查等。 |
未使能槽位(**`bs_enable == 0`**)在本轮循环中跳过。
**`BS_IES_EN`** 时槽位 **0** 常为 **`"ieszt"`**`v_ies_*`),协议态在 **`s_bs_task_ctrl.s_ies_data`**(含 **`IES_SEND_TIMER` + `s_timerCnt[]`**,与 OCPP **`s_timerCnt`** 同套路),详见 **[ieszt/IES积成平台功能说明.md](ieszt/IES积成平台功能说明.md)**。
### 4.2 单槽位连接状态(`S_BS_CTRL_DATA`
| 字段 | 含义 |
|------|------|
| **`u8_TCP_connect_flag`** | 1 = 传输层已就绪(OCPP 下为 TCP+WS 成功)。 |
| **`u8_log_on_flag`** | 1 = 平台登录成功(BootNotification Accepted/Pending)。 |
| **`u8_netType`** | 0 = 4G1 = 网线。 |
| **`fd`** | 网口 socket 描述符;4G 路径不使用。 |
| **`is_secure` / `tls_ctx`** | 网口 **wss** 时 TLS 会话(**`BS_TLS_MBEDTLS_EN`**)。 |
| **`u8_updata_flag`** | 远程升级进行中:0 无,1 FTP 固件,2 SDK OTA 等。 |
| **`u8_4g_soft_tcp_reconnect`** | 4G**软重连**(仅 QICLOSE+QIOPEN,不整网重驻网)。 |
| **`s_gundata[]`** | 每枪:`txFlag_tradUpload``txFlag_startResult``txFlag_autoStart` 等与 OCPP/flow 协同。 |
### 4.3 每周期调度顺序(`v_plat_comm_network_round`
对每个使能槽位 **i**
```text
若 u8_TCP_connect_flag == 0
→ v_bs_tcp_connect() // 建链
否则若 u8_log_on_flag == 0
→ v_bs_logon_connect() // 登录
否则
→ v_bs_send_ctrl() // 已登录:主动发送
→ v_bs_recv_data() // 收包(任意阶段均可收)
→ v_bs_private_logic(i) // 私有逻辑(未结单、离线 MV、超时等)
```
日志示例:`[0] tcp connect``[0] logOn is ok``Plat_Comm_LOG`)。
### 4.4 与 FTP 的分时调度(4G 重点)
**`u8_plat_comm_ftp_transfer_active()`** 为真时(**`u8_updata_flag != 0`**,或 4G 下 **`u8_log_file_upload`** 置位):
- **偶数周期**:仅 **`v_plat_comm_network_round()`**(保证 OCPP 仍能 recv/send)。
- **奇数周期**:仅 **`v_plat_comm_ftp_round()`** → **`v_ftp_update_action()`** + 双路 **`v_bs_recv_data()`**(收 **+QFTP*** 等 URC)。
无 FTP 时:每周期只跑联网,相位复位。
> 原因:4G 与 FTP **共用一根 UART**;若在同一周期内既跑大量 AT 又跑 **QISEND**,易出现粘连、**`break off=0`**。详见 **`4g_module/4G模块AT指令配置功能说明.md`**。
---
## 5. 重连与初始化策略(`v_set_bs_renew`
**`void v_set_bs_renew(U8_T u8_bs)`**`plat_comm_task.c`)为各模块统一的「断链重连」入口:
| 联网方式 | 行为 |
|----------|------|
| **网线** | **`v_bs_eth_close_socket`**:关 socket、清 **`fd`**、**`u8_TCP_connect_flag`**,避免 LwIP **socket 泄漏**导致「再也连不上」。 |
| **4G** | 清 TCP 标志、**`fd=-1`**,置 **`u8_4g_soft_tcp_reconnect=1`**(下次 **`u8_tcp_connect`** 走软重开 Socket)。 |
| **公共** | **`u8_log_on_flag=0`****不在此处**调用 **`v_bs_data_init`**,避免每次断线都 **整模组重驻网**。 |
**全量 OCPP 复位**(含 **`v_ocpp_mv_offline_init`**、运行态 RAM)由 **`v_ocpp_init_data` / `v_ocpp_init_data_ex`** 在 **上电****TCP 长时间失败**(如 RX 超时 2min)时触发,与软重连区分。
OCPP 侧 **WS 握手失败**会关 socket 并清 **`u8_TCP_connect_flag`**,下一周期重试(**`v_ocpp_tcp_connect`**)。
---
## 6. 连接实现层(`impl/bs_connect_impl`
### 6.1 URL 解析
**`parse_websocket_url_simple(url, ws_url_t *)`** 解析配置中的服务器串,得到:
- **`host`**、**`port`**、**`path`**WebSocket 路径)
- **`is_secure`**`ws://` → 0`wss://` → 1
- **`apn_str`**4G 用,来自变量区 APN
全局 **`url_info`** 供网口/4G 共用。
### 6.2 `u8_tcp_connect(u8_id)`
| `u8_netType` | 路径 |
|--------------|------|
| **04G** | 若 **`u8_4g_soft_tcp_reconnect`**`s8_ec200a_tcp_socket_soft_reopen()`;失败累计达 **`EC200A_TCP_SOFT_REOPEN_FAIL_MAX`** 再 **`v_bs_4G_data_init()`**。否则 **`s8_ec200a_4g_init()`** 推进模组状态机至 Socket 就绪。 |
| **1(网线)** | **`eth_do_tcp_connect`**`netif_is_link_up` 检查 → **`lwip_tcp_connect`** → 可选 **`bs_tls_client_connect`**wss)。成功写 **`fd`**,返回 1。 |
### 6.3 统一收发
| 接口 | 4G | 网线 |
|------|-----|------|
| **`v_bs_send`** | 透传 **`v_4g_send_data`** / 非透传 **`v_ec200a_tcp_app_send`QISEND/QSSLSEND** | **`lwip_tcp_send`** 或 **`bs_tls_client_send`** |
| **`u16_bs_recv`** | **`u16_4g_recv_data`** / **`u16_ec200a_tcp_app_recv`QIRD/QSSLRECV** | **`lwip_tcp_recv`** / **`bs_tls_client_recv`**;对端关闭或 errno 异常 → **`eth_close_socket`** |
每次 **`v_bs_send`** 后 **`mSleep(Plat_Send_Sleep)`**(默认 50ms),并累加 **`u8_sendCnt`** 用于任务末尾睡眠补偿。
接收缓冲区上限:**`BS_BUF_SIZE_RECV`**(约 3KB,远程升级 JSON 可能较长)。
---
## 7. 公共实现层(`impl/bs_public_impl`
面向 **flow / meter / fault / UI / OCPP** 的**统一数据与控制 API**,是「业务世界」与「平台任务」的边界。
### 7.1 主要能力分类
| 分类 | 代表接口 | 说明 |
|------|----------|------|
| **系统/枪数据读** | **`u32_bs_get_sys_data`**、**`u8_get_str_data`** | 枪状态、电量、SOC、订单流水号、桩编号、4G 信号等。 |
| **充电控制** | **`v_bs_charge_action_ctrl`** | 启停充、改订单字段(含 **`preTradeNo`** 写入计量日志)。 |
| **状态/订单标志** | **`v_bs_log_txFlag_tradUpload`**、**`u8_bs_get_flow_state`** | 与 **`s_gundata`**、flowctrl 联动。 |
| **未结订单** | **`s8_plat_get_unsettled_order`** | 优先 **EEPROM 掉电临时单**,再 **Flash 未结索引**OCPP 补单数据源。 |
| **结算确认** | **`v_bs_platform_settlement_confirm`** | StopTx 成功后删未结索引。 |
| **FTP** | **`v_ftp_update_action`** | 按 **`t_ftp_update_info`** 与 **`u8_netType`** 分发 4G / LwIP FTP。 |
| **OCPP 配置** | **`u8_set_ocpp_cfg_info`**、**`v_get_ocpp_cfg_info`** | 读写 Flash 中的 OCPP 配置项。 |
| **本地鉴权** | **`v_ocpp_offline_local_authentication_check`**、**`u8_card_info_add`** | 离线卡列表与即插即充。 |
### 7.2 未结订单来源(与掉电续传)
**`s8_plat_get_unsettled_order`** 顺序:
1. **EEPROM 临时记录****`v_check_temp_chg_record_valid`**):充电中每 60s 快照;真正掉电后上电读出,置 **`E_FAULT_13073`**(异常掉电)。**充电中(flow=2)不当作掉电单**,避免误 StopTx。
2. **Flash 未结列表****`unsettled_order_mng`**):正常结束但未收到平台确认的订单索引。
计量侧:**`v_meterlog_restore_temp_from_eeprom`** 上电恢复 RAM**`preTradeNo` 更新后立即写 EEPROM**(见 **`meter_calculate_impl.c`**)。
### 7.3 FTP 全局参数 `T_FTP_UPDATE_INFO`
由 OCPP **UpdateFirmware** / **GetDiagnostics** 解析 **`parse_ftp_url()`** 填入:
- 服务器地址、端口、用户、密码、路径、文件名
- **`u8_netType`**:决定走 **4G AT FTP** 还是 **LwIP FTP**
- **`u8_log_file_upload`**(OCPP 扩展):1 = 诊断日志上传,0 = 固件升级
**`v_ftp_update_action()`** 每 FTP 半周期推进一步,避免阻塞 PlatComm。
---
## 8. 4G 子系统(`4g_module/`
| 项目 | 说明 |
|------|------|
| **模组** | Quectel **EC200A** |
| **业务 TCP** | **`QIOPEN` / `QSSLOPEN`**wss 时模组内置 TLS),PDP **`EC200A_TCP_PDP_CTX_ID`** |
| **FTP** | 独立 PDP **`EC200A_FTP_PDP_CTX_ID`**,与业务 TCP **禁止同 context** |
| **模式** | **`EC200A_TCP_USE_TRANSPARENT_MODE`**:透传 vs **`QISEND`/`QIRD`** 缓冲模式 |
| **与 PlatComm** | 仅通过 **`bs_connect_impl`** 的 **`v_bs_send`/`u16_bs_recv`** 接入;FTP 由 **`v_ftp_update_action`** 驱动 |
**详细 AT 流程、QFTPGET/QFREAD 分片、与 OCPP 互斥宏****`4g_module/4G模块AT指令配置功能说明.md`**。
---
## 9. 网口子系统(`lwip_module/`
| 文件 | 职责 |
|------|------|
| **`lwip_module.c`** | **`lwip_tcp_connect` / `send` / `recv` / `close`**,非阻塞 socket |
| **`lwip_ftp_upgrade.c`** | 网线 **FTP 固件下载** 状态机(PASV/RETR/按 1024B 写 Flash |
**`eth_do_tcp_connect`** 在 PHY **link down** 时不建连;重连前必须 **`v_bs_eth_close_socket`**。
**详细状态机与触发条件****`lwip_module/lwip_ftp功能说明.md`**。
---
## 10. OCPP 子系统(`ocpp/`
**`BS_OCPP_EN`** 下,OCPP 作为 **`MasterFuncArray`** 中的一套 **Master** 实现:
```text
TCP (u8_tcp_connect)
→ WebSocket 握手 (websocket_build)
→ BootNotification (v_ocpp_logo_on)
→ ocpp_send_mag_ctrlHeartbeat / StatusNotification / MeterValues / Start&StopTransaction …
→ ocpp_recv → recv_mags:平台下行与 CALLRESULT
→ v_ocpp_private_logic:未结单 s8_unsettled_order_chaek、离线 MV 桥接、RX 超时重连
```
**离线 MeterValues**:未连接或未登录时 **`BS_ocpp_mv_offline_bridge`** 写 Flash;联网后 **session 补发**(在线会话)或 **unsettled 补发**(掉电/未结单)。
**完整 PDU 列表、未结单状态机、session_001x 类问题修复说明****`ocpp/OCPP功能模块说明.md`**。
---
## 11. 端到端数据流(示意)
### 11.1 正常充电上送(已登录)
```mermaid
flowchart TB
subgraph tasks
FC[FlowCtrl / Meterfee]
PC[PlatComm v_Plat_Comm]
end
subgraph ocpp
SEND[ocpp_send_mag_ctrl / send_mag]
WS[ws_send / bs_websocket]
end
subgraph link
BS[v_bs_send / u16_bs_recv]
TCP[u8_tcp_connect 4G or LwIP]
end
FC -->|订单/枪状态| PUB[bs_public_impl]
PUB --> SEND
PC --> SEND
SEND --> WS --> BS --> TCP
TCP --> CSMS[CSMS]
CSMS --> TCP --> BS --> WS --> RECV[ocpp_recv / recv_mags]
RECV --> PUB
PC --> RECV
```
### 11.2 掉电后补单(简化)
```mermaid
sequenceDiagram
participant M as meter/EEPROM
participant P as bs_public_impl
participant O as BS_ocpp_ctrl
participant S as CSMS
M->>P: s8_plat_get_unsettled_order
P->>O: s_unsettled_order_data
alt 无 preTradeNo
O->>S: Authorize
S->>O: Accepted
O->>S: StartTransaction
S->>O: transactionId
end
O->>S: MeterValues 离线补发
O->>S: StopTransaction PowerLoss
```
---
## 12. 关键全局变量与调试
| 符号 | 位置 | 用途 |
|------|------|------|
| **`s_bs_task_ctrl`** | `plat_comm_task.c` | 双平台连接态、枪标志、OCPP **`ocpp_data`**、IES **`s_ies_data`** |
| **`t_ftp_update_info`** | `bs_public_impl.c` | FTP 升级/日志参数 |
| **`url_info`** | `bs_connect_impl.c` | 解析后的 host/port/TLS |
| **`private_ocpp_data`** | `BS_ocpp_ctrl.c` | OCPP 定时器、未结单指针、pending 通知 |
| **`Plat_Comm_LOG` / `OCPP_PRINTF_LOG`** | 宏 | 任务日志(**`TASK_ID_PlatComm`** / **0xFF** |
**常见日志关键字**
| 日志 | 含义 |
|------|------|
| `[n] tcp connect` | 第 n 槽传输层就绪 |
| `[n] logOn is ok` | BootNotification 成功 |
| `Load pwr-loss order gun=` | EEPROM 掉电单加载 |
| `Unsettled: send StopTx` | 未结单补 StopTransaction |
| `MV offline: session send ok` | 在线会话离线 MV 补发 |
| `ws handshake fail` | WS 失败,将重试 TCP |
| `End rx TO, relink` / `RX TO, relink` | 收包超时触发 **`v_set_bs_renew`** |
---
## 13. 关联文档索引
| 文档路径 | 内容 |
|----------|------|
| **`ocpp/OCPP功能模块说明.md`** | OCPP 报文、未结单、离线 MV、Boot/Heartbeat、平台下行指令 |
| **`ieszt/IES积成平台功能说明.md`** | IES 0x68 协议、`S_BS_IES_CTRL_DATA`、登录/心跳与 PlatComm 七回调 |
| **`4g_module/4G模块AT指令配置功能说明.md`** | EC200A 初始化、透传/非透传、FTP 与 OCPP 分时、配置宏 |
| **`lwip_module/lwip_ftp功能说明.md`** | 网线 FTP 升级状态机、与 **`v_ftp_update_action`** 关系 |
| **`BSP/flash_file_mgr/`**(工程内) | 订单 Flash、未结索引、OCPP 离线 MV FRAM/SPI |
| **`app/meter_calculate/`**(工程内) | 订单 RAM、EEPROM 临时单、周期存盘 |
---
## 14. 维护说明
1. **新增平台协议**:在 **`plat_comm/`** 下增加子目录,实现 **`MasterFuncPtl`** 七个回调,并在 **`MasterFuncArray`** 中注册;**`BS_*_EN`** 与 **`E_BS_GET_SYS_DATA_PLAT_EN`** 对齐。
2. **修改建链逻辑**:优先改 **`bs_connect_impl.c`**,业务层保持 **`v_bs_send`/`u16_bs_recv`** 不变。
3. **FTP 与 OCPP 并发**:改 **`plat_comm_task.c`** 相位策略或 **`ec200a_4g_cfg.h`** 互斥宏前,须在 **4G 实机** 验证 **QISEND****QFTP** 不互相破坏。
4. **未结/掉电单**:同时核对 **`bs_public_impl.c`**、**`meter_calculate_impl.c`**、**`BS_ocpp_ctrl.c`** 三处,避免 EEPROM 周期快照被误判为掉电单。
---
*文档版本:与 CCU601E_D plat_comm 源码同步维护;若宏或文件名变更,请以实际编译配置为准。*