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
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