Files

5408 lines
240 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "BS_ocpp_ctrl.h"
#if EC200A_4G_EN
#include "plat_comm/4g_module/ec200a_4g_ftp.h"
#endif
#if BS_OCPP_EN //OCPP平台使能
#include <string.h>
#include "bs_websocket.h"
#include "bs_ocpp_json_data.h"
#include "app_rtc/app_rtc.h"
#include "plat_comm/plat_comm_task.h"
#if EC200A_4G_EN
#include "plat_comm/4g_module/ec200a_4g.h"
#include "plat_comm/4g_module/ec200a_4g_cfg.h"
#endif
#include "plat_comm/lwip_module/lwip_module.h"
#include "BS_ocpp_configuration.h"
#include "flash_file_mgr/unsettled_order_mng.h"
#include "app_fatfs/fatfs_order.h"
#include "plat_comm/impl/bs_public_impl.h"
#include "flash_file_mgr/ocpp_mv_offline_flash_impl.h"
#include "bsp_include.h"
#include "BS_ocpp_mv_offline_bridge.h"
#include "flash_file_mgr/meter_calculate_flash_impl.h"
#include "meter_calculate/meter_calculate_impl.h"
#if(MY_SHELL_EN)
#include "letter_shell/shell_port.h"
#endif
#define OCPP_PRINTF_LOG(fmt, ...) \
MYLOG_MSG(OCPP_PRINT_FLAG,fmt, ##__VA_ARGS__) //任务打印
static int32_t send_mag(int gunNo,int cmd,OCPP_DATA_RECV_ACK *ack_data,int type); //
#define OCPP_SAVE_ACK_NUM (15)
OCPP_DATA_SAVE ocpp_data_save[OCPP_SAVE_ACK_NUM];//收发保存数据
OCPP_DATA_CTRL *private_ocpp_data; //内部指针引用,初始化时赋值全局变量的值
#define CHARGE_TEST_FLAG 0 //裸机充电测试标志
#if CHARGE_TEST_FLAG //裸机充电测试标志
typedef struct
{
U8_T flag;
U8_T gunNo; //抢号
U8_T u8_soc; //soc
U8_T u8_vin_start; //鉴权标志
int transactionId;
U16_T u16_power_data; //电量值
F32_T f32_charge_vol; //充电电压
F32_T f32_charge_cur; //充电电流
U8_T idTag[20];
}CHARGE_TEST_DATA;
typedef enum{
CHARGE_TES_free=0,
CHARGE_TES_start,
CHARGE_TES_vin,
CHARGE_TES_waitstart,
CHARGE_TES_charge,
CHARGE_TES_stop,
CHARGE_TES_stop_ok,
}CHARGE_TEST_CTRL;
CHARGE_TEST_DATA charge_test;
static U8_T u8_ocpp_timer_ctrl(OCPP_SEND_TIMER cmd ,FLAG_CTLR flag);
static int32_t send_mag(int gunNo,int cmd,OCPP_DATA_RECV_ACK *ack_data,int type);
static U8_T u8_charge_flag_ctrl(U8_T gunNo,OCPP_CHARGE_FLAG flag,FLAG_CTLR ctrl_flag);
static U8_T u8_charge_test() //裸机充电测试
{
U8_T ret = 0;
switch (charge_test.flag)
{
case CHARGE_TES_free:{ //未启动 清空数据
memset(&charge_test,0,sizeof(CHARGE_TEST_DATA));
}
break;
case CHARGE_TES_start:{ //启动充电 初始化赋值
charge_test.gunNo = 0;
charge_test.flag = CHARGE_TES_vin;
memcpy(charge_test.idTag ,"AABBCC",sizeof("AABBCC"));
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_VIN_START_FLAG,SET_FALG);
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_STATE_CHANGE_FLAG,SET_FALG);
}
break;
case CHARGE_TES_vin:{ //充电鉴权
if(charge_test.u8_vin_start == 1) //鉴权成功
{
charge_test.u8_soc = 42;
charge_test.u16_power_data = 0;
charge_test.f32_charge_vol = 499;
charge_test.f32_charge_cur = 10;
charge_test.flag = CHARGE_TES_waitstart;
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_START_RESULT_FLAG,SET_FALG); //发送启动充电帧
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_STATE_CHANGE_FLAG,SET_FALG);
}
}
break;
case CHARGE_TES_waitstart:{ //启动中
charge_test.flag = CHARGE_TES_charge;
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_STATE_CHANGE_FLAG,SET_FALG);
}
break;
case CHARGE_TES_charge:{ //充电中
charge_test.u16_power_data += 1;
if(charge_test.u16_power_data % 60 == 0 && charge_test.u8_soc < 100)
charge_test.u8_soc += 2;
if(charge_test.u16_power_data > 10*60*3) //1800
{
charge_test.flag = CHARGE_TES_stop;
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_STATE_CHANGE_FLAG,SET_FALG);
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_TRADE_UPLOAD_FLAG,SET_FALG); //发送状交易结束
}
}
break;
case CHARGE_TES_stop:{ // 充电停止
u8_charge_flag_ctrl(charge_test.gunNo,OCPP_TRADE_UPLOAD_FLAG,SET_FALG); //发送状交易结束
}
break;
case CHARGE_TES_stop_ok:
{
memset(&charge_test,0,sizeof(CHARGE_TEST_DATA));
}
break;
default:
break;
}
return ret;
}
#endif
#if 1 //公共
/**
* ************************************************************************
* @brief 生成类 UUID 格式的随机消息唯一键(Message ID)
*
* @details 生成格式为 36 字符的伪 UUID 字符串:
* - 前 8 字符:随机字母数字(a-z/A-Z/0-9
* - 位置 8 固定 '-'
* - 中间段:嵌入当前日期时间信息 "DDHH-MMSS-XXXX"(日时分秒+序号)
* - 位置 23 固定 '-'
* - 后 12 字符:随机字母数字
*
* 随机种子来源:(递增序号 + 当前秒数) % 100,配合 rand() 使用。
* 该函数用于 OCPP 消息的唯一标识,与 send_mag 主动发送时的 key 配对,
* 接收时通过 get_json_cmd 匹配 key 找到对应的原始命令。
*
* @note 安全性:种子仅 100 种取值,不可用于安全敏感场景。
* timer_data 为 static 变量,每次调用递增,U16_T 溢出后会回绕。
*
* @param[out] data - 输出缓冲区,长度至少 37 字节(36字符 + '\0'
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static void rand_data(char *data)
{
uint8_t i,temp;
#if 1
static int timer_data = 0;
Comm_Time time;
timer_data++; //序号自加
GetCurrentTime(&time); //获取当前时间
srand((timer_data + time.ucSec)%100); //取当前时间秒数取余100 作为随机数种子
/*UUID 类:8-4-4-4-12的32个字符(含分隔符,如 550e8400-e29b-41d4-a716-446655440000
ascii表
0x30-0x39 0-9
0x41-0x5A A-Z
0x61-0x7a a-z
*/
for(i=0;i<36;i++)
{
temp = (uint8_t)(rand() % 0x7a);
if((temp >= '0' && temp <= '9')||(temp >= 'a' && temp <= 'z')||(temp >= 'A' && temp <= 'Z'))
{
*(data+i) = temp;
}
else
data[i] = temp % 10 + 'a';
// if(i == 8 || i == 13 || i == 18 || i == 23)
// data[i];
}
sprintf(&data[8],"-%02d%02d-%02d%02d-%04d",time.ucDay,time.ucHour,time.ucMin,time.ucSec,timer_data);
data[23] = '-';
#endif
#if 0
ULONG ulong_timer = get_current_seconds();
static int timer_data = 0,test=0;
if(timer_data == ulong_timer%100)//种子和上次想等 数值 - 40
{
timer_data = (ulong_timer - 40 - test%10)%100;
}
else
{
timer_data = ulong_timer%100;
}
srand(timer_data);//取当前时间秒数取余100 作为随机数种子
/*
ascii表
0x30-0x39 0-9
0x41-0x5A A-Z
0x61-0x7a a-z
*/
for(i=0;i<32;i++)
{
temp = (uint8_t)(rand() % 0x7a);
if((temp >= '0' && temp <= '9')||(temp >= 'a' && temp <= 'z')||(temp >= 'A' && temp <= 'Z'))
{
*(data+i) = temp;
}
else
data[i] = temp % 10 + 'a';
}
#endif
}
/**
* ************************************************************************
* @brief 将 cJSON 对象序列化为字符串(无格式化压缩输出)
*
* @details 当前实现始终调用 cJSON_PrintUnformatted 进行无格式化输出,
* 减少网络传输字节数。if(0) 分支(cJSON_Print 带缩进)已废弃,
* 调试时可改为 if(1) 启用可读格式。
*
* @note 返回值由 cJSON 内部 malloc 分配,调用者须用 free() 释放。
*
* @param[in] root - cJSON 对象指针(不可为 NULL
*
* @return char* - 序列化后的 JSON 字符串,需调用者 free。
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static char* print_out(cJSON* root)
{
if(0)
return cJSON_Print(root);
else
return cJSON_PrintUnformatted(root);
}
/**
* ************************************************************************
* @brief 存储待应答的 OCPP 发送命令信息到全局循环缓冲区
*
* @details 在 ocpp_data_save[] 数组中查找空闲槽位(flag==0),
* 记录命令类型、枪号、单调递增序号(u16_cnt)和消息唯一键(key)。
* 该 key 与 rand_data 生成的 Message ID 配对,用于接收应答时
* 通过 get_json_cmd 匹配对应的原始请求。
*
* 数组满时(OCPP_SAVE_ACK_NUM=15),不会自动覆盖;调用者需
* 先调用 v_clear_save_cmd 清理最早条目后重试。
*
* @note u16_cnt 为 static U16_T,每次调用递增;满表时 v_clear_save_cmd
* 清除 u16_cnt 最小的一条。
*
* @param[in] cmd - OCPP 命令枚举值(OCPP_CMD
* @param[in] gunNo - 枪号(0~GUN_MAX_CNT-1
* @param[in] key - 消息唯一键(rand_data 生成的 36 字符字符串)
*
* @return int - 1=存储成功,0=数组已满需清理
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static int save_json_cmd(int cmd,int gunNo,char *key)
{
int i;
static U16_T u16_cnt = 0;
for(i=0;i<OCPP_SAVE_ACK_NUM;i++)
{
if(ocpp_data_save[i].flag == 0)
{
memset(&ocpp_data_save[i],0,sizeof(OCPP_DATA_SAVE));
ocpp_data_save[i].flag = 1;
ocpp_data_save[i].gunNo = gunNo;
ocpp_data_save[i].u16_cnt = u16_cnt++;
ocpp_data_save[i].cmd = (OCPP_CMD)cmd;
strncpy(ocpp_data_save[i].key, key, sizeof(ocpp_data_save[i].key) - 1);
ocpp_data_save[i].key[sizeof(ocpp_data_save[i].key) - 1] = '\0';
//OCPP_PRINTF_LOG("插入 %d %d,%s\n",i,ocpp_data_save[i].u16_cnt,ocpp_data_save[i].key);
return 1;
}
}
return 0;
}
/**
* ************************************************************************
* @brief 清理 ocpp_data_save[] 中最旧的一条命令存储记录
*
* @details 当 save_json_cmd 返回 0(缓冲区满)时调用。
* 遍历 OCPP_SAVE_ACK_NUM 个槽位,找到 u16_cnt 最小的
* 已占用槽位(即最早存储的命令)并清零释放。
*
* 若发现任一槽位空闲(flag==0),说明数组未满,直接返回。
* 此防御逻辑避免在数组未满时误清理。
*
* @note u16_cnt 单调递增;未回绕时最小即最早。回绕后会出现 0、1 与 0xFFFE、
* 0xFFFF 并存,此时应淘汰旧世代((cnt-全局min)>32767)里 cnt 最小者。
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static void v_clear_save_cmd(void)
{
int i; /* 循环索引 */
int n = 0; /* 目标槽位索引:未回绕=最小cnt槽位 / 回绕=回绕前最小cnt槽位 */
U16_T min_cnt = 0U; /* 全部占用槽位中的最小 u16_cnt */
U16_T max_cnt = 0U; /* 全部占用槽位中的最大 u16_cnt */
/* ---- 第一遍:确认数组已满(无空闲槽位)---- */
for (i = 0; i < OCPP_SAVE_ACK_NUM; i++) { /* 遍历 OCPP_SAVE_ACK_NUM(15) 个槽位 */
if (ocpp_data_save[i].flag == 0U) { /* flag==0 表示该槽位未被占用 */
return; /* 有空闲槽,无需清理 */ /* 发现空闲槽位 → 数组未满,直接返回 */
}
}
/* ---- 第二遍:收集 min/max 极值,同时记录未回绕场景的最早槽位 ---- */
min_cnt = ocpp_data_save[0].u16_cnt; /* 以 slot[0] 的 cnt 作为 min 初始基准 */
max_cnt = ocpp_data_save[0].u16_cnt; /* 以 slot[0] 的 cnt 作为 max 初始基准 */
for (i = 1; i < OCPP_SAVE_ACK_NUM; i++) { /* 从 slot[1] 扫描到 slot[N-1] */
U16_T c = ocpp_data_save[i].u16_cnt; /* 暂存当前槽位的 cnt,避免重复访存 */
if (c < min_cnt) { /* 当前 cnt 比已记录的 min 更小 */
min_cnt = c; /* 更新最小值 */
}
if (c > max_cnt) { /* 当前 cnt 比已记录的 max 更大 */
max_cnt = c; /* 更新最大值 */
}
if (ocpp_data_save[i].u16_cnt < ocpp_data_save[n].u16_cnt) { /* 未回绕时:找最小 cnt 槽位 */
n = i; /* 记录当前最小 cnt 的索引 */
}
}
/* ---- 回绕检测:max-min > U16_MAX/2 (32767) 说明 u16_cnt 已溢出回绕 ---- */
if ((U16_T)(max_cnt - min_cnt) > 32767U) { /* 差值超过半量程 → 存在回绕 */
n = -1; /* 重置 n 为无效值,进入回绕分支重新查找 */
for (i = 0; i < OCPP_SAVE_ACK_NUM; i++) { /* 重新遍历全部槽位 */
U16_T c = ocpp_data_save[i].u16_cnt; /* 暂存当前 cnt */
/* (c - min_cnt) > 32767 表示 c 在回绕前(大数值区间),min_cnt 在回绕后
* 例:c=65530, min_cnt=2 → 65528 > 32767 → c 是回绕前的旧数据 */
if ((U16_T)(c - min_cnt) > 32767U) { /* 当前槽位属于回绕前的旧数据 */
if (n < 0 || c < ocpp_data_save[n].u16_cnt) { /* 首个回绕前槽位 或 找到更小的回绕前 cnt */
n = i; /* 记录回绕前数据中 cnt 最小的槽位(最早)*/
}
}
}
}
/* ---- 清除最早槽位 ---- */
if (n >= 0) { /* n 有效(未回绕最小值 / 回绕前最小值)*/
(void)memset(&ocpp_data_save[n], 0, sizeof(OCPP_DATA_SAVE)); /* 清零释放该槽位 */
}
}
/**
* ************************************************************************
* @brief 通过消息唯一键(key)查找对应的原始 OCPP 命令
*
* @details 遍历 ocpp_data_save[],使用 strcmp 与已保存的 key 做
* 全串精确匹配。匹配成功后清零该槽位并返回对应的
* 命令枚举值和枪号。
*
* 对每个未匹配的已占用槽位递增 check_count 计数器,
* 超过阈值(>20次)后自动清理,防止已失效命令长期占用槽位。
*
* @note 使用 strcmp 进行全串精确匹配,确保 key 不会因子串包含
* 关系产生误命中。
*
* @param[out] gunNo - 输出参数,返回匹配命令的枪号
* @param[in] key - 待匹配的消息唯一键(来自接收应答的 ack_key)
*
* @return int - OCPP_CMD 枚举值(匹配成功),OCPP_NULL(未找到)
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static int get_json_cmd(int *gunNo,char* key)
{
int i;
for(i=0;i<OCPP_SAVE_ACK_NUM;i++)
{
if(ocpp_data_save[i].flag == 1)
{
//OCPP_PRINTF_LOG("存储 %d ,%s\n",i,ocpp_data_save[i].key);
if(strcmp(ocpp_data_save[i].key, key) == 0)
{
ocpp_data_save[i].flag = 0;
ocpp_data_save[i].check_count=0;
//OCPP_PRINTF_LOG("查询到 %d ,%d\n",i,ocpp_data_save[i].cmd);
*gunNo = ocpp_data_save[i].gunNo;
return (OCPP_CMD)ocpp_data_save[i].cmd;
}
else
{
if(ocpp_data_save[i].check_count++ >20)//10次查询未用 则清除
{
/* 这里只需要清空单个元素,使用 sizeof(OCPP_DATA_SAVE) 防止越界 */
memset(&ocpp_data_save[i],0,sizeof(OCPP_DATA_SAVE));
OCPP_PRINTF_LOG("%d,%s not found x10, clr\n",i,key);
}
}
}
}
return OCPP_NULL;
}
/**
* ************************************************************************
* @brief 每秒递增所有 OCPP 发送周期计数器(1 秒节拍)
*
* @details 以 get_current_seconds() 的秒变化为节拍,每秒执行一次:
* - 遍历 s_timerCnt[] 数组,对每个未到上限的计数器递增
* - 递增 ulong_timer(通用秒计数,用于超时判断)
* - 递增 u16_ocpp_ping_timerWebSocket Ping 间隔计时)
*
* 该函数由 v_ocpp_tcp_connect、v_ocpp_logo_on、v_ocpp_send_ctrl
* 等多处周期调用,确保计时器在 TCP 建链/登录/已登录各阶段均推进。
*
* @note u16_timerCnt 与 u16_timeNUM 比较:timerCnt 达到 timeNUM 时
* 停止递增(避免溢出),由 u8_ocpp_timer_ctrl 检测并重置。
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static void v_ocpp_timer_add()
{
U8_T i;
static ULONG time_old = 0;
ULONG time_ing = get_current_seconds(); //获取当前秒数
if(time_old != time_ing)
{
time_old = time_ing;
for (i = 0; i < OCPP_TIMERCNT_NUM; i++)
{
if(private_ocpp_data->s_timerCnt[i].u16_timerCnt < private_ocpp_data->s_timerCnt[i].u16_timeNUM)
{
private_ocpp_data->s_timerCnt[i].u16_timerCnt ++;
}
}
private_ocpp_data->ulong_timer ++;
private_ocpp_data->u16_ocpp_ping_timer ++;
}
}
/**
* ************************************************************************
* @brief 检查或清除指定 OCPP 消息类型的发送周期定时器
*
* @details 两种操作模式:
* - CLEAR_FALG:清零指定定时器的计数(u16_timerCnt=0),返回 1
* - GET_FLAG:当 u16_timeNUM!=0 且 u16_timerCnt>=u16_timeNUM
* 时判定周期到期,自动清零计数并返回 1;否则返回 0
*
* 定时器枚举 OCPP_SEND_TIMER 涵盖:BootNotification、Heartbeat、
* StatusNotification(GunA/B)、MeterValues(GunA/B)、
* StopTransaction(GunA/B)、Authorize(GunA/B) 等。
*
* @param[in] cmd - 定时器枚举值(OCPP_SEND_TIMER
* @param[in] flag - 操作类型:CLEAR_FALG=清零,其他=检查到期
*
* @return U8_T - 1=周期已到(或清零成功),0=周期未到
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_ocpp_timer_ctrl(OCPP_SEND_TIMER cmd ,FLAG_CTLR flag)
{
U8_T ret = 0;
if(flag == CLEAR_FALG) //清空对应计时计数
{
private_ocpp_data->s_timerCnt[(U8_T)cmd].u16_timerCnt = 0;
ret = 1;
}
else if(private_ocpp_data->s_timerCnt[(U8_T)cmd].u16_timeNUM != 0 &&
private_ocpp_data->s_timerCnt[(U8_T)cmd].u16_timerCnt >= private_ocpp_data->s_timerCnt[(U8_T)cmd].u16_timeNUM)
{
private_ocpp_data->s_timerCnt[(U8_T)cmd].u16_timerCnt = 0;
ret = 1;
}
return ret;
}
/**
* ************************************************************************
* @brief 将 Comm_Time 结构体转换为 OCPP ISO 8601 时间戳字符串
*
* @details 输出格式由宏 OCPP_TIMESTAMP_UTC_OUTPUT 控制:
* - 0:东八区格式 "YYYY-MM-DDTHH:MM:SS+08:00"
* - 1:零时区格式 "YYYY-MM-DDTHH:MM:SSZ"
*
* ctTime 为 NULL 时自动调用 GetCurrentTime 获取当前东八区时间。
* 零时区模式下会对时间做 -8 小时偏移转换。
*
* @note ⚠️ 注意:OCPP_TIMESTAMP_UTC_OUTPUT==1 且 ctTime 非 NULL 时,
* 函数会通过 v_adjust_time_with_timezone 修改传入的 Comm_Time
* 结构体(减 8 小时),调用者若后续还需使用原始时间需自行备份。
*
* @param[in,out] ctTime - 时间结构体指针(NULL=取当前时间;UTC 模式会被修改)
* @param[out] time_str - 输出缓冲区,长度至少 30 字节
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static void v_ocpp_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;
}
/* 东八区 -> 零时区:减 8 小时 */
v_adjust_time_with_timezone((Comm_Time *)ctTime, -8, 0);
sprintf(time_str, "%04d-%02d-%02dT%02d:%02d:%02dZ",
ctTime->iYear, ctTime->ucMonth, ctTime->ucDay,
ctTime->ucHour, ctTime->ucMin, ctTime->ucSec);
#else
sprintf(time_str, "%04d-%02d-%02dT%02d:%02d:%02d+08:00",
ctTime->iYear, ctTime->ucMonth, ctTime->ucDay,
ctTime->ucHour, ctTime->ucMin, ctTime->ucSec);
#endif
}
/**
* ************************************************************************
* @brief 释放 ack_ctrl[] 中指定索引或全部的应答结构体动态内存
*
* @details 释放 OCPP_DATA_RECV_ACK 结构体占用的堆内存(由 alloc_single_ackdata
* 通过 OCPP_MALLOC 分配),并将对应指针置 NULL 防止野指针。
*
* - index == -1:遍历释放所有 OCPP_ACK_STRUCT_NUM 个槽位
* - index >= 0:仅释放指定槽位
*
* 含越界保护:index > OCPP_ACK_STRUCT_NUM 或
* 目标槽位已为 NULL 时直接返回。
*
* @param[in] index - 槽位索引(-1=全部释放,0~N-1=指定槽位)
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
void v_free_ack_data(int index)
{
int i;
if (index > OCPP_ACK_STRUCT_NUM ||
(index != -1 && private_ocpp_data->ack_ctrl[index] == NULL)) {
return; // 无效索引或未分配
}
if(index == -1) //释放所有的内存数据
{
for (i = 0; i < OCPP_ACK_STRUCT_NUM; i++)
{
if(private_ocpp_data->ack_ctrl[i] != NULL) //结构体不为空
{
// 释放内存
OCPP_FREE(private_ocpp_data->ack_ctrl[i]);
private_ocpp_data->ack_ctrl[i] = NULL;
}
}
}
else
{
if(private_ocpp_data->ack_ctrl[index] != NULL) //结构体不为空
{
// 释放内存
OCPP_FREE(private_ocpp_data->ack_ctrl[index]);
private_ocpp_data->ack_ctrl[index] = NULL;
}
}
}
/**
* ************************************************************************
* @brief 从 ack_ctrl[] 池中动态申请一个空闲的应答结构体槽位
*
* @details 遍历 OCPP_ACK_STRUCT_NUM 个槽位:
* - 若槽位为 NULL,通过 OCPP_MALLOC 分配 OCPP_DATA_RECV_ACK
* 大小的堆内存并返回其索引
* - 若槽位已被占用,递增其 timer_cnt 计数器;超过 30 次后
* 强制释放该槽位(防止接收超时未释放导致内存泄漏)
*
* 所有槽位均被占用且分配失败时,打印错误日志并返回 -1。
*
* @note ⚠️ 潜在问题:每次调用时对所有已占用槽位无条件递增 timer_cnt,
* 频繁调用(如连续收到多条平台指令)会导致正常使用中的应答
* 结构体 timer_cnt 快速累积到 30 而被强制释放,造成应答丢失。
* 建议改为仅对当前扫描槽位计数,或使用时间戳而非调用次数。
*
* @return int - >=0 申请成功的槽位索引,-1 申请失败(内存不足)
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
int alloc_single_ackdata() {
int i,index = -1;
for (i = 0; i < OCPP_ACK_STRUCT_NUM; i++)
{
if(private_ocpp_data->ack_ctrl[i] ==NULL) //结构体指向为空,申请新空间并返回索引号
{
private_ocpp_data->ack_ctrl[i] = (OCPP_DATA_RECV_ACK*)OCPP_MALLOC(sizeof(OCPP_DATA_RECV_ACK));
if (private_ocpp_data->ack_ctrl[i] != NULL) {
index = i; // 内存分配成功
break;
}
}
else //指向不为空,则计数值加一 计数值超30次后,强制释放内存
{
private_ocpp_data->ack_ctrl[i]->timer_cnt ++;
if(private_ocpp_data->ack_ctrl[i]->timer_cnt > 30)
v_free_ack_data(i);
}
}
if(index == -1) OCPP_PRINTF_LOG("alloc ackdata fail\n");
return index;
}
/**
* ************************************************************************
* @brief 解析 FTP/FTPS URL 字符串,提取服务器地址、端口、路径、文件名、用户名、密码
*
* @details 支持以下 URL 格式:
* - ftp://host:port/path/file
* - ftp://user:pass@host:port/path/file
* - ftp://user@host/path/file(无密码)
* - ftp://host/path(无端口,默认 21
*
* 解析流程:
* 1. 跳过 "ftp://" 前缀
* 2. 解析 @ 前的用户名/密码(可选)
* 3. 解析 : 分隔的端口号(可选,默认 21)
* 4. 解析 / 后的路径和文件名
*
* 智能目录检测:若文件名部分不含 '.' 字符,视为目录而非文件,
* 自动追加到路径末尾并添加 '/' 结尾,同时清空文件名字段。
* 此特性用于固件升级时平台下发的目录型 FTP 地址。
*
* 所有字段均有长度保护,超长时通过 FTP_PARSE_FAIL 宏返回 -1
* 并打印详细错误日志。
*
* @note 该函数被 UpdateFirmware 和 GetDiagnostics 共用,
* 分别用于固件升级 FTP 下载和诊断日志 FTP 上传。
*
* @param[in] url - FTP URL 字符串(以 "ftp://" 开头)
* @param[out] info - 解析结果输出结构体(T_FTP_UPDATE_INFO
*
* @return int - 0=解析成功,-1=解析失败(格式错误或字段超长)
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
int parse_ftp_url(const char* url, T_FTP_UPDATE_INFO* info) {
#define FTP_PARSE_FAIL(fmt, ...) do { \
OCPP_PRINTF_LOG("parse_ftp_url fail: " fmt "\n", ##__VA_ARGS__); \
return -1; \
} while(0)
// 初始化结构体
if (url == NULL || info == NULL) {
FTP_PARSE_FAIL("null ptr, url=%p, info=%p", url, info);
}
memset(info, 0, sizeof(T_FTP_UPDATE_INFO));
info->u16_update_server_port = 21; // 默认端口
// 检查是否以ftp://开头
if (strncmp(url, "ftp://", 6) != 0) {
FTP_PARSE_FAIL("invalid schema, url=%s", url);
}
const char* p = url + 6; // 跳过"ftp://"
const char* at_pos = strchr(p, '@');
const char* colon_pos;
const char* slash_pos;
// 解析用户名和密码(如果有)
if (at_pos != NULL) {
// 查找用户名和密码之间的冒号
colon_pos = strchr(p, ':');
if (colon_pos != NULL && colon_pos < at_pos) {
// 提取用户名
int user_len = colon_pos - p;
if (user_len >= sizeof(info->u8_user)) {
FTP_PARSE_FAIL("user too long, len=%d, max=%d", user_len, (int)sizeof(info->u8_user)-1);
}
strncpy((char *)info->u8_user, p, user_len);
info->u8_user[user_len] = '\0';
// 提取密码
int pass_len = at_pos - (colon_pos + 1);
if (pass_len >= sizeof(info->u8_pwd)) {
FTP_PARSE_FAIL("pwd too long, len=%d, max=%d", pass_len, (int)sizeof(info->u8_pwd)-1);
}
strncpy((char *)info->u8_pwd, colon_pos + 1, pass_len);
info->u8_pwd[pass_len] = '\0';
} else {
// 只有用户名,没有密码
int user_len = at_pos - p;
if (user_len >= sizeof(info->u8_user)) {
FTP_PARSE_FAIL("user(no pwd) too long, len=%d, max=%d", user_len, (int)sizeof(info->u8_user)-1);
}
strncpy((char *)info->u8_user, p, user_len);
info->u8_user[user_len] = '\0';
}
p = at_pos + 1; // 移动到@之后
}
// 查找端口分隔符(:)和路径分隔符(/)
colon_pos = strchr(p, ':');
slash_pos = strchr(p, '/');
// 解析服务器地址和端口
if (colon_pos != NULL && (slash_pos == NULL || colon_pos < slash_pos)) {
// 提取服务器地址
int server_len = colon_pos - p;
if (server_len >= sizeof(info->u8_update_server_addr)) {
FTP_PARSE_FAIL("host too long, len=%d, max=%d", server_len, (int)sizeof(info->u8_update_server_addr)-1);
}
strncpy((char *)info->u8_update_server_addr, p, server_len);
info->u8_update_server_addr[server_len] = '\0';
// 提取端口号
p = colon_pos + 1;
char port_str[16] = {0};
int port_len;
if (slash_pos != NULL) {
port_len = slash_pos - p;
} else {
port_len = strlen(p);
}
if (port_len <= 0 || port_len >= sizeof(port_str)) {
FTP_PARSE_FAIL("invalid port len=%d", port_len);
}
strncpy(port_str, p, port_len);
port_str[port_len] = '\0';
// 检查端口号是否全为数字
for (int i = 0; i < port_len; i++) {
if (!isdigit(port_str[i])) {
FTP_PARSE_FAIL("port has non-digit, port=%s", port_str);
}
}
info->u16_update_server_port = atoi(port_str);
p = slash_pos; // 移动到路径部分
} else {
// 没有指定端口号,提取服务器地址
int server_len;
if (slash_pos != NULL) {
server_len = slash_pos - p;
} else {
server_len = strlen(p);
}
if (server_len >= sizeof(info->u8_update_server_addr)) {
FTP_PARSE_FAIL("host(no port) too long, len=%d, max=%d", server_len, (int)sizeof(info->u8_update_server_addr)-1);
}
strncpy((char *)info->u8_update_server_addr, p, server_len);
info->u8_update_server_addr[server_len] = '\0';
p = slash_pos; // 移动到路径部分
}
// 解析路径和文件名
if (p != NULL && *p == '/') {
const char* last_slash = strrchr(p, '/');
// 提取目录路径
int dir_len = last_slash - p;
// 处理根目录情况
if (dir_len == 0) {
if (sizeof(info->u8_path) > 1) {
strcpy((char *)info->u8_path, "/");
} else {
FTP_PARSE_FAIL("path buf too small");
}
} else {
// 检查目录路径长度
if (dir_len >= (int)sizeof(info->u8_path)) {
FTP_PARSE_FAIL("dir too long, len=%d, max=%d, dir=%.*s", dir_len, (int)sizeof(info->u8_path)-1, dir_len, p);
}
// 安全复制目录路径
strncpy((char *)info->u8_path, p, dir_len);
info->u8_path[dir_len] = '\0'; // 确保null终止
}
// 提取文件名
const char* file_start = last_slash + 1;
if (*file_start != '\0') {
if (strlen(file_start) >= sizeof(info->u8_file_name)) {
FTP_PARSE_FAIL("file too long, len=%d, max=%d, file=%s", (int)strlen(file_start), (int)sizeof(info->u8_file_name)-1, file_start);
}
strcpy((char *)info->u8_file_name, file_start);
// 新添加的功能:检查文件名中是否存在'.'
// 如果没有点号,则认为是目录,将文件名追加到路径后面
if (strchr(file_start, '.') == NULL) {
// 当前路径长度
size_t current_path_len = strlen((char *)info->u8_path);
// 确保路径以'/'结尾(如果不是根目录)
if (current_path_len > 1 && info->u8_path[current_path_len - 1] != '/') {
// 添加斜杠
if (current_path_len + 1 < sizeof(info->u8_path)) {
strcat((char *)info->u8_path, "/");
current_path_len++;
} else {
FTP_PARSE_FAIL("path append '/' overflow, cur=%d, max=%d", (int)current_path_len, (int)sizeof(info->u8_path)-1);
}
}
// 检查是否有空间添加文件名
if (current_path_len + strlen(file_start) + 1 < sizeof(info->u8_path)) {
// 将文件名追加到路径
strcat((char *)info->u8_path, file_start);
// 确保路径以'/'结尾(对于目录)
if (current_path_len + strlen(file_start) + 1 < sizeof(info->u8_path)) {
strcat((char *)info->u8_path, "/");
}
} else {
FTP_PARSE_FAIL("path append file overflow, cur=%d, file_len=%d, max=%d", (int)current_path_len, (int)strlen(file_start), (int)sizeof(info->u8_path)-1);
}
// 清空文件名
strcpy((char *)info->u8_file_name, "");
}
} else {
strcpy((char *)info->u8_file_name, "");
}
} else {
strcpy((char *)info->u8_path, "/");
strcpy((char *)info->u8_file_name, "");
}
OCPP_PRINTF_LOG("parse_ftp_url ok: host=%s,port=%d,path=%s,file=%s,user=%s\n",
info->u8_update_server_addr,
info->u16_update_server_port,
info->u8_path,
info->u8_file_name,
info->u8_user);
#undef FTP_PARSE_FAIL
return 0; // 解析成功
}
#endif
#if 1 //数据获取
/**
* ************************************************************************
* @brief 读取/设置/清除指定枪的 OCPP 充电流程标志位
*
* @details 根据 OCPP_CHARGE_FLAG 枚举映射到对应的控制标志变量地址:
* - OCPP_STATE_CHANGE_FLAG → u8_pile_state_change_flag(状态变化标志)
* - OCPP_TRADE_UPLOAD_FLAG → txFlag_tradUpload(交易订单上传标志)
* - OCPP_START_RESULT_FLAG → txFlag_startResult(启动结果上送标志)
* - OCPP_STOP_RESULT_FLAG → txFlag_stopResult(停止结果上送标志)
* - OCPP_VIN_START_FLAG → txFlag_autoStartVIN 鉴权请求上送标志)
*
* 三种操作模式:
* - GET_FLAG:返回当前标志值(0 或 1)
* - CLEAR_FALG:清零标志
* - SET_FALG(其他值):置位标志为 1
*
* @param[in] gunNo - 枪号(0~GUN_MAX_CNT-1
* @param[in] flag - 标志类型枚举(OCPP_CHARGE_FLAG
* @param[in] ctrl_flag - 操作类型:GET_FLAG=读取,CLEAR_FALG=清除,其他=置位
*
* @return U8_T - GET_FLAG 时返回标志值(0/1),其他操作返回 0
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_charge_flag_ctrl(U8_T gunNo,OCPP_CHARGE_FLAG flag,FLAG_CTLR ctrl_flag)
{
U8_T ret = 0 ,id = private_ocpp_data->id;
U8_T *ctrl_data = NULL;
switch (flag)
{
case OCPP_STATE_CHANGE_FLAG: //状态变位标志
ctrl_data = &s_bs_task_ctrl.s_bs_ctrl[id].s_gundata[gunNo].u8_pile_state_change_flag;
break;
case OCPP_TRADE_UPLOAD_FLAG: //交易订单上传标志
ctrl_data = &s_bs_task_ctrl.s_bs_ctrl[id].s_gundata[gunNo].txFlag_tradUpload;
break;
case OCPP_START_RESULT_FLAG: //启动结果上送标志
ctrl_data = &s_bs_task_ctrl.s_bs_ctrl[id].s_gundata[gunNo].txFlag_startResult;
break;
case OCPP_STOP_RESULT_FLAG: //停止结果上送标志
ctrl_data = &s_bs_task_ctrl.s_bs_ctrl[id].s_gundata[gunNo].txFlag_stopResult;
break;
case OCPP_VIN_START_FLAG: //VIN鉴权请求上送标志
ctrl_data = &s_bs_task_ctrl.s_bs_ctrl[id].s_gundata[gunNo].txFlag_autoStart;
break;
case OCPP_VIN_RESULT: //VIN鉴权结果
case OCPP_BILL_INDEX: //上送订单索引号
break;
default:
break;
}
if(ctrl_data != NULL){
if(ctrl_flag == GET_FLAG)
ret = *ctrl_data;
else if(ctrl_flag == CLEAR_FALG)
*ctrl_data = 0;
else
*ctrl_data = 1;
}
return ret;
}
/**
* ************************************************************************
* @brief 获取指定枪的当前充电流程状态
*
* @details 封装 u8_bs_get_flow_state 调用,返回充电桩流程状态机当前阶段。
* 状态值定义(与底层 flowctrl 模块一致):
* - 0:空闲
* - 1:充电启动中
* - 2:充电中
* - 3+:充电完成/停止等后续状态
*
* 若 CHARGE_TEST_FLAG 使能且当前处于裸机测试充电状态,
* 强制返回 1(启动中),用于绕过正常流程逻辑。
*
* @param[in] gunNo - 枪号(0~GUN_MAX_CNT-1
*
* @return U8_T - 充电流程状态值(0=空闲, 1=启动中, 2=充电中, ...
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_get_pile_state(U8_T gunNo)
{
U8_T ret = 0;
ret =u8_bs_get_flow_state(gunNo);
#if CHARGE_TEST_FLAG //裸机充电测试标志
if(charge_test.gunNo == gunNo && charge_test.flag == CHARGE_TES_charge)
ret = 1;
#endif
return ret;
}
/**
* ************************************************************************
* @brief - 获取对应数据 存储到枚举数据中
*
* @param[in] u8_gunNo - 枪号 0-1
* @param[in] e_type - 类型 枚举 22各
* @param[in] sampledValue - 数据赋值位置
* @param[in] u8_data_type - 数据类型 区分SOC 0当前SOC 1起始SOC 2停止SOC
*
* @return U8_T - 0 获取失败 1获取成功
*
* @author jiankalka
* @date 2026-01-21
* ************************************************************************
*/
static U8_T u8_get_ocpp_measurand_data(SampledValue *sampledValue,U8_T u8_gunNo,OCPP_MEASURAND_ID e_type,U8_T u8_data_type)
{
U8_T u8_ret = 0;
char data_str[24] = {0}; /* U64_T 最大值 20 字符 + '\0' = 21,留 24 余量 */
switch (e_type)
{
case ENUM_EnergyActiveImportRegister: // 有功电能输入累计值(Wh)- 电网向设备输入的总有功电能累计
{
U64_T u64_energe = u64_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_MEMTER_ELECT ) / 10; //电表电量 0.0001kWh //u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_LOG_ENERGY,2); //充电电量 0.001kW
sprintf(data_str,"%llu",u64_energe);//充电电量值 wh
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_EnergyActiveImportRegister ,strlen(OCPP_STR_EnergyActiveImportRegister)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_Outlet ,strlen(OCPP_STR_Outlet)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_Wh ,strlen(OCPP_STR_Wh)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_PowerActiveImport: // 有功功率输入(W)- 电网向设备输入的实时有功功率
{
U16_T u16_put_vol = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_METER_VOL,0); //电表电压 单位0.1V
U16_T u16_put_cur = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_METER_CUR,0); //电表电流 单位0.1A
sprintf(data_str,"%.3f",(F32_T)(u16_put_cur / 10.0)*u16_put_vol/10.0); //充电功率
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_PowerActiveImport ,strlen(OCPP_STR_PowerActiveImport)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_Outlet ,strlen(OCPP_STR_Outlet)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_W ,strlen(OCPP_STR_W)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_PowerOffered: // 提供的功率(W)- 充电桩可向EV提供的最大可用有功功率
{
U16_T u16_rated_power = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_RATED_POWER,0); //充电桩额定功率 单位0.1kW
sprintf(data_str,"%.3f",(F32_T)u16_rated_power*100); //充电功率
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_PowerOffered ,strlen(OCPP_STR_PowerOffered)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_Outlet ,strlen(OCPP_STR_Outlet)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_W ,strlen(OCPP_STR_W)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_CurrentImport: // 输入电流(A)- 电网向设备输入的实时电流
{
U16_T u16_put_cur = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_METER_CUR,0); //电表电流 单位0.1A
sprintf(data_str,"%.3f",(F32_T)(u16_put_cur / 10.0)); //充电电流
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_CurrentImport ,strlen(OCPP_STR_CurrentImport)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_Outlet ,strlen(OCPP_STR_Outlet)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_A ,strlen(OCPP_STR_A)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_CurrentOffered: // 提供的电流(A)- 充电桩可向EV提供的最大可用电流
{
U32_T u32_max_a = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_PILE_PUT_PARA,1);
sprintf(data_str,"%.3f",(F32_T)(u32_max_a / 10.0));
memcpy(sampledValue->value , data_str ,strlen(data_str));
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic));
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw));
memcpy(sampledValue->measurand , OCPP_STR_CurrentOffered ,strlen(OCPP_STR_CurrentOffered));
memcpy(sampledValue->location , OCPP_STR_Outlet ,strlen(OCPP_STR_Outlet));
memcpy(sampledValue->unit , OCPP_STR_A ,strlen(OCPP_STR_A));
u8_ret = 1;
break;
}
case ENUM_Voltage: // 电压(V)- 充电桩输出端的实时线电压
{
U16_T u16_put_vol = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_METER_VOL,0); //电表电压 单位0.1V
sprintf(data_str,"%.3f",(F32_T)(u16_put_vol / 10.0));
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_Voltage ,strlen(OCPP_STR_Voltage)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_Outlet ,strlen(OCPP_STR_Outlet)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_V ,strlen(OCPP_STR_V)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_SoC: // 电池剩余电量(%- EV电池的State of Charge0~100%
{
U8_T u8_soc;
if(u8_data_type == 0) //当前SOC
{
u8_soc = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_SOC_DATA,0); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
}
else if(u8_data_type == 1) //起始SOC
{
u8_soc = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_SOC_DATA,1); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
memcpy(sampledValue->context , OCPP_STR_TransactionBegin ,strlen(OCPP_STR_TransactionBegin)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
}
else //中止SOC
{
u8_soc = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_SOC_DATA,2); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
memcpy(sampledValue->context , OCPP_STR_TransactionEnd ,strlen(OCPP_STR_TransactionEnd)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
}
sprintf(data_str,"%d",u8_soc); //当前OC值
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_SOC ,strlen(OCPP_STR_SOC)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_Percent ,strlen(OCPP_STR_Percent)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_EnergyActiveImportInterval: // 本事务内有功输入增量(Wh),近似时段能量
{
U64_T cur_wh = u64_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_MEMTER_ELECT) / 10;
U32_T start_wh = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_LOG_ENERGY,1);
U64_T delta = (cur_wh > (U64_T)start_wh) ? (cur_wh - (U64_T)start_wh) : 0;
sprintf(data_str,"%llu",(unsigned long long)delta);
memcpy(sampledValue->value , data_str ,strlen(data_str));
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic));
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw));
memcpy(sampledValue->measurand , OCPP_STR_EnergyActiveImportInterval ,strlen(OCPP_STR_EnergyActiveImportInterval));
memcpy(sampledValue->location , OCPP_STR_Outlet ,strlen(OCPP_STR_Outlet));
memcpy(sampledValue->unit , OCPP_STR_Wh ,strlen(OCPP_STR_Wh));
u8_ret = 1;
break;
}
case ENUM_EnergyActiveExportRegister: // 有功电能输出累计值(Wh)- 设备向电网输出的总有功电能累计
case ENUM_EnergyReactiveExportRegister: // 无功电能输出累计值(VARh)- 设备向电网输出的总无功电能累计
case ENUM_EnergyReactiveImportRegister: // 无功电能输入累计值(VARh)- 电网向设备输入的总无功电能累计
case ENUM_EnergyActiveExportInterval: // 有功电能输出时段值(Wh)- 单个时钟对齐时段内输出的有功电能
case ENUM_EnergyReactiveExportInterval: // 无功电能输出时段值(VARh)- 单个时钟对齐时段内输出的无功电能
case ENUM_EnergyReactiveImportInterval: // 无功电能输入时段值(VARh)- 单个时钟对齐时段内输入的无功电能
case ENUM_PowerActiveExport: // 有功功率输出(W)- 设备向电网输出的实时有功功率
case ENUM_PowerReactiveExport: // 无功功率输出(VAR)- 设备向电网输出的实时无功功率
case ENUM_PowerReactiveImport: // 无功功率输入(VAR)- 电网向设备输入的实时无功功率
case ENUM_PowerFactor: // 功率因数(无单位,0~1)- 实时有功功率与视在功率的比值
case ENUM_CurrentExport: // 输出电流(A)- 设备向电网输出的实时电流
case ENUM_Frequency: // 电网频率(Hz)- 接入电网的实时频率(50/60Hz)
case ENUM_Temperature: // 温度(℃)- 充电桩/EV电池的实时温度(文档标注为℃/℉,默认℃)
case ENUM_RPM: // 转速(转/分钟)- 冷却风扇/泵等设备的实时转速
{
break;
}
default:
break;
}
return u8_ret;
}
/**
* ************************************************************************
* @brief 从已落盘订单日志(S_LOG_DATA)中提取 StopTransaction 所需的 Measurand 数据
*
* @details 与 u8_get_ocpp_measurand_data(从实时系统数据获取)互补,本函数从
* Flash 存储的历史订单快照中读取数据,用于 StopTransaction 帧组包。
*
* 当前支持的测量项:
* - ENUM_EnergyActiveImportRegister:取 t_log->u64_endKWh / 10Wh
* - ENUM_SoC:按 u8_data_type 取启动/当前/结束 SOC
* 优先使用 t_log 中记录值(u8_startSoc/u8_endSoc),
* 若为 0 则回退到实时系统数据
*
* 其余测量项(电压/电流/功率等)当前未实现,预留 case 分支供扩展。
*
* @param[out] sampledValue - 输出的 SampledValue 结构体指针
* @param[in] t_log - 订单日志快照(来自 Flash 未结算订单或实时订单)
* @param[in] u8_gunNo - 枪号(0~GUN_MAX_CNT-1),SOC 回退查实时数据时使用
* @param[in] e_type - 测量项枚举(OCPP_MEASURAND_ID
* @param[in] u8_data_type - SOC 子类型:0=当前SOC, 1=起始SOC, 2=结束SOC
*
* @return U8_T - 1=获取成功,0=该测量项未实现或获取失败
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_get_ocpp_transaction_data(SampledValue *sampledValue, const S_LOG_DATA *t_log,U8_T u8_gunNo,OCPP_MEASURAND_ID e_type,U8_T u8_data_type)
{
U8_T u8_ret = 0;
char data_str[24] = {0}; /* U64_T 最大值 20 字符 + '\0' = 21,留 24 余量 */
switch (e_type)
{
case ENUM_EnergyActiveImportRegister: // 有功电能输入累计值(Wh)- 电网向设备输入的总有功电能累计
{
if (t_log == NULL) {
break; /* 防御:调用者不应传 NULL,但兜底避免空指针 */
}
U64_T u64_energe = t_log->u64_endKWh / 10; //电表电量 0.0001kWh
sprintf(data_str,"%llu",u64_energe);//充电电量值 wh
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->context , OCPP_STR_TransactionEnd ,strlen(OCPP_STR_TransactionEnd)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_EnergyActiveImportRegister ,strlen(OCPP_STR_EnergyActiveImportRegister)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_Body ,strlen(OCPP_STR_Body)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_Wh ,strlen(OCPP_STR_Wh)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_SoC: // 电池剩余电量(%- EV电池的State of Charge0~100%
{
U8_T u8_soc = 0U;
if(u8_data_type == 0) //当前SOC
{
u8_soc = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_SOC_DATA,0); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
memcpy(sampledValue->context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
}
else if(u8_data_type == 1) //起始SOC
{
if (t_log != NULL && t_log->u8_startSoc != 0U) {
u8_soc = t_log->u8_startSoc;
} else {
u8_soc = (U8_T)u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_SOC_DATA,1);
}
memcpy(sampledValue->context , OCPP_STR_TransactionBegin ,strlen(OCPP_STR_TransactionBegin)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
}
else //中止SOC
{
if (t_log != NULL && t_log->u8_endSoc != 0U) {
u8_soc = t_log->u8_endSoc;
} else {
u8_soc = (U8_T)u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_SOC_DATA,2);
}
memcpy(sampledValue->context , OCPP_STR_TransactionEnd ,strlen(OCPP_STR_TransactionEnd)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
}
sprintf(data_str,"%d",u8_soc); //当前OC值
memcpy(sampledValue->value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(sampledValue->format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(sampledValue->measurand , OCPP_STR_SOC ,strlen(OCPP_STR_SOC)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(sampledValue->phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(sampledValue->location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(sampledValue->unit , OCPP_STR_Percent ,strlen(OCPP_STR_Percent)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_ret =1;
break;
}
case ENUM_PowerActiveImport: // 有功功率输入(W)- 电网向设备输入的实时有功功率
case ENUM_PowerOffered: // 提供的功率(W)- 充电桩可向EV提供的最大可用有功功率
case ENUM_CurrentImport: // 输入电流(A)- 电网向设备输入的实时电流
case ENUM_CurrentOffered: // 提供的电流(A)- 充电桩可向EV提供的最大可用电流
case ENUM_Voltage: // 电压(V)- 充电桩输出端的实时线电压
case ENUM_EnergyActiveExportRegister: // 有功电能输出累计值(Wh)- 设备向电网输出的总有功电能累计
case ENUM_EnergyReactiveExportRegister: // 无功电能输出累计值(VARh)- 设备向电网输出的总无功电能累计
case ENUM_EnergyReactiveImportRegister: // 无功电能输入累计值(VARh)- 电网向设备输入的总无功电能累计
case ENUM_EnergyActiveExportInterval: // 有功电能输出时段值(Wh)- 单个时钟对齐时段内输出的有功电能
case ENUM_EnergyActiveImportInterval: // 有功电能输入时段值(Wh)- 单个时钟对齐时段内输入的有功电能
case ENUM_EnergyReactiveExportInterval: // 无功电能输出时段值(VARh)- 单个时钟对齐时段内输出的无功电能
case ENUM_EnergyReactiveImportInterval: // 无功电能输入时段值(VARh)- 单个时钟对齐时段内输入的无功电能
case ENUM_PowerActiveExport: // 有功功率输出(W)- 设备向电网输出的实时有功功率
case ENUM_PowerReactiveExport: // 无功功率输出(VAR)- 设备向电网输出的实时无功功率
case ENUM_PowerReactiveImport: // 无功功率输入(VAR)- 电网向设备输入的实时无功功率
case ENUM_PowerFactor: // 功率因数(无单位,0~1)- 实时有功功率与视在功率的比值
case ENUM_CurrentExport: // 输出电流(A)- 设备向电网输出的实时电流
case ENUM_Frequency: // 电网频率(Hz)- 接入电网的实时频率(50/60Hz)
case ENUM_Temperature: // 温度(℃)- 充电桩/EV电池的实时温度(文档标注为℃/℉,默认℃)
case ENUM_RPM: // 转速(转/分钟)- 冷却风扇/泵等设备的实时转速
{
break;
}
default:
break;
}
return u8_ret;
}
/**
* ************************************************************************
* @brief 向 GetConfiguration 应答结构体中追加一个配置项的 key/value/readonly
*
* @details 将指定 key 的配置值写入 json_data->configurationKey[*k]
* - 复制 key 名称到 configurationKey[*k].key
* - 通过 u8_OCPP_config_operate(ENUM_TYPE_GET) 查询当前值写入 value
* - 同时返回 readonly 属性
* - 写入完成后 *k 自增 1
*
* 若 *k 已达 GetConfiguration_NUM 上限则直接返回(防越界)。
* 在 GetConfiguration_res 中用于回填平台请求的全部/部分配置项。
*
* @param[in] key - 配置项键名(OCPP 标准 key 字符串)
* @param[out] json_data - GetConfiguration 应答结构体指针
* @param[in,out] k - 当前写入索引指针(调用前为当前下标,调用后 +1)
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static void v_ocpp_GetConfiguration_Key(char *key,GetConfigurationResponse *json_data,int *k)
{
if(*k >= GetConfiguration_NUM)
return;
strncpy(json_data->configurationKey[*k].key , key , OCPP_CiString50);
json_data->configurationKey[*k].readonly = u8_OCPP_config_operate(ENUM_TYPE_GET,
json_data->configurationKey[*k].key, json_data->configurationKey[*k].value, OCPP_CiString100);
*k = *k + 1;
}
/**
* ************************************************************************
* @brief - 获取状态通知变位数据
*
* @param[in] gunNo - 枪号 0-1
* @param[in] transactionId - 事务ID
* @param[in] e_error_id - 错误代码变量地址 为NULL时不获取 枚举 OCPP_STATUSNOTIFICATION_ERRORCODE
* @param[in] e_state_id - 状态代码变量地址 为NULL时不获取 枚举 OCPP_STATUSNOTIFICATION_STATUSCODE
*
* @param[in] e_mode 见 OCPP_STATUS_NOTIFY_MODE:正常变位检测,或发送成功后同步静态缓存
* @return U8_T - 0未变化 1状态变位(SYNC_CACHE 模式下恒为 0
*
* @author jiankalka
* @date 2026-03-04
* ************************************************************************
*/
/** StatusNotification 变位判断与「上次已上送快照」缓存的协同模式 */
typedef enum
{
/** 将当前推导的 status/error 与静态缓存比较;相对上次成功上送有变化则返回 1,用于决定是否发报文 */
OCPP_STATUS_NOTIFY_MODE_NORMAL = 0,
/** 用当前推导的 status/error 覆盖静态缓存(通常在 send_mag 成功后调用);不触发上报、返回 0 */
OCPP_STATUS_NOTIFY_MODE_SYNC_CACHE = 1
} OCPP_STATUS_NOTIFY_MODE;
static U8_T u8_ocpp_StatusNotification_change_data(int gunNo,OCPP_STATUSNOTIFICATION_ERRORCODE *e_error_id,
OCPP_STATUSNOTIFICATION_STATUSCODE *e_state_id, OCPP_STATUS_NOTIFY_MODE e_mode)
{
U8_T u8_ret = 0;
U8_T u8_state = u8_get_pile_state(gunNo);
U8_T u8_gunLink = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_GUN_STATE,0); //抢连接
U8_T u8_flaut_num = u32_bs_get_sys_data(GUN_NULL,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM,0); //获取故障数量
U16_T u16_flaut_data = u32_bs_get_sys_data(GUN_NULL,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_DATA,0); //获取故障内容
OCPP_STATUSNOTIFICATION_ERRORCODE error_id;
OCPP_STATUSNOTIFICATION_STATUSCODE state_id;
static OCPP_STATUSNOTIFICATION_ERRORCODE error_id_static[GUN_MAX_CNT];
static OCPP_STATUSNOTIFICATION_STATUSCODE state_id_static[GUN_MAX_CNT]; //用于记录上次状态 避免重复上送
/*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, //信号弱
*/
if(u8_flaut_num == 0){
error_id = OCPP_ERRORCODE_NoError;
}
else{
if(u16_flaut_data == E_FAULT_13016){
error_id = OCPP_ERRORCODE_ConnectorLockFailure;//连接器锁定故障
}
else if(u16_flaut_data >= E_FAULT_15000 && u16_flaut_data <= E_FAULT_15046){
error_id = OCPP_ERRORCODE_EVCommunicationError;//EV通讯故障
}
else if(u16_flaut_data == E_FAULT_13013){
error_id = OCPP_ERRORCODE_HighTemperature;//高温 -- 枪过温
}
else if(u16_flaut_data == E_FAULT_13071){
error_id = OCPP_ERRORCODE_PowerMeterFailure;//功率表故障
}
else if(u16_flaut_data == E_FAULT_13049){
error_id = OCPP_ERRORCODE_OverCurrentFailure;//过电流故障
}
else if(u16_flaut_data == E_FAULT_13048){
error_id = OCPP_ERRORCODE_OverVoltage;//电压过高
}
else{
error_id = OCPP_ERRORCODE_OtherError;//其他错误
}
}
/*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, //故障
*/
if(error_id != OCPP_ERRORCODE_NoError){ //故障状态存在
state_id = OCPP_STATUSCODE_Faulted;
}
else if(s_ocpp_flash_cfg_data.s_ocpp_charge_data.s8_ChangeAvailability[gunNo] == -1) //配置参数中 存在状态上送位置 则按照配置参数上送状态
{
state_id = OCPP_STATUSCODE_Unavailable;
}
else if(u8_state == 0){//0空闲 1启动中 2充电中 3充电完成
if(u8_gunLink) //插枪 变为准备
state_id = OCPP_STATUSCODE_Preparing;
else
state_id = OCPP_STATUSCODE_Available;
}
else if(u8_state == 1){
state_id = OCPP_STATUSCODE_Preparing;
}
else if(u8_state == 2){
char u8_str_data[BS_BUF_SIZE_64];
int t = 0;
u8_get_str_data(gunNo,E_STR_LOG_preTradeNo,0,u8_str_data,BS_BUF_SIZE_64); //获取流水号
t = atoi((char *)u8_str_data);
if(t != 0)
state_id = OCPP_STATUSCODE_Charging;
else
state_id = OCPP_STATUSCODE_Preparing;
}
else {
state_id = OCPP_STATUSCODE_Finishing;
}
if(e_state_id != NULL)
*e_state_id = state_id;
if(e_error_id != NULL)
*e_error_id = error_id;
if(e_mode == OCPP_STATUS_NOTIFY_MODE_SYNC_CACHE)
{
state_id_static[gunNo] = state_id;
error_id_static[gunNo] = error_id;
return 0;
}
/* 与上次已成功上送的状态比较;缓存仅在 send 后由 SYNC_CACHE 模式更新 */
if(state_id != state_id_static[gunNo] || error_id != error_id_static[gunNo])
{
u8_ret = 1;
// OCPP_PRINTF_LOG("gun%d[%d] ,%d .\n,",gunNo,u8_state,u16_flaut_data);
// OCPP_PRINTF_LOG("state_id %d - error_id %d\n,",state_id,error_id);
}
else
{
u8_ret = 0;
}
return u8_ret;
}
/**
* ************************************************************************
* @brief 查询当前是否存在故障告警(用于 StatusNotification 周期上送判定)
*
* @details 直接读取系统故障告警数量,若非零则说明桩处于 Faulted 状态。
* 与 u8_ocpp_StatusNotification_change_data 逻辑一致:
* u8_flaut_num != 0 → error_id != NoError → status = Faulted。
*
* 在 ocpp_send_mag_ctrl 中作为 StatusNotification 周期上送的前置条件:
* 仅当桩处于 Faulted 状态时,定时器到期才触发 StatusNotification 上送,
* 避免空闲/正常充电时产生过多状态通知报文。
*
* @return U8_T - 1=有故障(应报 Faulted),0=无故障
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_ocpp_statusnotification_pile_faulted(void)
{
return u32_bs_get_sys_data(GUN_NULL,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_NUM,0) != 0 ? 1U : 0U;
}
#endif
#if 1 //OCPP 诊断日志 和固件升级标志处理
/**
* @brief 置诊断日志上传结果待上送标志(GetDiagnostics / 4G 日志 FTP 收尾时调用)
*
* @details 仅接受 Uploaded / UploadFailed 两种状态写入 RAM 标志
* `u8_diag_notify_pending`。实际上送 DiagnosticsStatusNotification
* 由已登录路径下的 `ocpp_send_mag_ctrl` 检测 pending 后 `send_mag` 完成。
* Uploading 由 GetDiagnostics CallResult 发出后在 `ocpp_send_mag_ctrl`
* 内置位并上送,不经过本函数。
*
* @param[in] pending - 仅 OCPP_DIAG_NOTIFY_PENDING_UPLOADED 或 UPLOAD_FAILED
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
void v_ocpp_set_diag_upload_notify_pending(U8_T pending)
{
if (pending != OCPP_DIAG_NOTIFY_PENDING_UPLOADED &&
pending != OCPP_DIAG_NOTIFY_PENDING_UPLOAD_FAILED) {
return;
}
s_bs_task_ctrl.ocpp_data.u8_diag_notify_pending = pending;
}
/**
* @brief 置固件升级状态待上送标志(FTP 升级状态机 / UpdateFirmware 流程调用)
*
* @details 只赋值 `u8_fw_notify_pending`FirmwareStatusNotification 由
* `ocpp_send_mag_ctrl` 发出。置 INSTALLING 时同步清零
* `u8_fw_installing_fsn_sent`,供 ec200a STEP_FTP_SAVE 等待
* 「Installing 帧已 ws_send 成功」后再写升级头并 `v_sys_reboot`。
*
* @param[in] pending - 见 OCPP_FW_NOTIFY_PENDING_EDownloading … InstallationFailed
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
void v_ocpp_set_fw_notify_pending(U8_T pending)
{
if (pending > (U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLATION_FAILED) {
return;
}
s_bs_task_ctrl.ocpp_data.u8_fw_notify_pending = pending;
if (pending == (U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLING) {
s_bs_task_ctrl.ocpp_data.u8_fw_installing_fsn_sent = 0U;
}
}
/**
* @brief 查询 FSN(Installing) 是否已由 `ocpp_send_mag_ctrl` 成功发出(sm>0
*
* @details ec200a_4g_ftp `STEP_FTP_SAVE` 在写升级头/复位前须本标志为 1,
* 避免仅置 pending 即重启导致升级失败。
*
* @return U8_T - 1=已发出,0=未发出或已清除
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
U8_T u8_ocpp_fw_installing_fsn_sent(void)
{
return s_bs_task_ctrl.ocpp_data.u8_fw_installing_fsn_sent;
}
/**
* @brief 清除 FSN(Installing) 已上送确认标志
*
* @details 在 `v_ftp_ctrl_init`、STEP_FTP_SAVE 复位前调用,避免残留影响下次升级。
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
void v_ocpp_clr_fw_installing_fsn_sent(void)
{
s_bs_task_ctrl.ocpp_data.u8_fw_installing_fsn_sent = 0U;
}
/**
* @brief 置「Installed 上送成功后是否在本模块内 v_sys_reboot」标志(旧流程保留)
*
* @details 现网 OCPP+4G FTP 升级在 Installing 上送后于 ec200a 侧重启,
* Installed 在 BootNotification 后上送;本标志供非 FTP 或历史路径使用。
*
* @param[in] pending - 非 0 表示 FSN(Installed) 发出后触发复位
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
void v_ocpp_set_fw_reboot_after_installed(U8_T pending)
{
s_bs_task_ctrl.ocpp_data.u8_fw_reboot_pending = (pending != 0U) ? 1U : 0U;
}
/**
* @brief 写 EEPROM:标记「复位后须在 BootNotification 成功后再上送 FSN(Installed)」
*
* @details 在 ec200a STEP_FTP_SAVE 写升级头之后、`v_sys_reboot` 之前调用。
* 新固件启动后 `BootNotification_res` 通过
* `u8_ocpp_fw_eeprom_take_installed_after_boot` 读取并清零该字节,
* 再置 `u8_fw_notify_pending=INSTALLED`。
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
void v_ocpp_fw_eeprom_set_installed_after_boot(void)
{
U8_T b = EEPROM_OCPP_FW_INSTALLED_PENDING_VAL;
(void)eeprom_write((S32_T)EEPROM_ADDR_OCPP_FW_INSTALLED_PENDING, &b, 1);
}
/**
* ************************************************************************
* @brief 读取并清零 EEPROM 中「Installed 待上送」标志
*
* @details 新固件启动后由 BootNotification_res 调用:
* 若 EEPROM 中标志为 EEPROM_OCPP_FW_INSTALLED_PENDING_VAL
* 则返回 1 并将 EEPROM 标志清零,后续再置
* `u8_fw_notify_pending = INSTALLED` 以触发 FSN 上送。
*
* @return U8_T - 1=须上送 Installed0=无需上送
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_ocpp_fw_eeprom_take_installed_after_boot(void)
{
U8_T b = EEPROM_OCPP_FW_INSTALLED_PENDING_CLEAR;
if (eeprom_read((S32_T)EEPROM_ADDR_OCPP_FW_INSTALLED_PENDING, &b, 1) != HAL_OK) {
return 0U;
}
if (b != EEPROM_OCPP_FW_INSTALLED_PENDING_VAL) {
return 0U;
}
b = EEPROM_OCPP_FW_INSTALLED_PENDING_CLEAR;
(void)eeprom_write((S32_T)EEPROM_ADDR_OCPP_FW_INSTALLED_PENDING, &b, 1);
return 1U;
}
/**
* ************************************************************************
* @brief 判断当前是否应阻止 RX 超时重连(FTP 升级或日志上传期间)
*
* @details FTP 升级 / GetDiagnostics 日志上传时 UART 长时间忙于模组交互,
* OCPP 往往得不到可解析下行,但 ulong_timer 仍每秒累加。
* 在此期间须抑制 RX TO / 重配 4G 重连,否则会拆掉 TCP、
* FTP 一并中断。检查条件:
* - s_bs_ctrl[id].u8_updata_flag != 0FTP 升级进行中)
* - private_ocpp_data->u8_log_file_upload != 0(日志上传进行中)
*
* @param[in] id - BS 控制槽位索引
*
* @return U8_T - 1=阻止重连,0=允许正常超时处理
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_ocpp_block_rx_timeout_relink(U8_T id)
{
return (s_bs_task_ctrl.s_bs_ctrl[id].u8_updata_flag != 0U)
|| (private_ocpp_data->u8_log_file_upload != 0U);
}
#endif
#if 1 //离线充电相关操作
/**
* @brief 登记本枪在线会话已成功上送 StopTransaction 的 transactionId
*
* @details 用于未结补单去重:同一 txId 若已在线停充上送,则不再对 Flash 未结副本
* 发 StopTx,避免 Remote 停充或链路恢复后重复结算。
*
* @param[in] gun_idx - 枪号 0..GUN_MAX_CNT-1
* @param[in] transaction_id - 平台返回且已上送成功的流水号(须 >0)
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
void v_ocpp_mark_live_stop_tx_done(U8_T gun_idx, int transaction_id)
{
if (gun_idx >= GUN_MAX_CNT) {
return;
}
if (transaction_id > 0) {
private_ocpp_data->s_ctrl_flag[gun_idx].s_ocpp_live_stop_tx_id = transaction_id;
OCPP_PRINTF_LOG("OCPP: live StopTx done gun=%u txId=%d\r\n",
(unsigned)gun_idx, transaction_id);
}
}
/**
* @brief 未结订单是否应跳过 StopTransaction(因同 txId 已在线停充上送完毕)
*
* @details 比对 `log->u8_preTradeNo` 与 `s_ocpp_live_stop_tx_id[]`
* 由 `s8_unsettled_order_chaek` 调用。若匹配则表示该 txId
* 已在线停充上送成功,无需对 Flash 未结副本重复发 StopTx。
*
* @param[in] log - 待补发的未结订单快照
*
* @return U8_T - 1=应跳过并删除未结索引,0=继续 Authorize/MV/StopTx 流程
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
U8_T u8_ocpp_skip_unsettled_stop_for_live_tx(S_LOG_DATA *log)
{
int txid;
U8_T gi;
if (log == NULL) {
return 0U;
}
txid = atoi((const char *)log->u8_preTradeNo);
if (txid <= 0) {
return 0U;
}
for (gi = 0U; gi < GUN_MAX_CNT; gi++) {
if (private_ocpp_data->s_ctrl_flag[gi].s_ocpp_live_stop_tx_id == txid) {
OCPP_PRINTF_LOG("Unsettled: skip dup StopTx gun=%u txId=%d (live stop done)\r\n",
(unsigned)gi, txid);
return 1U;
}
}
return 0U;
}
/**
* @brief 判断未结订单是否与该枪当前正在进行的在线充电为同一会话
*
* @details 判断条件(满足任一即视为同一会话):
* - 该枪 flow==充电中,且实时订单有效,preTradeNo 相同
* - StartChargeTime 完全一致
* 若为同一会话,未结补单应暂缓(勿 StopTx、勿与 session MV 补发抢队列)。
* 用于 `s8_unsettled_order_chaek` 与离线 MV session 补发互斥。
*
* @param[in] log - 未结订单(Flash 读出)
*
* @return U8_T - 1=同一会话应暂缓,0=可独立补单
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
U8_T u8_ocpp_unsettled_is_live_charging_session(S_LOG_DATA *log)
{
U8_T gun;
S_LOG_DATA live;
int unsettled_tx;
int live_tx;
if (log == NULL) {
return 0U;
}
gun = log->u8_gunNo;
if (gun >= GUN_MAX_CNT) {
gun = 0U;
}
if (u8_bs_get_flow_state(gun) != 2u) {
return 0U;
}
(void)memset(&live, 0, sizeof(live));
if (u8_plat_get_real_order(&live, gun) == 0u) {
return 0U;
}
if (u8_ocpp_is_pre_trade_no_pending(live.u8_preTradeNo) != 0U) {
return 0U;
}
if (u8_ocpp_is_pre_trade_no_pending(log->u8_preTradeNo) != 0U) {
return 0U;
}
unsettled_tx = atoi((const char *)log->u8_preTradeNo);
live_tx = atoi((const char *)live.u8_preTradeNo);
if (unsettled_tx > 0 && unsettled_tx == live_tx) {
OCPP_PRINTF_LOG("Unsettled: skip live charging gun=%u txId=%d\r\n",
(unsigned)gun, unsettled_tx);
return 1U;
}
if (memcmp(&log->StartChargeTime, &live.StartChargeTime, sizeof(Comm_Time)) == 0) {
OCPP_PRINTF_LOG("Unsettled: skip live charging gun=%u (same start)\r\n",
(unsigned)gun);
return 1U;
}
return 0U;
}
#endif
#if 1 //OCPP 重启指令操作函数
/* “停机后重启”保护窗口起点(秒计数,使用 private_ocpp_data->ulong_timer 为基准)
* 不能复用 private_ocpp_data->u32_timer:该字段同时用于未结算订单周期检查等其他逻辑。
*/
/* u8_reboot_flag 编码(按枪4bit状态机,高4位=枪0,低4位=枪1)
* - 0x00:无重启请求
* - 0xF0:立即重启(枪空闲时)
*
* 每枪状态(nibble):
* - 0:本次不需要等待该枪结算
* - 1:该枪需要停机结算(等待 txFlag_tradUpload 出现置1
* - 2:已观察到 txFlag_tradUpload 置1(等待其清0
* - 3:已观察到 txFlag_tradUpload 1->0(订单已上送结算)
*
* 例:
* - 0x11:双枪需停机结算
* - 0x10:仅枪0需停机结算
* - 0x01:仅枪1需停机结算
* - 0x22:双枪均已观察到 txFlag_tradUpload=1
* - 0x33:双枪均已上送结算完成
*/
/**
* @brief 从 u8_reboot_flag 中提取指定枪的重启状态 nibble
* @details 高 4 位=枪0,低 4 位=枪1。nibble 值含义见 u8_reboot_flag 注释。
* @param[in] flag - u8_reboot_flag 编码值
* @param[in] gunNo - 枪号(0 或 1
* @return U8_T - 该枪的 4-bit 重启状态(0~3
*/
static inline U8_T u8_get_gun_reboot_state(U8_T flag, U8_T gunNo)
{
return (gunNo == 0) ? ((flag >> 4) & 0x0F) : (flag & 0x0F);
}
/**
* @brief 修改 u8_reboot_flag 中指定枪的重启状态 nibble
* @details 高 4 位=枪0,低 4 位=枪1。先清零目标 nibble,再写入新状态。
* @param[in] flag - 当前 u8_reboot_flag 编码值
* @param[in] gunNo - 枪号(0 或 1
* @param[in] state - 新状态值(仅低 4 位有效,0~3)
* @return U8_T - 修改后的 u8_reboot_flag 值
*/
static inline U8_T u8_set_gun_reboot_state(U8_T flag, U8_T gunNo, U8_T state)
{
state &= 0x0F;
if (gunNo == 0) {
flag &= 0x0F;
flag |= (U8_T)(state << 4);
} else {
flag &= 0xF0;
flag |= state;
}
return flag;
}
/**
* ************************************************************************
* @brief 重启状态机:标记”该枪订单已上送结算完成”
*
* @details 调用时机(两个条件同时满足):
* - StopTransaction 已通过 send_mag 触发发送
* - 本轮已对 txFlag_tradUpload 执行 CLEAR
*
* 设计原因:StopTx 上送路径里会紧接着清 txFlag_tradUpload
* 若仅在重启控制里轮询 txFlag,可能观测不到 txFlag!=0 的瞬态,
* 导致重启状态卡在 0x10/0x01/0x11 不推进。
* 因此在这里直接把对应枪 nibble 从 1/2 推进到 3。
*
* @param[in] gunNo - 枪号(0~GUN_MAX_CNT-1
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static void v_ocpp_reboot_mark_trade_uploaded(U8_T gunNo)
{
U8_T rf = private_ocpp_data->u8_reboot_flag;
if (rf == 0 || rf == 0xF0)
{
return;
}
U8_T st = u8_get_gun_reboot_state(rf, gunNo);
if (st == 1 || st == 2)
{
private_ocpp_data->u8_reboot_flag = u8_set_gun_reboot_state(rf, gunNo, 3);
}
}
/**
* ************************************************************************
* @brief - 停机后重启控制
*
* @param[in] void
*
* @return void
*
* @author jiankalka
* @date 2026-04-09
* ************************************************************************
*/
static void v_ocpp_reboot_ctrl(void)
{
U8_T i;
if (private_ocpp_data->u8_reboot_flag == 0)
{
return;
}
if(private_ocpp_data->u8_reboot_flag == 0xF0) //立即重启(空闲时)
{
/* 立即重启:仅当所有枪空闲(flow_state==0 或 10)且无订单待上送(txFlag_tradUpload==0)时允许 */
U8_T u8_gunCnt = u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_GUN_NUM,0);
for (i = 0; i < u8_gunCnt; i++)
{
U8_T u8_flow_state = u32_bs_get_sys_data(i,E_BS_GET_SYS_DATA_GUN_FLOW_STATE,0);
U8_T txFlag = s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].s_gundata[i].txFlag_tradUpload;
if ((u8_flow_state != 0 && u8_flow_state != 10) || txFlag != 0)
{
break;
}
}
if (i == u8_gunCnt)
{
OCPP_PRINTF_LOG("Reboot dev\n");
v_remote_reboot(0);
}
return;
}
/* 停机后重启:
* - nibble 的推进在 StopTx 上送处完成(v_ocpp_reboot_mark_trade_uploaded
* - 这里仅做“是否可以重启”的判定
*/
{
U8_T u8_gunCnt = u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_GUN_NUM,0); //枪数量
U8_T rebootFlag = private_ocpp_data->u8_reboot_flag;
U8_T all_idle = 1;
U8_T all_done = 1;
for (i = 0; i < u8_gunCnt; i++)
{
U8_T u8_flow_state = u32_bs_get_sys_data(i,E_BS_GET_SYS_DATA_GUN_FLOW_STATE,0); ////终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
U8_T st = u8_get_gun_reboot_state(rebootFlag, (U8_T)i);
if (u8_flow_state != 0 && u8_flow_state != 10)
{
all_idle = 0;
}
/* 需要停机结算的枪必须到 st==3 才算完成 */
if (st != 0 && st != 3)
{
all_done = 0;
}
}
if(all_idle && all_done) //所有枪空闲且所需结算枪已完成
{
OCPP_PRINTF_LOG("Reboot dev\n");
v_remote_reboot(0);
}
}
}
#endif
#if 1 /* 未结订单与充电鉴权(任务侧辅助,非报文收发帧体) */
/**
* ************************************************************************
* @brief 判断未结订单是否尚未取得平台 transactionId
*
* @details 离线刷卡等场景本地启动时 u8_preTradeNo 填 0xFF 占位(见 ui_interface),
* strlen 可能非 0,不得仅用 strlen 判断。需先 Authorize 再 StartTransaction
* 才能获取正式 txId 以完成 StopTransaction。
*
* 判定为待补发(pending)的条件:
* - p 为 NULL 或空串
* - p[0] == 0xFF(离线占位符)
*
* @param[in] p - preTradeNo 字段指针
*
* @return U8_T - 1=尚无有效 txId(需补发),0=已有有效 txId
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
U8_T u8_ocpp_is_pre_trade_no_pending(const U8_T *p)
{
if (p == NULL || p[0] == '\0')
return 1;
if (p[0] == (U8_T)0xFF)
return 1;
return 0;
}
/**
* ************************************************************************
* @brief 将 StartTransaction 应答中的 transactionId 写入未结订单
*
* @details 安全策略:若平台返回 transactionId<=0 但本地已保存有效 txId
* 则保留本地值,避免用无效 txId 覆盖。正常情况下将 txId 写入
* s_log->u8_preTradeNo 并复位鉴权阶段与计时器。
*
* @param[in] transaction_id - 平台返回的 transactionId
*
* @return void
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static void v_ocpp_unsettled_apply_starttx_txid(int transaction_id)
{
S_LOG_DATA *s_log;
int saved_tx;
if (private_ocpp_data == NULL
|| private_ocpp_data->s_unsettled_order_data == NULL) {
return;
}
s_log = private_ocpp_data->s_unsettled_order_data;
saved_tx = atoi((const char *)s_log->u8_preTradeNo);
if (transaction_id <= 0 && saved_tx > 0) {
OCPP_PRINTF_LOG("Unsettled: keep saved txId=%d, ignore StartTx txId=%d\r\n",
saved_tx, transaction_id);
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_NONE;
private_ocpp_data->u32_unsettled_last_stx_s = 0U;
private_ocpp_data->u32_timer = 0U;
return;
}
memset(s_log->u8_preTradeNo, 0, sizeof(s_log->u8_preTradeNo));
sprintf((char *)s_log->u8_preTradeNo, "%d", transaction_id);
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_NONE;
private_ocpp_data->u32_unsettled_last_stx_s = 0U;
private_ocpp_data->u32_timer = 0U;
}
/**
* ************************************************************************
* @brief 未结补单:Authorize 返回非 Accepted 时是否仍继续 StartTransaction
*
* @details 对于 Bloced / Invalid 状态,未结订单仍需上送 MV 数据和 StopTx,
* 因此允许绕过授权失败继续执行 StartTransaction。
* Expired / ConcurrentTx 等状态不绕过,须重试 Authorize。
*
* @param[in] status - Authorize.conf 返回的 idTagInfo.status 字符串
*
* @return U8_T - 1=绕过授权继续 StartTx0=需重试或放弃
*
* @author jiankalka
* @date 2026-06-10
* ************************************************************************
*/
static U8_T u8_unsettled_authorize_bypass_to_starttx(const char *status)
{
if (status == NULL) {
return 0U;
}
if (strcmp(status, OCPP_STR_Blocked) == 0 || strcmp(status, OCPP_STR_Invalid) == 0) {
return 1U;
}
return 0U;
}
/**
* @brief 安全释放未结算订单内存
*
* 统一释放动态内存并置NULL,避免重复释放
*/
static void v_safe_free_unsettled_order_data(void)
{
if (private_ocpp_data == NULL) {
return;
}
private_ocpp_data->u32_unsettled_last_stx_s = 0U;
private_ocpp_data->u32_unsettled_last_auth_s = 0U;
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_NONE;
v_ocpp_mv_offline_unsettled_abort();
if (private_ocpp_data->s_unsettled_order_data != NULL)
{
OCPP_FREE(private_ocpp_data->s_unsettled_order_data);
private_ocpp_data->s_unsettled_order_data = NULL;
//OCPP_PRINTF_LOG("[DEBUG] 未结算订单:释放动态内存\n");
}
}
/**
* 未结订单且尚无有效 transactionIdAuthorize(idTag) → 仅 Accepted 后 StartTransaction → 应答写 preTradeNo → 外层再 StopTransaction。
* @return 0 订单已释放;1 仍处理中(含等待 Authorize.conf / 节流窗口内)
*/
static U8_T u8_unsettled_pretrade_pipeline(void)
{
S_LOG_DATA *s_log = private_ocpp_data->s_unsettled_order_data;
U32_T now;
U8_T gunIdx;
if (private_ocpp_data == NULL || s_log == NULL) {
return 0U;
}
if (private_ocpp_data->u8_unsettled_auth_phase == OCPP_UNSETTLED_AUTH_NONE) {
now = (U32_T)get_current_seconds();
if (private_ocpp_data->u32_unsettled_last_auth_s != 0U
&& (now - private_ocpp_data->u32_unsettled_last_auth_s) < 8U) {
return 1U;
}
gunIdx = s_log->u8_gunNo;
if (gunIdx >= GUN_MAX_CNT) {
gunIdx = 0U;
}
OCPP_PRINTF_LOG("Unsettled: Authorize before StartTx (gun=%u)\r\n", (unsigned)gunIdx);
private_ocpp_data->u32_unsettled_last_auth_s = now;
{
int32_t sm = send_mag((int)gunIdx, OCPP_Authorize, NULL, 1);
if (sm <= 0) {
OCPP_PRINTF_LOG("Unsettled: Authorize ws_send fail, retry later\r\n");
return 1U;
}
}
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_WAIT;
return 1U;
}
if (private_ocpp_data->u8_unsettled_auth_phase == OCPP_UNSETTLED_AUTH_WAIT) {
return 1U;
}
/* OCPP_UNSETTLED_AUTH_OK:本单仅允许发送一次 StartTransaction;已发则等 conf 写入 preTradeNo */
if (private_ocpp_data->u32_unsettled_last_stx_s != 0U) {
if (!u8_ocpp_is_pre_trade_no_pending(s_log->u8_preTradeNo)) {
private_ocpp_data->u32_unsettled_last_stx_s = 0U;
return 0U;
}
return 1U;
}
now = (U32_T)get_current_seconds();
OCPP_PRINTF_LOG("Unsettled: send StartTx once to get txId (after Authorize ok)\n");
{
int32_t sm = send_mag(0, OCPP_StartTransaction, NULL, 1);
if (sm <= 0) {
OCPP_PRINTF_LOG("Unsettled: StartTx ws_send fail, retry later\r\n");
return 1U;
}
}
private_ocpp_data->u32_unsettled_last_stx_s = now;
return 1U;
}
/**
* @brief 检查未结算订单并提取订单信息(联网已登录后周期调用)
*
* 未结上送分两类(均使用 `send_mag(..., type==1)`,数据来自 `s_unsettled_order_data`):
*
* 1) **本地已有平台 transactionId**`u8_preTradeNo` 非空且非 0xFF 占位,见 `u8_ocpp_is_pre_trade_no_pending`
* 直接 **StopTransaction**:携带结束时间、meterStop、reason、transactionData 等订单信息;发送后释放内存
* (本实现为 fire-and-forget,不等待 StopTransaction.conf 再删上下文)。
*
* 2) **尚无流水号**(空串或 0xFF 占位,常见于离线启动)
* **Authorize** → **Accepted** 或 **Blocked/Invalid**(未结补单仍上送数据)后 **StartTransaction**
* → **StartTransaction.conf** 写入 `u8_preTradeNo`(含 **transactionId=0**
* → 离线 **MeterValues** 补发 → **StopTransaction**。
* **Authorize** 带节流;**Expired/ConcurrentTx** 等仍重试 Authorize。
*
* @return >0 仍有未结条数(含当前在内存处理中的一单);0 无单或本单已丢弃;**-1** malloc 失败
*/
static S8_T s8_unsettled_order_chaek(void)
{
S8_T unsettledCount = 0;
static U8_T u8_cnt = 0;
// 如果已经有订单数据,检查是否需要继续处理
if (private_ocpp_data->s_unsettled_order_data != NULL)
{
// 检查用户ID是否有效
if (strlen((char *)private_ocpp_data->s_unsettled_order_data->u8_user_id) == 0)
{
OCPP_PRINTF_LOG("Unsettled: invalid uid, drop\n");
v_safe_free_unsettled_order_data();
return 0;
}
/* 尚无 txIdAuthorize → StartTransaction;已取得 txId 则继续离线 MV 补发与 StopTx */
if (u8_ocpp_is_pre_trade_no_pending(private_ocpp_data->s_unsettled_order_data->u8_preTradeNo))
{
(void)u8_unsettled_pretrade_pipeline();
if (u8_ocpp_is_pre_trade_no_pending(private_ocpp_data->s_unsettled_order_data->u8_preTradeNo)) {
return 1;
}
}
{
if (u8_ocpp_unsettled_is_live_charging_session(private_ocpp_data->s_unsettled_order_data) != 0U) {
v_safe_free_unsettled_order_data();
return 0;
}
if (u8_ocpp_skip_unsettled_stop_for_live_tx(private_ocpp_data->s_unsettled_order_data) != 0U) {
(void)v_clear_temp_chg_record_in_eeprom(private_ocpp_data->s_unsettled_order_data->u8_gunNo);
v_safe_free_unsettled_order_data();
return 0;
}
if (u8_ocpp_mv_offline_unsettled_step(private_ocpp_data->s_unsettled_order_data) != 0U) {
return 1;
}
if (u8_cnt++ > 3) // 3次未成功则不再上送
{
u8_cnt = 0;
OCPP_PRINTF_LOG("Unsettled: retry limit, drop\n");
v_safe_free_unsettled_order_data();
return 0;
}
OCPP_PRINTF_LOG("Unsettled: send StopTx, txId=%s\r\n",
private_ocpp_data->s_unsettled_order_data->u8_preTradeNo);
{
U32_T order_idx = private_ocpp_data->s_unsettled_order_data->u32_index;
int32_t sm = send_mag(0, OCPP_StopTransaction, NULL, 1);
if (sm > 0) {
(void)u8_platform_settlement_confirm(order_idx);
}
}
v_safe_free_unsettled_order_data();
}
return 1; // 表示有订单正在处理
}
/* 充电中且本枪仍有在线会话离线 MV 待补发:延后拉未结单,避免与 session 抢同一 Flash 映射 */
{
U8_T gi;
for (gi = 0U; gi < GUN_MAX_CNT; gi++) {
if (u8_bs_get_flow_state(gi) == 2u
&& u8_ocpp_mv_offline_session_replay_pending(gi) != 0u) {
return 0;
}
}
}
// 没有订单数据,申请新内存并获取订单
u8_cnt = 0;
private_ocpp_data->s_unsettled_order_data = (S_LOG_DATA *)OCPP_MALLOC(sizeof(S_LOG_DATA));
if (private_ocpp_data->s_unsettled_order_data == NULL)
{
OCPP_PRINTF_LOG("Unsettled: malloc fail\n");
return -1;
}
memset(private_ocpp_data->s_unsettled_order_data, 0, sizeof(S_LOG_DATA));
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_NONE;
// 获取未结算订单
unsettledCount = s8_plat_get_unsettled_order( private_ocpp_data->s_unsettled_order_data,0);
if (unsettledCount <= 0) // 没有未结算订单或获取失败
{
//OCPP_PRINTF_LOG("未结算订单:无未结算订单或获取失败,count=%d\n", unsettledCount);
v_safe_free_unsettled_order_data();
return unsettledCount;
}
// 检查获取的订单数据是否有效
if (strlen((char *)private_ocpp_data->s_unsettled_order_data->u8_user_id) == 0)
{
OCPP_PRINTF_LOG("Unsettled: uid invalid\n");
v_safe_free_unsettled_order_data();
return 0;
}
if (u8_ocpp_is_pre_trade_no_pending(private_ocpp_data->s_unsettled_order_data->u8_preTradeNo))
{
(void)u8_unsettled_pretrade_pipeline();
if (u8_ocpp_is_pre_trade_no_pending(private_ocpp_data->s_unsettled_order_data->u8_preTradeNo)) {
return unsettledCount;
}
}
if (u8_ocpp_unsettled_is_live_charging_session(private_ocpp_data->s_unsettled_order_data) != 0U) {
v_safe_free_unsettled_order_data();
return 0;
}
if (u8_ocpp_skip_unsettled_stop_for_live_tx(private_ocpp_data->s_unsettled_order_data) != 0U) {
(void)v_clear_temp_chg_record_in_eeprom(private_ocpp_data->s_unsettled_order_data->u8_gunNo);
v_safe_free_unsettled_order_data();
return 0;
}
if (u8_ocpp_mv_offline_unsettled_step(private_ocpp_data->s_unsettled_order_data) != 0U) {
return unsettledCount;
}
OCPP_PRINTF_LOG("Unsettled: send StopTx, txId=%s uid=%s\r\n",
private_ocpp_data->s_unsettled_order_data->u8_preTradeNo,
private_ocpp_data->s_unsettled_order_data->u8_user_id);
{
U32_T order_idx = private_ocpp_data->s_unsettled_order_data->u32_index;
int32_t sm = send_mag(0, OCPP_StopTransaction, NULL, 1);
if (sm > 0) {
(void)u8_platform_settlement_confirm(order_idx);
}
}
v_safe_free_unsettled_order_data();
return unsettledCount;
}
/**
* @brief 充电鉴权相关节拍复位(按枪轮询调用)
*
* @details
* - 调用时机:已登录 CSMS 后,主循环内对每把枪调用(见本文件末尾 for GUN_MAX_CNT)。
* - **充电中**u8_get_pile_state == 2):不再根据本地 preTradeNo/transactionId 做补发 StartTransaction 或停充(原逻辑已移除)。
* - **非充电中**:清零 u8_aut_cnt,并重置 OCPP_TIMER_Authorize_GUNx,便于下次会话鉴权节拍。
*
* @param[in] u8_gunNo 枪索引 0..GUN_MAX_CNT-1
*/
static void v_ocpp_private_charge_authentication_cheak(U8_T u8_gunNo)
{
U8_T id = s_bs_task_ctrl.ocpp_data.id;
if(u8_get_pile_state(u8_gunNo) == 2) //充电中 一分钟后再判断
{
U8_T u8_charge_time = u32_bs_get_sys_data(u8_gunNo,E_BS_GET_SYS_DATA_LOG_CHARGE_TIME,0); //充电时间
if(u8_charge_time < 1)
return;
if (private_ocpp_data->ulong_timer - private_ocpp_data->u32_timer < 10) // 30秒检查一次
return;
private_ocpp_data->u32_timer = private_ocpp_data->ulong_timer;
#if 0 //屏蔽此功能
if(s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].s_gundata[u8_gunNo].txFlag_autoRwsult != 1) //充电中,鉴权结果不为1
{
if(private_ocpp_data->s_ctrl_flag[u8_gunNo].u8_aut_cnt == 0)
{
private_ocpp_data->s_timerCnt[OCPP_TIMER_Authorize_GUNA+u8_gunNo].u16_timerCnt = private_ocpp_data->s_timerCnt[OCPP_TIMER_Authorize_GUNA+u8_gunNo].u16_timeNUM;
OCPP_PRINTF_LOG("Chg: auth fail\n");
}
else if(private_ocpp_data->s_ctrl_flag[u8_gunNo].u8_aut_cnt > 2) //100ms * 200 = 20秒
{
S_BS_CHAR_CTRL remote_data;
memset(&remote_data,0,sizeof(S_BS_CHAR_CTRL));
remote_data.u16_ctrl_res = E_FAULT_13064; //非法鉴权
v_bs_charge_action_ctrl(u8_gunNo,E_CHAR_CTRL_STOP,&remote_data); //控制停止
}
private_ocpp_data->s_ctrl_flag[u8_gunNo].u8_aut_cnt ++;
}
#endif
}
else{
private_ocpp_data->s_ctrl_flag[u8_gunNo].u8_aut_cnt = 0;
private_ocpp_data->s_timerCnt[OCPP_TIMER_Authorize_GUNA+u8_gunNo].u16_timeNUM = 60*5; //秒 A枪鉴权查询上传
private_ocpp_data->s_timerCnt[OCPP_TIMER_Authorize_GUNA+u8_gunNo].u16_timerCnt = private_ocpp_data->s_timerCnt[OCPP_TIMER_Authorize_GUNA+u8_gunNo].u16_timeNUM; //鉴权计时初始化,确保充电时立即进行一次鉴权
}
}
#endif /* 未结订单与充电鉴权 */
#if 1 //发送
/**
* @brief 组帧 BootNotification.req:上报充电桩厂商/型号/固件/ICCID 等注册信息
*/
static cJSON* BootNotification_mag(OCPP_CMD cmd)
{
BootNotificationRequest json_data;
char u8_str_data[BS_BUF_SIZE_32];
U8_T u8_gunCnt = u32_bs_get_sys_data(GUN_NULL,E_BS_GET_SYS_DATA_GUN_NUM,0); //获取枪数量
memset(u8_str_data,0,BS_BUF_SIZE_32);
u8_get_str_data(GUN_NULL,E_STR_PILE_NO, 0 , u8_str_data,BS_BUF_SIZE_32);
memset(&json_data , 0, sizeof(BootNotificationRequest));
//必选项
strncpy(&json_data.chargePointVendor[0], s_ocpp_flash_cfg_data.s_ocpp_charge_data.chargePointVendor
, sizeof(json_data.chargePointVendor)-1 ); //供应商
if(u8_gunCnt == 1)
memcpy(&json_data.chargePointModel[0], "SingleSocketCharger" , strlen((char *)"SingleSocketCharger") ); //充电桩模式--单插座充电
else
memcpy(&json_data.chargePointModel[0], "DualSocketCharger" , strlen((char *)"DualSocketCharger") ); //充电桩模式--双枪充电
//可选项
memcpy(&json_data.chargePointSerialNumber[0], u8_str_data , strlen((char *)u8_str_data) ); //充电桩序列号
memcpy(&json_data.chargeBoxSerialNumber[0], u8_str_data , strlen((char *)u8_str_data) ); //充电箱序列号
memset(u8_str_data,0,BS_BUF_SIZE_32);
u8_get_str_data(GUN_NULL,E_STR_SOFTWARE_VERSION, 0 , u8_str_data,BS_BUF_SIZE_32);
memcpy(&json_data.firmwareVersion[0], u8_str_data , strlen((char *)u8_str_data) ); //充电桩固件版本
memset(u8_str_data,0,BS_BUF_SIZE_32);
u8_get_str_data(GUN_NULL,E_STR_SDK_SIM_ID, 0 , u8_str_data,BS_BUF_SIZE_32);
strncpy(&json_data.iccid[0], u8_str_data, BS_BUF_SIZE_32-1 ); //SIM卡标识号
strncpy(&json_data.imsi[0], u8_str_data , BS_BUF_SIZE_32-1 ); //imsi号
strncpy(&json_data.meterType[0], "Meter645" , strlen((char *)"Meter645") ); //电表类型
strncpy(&json_data.meterSerialNumber[0], "123456" , strlen((char *)"123456") ); //电表号
return j_get_ocpp_json(cmd , &json_data);
}
/**
* @brief 组帧 Heartbeat.req:心跳保活(空对象 body
*/
static cJSON* Heartbeat_mag()
{
cJSON* body;
//必须项
body = cJSON_CreateObject();
return body;
}
/**
* @brief 组帧 StatusNotification.req:上报连接器状态(Available/Charging/Faulted 等)及错误码
* @param[in] gunNo - 枪号
* @param[in] cmd - OCPP 命令枚举
* @param[in] type - 0xFF=召测(connectorId=0),其他=按枪号计算 connectorId
*/
static cJSON* StatusNotification_mag(int gunNo,OCPP_CMD cmd,int type)
{
StatusNotificationRequest json_data;
OCPP_STATUSNOTIFICATION_ERRORCODE error_id;
OCPP_STATUSNOTIFICATION_STATUSCODE state_id;
U16_T u16_flaut_data = u32_bs_get_sys_data(GUN_NULL,E_BS_GET_SYS_DATA_NOW_FAULT_ALARM_DATA,0); //获取故障内容
char time_str[30] = {0};
//char u8_str_data[BS_BUF_SIZE_64];
const char *data_str = NULL;
int transactionId = 0;
v_ocpp_timestamp_to_str(NULL , time_str); //时间格式 转换成字符串
//u8_get_str_data(gunNo,E_STR_LOG_preTradeNo,0,u8_str_data,BS_BUF_SIZE_64); //获取流水号
// transactionId = atoi((char *)u8_str_data);
u8_ocpp_StatusNotification_change_data(gunNo,&error_id,&state_id,OCPP_STATUS_NOTIFY_MODE_NORMAL); //获取状态和错误状态
memset(&json_data , 0, sizeof(StatusNotificationRequest));
#if CHARGE_TEST_FLAG //裸机充电测试标志
if(charge_test.gunNo == gunNo)
{
if(charge_test.flag == CHARGE_TES_free)
state_id = OCPP_STATUSCODE_Available; //空闲
else if(charge_test.flag == CHARGE_TES_stop)
state_id = OCPP_STATUSCODE_Finishing; //完成
else if(charge_test.flag == CHARGE_TES_charge)
state_id = OCPP_STATUSCODE_Charging; //充电
else
state_id = OCPP_STATUSCODE_Preparing; //准备
error_id = OCPP_ERRORCODE_NoError;
}
#endif
//必选
if(type == 0xFF) //召测信息 0桩信息
{
json_data.connectorId = 0;
}
else
{
json_data.connectorId = gunNo + 1; //连接器id
}
data_str = s_get_ocpp_enum_data_str(OCPP_StatusNotification_statusCode,state_id);
memcpy(json_data.status , data_str , strlen(data_str)); //枚举 ocpp_errorCode 当前的错误状态代码。应当表示充电桩或连接器当前的错误情况。
data_str = s_get_ocpp_enum_data_str(OCPP_StatusNotification_errorCode,error_id);
memcpy(json_data.errorCode , data_str , strlen(data_str)); //枚举 statusCode 连接器的当前状态。
//可选
//memcpy(json_data.info , "null" ,strlen("null")); //与错误相关的其他自由格式的信息
memcpy(json_data.timestamp , time_str ,strlen(time_str)); //日期时间 状态变化发生的时间戳
memcpy(json_data.vendorId , "iECharge" ,strlen("iECharge")); //制造商标识。
if (error_id == OCPP_ERRORCODE_NoError || u16_flaut_data == 0U) {
strcpy(json_data.vendorErrorCode, "0");
} else {
snprintf(json_data.vendorErrorCode, sizeof(json_data.vendorErrorCode), "%u", (unsigned)u16_flaut_data);
}
return j_get_ocpp_json(cmd , &json_data);
}
/**
* @brief 组帧 MeterValues.req:上送计量数据(电压/电流/功率/电量/SOC 等)
* @param[in] gunNo - 枪号
* @param[in] cmd - OCPP 命令枚举
* @param[in] type - 上送场景:0=空闲, 1=充电中周期, 2=交易开始, 3=交易停止
*/
static cJSON* MeterValues_mag(int gunNo,OCPP_CMD cmd,int type)
{
MeterValuesRequest json_data;
char time_str[30] = {0},data_str[20];
char u8_str_data[BS_BUF_SIZE_64];
#if 1 // 根据 枚举MeterValues测量项 配置参数 上送数据内容
U8_T i,u8_data_num=0;
v_ocpp_timestamp_to_str(NULL , time_str); //时间格式 转换成字符串
memset(&json_data , 0, sizeof(MeterValuesRequest));
json_data.connectorId = gunNo + 1; //必须 表示充电连接器的唯一标识符。
memcpy(json_data.meterValue[0].timestamp , time_str ,strlen(time_str)); //必须 记录取样数据的时间,ISO 8601 格式。
u8_get_str_data(gunNo,E_STR_LOG_preTradeNo,0,u8_str_data,BS_BUF_SIZE_64); //获取流水号
json_data.transactionId = atoi((char *)u8_str_data);
// 遍历所有 枚举MeterValues测量项
for (i = 0; i < 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_get_ocpp_measurand_data(&json_data.meterValue[0].sampledValue[u8_data_num],gunNo,(OCPP_MEASURAND_ID)i,0) == 1) //获取当前数据项
{
// OCPP_PRINTF_LOG("MeterValues_measurand[%d]\n",i);
if(++u8_data_num >= MeterValuesRequest_SV_NUM) //超过最大数量 不再写入
break;
}
}
}
#if OCPP_METERVALUES_VOL_CUR_REQUEST_EN
if(u8_data_num < MeterValuesRequest_SV_NUM-2) //还有空闲位置 将当前SOC上传
{
U16_T u16_need_vol = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_BMS_NEED_VOL,0); //需求电压 单位0.1V
U16_T u16_need_cur = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_BMS_NEED_CUR,0); //需求电流 单位0.1A
memset(data_str , 0, 20);
sprintf(data_str,"%.3f",(F32_T)(u16_need_vol / 10.0));
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].measurand , OCPP_STR_VoltageRequest ,strlen(OCPP_STR_VoltageRequest)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(json_data.meterValue[0].sampledValue[u8_data_num].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
//memcpy(json_data.meterValue[0].sampledValue[u8_data_num].location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].unit , OCPP_STR_V ,strlen(OCPP_STR_V)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_data_num++;
memset(data_str , 0, 20);
sprintf(data_str,"%.3f",(F32_T)(u16_need_cur / 10.0)); //需求电流
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].measurand , OCPP_STR_CurrentRequest ,strlen(OCPP_STR_CurrentRequest)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(json_data.meterValue[0].sampledValue[u8_data_num].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
//memcpy(json_data.meterValue[0].sampledValue[u8_data_num].location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.meterValue[0].sampledValue[u8_data_num].unit , OCPP_STR_A ,strlen(OCPP_STR_A)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
u8_data_num++;
// OCPP_PRINTF_LOG("MeterValues_need_vol=%d,need_cur=%d\n",u16_need_vol ,u16_need_cur);
}
#endif
if(u8_data_num == 0)
{
OCPP_PRINTF_LOG("u8_data_num is 0,dont send meterValue\n");
return NULL;
}
else if(u8_data_num < MeterValuesRequest_SV_NUM) //还有空闲位置 将起始SOC上传
{
u8_get_ocpp_measurand_data(&json_data.meterValue[0].sampledValue[u8_data_num],gunNo,ENUM_SoC,1);
}
#else
U8_T u8_start_soc = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_SOC_DATA,1); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
U8_T u8_now_soc = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_SOC_DATA,0); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
U16_T u16_put_vol = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_METER_VOL,0); //电表电压 单位0.1V
U16_T u16_put_cur = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_METER_CUR,0); //电表电流 单位0.1A
U64_T u64_energe = u64_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_MEMTER_ELECT ) / 10; //电表电量 0.0001kWh //u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_LOG_ENERGY,2); //充电电量 0.001kW
v_ocpp_timestamp_to_str(NULL , time_str); //时间格式 转换成字符串
memset(&json_data , 0, sizeof(MeterValuesRequest));
json_data.connectorId = gunNo + 1; //必须 表示充电连接器的唯一标识符。
memcpy(json_data.meterValue[0].timestamp , time_str ,strlen(time_str)); //必须 记录取样数据的时间,ISO 8601 格式。
u8_get_str_data(gunNo,E_STR_LOG_preTradeNo,0,u8_str_data,BS_BUF_SIZE_64); //获取流水号
json_data.transactionId = atoi((char *)u8_str_data);
#if CHARGE_TEST_FLAG //裸机充电测试标志
json_data.transactionId = charge_test.transactionId;
#endif
memset(data_str , 0, 20);
sprintf(data_str,"%f",(F32_T)(u16_put_vol / 10.0));
#if CHARGE_TEST_FLAG //裸机充电测试标志 //电压值
memset(data_str , 0, 20);
sprintf(data_str,"%f",charge_test.f32_charge_vol);
#endif
memcpy(json_data.meterValue[0].sampledValue[0].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.meterValue[0].sampledValue[0].context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.meterValue[0].sampledValue[0].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.meterValue[0].sampledValue[0].measurand , OCPP_STR_Voltage ,strlen(OCPP_STR_Voltage)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
memcpy(json_data.meterValue[0].sampledValue[0].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.meterValue[0].sampledValue[0].location , OCPP_STR_Body ,strlen(OCPP_STR_Body)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.meterValue[0].sampledValue[0].unit , OCPP_STR_V ,strlen(OCPP_STR_V)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
memset(data_str , 0, 20);
sprintf(data_str,"%f",(F32_T)(u16_put_cur / 10.0)); //充电电流
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(data_str , 0, 20);
sprintf(data_str,"%f",charge_test.f32_charge_cur); //电流值
#endif
memcpy(json_data.meterValue[0].sampledValue[1].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.meterValue[0].sampledValue[1].context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.meterValue[0].sampledValue[1].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.meterValue[0].sampledValue[1].measurand , OCPP_STR_CurrentImport ,strlen(OCPP_STR_CurrentImport)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
memcpy(json_data.meterValue[0].sampledValue[1].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.meterValue[0].sampledValue[1].location , OCPP_STR_Body ,strlen(OCPP_STR_Body)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.meterValue[0].sampledValue[1].unit , OCPP_STR_A ,strlen(OCPP_STR_A)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
memset(data_str , 0, 20);
sprintf(data_str,"%llu",u64_energe); //充电电量值 wh
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(data_str , 0, 20);
sprintf(data_str,"%d",charge_test.u16_power_data); //电量值 Wh
#endif
memcpy(json_data.meterValue[0].sampledValue[2].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.meterValue[0].sampledValue[2].context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.meterValue[0].sampledValue[2].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.meterValue[0].sampledValue[2].measurand , OCPP_STR_EnergyActiveImportRegister ,strlen(OCPP_STR_EnergyActiveImportRegister)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
memcpy(json_data.meterValue[0].sampledValue[2].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.meterValue[0].sampledValue[2].location , OCPP_STR_Body ,strlen(OCPP_STR_Body)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.meterValue[0].sampledValue[2].unit , OCPP_STR_Wh ,strlen(OCPP_STR_Wh)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
memset(data_str , 0, 20);
sprintf(data_str,"%d",u8_start_soc); //起始SOC值
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(data_str , 0, 20);
sprintf(data_str,"%d",0); //起始SOC值
#endif
memcpy(json_data.meterValue[0].sampledValue[3].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.meterValue[0].sampledValue[3].context , OCPP_STR_TransactionBegin ,strlen(OCPP_STR_TransactionBegin)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.meterValue[0].sampledValue[3].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.meterValue[0].sampledValue[3].measurand , OCPP_STR_SOC ,strlen(OCPP_STR_SOC)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(json_data.meterValue[0].sampledValue[3].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.meterValue[0].sampledValue[3].location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.meterValue[0].sampledValue[3].unit , OCPP_STR_Percent ,strlen(OCPP_STR_Percent)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
memset(data_str , 0, 20);
sprintf(data_str,"%d",u8_now_soc); //当前OC值
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(data_str , 0, 20);
sprintf(data_str,"%d",charge_test.u8_soc); //周期SOC值
#endif
memcpy(json_data.meterValue[0].sampledValue[4].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.meterValue[0].sampledValue[4].context , OCPP_STR_SamplePeriodic ,strlen(OCPP_STR_SamplePeriodic)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.meterValue[0].sampledValue[4].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.meterValue[0].sampledValue[4].measurand , OCPP_STR_SOC ,strlen(OCPP_STR_SOC)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(json_data.meterValue[0].sampledValue[3].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.meterValue[0].sampledValue[4].location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.meterValue[0].sampledValue[4].unit , OCPP_STR_Percent ,strlen(OCPP_STR_Percent)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
#endif
return j_get_ocpp_json(cmd , &json_data);
}
/**
* @brief 组帧 StartTransaction.req:上报交易开始信息(idTag/meterStart/timestamp
* @param[in] gunNo - 枪号
* @param[in] type - 0=实时订单, 1=未结订单(s_unsettled_order_data)
*/
static cJSON* StartTransaction_mag(int gunNo,OCPP_CMD cmd,int type)
{
StartTransactionRequest json_data;
char u8_str_data[BS_BUF_SIZE_64] = {0};
U64_T u64_meter_data; //电表电量 0.0001kWh
memset(&json_data , 0, sizeof(StartTransactionRequest));
if(type == 0) //从实时订单获取
{
u64_meter_data = u64_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_MEMTER_ELECT );
v_ocpp_timestamp_to_str(NULL , u8_str_data); //时间格式 转换成字符串
//必选
json_data.connectorId = gunNo + 1; //连接器id
json_data.meterStart = u64_meter_data/10; //交易开始时的电表读数,这通常是充电开始时的初始能量值。 wH
memcpy(json_data.timestamp , u8_str_data ,strlen(u8_str_data)); //日期时间 状态变化发生的时间戳
u8_get_str_data(gunNo,E_STR_LOG_user_id,0,u8_str_data,BS_BUF_SIZE_64);
memcpy(json_data.idTag ,u8_str_data , strlen(u8_str_data)); //标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
if(strlen(json_data.idTag) == 0)
{
OCPP_PRINTF_LOG("idTag is null\n");
return NULL;
}
}
else //从临时订单获取
{
S_LOG_DATA *s_log = private_ocpp_data->s_unsettled_order_data;
//必选
json_data.connectorId = s_log->u8_gunNo + 1; //连接器id
json_data.meterStart = s_log->u64_startKWh/10; //交易开始时的电表读数,这通常是充电开始时的初始能量值。 wH
v_ocpp_timestamp_to_str(&s_log->StartChargeTime , u8_str_data); //时间格式 转换成字符串
memcpy(json_data.timestamp , u8_str_data ,strlen(u8_str_data)); //日期时间 状态变化发生的时间戳
memcpy(json_data.idTag ,s_log->u8_user_id , strlen((char *)s_log->u8_user_id)); //标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
}
//可选 E_STR_LOG_preTradeNo
json_data.reservationId = 0; // 如果交易基于事先的预定进行,此字段包含预定的ID。
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(json_data.idTag , 0 , sizeof(json_data.idTag));
memcpy(json_data.idTag , charge_test.idTag, strlen(charge_test.idTag));
#endif
return j_get_ocpp_json(cmd , &json_data);
}
/**
* @brief 组帧 StopTransaction.req:上报交易结束信息(meterStop/reason/transactionData
* @param[in] gunNo - 枪号
* @param[in] type - 0=实时订单, 1=未结订单
*/
static cJSON* StopTransaction_mag(int gunNo,OCPP_CMD cmd,int type)
{
StopTransactionRequest json_data;
char u8_str_data[BS_BUF_SIZE_64] = {0},data_str[20];
const char *mag = NULL;
U8_T stop_reason_id = 0;
#if 1 // 根据 枚举MeterValues测量项 配置参数 上送数据内容
U8_T i,u8_data_num=0;
S_LOG_DATA *s_log = NULL;
memset(&json_data , 0, sizeof(StopTransactionRequest));
if(type == 0) //从实时订单获取
{
s_log = (S_LOG_DATA *)OCPP_MALLOC(sizeof(S_LOG_DATA)); //动态申请内存空间
if(u8_plat_get_real_order(s_log,gunNo) == 0) //获取订单信息
{
return NULL;
}
}
else //从临时订单获取
{
s_log = private_ocpp_data->s_unsettled_order_data;
}
if(s_log == NULL)
{
if(type == 0) //从实时订单获取
{
OCPP_FREE(s_log);
s_log = NULL;
}
return NULL; //申请失败
}
json_data.transactionId = atoi((char *)s_log->u8_preTradeNo);
memcpy(json_data.idTag ,s_log->u8_user_id , strlen((char *)s_log->u8_user_id)); //标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
v_ocpp_timestamp_to_str(&s_log->EndChargeTime , u8_str_data); //时间格式 转换成字符串
memcpy(json_data.timestamp , u8_str_data ,strlen(u8_str_data)); //必选 交易结束的时间戳,表示交易停止的准确时间
json_data.meterStop = s_log->u64_endKWh/10;;//充电电量值 wh
/*"EmergencyStop", //紧急停止
"EVDisconnected", //电动汽车断开连接
"HardReset", //硬重置
"Local", //本地操作
"Other", //其他原因
"PowerLoss", //断电
"Reboot", //重启
"Remote", //远程操作
"SoftReset", //软重置
"UnlockCommand", //解锁命令
"DeAuthorized" //取消授权*/
//充电订单停止原因
if(s_log->e_stop_reason[0] == E_FAULT_13017){ //急停
stop_reason_id = 0;
}
else if(s_log->e_stop_reason[0] == E_FAULT_13016){ //充电中 控制导引故障
stop_reason_id = 1;
}
else if(s_log->e_stop_reason[0] == E_NORMAL_1001){ //本地停止
stop_reason_id = 3;
}
else if(s_log->e_stop_reason[0] == E_NORMAL_1002){ //后天停止
stop_reason_id = 7;
}
else if(s_log->e_stop_reason[0] == E_FAULT_13093) //平台 UnlockConnector 应答路径停机 → OCPP UnlockCommand
{
stop_reason_id = 9;
}
else if(s_log->e_stop_reason[0] == E_FAULT_13062) //硬重启
{
stop_reason_id = 2;
}
else if(s_log->e_stop_reason[0] == E_FAULT_13063) //软重启
{
stop_reason_id = 8;
}
else if(s_log->e_stop_reason[0] == E_FAULT_13073) //异常掉电
{
stop_reason_id = 5;
}
else if(s_log->e_stop_reason[0] == E_FAULT_13064) //
{
stop_reason_id = 10;
}
else{ //其他原因
stop_reason_id = 3;
}
mag = s_get_ocpp_enum_data_str(OCPP_StopTransactionRequest_reason,stop_reason_id);
memcpy(json_data.reason , mag ,strlen(mag)); //可选 指示停止充电的原因,这可以包括:正常结束、紧急停止等情况。
memcpy(json_data.transactionData[0].timestamp , json_data.timestamp ,strlen(json_data.timestamp)); //
// 遍历所有 枚举MeterValues测量项
for (i = 0; i < 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_get_ocpp_transaction_data(&json_data.transactionData[0].sampledValue[u8_data_num],
s_log,
gunNo,(OCPP_MEASURAND_ID)i,2) == 1) //获取当前数据项
{
if(++u8_data_num >= StopTransactionRequest_SV_NUM) //超过最大数量 不再写入
break;
}
}
}
if(u8_data_num == 0)
{
OCPP_PRINTF_LOG("u8_data_num is 0,dont send StopTransaction\n");
if(type == 0) //从实时订单获取
{
OCPP_FREE(s_log);
s_log = NULL;
}
return NULL;
}
else if(u8_data_num < StopTransactionRequest_SV_NUM) //还有空闲位置 将起始SOC上传
{
u8_get_ocpp_transaction_data(&json_data.transactionData[0].sampledValue[u8_data_num],
s_log,
gunNo,ENUM_SoC,1);
}
if(type == 0) //从实时订单获取
{
OCPP_FREE(s_log);
s_log = NULL;
}
#else
U8_T u8_start_soc ; //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
U8_T u8_stop_soc; //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
U64_T u64_energe ; //电表电量 0.0001kWh //u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_LOG_ENERGY,2); //充电电量 0.001kW
U16_T u16_log_stop_res ; //充电订单停止原因
memset(&json_data , 0, sizeof(StopTransactionRequest));
if(type == 0) //从实时订单获取
{
u8_start_soc = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_SOC_DATA,1); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
u8_stop_soc = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_SOC_DATA,2); //SOC 数据 type=0,当前SOCtype=1,启动时SOC;type=2,结束时SOC
u64_energe = u64_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_MEMTER_ELECT ) / 10; //电表电量 0.0001kWh //u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_LOG_ENERGY,2); //充电电量 0.001kW
u16_log_stop_res = u32_bs_get_sys_data(gunNo,E_BS_GET_SYS_DATA_LOG_STOP_REASON,0);
u8_get_str_data(gunNo,E_STR_LOG_preTradeNo,0,u8_str_data,BS_BUF_SIZE_64); //获取流水号
json_data.transactionId = atoi(u8_str_data);
u8_get_str_data(gunNo,E_STR_LOG_user_id,0,u8_str_data,BS_BUF_SIZE_64);
memcpy(json_data.idTag ,u8_str_data , strlen(u8_str_data)); //标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
v_ocpp_timestamp_to_str(NULL , u8_str_data); //时间格式 转换成字符串
}
else{ //从临时订单获取
S_LOG_DATA *s_log = private_ocpp_data->s_unsettled_order_data;
u8_start_soc = s_log->u8_startSoc;
u8_stop_soc = s_log->u8_endSoc;
u64_energe = s_log->u64_endKWh/10;
u16_log_stop_res = s_log->e_stop_reason[0];
v_ocpp_timestamp_to_str(&s_log->EndChargeTime , u8_str_data); //时间格式 转换成字符串
json_data.transactionId = atoi((char *)s_log->u8_preTradeNo);
memcpy(json_data.idTag ,s_log->u8_user_id , strlen((char *)s_log->u8_user_id)); //标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
}
json_data.meterStop = u64_energe;//充电电量值 wh
memcpy(json_data.timestamp , u8_str_data ,strlen(u8_str_data)); //必选 交易结束的时间戳,表示交易停止的准确时间
/*"EmergencyStop", //紧急停止
"EVDisconnected", //电动汽车断开连接
"HardReset", //硬重置
"Local", //本地操作
"Other", //其他原因
"PowerLoss", //断电
"Reboot", //重启
"Remote", //远程操作
"SoftReset", //软重置
"UnlockCommand", //解锁命令
"DeAuthorized" //取消授权*/
//充电订单停止原因
if(u16_log_stop_res == E_FAULT_13017){ //急停
stop_reason_id = 0;
}
else if(u16_log_stop_res == E_FAULT_13016){ //充电中 控制导引故障
stop_reason_id = 1;
}
else if(u16_log_stop_res == E_NORMAL_1001){ //本地停止
stop_reason_id = 3;
}
else if(u16_log_stop_res == E_NORMAL_1002){ //后天停止
stop_reason_id = 7;
}
else{ //其他原因
stop_reason_id = 4;
}
mag = s_get_ocpp_enum_data_str(OCPP_StopTransactionRequest_reason,stop_reason_id);
memcpy(json_data.reason , mag ,strlen(mag)); //可选 指示停止充电的原因,这可以包括:正常结束、紧急停止等情况。
#if CHARGE_TEST_FLAG //裸机充电测试标志
json_data.meterStop = charge_test.u16_power_data; //必选 交易结束时的电表读数,这通常是充电结束时的最终能量值
json_data.transactionId = charge_test.transactionId; //必选 要停止的充电事务的唯一标识符
memcpy(json_data.idTag , charge_test.idTag ,strlen(charge_test.idTag)); //可选 标识电动汽车用户或车主的唯一ID标识。
mag = s_get_ocpp_enum_data_str(OCPP_StopTransactionRequest_reason,3);
memcpy(json_data.reason , mag ,strlen(mag)); //可选 指示停止充电的原因,这可以包括:正常结束、紧急停止等情况。
#endif
memcpy(json_data.transactionData[0].timestamp , json_data.timestamp ,strlen(json_data.timestamp)); //
sprintf(data_str,"%llu",u64_energe);//充电电量值 wh
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(data_str , 0, 20);
sprintf(data_str,"%d",charge_test.u16_power_data); //结束电量值
#endif
memcpy(json_data.transactionData[0].sampledValue[0].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.transactionData[0].sampledValue[0].context , OCPP_STR_TransactionEnd ,strlen(OCPP_STR_TransactionEnd)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.transactionData[0].sampledValue[0].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.transactionData[0].sampledValue[0].measurand , OCPP_STR_EnergyActiveImportRegister ,strlen(OCPP_STR_EnergyActiveImportRegister)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
memcpy(json_data.transactionData[0].sampledValue[0].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.transactionData[0].sampledValue[0].location , OCPP_STR_Body ,strlen(OCPP_STR_Body)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.transactionData[0].sampledValue[0].unit , OCPP_STR_Wh ,strlen(OCPP_STR_Wh)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
sprintf(data_str,"%d",u8_start_soc);//起始SOC值
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(data_str , 0, 20);
sprintf(data_str,"%d",0); //
#endif
memcpy(json_data.transactionData[0].sampledValue[1].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.transactionData[0].sampledValue[1].context , OCPP_STR_TransactionBegin ,strlen(OCPP_STR_TransactionBegin)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.transactionData[0].sampledValue[1].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.transactionData[0].sampledValue[1].measurand , OCPP_STR_SOC ,strlen(OCPP_STR_SOC)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(json_data.transactionData[0].sampledValue[3].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.transactionData[0].sampledValue[1].location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.transactionData[0].sampledValue[1].unit , OCPP_STR_Percent ,strlen(OCPP_STR_Percent)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
sprintf(data_str,"%d",u8_stop_soc);//结束SOC
#if CHARGE_TEST_FLAG //裸机充电测试标志
memset(data_str , 0, 20);
sprintf(data_str,"%d",charge_test.u8_soc); //
#endif
memcpy(json_data.transactionData[0].sampledValue[2].value , data_str ,strlen(data_str)); //必选 计量值的实际数值,类型为字符串以支持任意长度的数值。
memcpy(json_data.transactionData[0].sampledValue[2].context , OCPP_STR_TransactionEnd ,strlen(OCPP_STR_TransactionEnd)); //可选 可选字段,表示计量上下文,例如 Sample.Periodic(定期采样)或 Sample.Alarm(报警采样)。
memcpy(json_data.transactionData[0].sampledValue[2].format , OCPP_STR_Raw ,strlen(OCPP_STR_Raw)); //可选 可选字段,表示计量值的格式,例如 Raw 或 SignedData。
memcpy(json_data.transactionData[0].sampledValue[2].measurand , OCPP_STR_SOC ,strlen(OCPP_STR_SOC)); //可选 可选字段,表示计量对象,例如 Energy.Active.Import.Register(导入的有效能量)。
//memcpy(json_data.transactionData[0].sampledValue[2].phase , OCPP_STR_L1 ,strlen(OCPP_STR_L1)); //可选 可选字段,表示电力相位,例如 L1, L2, L3。
memcpy(json_data.transactionData[0].sampledValue[2].location , OCPP_STR_EV ,strlen(OCPP_STR_EV)); //可选 可选字段,表示计量装置的位置,例如 Inlet, Outlet.
memcpy(json_data.transactionData[0].sampledValue[2].unit , OCPP_STR_Percent ,strlen(OCPP_STR_Percent)); //可选 可选字段,表示计量单位,例如 Wh, kWh, V, A.
#endif
return j_get_ocpp_json(cmd , &json_data);
}
/**
* @brief 组帧 Authorize.req:上传用户 idTag 请求鉴权
* @param[in] type - 0=实时订单, 1=未结补单
*/
/** @param type 0:从当前枪订单取 idTag1:从 `s_unsettled_order_data` 取 idTag(未结补单先鉴权) */
static cJSON* Authorize_mag(int gunNo,OCPP_CMD cmd,int type)
{
AuthorizeRequest json_data;
char u8_str_data[BS_BUF_SIZE_64] = {0};
memset(&json_data , 0, sizeof(AuthorizeRequest));
if (type == 1) { /* 未结补单先鉴权 */
S_LOG_DATA *s_log = private_ocpp_data->s_unsettled_order_data; /* 未结订单数据 */
if (s_log == NULL || strlen((char *)s_log->u8_user_id) == 0) {
OCPP_PRINTF_LOG("Authorize_mag unsettled: no order or empty idTag\r\n");
return NULL;
}
memcpy(json_data.idTag, s_log->u8_user_id, strlen((char *)s_log->u8_user_id)); /* 未结订单 idTag */
} else {
u8_get_str_data(gunNo,E_STR_LOG_user_id,0,u8_str_data,BS_BUF_SIZE_64);
memcpy(json_data.idTag ,u8_str_data , strlen(u8_str_data)); //标识电动汽车用户或车主的唯一ID标识,通过RFID卡或其他识别机制获取。
}
#if CHARGE_TEST_FLAG //裸机充电测试标志
if (type != 1) {
memset(json_data.idTag , 0 , sizeof(json_data.idTag));
memcpy(json_data.idTag , charge_test.idTag, strlen(charge_test.idTag));
}
#endif
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 UnlockConnector.conf:解枪锁应答 */
static cJSON* UnlockConnector_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
UnlockConnectorResponse json_data;
int state_id;
const char *data_str = NULL;
memset(&json_data , 0, sizeof(UnlockConnectorResponse));
state_id = ack_data->type;
/* 在应答组帧前停机+解锁:停机原因 E_FAULT_13093 → 订单 StopTransaction.reason = UnlockCommand */
if(state_id == 0 && ack_data->gunNo < GUN_MAX_CNT)
{
U8_T gunIdx = ack_data->gunNo;
if(u8_get_pile_state(gunIdx) == 1 || u8_get_pile_state(gunIdx) == 2)
{
S_BS_CHAR_CTRL remote_data;
memset(&remote_data, 0, sizeof(S_BS_CHAR_CTRL));
remote_data.u16_ctrl_res = E_FAULT_13093;
v_bs_charge_action_ctrl(gunIdx, E_CHAR_CTRL_STOP, &remote_data);
OCPP_PRINTF_LOG("UnlockConnector.conf: stop gun=%u reason=E_FAULT_13093\n", (unsigned)gunIdx);
}
v_remote_gunLock_ctrl((U8_T)(gunIdx + 1), 0);
}
//必选
data_str = s_get_ocpp_enum_data_str(OCPP_UnlockConnectorResponse_stateCode,state_id);
memcpy(json_data.status , data_str , strlen(data_str)); //枚举
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 RemoteStartTransaction.conf:远程启动应答 */
static cJSON* RemoteStartTransaction_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
RemoteStartTransactionResponse json_data;
int state_id;
const char *data_str = NULL;
memset(&json_data , 0, sizeof(RemoteStartTransactionResponse));
state_id = ack_data->type;
//必选
data_str = s_get_ocpp_enum_data_str(OCPP_RemoteStartTransactionResponse_statusCode,state_id);
memcpy(json_data.status , data_str , strlen(data_str)); //枚举 "Accepted", //接受,"Rejected" //拒绝
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 RemoteStopTransaction.conf:远程停止应答 */
static cJSON* RemoteStopTransaction_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
RemoteStopTransactionResponse json_data;
int state_id;
const char *data_str = NULL;
memset(&json_data , 0, sizeof(RemoteStopTransactionResponse));
state_id = ack_data->type;
//必选
data_str = s_get_ocpp_enum_data_str(OCPP_RemoteStopTransactionResponse_statusCode,state_id);
memcpy(json_data.status , data_str , strlen(data_str)); //枚举 "Accepted", //接受,"Rejected" //拒绝
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 UpdateFirmware.conf:固件升级应答,成功则置 Downloading pending */
static cJSON* UpdateFirmware_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
/* 仅在 URL 解析成功时启动升级流程,避免解析失败仍空跑状态机。
* 与 GetDiagnostics 对齐:先 CallResult,再由 ocpp_send_mag_ctrl 发 FirmwareStatusNotification(Downloading)
* 在 FSN 成功发出后置 u8_updata_flag,避免与 4G FTP/QISEND 互斥时挡应答。 */
if (private_ocpp_data != NULL) {
if (ack_data != NULL && ack_data->type == 1) {
private_ocpp_data->u8_fw_notify_pending = (U8_T)OCPP_FW_NOTIFY_PENDING_DOWNLOADING;
OCPP_PRINTF_LOG("FW update start, parse ok\n");
} else {
private_ocpp_data->u8_fw_notify_pending = (U8_T)OCPP_FW_NOTIFY_PENDING_DOWNLOAD_FAILED;
OCPP_PRINTF_LOG("FW update skip, parse_ftp_url fail\n");
}
}
return j_get_ocpp_json(cmd , NULL);
}
/** @brief 组帧 Reset.conf:远程重启应答,根据 type 选择立即/停机后重启 */
static cJSON* Reset_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
int state_id,i;
const char *data_str = NULL;
ResetResponse json_data;
S_BS_CHAR_CTRL remote_data;
memset(&json_data , 0, sizeof(ResetResponse));
state_id = ack_data->type;
//必选
if(state_id == 0)
{
/* 立即重启请求:由主循环判断是否允许 */
private_ocpp_data->u8_reboot_flag = 0xF0;
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted)); //枚举 "Accepted", //接受,"Rejected" //拒绝
}
else
{
//memcpy(json_data.status , OCPP_STR_Rejected , strlen(OCPP_STR_Rejected)); //枚举 "Accepted", //接受,"Rejected" //拒绝
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted)); //枚举 "Accepted", //接受,"Rejected" //拒绝
U8_T u8_gunCnt = u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_GUN_NUM,0); //枪数量
U8_T rebootFlag = 0;
for (i = 0; i < u8_gunCnt; i++)
{
U8_T u8_flow_state = u32_bs_get_sys_data(i,E_BS_GET_SYS_DATA_GUN_FLOW_STATE,0); ////终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
/* 仅当该枪处于“非0且非10”的状态时,才认为需要停机结算并等待 txFlag_tradUpload 1->0 */
if(u8_flow_state != 0 && u8_flow_state != 10)
{
rebootFlag = u8_set_gun_reboot_state(rebootFlag, (U8_T)i, 1);
memset(&remote_data,0,sizeof(S_BS_CHAR_CTRL));
remote_data.u16_ctrl_res = (state_id == 1)?E_FAULT_13062:E_FAULT_13063; //重启停止
v_bs_charge_action_ctrl(i,E_CHAR_CTRL_STOP,&remote_data); //控制停止
}
}
private_ocpp_data->u8_reboot_flag = rebootFlag;
}
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 CancelReservation.conf:取消预定应答 */
static cJSON* CancelReservation_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
int state_id;
const char *data_str = NULL;
CancelReservationResponse json_data;
memset(&json_data , 0, sizeof(CancelReservationResponse));
//必选
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted)); //枚举 "Accepted", //接受,"Rejected" //拒绝
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 ChangeAvailability.conf:枪可用性设置应答 */
static cJSON* ChangeAvailability_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
int state_id;
const char *data_str = NULL;
ChangeAvailabilityResponse json_data;
memset(&json_data , 0, sizeof(ChangeAvailabilityResponse));
//必选
if(ack_data->type == 0)
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted)); //枚举 "Accepted", //接受,"Rejected" //拒绝
else
memcpy(json_data.status , OCPP_STR_Rejected , strlen(OCPP_STR_Rejected)); //枚举 "Accepted", //接受,"Rejected" //拒绝
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 ChangeConfiguration.conf:参数配置变更应答(Accepted/Rejected/RebootRequired/NotSupported*/
static cJSON* ChangeConfiguration_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
int state_id;
const char *data_str = NULL;
ChangeConfigurationResponse json_data;
memset(&json_data , 0, sizeof(ChangeConfigurationResponse));
/*必选
0 "Rejected" 拒绝
1 "Accepted" 接受
2 "RebootRequired" 重启生效
3 "NotSupported" 不支持
*/
switch(ack_data->type)
{
case 0:
memcpy(json_data.status , OCPP_STR_Rejected , strlen(OCPP_STR_Rejected));
break;
case 1:
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted));
break;
case 2:
memcpy(json_data.status , OCPP_STR_RebootRequired , strlen(OCPP_STR_RebootRequired));
break;
case 3:
memcpy(json_data.status , OCPP_STR_NotSupported , strlen(OCPP_STR_NotSupported));
break;
default:
break;
}
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 TriggerMessage.conf:数据召测应答 */
static cJSON* TriggerMessage_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
int state_id;
const char *data_str = NULL;
TriggerMessageResponse json_data;
memset(&json_data , 0, sizeof(TriggerMessageResponse));
//必选
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted)); //枚举 "Accepted", //接受,"Rejected" //拒绝
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 GetConfiguration.conf:配置项查询应答 */
static cJSON* GetConfiguration_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
int state_id,i,j;
int unknown_num = 0,ack_num = 0;
const char *data_str = NULL;
GetConfigurationResponse json_data;
memset(&json_data , 0, sizeof(GetConfigurationResponse));
for (i = 0, j = 0; i < GetConfiguration_NUM; i++) //i:遍历需要获取参数配置的键 j:每遇到一个应答键则加1
{
if(ack_data->ack_data[i] != 0)
{
//赋值应答key
find_OCPP_configMag(ack_data->ack_data[i], json_data.configurationKey[j].key, OCPP_CiString50);
//赋值应答value、readonly
json_data.configurationKey[j].readonly = u8_OCPP_config_operate(ENUM_TYPE_GET, json_data.configurationKey[j].key, json_data.configurationKey[j].value, OCPP_CiString100);
//对于已识别但不支持键,将应答value清空,代表未被设置
if (u8_check_OCPP_configSupport(ack_data->ack_data[i]) == ENUM_SUPPORT_NOT)
memset(json_data.configurationKey[j].value , 0, OCPP_CiString100);
j++;
ack_num++;
}
}
for (i = 0, j = 0; i < GetConfiguration_NUM; i++) //i:遍历unknown动态数组 j:每遇到一个未知键则加1
{
if(unknown_tmpbuf[i] != NULL)
{
//赋值应答unknown
memcpy(json_data.unknownKey[j] , unknown_tmpbuf[i] ,strlen(unknown_tmpbuf[i]));
j++;
unknown_num++;
//释放动态内存
OCPP_FREE(unknown_tmpbuf[i]);
unknown_tmpbuf[i] = NULL;
}
}
#if 1
if(unknown_num == 0 && ack_num == 0) //没有应答键也没有未知键 则至少要有一个空对象
{
int k=0;
// v_ocpp_GetConfiguration_Key(KEY_ALLOW_OFFLINE_TX_FOR_UNKNOWN_ID , &json_data, &k);/* 可选 允许未知用户ID的离线交易 (布尔: 0/1) */
// v_ocpp_GetConfiguration_Key(KEY_AUTHORIZATION_CACHE_ENABLED , &json_data, &k);/* 可选 启用本地授权缓存 (布尔: 0/1) */
v_ocpp_GetConfiguration_Key(KEY_AUTHORIZE_REMOTE_TX_REQUESTS , &json_data, &k);/* 必选 允许远程启动充电事务 (布尔: 0/1) */
// v_ocpp_GetConfiguration_Key(KEY_BLINK_REPEAT , &json_data, &k);/* 可选 充电桩指示灯闪烁次数 (整数: 次数) */
v_ocpp_GetConfiguration_Key(KEY_CLOCK_ALIGNED_DATA_INTERVAL , &json_data, &k);/* 必选 时钟对齐的数据上报间隔 (整数: 秒) */
v_ocpp_GetConfiguration_Key(KEY_CONNECTION_TIMEOUT , &json_data, &k);/* 必选 TCP连接超时时间 (整数: 秒) */
// v_ocpp_GetConfiguration_Key(KEY_CONNECTOR_PHASE_ROTATION , &json_data, &k);/* 必选 连接器相位旋转配置 (字符串: L1/L2/L3顺序) */
// v_ocpp_GetConfiguration_Key(KEY_CONNECTOR_PHASE_ROTATION_MAX_LENGTH , &json_data, &k);/* 可选 相位旋转配置最大长度 (整数: 字节数) */
v_ocpp_GetConfiguration_Key(KEY_GET_CONFIGURATION_MAX_KEYS , &json_data, &k);/* 必选 GetConfiguration请求最大返回Key数 (整数: 数量) */
v_ocpp_GetConfiguration_Key(KEY_HEARTBEAT_INTERVAL , &json_data, &k);/* 必选 心跳间隔 (整数: 秒) */
// v_ocpp_GetConfiguration_Key(KEY_LIGHT_INTENSITY , &json_data, &k);/* 可选 充电桩灯光亮度 (整数: 百分比) */
v_ocpp_GetConfiguration_Key(KEY_LOCAL_AUTHORIZE_OFFLINE , &json_data, &k);/* 必选 本地离线授权模式 (布尔: 0/1) */
v_ocpp_GetConfiguration_Key(KEY_LOCAL_PRE_AUTHORIZE , &json_data, &k);/* 必选 本地预授权模式 (布尔: 0/1) */
// v_ocpp_GetConfiguration_Key(KEY_MAX_ENERGY_ON_INVALID_ID , &json_data, &k);/* 可选 无效ID用户的最大允许充电能量 (整数: Wh) */
v_ocpp_GetConfiguration_Key(KEY_METER_VALUES_ALIGNED_DATA , &json_data, &k);/* 必选 对齐数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
// v_ocpp_GetConfiguration_Key(KEY_METER_VALUES_ALIGNED_DATA_MAX_LENGTH , &json_data, &k);/* 可选 对齐数据列表最大长度 (整数: 字符数) */
v_ocpp_GetConfiguration_Key(KEY_METER_VALUES_SAMPLED_DATA , &json_data, &k);/* 必选 采样数据上报的测量项列表 (字符串: 逗号分隔的measurand值) */
// v_ocpp_GetConfiguration_Key(KEY_METER_VALUES_SAMPLED_DATA_MAX_LENGTH , &json_data, &k);/* 可选 采样数据列表最大长度 (整数: 字符数) */
v_ocpp_GetConfiguration_Key(KEY_METER_VALUE_SAMPLE_INTERVAL , &json_data, &k);/* 必选 采样间隔 (整数: 秒) */
// v_ocpp_GetConfiguration_Key(KEY_MINIMUM_STATUS_DURATION , &json_data, &k);/* 可选 状态最小持续时间 (整数: 秒) */
v_ocpp_GetConfiguration_Key(KEY_NUMBER_OF_CONNECTORS , &json_data, &k);/* 必选 充电桩连接器数量 (整数: 数量) */
//v_ocpp_GetConfiguration_Key(KEY_RESET_RETRIES , &json_data, &k);/* 必选 复位重试次数 (整数: 次数) */
v_ocpp_GetConfiguration_Key(KEY_STOP_TXN_ON_EV_SIDE_DISCONNECT , &json_data, &k);/* 必选 EV端断开时停止事务 (布尔: 0/1) */
v_ocpp_GetConfiguration_Key(KEY_STOP_TXN_ON_INVALID_ID , &json_data, &k);/* 必选 无效ID时停止事务 (布尔: 0/1) */
v_ocpp_GetConfiguration_Key(KEY_STOP_TXN_ALIGNED_DATA , &json_data, &k);/* 必选 事务停止时的对齐数据项 (字符串: 逗号分隔的measurand值) */
// v_ocpp_GetConfiguration_Key(KEY_STOP_TXN_ALIGNED_DATA_MAX_LENGTH , &json_data, &k);/* 可选 停止事务对齐数据最大长度 (整数: 字符数) */
v_ocpp_GetConfiguration_Key(KEY_STOP_TXN_SAMPLED_DATA , &json_data, &k);/* 必选 停止事务时的采样数据项 (字符串: 逗号分隔的measurand值) */
// v_ocpp_GetConfiguration_Key(KEY_STOP_TXN_SAMPLED_DATA_MAX_LENGTH , &json_data, &k);/* 可选 停止事务采样数据最大长度 (整数: 字符数) */
v_ocpp_GetConfiguration_Key(KEY_SUPPORTED_FEATURE_PROFILES , &json_data, &k);/* 必选 支持的功能配置文件 (字符串: 逗号分隔的FeatureProfile值) */
// v_ocpp_GetConfiguration_Key(KEY_SUPPORTED_FEATURE_PROFILES_MAX_LENGTH , &json_data, &k);/* 可选 功能配置文件列表最大长度 (整数: 字符数) */
v_ocpp_GetConfiguration_Key(KEY_TRANSACTION_MESSAGE_ATTEMPTS , &json_data, &k);/* 必选 事务消息重试次数 (整数: 次数) */
v_ocpp_GetConfiguration_Key(KEY_TRANSACTION_MESSAGE_RETRY_INTERVAL , &json_data, &k);/* 必选 事务消息重试间隔 (整数: 秒) */
v_ocpp_GetConfiguration_Key(KEY_UNLOCK_CONNECTOR_ON_EV_DISCONNECT , &json_data, &k);/* 必选 EV端断开时解锁连接器 (布尔: 0/1) */
// v_ocpp_GetConfiguration_Key(KEY_WEBSOCKET_PING_INTERVAL , &json_data, &k);/* 可选 WebSocket Ping间隔 (整数: 秒) */
// // --------------------------------------------------------
// // 本地授权与充电配置管理
// // --------------------------------------------------------
v_ocpp_GetConfiguration_Key(KEY_LOCAL_AUTH_LIST_ENABLED , &json_data, &k); /* 必选 启用本地授权列表 (布尔: 0/1) */
//v_ocpp_GetConfiguration_Key(KEY_LOCAL_AUTH_LIST_MAX_LENGTH , &json_data, &k); /* 必选 本地授权列表的最大条目数 (整数) */
//v_ocpp_GetConfiguration_Key(KEY_SEND_LOCAL_LIST_MAX_LENGTH , &json_data, &k); /* 必选 发送本地列表的最大条目数 (整数) */
// // --------------------------------------------------------
// // 连接器与充电行为控制
// // --------------------------------------------------------
// v_ocpp_GetConfiguration_Key(KEY_RESERVE_CONNECTOR_ZERO_SUPPORTED , &json_data, &k); /* 可选 是否支持预留连接器0 (布尔: 0/1) */
// // --------------------------------------------------------
// // 充电计划与配置限制
// // --------------------------------------------------------
// v_ocpp_GetConfiguration_Key(KEY_CHARGE_PROFILE_MAX_STACK_LEVEL , &json_data, &k); /* 必选 充电配置最大堆叠层级 (整数) */
// v_ocpp_GetConfiguration_Key(KEY_CHARGING_SCHEDULE_ALLOWED_RATE_UNIT , &json_data, &k); /* 必选 充电速率允许单位 (字符串: "A"或"W") */
// v_ocpp_GetConfiguration_Key(KEY_CHARGING_SCHEDULE_MAX_PERIODS , &json_data, &k); /* 必选 充电计划最大时段数 (整数) */
// v_ocpp_GetConfiguration_Key(KEY_CONNECTOR_SWITCH_3TO1_PHASE_SUPPORTED , &json_data, &k); /* 可选 是否支持三相切单相充电 (布尔: 0/1) */
// v_ocpp_GetConfiguration_Key(KEY_MAX_CHARGING_PROFILES_INSTALLED , &json_data, &k); /* 必选 最大可安装充电配置数 (整数) */
}
#endif
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 ClearCache.conf:清空缓存应答 */
static cJSON* ClearCache_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
const char *data_str = NULL;
ClearCacheResponse json_data;
memset(&json_data , 0, sizeof(ClearCacheResponse));
//必选
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted)); //枚举 "Accepted", //接受,"Rejected" //拒绝
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 SendLocalList.conf:本地授权列表设置应答 */
static cJSON* SendLocalList_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
const char *data_str = NULL;
SendLocalListResponse json_data;
memset(&json_data , 0, sizeof(SendLocalListResponse));
//必选
memcpy(json_data.status , OCPP_STR_Accepted , strlen(OCPP_STR_Accepted)); //枚举 "Accepted", //接受,"Rejected" //拒绝
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 GetLocalListVersion.conf:本地列表版本应答 */
static cJSON* GetLocalListVersion_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
GetLocalListVersionResponse json_data;
json_data.listVersion = s_ocpp_flash_cfg_data.s_ocpp_charge_data.card_ver;
return j_get_ocpp_json(cmd , &json_data);
}
/** @brief 组帧 GetDiagnostics.conf:诊断日志上传应答 */
static cJSON* GetDiagnostics_res(OCPP_CMD cmd,OCPP_DATA_RECV_ACK *ack_data)
{
GetDiagnosticsResponse json_data;
char logFileName[64] = {0};
memset(&json_data , 0 ,sizeof(GetDiagnosticsResponse));
/* 回包前兜底获取一次当前日志文件名,避免 fileName 为空 */
if (t_ftp_update_info.u8_file_name[0] == '\0') {
v_ocpp_get_real_log_file_name((char *)t_ftp_update_info.u8_file_name,
sizeof(t_ftp_update_info.u8_file_name));
}
strcpy(logFileName , (char *)t_ftp_update_info.u8_file_name);
strncpy(json_data.fileName , logFileName , sizeof(json_data.fileName)-1);
/* 与 UpdateFirmware 对齐:绑定当前平台联网方式,并复位 FTP 状态机。
* 否则 t_ftp_update_info.u8_netType 未赋值时网线桩会误走 4G AT;或 e_ec200a_ftp_init_step
* 停留在上次升级/上传的步骤,u8_ec200a_4g_ftp_log_upload 进 default 无进展,Plat_Comm 卡在 u8_updata_flag 分支无后续 OCPP。 */
t_ftp_update_info.u8_netType = s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_netType;
t_ftp_update_info.u8_init_flag = 0;
if (t_ftp_update_info.u8_netType == 0) {
#if EC200A_4G_EN
v_ftp_ctrl_init();
#endif
}
/* 勿在此置 u8_updata_flag:须等 Uploading 帧发出后再置(见 ocpp_send_mag_ctrl)。
* 此处已组好 CallResult 体,同时排队 Uploading,下一周期先发 DiagnosticsStatusNotification。 */
if (ack_data != NULL) {
private_ocpp_data->u8_diag_notify_pending = OCPP_DIAG_NOTIFY_PENDING_UPLOADING;
}
private_ocpp_data->u8_log_file_upload = 1; //上传日志文件标志置一
return j_get_ocpp_json(cmd , &json_data);
}
/* OCPP 1.6 DiagnosticsStatusNotificationstatus 与 u8_diag_notify_pending 一致,枚举串见 DiagnosticsStatusNotification_status[] */
static cJSON *DiagnosticsStatusNotification_mag(void)
{
DiagnosticsStatusNotificationRequest jd;
const char *data_str;
int status_id;
memset(&jd, 0, sizeof(jd));
if (private_ocpp_data == NULL) {
return j_get_ocpp_json(OCPP_DiagnosticsStatusNotification, &jd);
}
status_id = (int)private_ocpp_data->u8_diag_notify_pending;
data_str = s_get_ocpp_enum_data_str(OCPP_DiagnosticsStatusNotification_status, status_id);
memcpy(jd.status, data_str, strlen(data_str));
return j_get_ocpp_json(OCPP_DiagnosticsStatusNotification, &jd);
}
/* OCPP 1.6 FirmwareStatusNotificationstatus 与 u8_fw_notify_pending 一致 */
static cJSON *FirmwareStatusNotification_mag(void)
{
FirmwareStatusNotificationRequest jd;
const char *data_str;
int status_id;
memset(&jd, 0, sizeof(jd));
if (private_ocpp_data == NULL) {
return j_get_ocpp_json(OCPP_FirmwareStatusNotification, &jd);
}
status_id = (int)private_ocpp_data->u8_fw_notify_pending;
data_str = s_get_ocpp_enum_data_str(OCPP_FirmwareStatusNotification_status, status_id);
memcpy(jd.status, data_str, strlen(data_str));
return j_get_ocpp_json(OCPP_FirmwareStatusNotification, &jd);
}
/* 返回值:>0 已调用 ws_send 且模组侧已尝试发出(含字节数);0 未发出或暂缓(如 4G FTP 期间 QISEND 暂缓);用于避免误清 pending/误释放应答 */
static int32_t send_mag(int gunNo,int cmd,OCPP_DATA_RECV_ACK *ack_data,int type)
{
cJSON *root=NULL,*mag1=NULL,*body=NULL;
char rand_keys[64]={0},*out;
int32_t ws_ret = 0;
rand_data(rand_keys);
root = cJSON_CreateArray();
//root = cJSON_CreateObject();
if(root == NULL){
OCPP_PRINTF_LOG("root NULL");
//root = cJSON_CreateArray();
cJSON_Delete(root);
return 0;
}
if(ack_data == NULL) //为主动发送
{
const char *cmd_str = s_get_ocpp_cmd_str(cmd);
cJSON_AddNumberToObject(root,"",2);
cJSON_AddStringToObject(root, "", rand_keys);//随机数
cJSON_AddStringToObject(root, "", cmd_str);//命令
if(save_json_cmd(cmd,gunNo,rand_keys) == 0){
v_clear_save_cmd();
save_json_cmd(cmd,gunNo,rand_keys);
}
OCPP_PRINTF_LOG("send cmd gunNo=%d, str= %s\n",gunNo,cmd_str);
}
else //应答发送
{
cJSON_AddNumberToObject(root,"",3);
cJSON_AddStringToObject(root, "", ack_data->ack_key);//命令
}
switch(cmd)
{
case OCPP_BootNotification://
body = BootNotification_mag(cmd);
break;
case OCPP_Heartbeat://
body = Heartbeat_mag();
break;
case OCPP_StatusNotification://
body = StatusNotification_mag(gunNo,cmd,type);
break;
case OCPP_MeterValues://
body = MeterValues_mag(gunNo,cmd,type);
break;
case OCPP_StartTransaction://
body = StartTransaction_mag(gunNo,cmd,type);
break;
case OCPP_StopTransaction://
body = StopTransaction_mag(gunNo,cmd,type);
break;
case OCPP_Authorize://4
body = Authorize_mag(gunNo,cmd,type);
break;
//应答回复
case OCPP_RemoteStartTransaction:// 平台下发启动充电应答
body = RemoteStartTransaction_res(cmd,ack_data);
break;
case OCPP_RemoteStopTransaction://
body = RemoteStopTransaction_res(cmd,ack_data);
break;
case OCPP_UnlockConnector://
body = UnlockConnector_res(cmd,ack_data);
break;
case OCPP_UpdateFirmware://
body = UpdateFirmware_res(cmd,ack_data);
break;
case OCPP_Reset://
body = Reset_res(cmd,ack_data);
break;
case OCPP_CancelReservation://
body = CancelReservation_res(cmd,ack_data);
break;
case OCPP_ChangeAvailability://设置枪可用状态 平台->桩
body = ChangeAvailability_res(cmd,ack_data);
break;
case OCPP_ChangeConfiguration://更改参数配置 平台->桩
body = ChangeConfiguration_res(cmd,ack_data);
break;
case OCPP_TriggerMessage://更改参数配置 平台->桩
body = TriggerMessage_res(cmd,ack_data);
break;
case OCPP_GetConfiguration:
body = GetConfiguration_res(cmd,ack_data);
break;
case OCPP_ClearCache:
body = ClearCache_res(cmd,ack_data);
break;
case OCPP_SendLocalList:
body = SendLocalList_res(cmd,ack_data);
break;
case OCPP_GetLocalListVersion:
body = GetLocalListVersion_res(cmd,ack_data);
break;
case OCPP_GetDiagnostics:
body = GetDiagnostics_res(cmd,ack_data);
break;
case OCPP_DiagnosticsStatusNotification:
body = DiagnosticsStatusNotification_mag();
break;
case OCPP_FirmwareStatusNotification:
body = FirmwareStatusNotification_mag();
break;
default:
break;
}
if(body != NULL)
cJSON_AddItemToArray(root, body);
else{
if(root != NULL)
cJSON_Delete(root);
return 0;
}
out = print_out(root);
if(out != NULL)
{
ws_ret = ws_send(out,strlen(out),1,WDT_TXTDATA);
}
if (cmd == OCPP_MeterValues && ws_ret <= 0 && gunNo >= 0) {
U8_T gi = (U8_T)gunNo;
if (gi < GUN_MAX_CNT && u8_bs_get_flow_state(gi) == 2U) {
private_ocpp_data->s_ctrl_flag[gi].s_u8_mv_ws_fail_snap_req = 1U;
}
}
// mag1 = cJSON_Parse(out);
// if(mag1 != NULL){
// #if OCPP_WEBSOCKET_EN == 1
// ws_send(out,strlen(out),1,WDT_TXTDATA);
// #else
// u8_bs_send(private_ocpp_data->id,out,strlen(out));
// #endif
// ocPP_flag = 0;
// cJSON_Delete(mag1);
// }
// else{
// OCPP_PRINTF_LOG("mag1 is NULL\n");
// }
if(root != NULL)
cJSON_Delete(root);
else
OCPP_PRINTF_LOG("root is NULL\n");
if(out != NULL)
free(out);
else
OCPP_PRINTF_LOG("out is NULL\n");
return ws_ret;
}
/**
* @brief 发送一条离线缓存的 MeterValues(帧格式与 `send_mag` 主动分支一致)
*/
int32_t s32_ocpp_send_meter_values_replay(int gunNo, int transactionId, const S_OCPP_MV_OFFLINE_MV *mv)
{
cJSON *root = NULL;
cJSON *body = NULL;
char rand_keys[64] = {0};
char *out = NULL;
int32_t ws_ret = 0;
if (mv == NULL) {
return 0;
}
rand_data(rand_keys);
root = cJSON_CreateArray();
if (root == NULL) {
OCPP_PRINTF_LOG("MV replay root NULL\n");
return 0;
}
cJSON_AddNumberToObject(root, "", 2);
cJSON_AddStringToObject(root, "", rand_keys);
cJSON_AddStringToObject(root, "", s_get_ocpp_cmd_str(OCPP_MeterValues));
if (save_json_cmd(OCPP_MeterValues, gunNo, rand_keys) == 0) {
v_clear_save_cmd();
(void)save_json_cmd(OCPP_MeterValues, gunNo, rand_keys);
}
body = MeterValues_mag_offline_replay(gunNo, transactionId, mv);
if (body == NULL) {
cJSON_Delete(root);
return 0;
}
cJSON_AddItemToArray(root, body);
out = print_out(root);
if (out != NULL) {
ws_ret = ws_send(out, (int)strlen(out), 1, WDT_TXTDATA);
}
cJSON_Delete(root);
if (out != NULL) {
free(out);
}
return ws_ret;
}
/** @brief OCPP 发送调度主函数:按优先级依次发送 Boot/Heartbeat/StartTx/StopTx/MV/StatusNotification/应答 */
static void ocpp_send_mag_ctrl()
{
U8_T i;
if(private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_BootNotification == 1)
{
private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_BootNotification = 0;
send_mag(GUN_NULL,OCPP_BootNotification,NULL,0);
return;
}
/* 此处已在登录后可发送路径;pending 合法则发 DiagnosticsStatusNotification,组帧见 DiagnosticsStatusNotification_mag;发完即清 pending */
if (private_ocpp_data->u8_diag_notify_pending == OCPP_DIAG_NOTIFY_PENDING_UPLOADING ||
private_ocpp_data->u8_diag_notify_pending == OCPP_DIAG_NOTIFY_PENDING_UPLOADED ||
private_ocpp_data->u8_diag_notify_pending == OCPP_DIAG_NOTIFY_PENDING_UPLOAD_FAILED) {
U8_T was_uploading = (U8_T)(private_ocpp_data->u8_diag_notify_pending == OCPP_DIAG_NOTIFY_PENDING_UPLOADING);
int32_t sm = send_mag(GUN_NULL, OCPP_DiagnosticsStatusNotification, NULL, 0);
if (sm > 0) {
if (was_uploading != 0U) {
/* 与 FTP 同 UART 互斥:仅在「Uploading」已真正发出后再拉 updata_flag,避免挡掉 CallResult/Uploading */
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_updata_flag = 1U;
}
private_ocpp_data->u8_diag_notify_pending = OCPP_DIAG_NOTIFY_PENDING_NONE;
}
/* sm<=0(如 4G FTP 期间暂缓 QISEND):保留 pending,下周期重试 */
return;
} else if (private_ocpp_data->u8_diag_notify_pending != OCPP_DIAG_NOTIFY_PENDING_NONE) {
private_ocpp_data->u8_diag_notify_pending = OCPP_DIAG_NOTIFY_PENDING_NONE;
}
/*
* FirmwareStatusNotificationFSN):与 Diagnostics 并列 pendingDiag 优先。
* 每周期最多发一条;sm<=04G FTP 占 UART 等)时保留 pending 下周期重试。
*
* 远程固件升级(OCPP + ec200a_4g_ftp STEP_FTP_SAVE)时序:
* Downloading/Downloaded → … → STEP_FTP_SAVE 首轮仅置 INSTALLING pending 并 break
* 本函数发出 FSN(Installing) 且 sm>0 后置 u8_fw_installing_fsn_sent 并清 pending
* STEP_FTP_SAVE 次轮须 u8_fw_installing_fsn_sent==1 后才写头/复位(见 ec200a_4g_ftp.c);
* 新固件启动后 BootNotification_res 置 INSTALLED,本函数再发 FSN(Installed)。
*
* 本处 v_sys_reboot 条件(与 INSTALLING 无关):
* sm>0 且 pending==INSTALLED 且 u8_fw_reboot_pending!=0。
* 供旧流程 v_ocpp_set_fw_reboot_after_installed;现网 OCPP FTP 不在 Installing 后于此复位。
* INSTALLING/Downloaded 等其它状态:仅上送 FSN,不调用 v_sys_reboot。
*/
if (private_ocpp_data->u8_fw_notify_pending >= (U8_T)OCPP_FW_NOTIFY_PENDING_DOWNLOADING &&
private_ocpp_data->u8_fw_notify_pending <= (U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLATION_FAILED) {
const U8_T was_dl = (U8_T)(private_ocpp_data->u8_fw_notify_pending == (U8_T)OCPP_FW_NOTIFY_PENDING_DOWNLOADING);
const U8_T was_inst = (U8_T)(private_ocpp_data->u8_fw_notify_pending == (U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLED);
const U8_T was_installing = (U8_T)(private_ocpp_data->u8_fw_notify_pending ==
(U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLING);
/* 仅 Installed 上送成功且显式置位时才复位;Installing 后复位在 FTP STEP_FTP_SAVE 次轮 */
const U8_T do_rb = (U8_T)(was_inst != 0U && private_ocpp_data->u8_fw_reboot_pending != 0U);
int32_t sm = send_mag(GUN_NULL, OCPP_FirmwareStatusNotification, NULL, 0);
if (sm > 0) {
if (was_dl != 0U) {
/* Downloading 已真正发出后再占 UART 跑 FTP,避免挡 UpdateFirmware CallResult */
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_updata_flag = 1U;
}
if (was_installing != 0U) {
private_ocpp_data->u8_fw_installing_fsn_sent = 1U;
}
private_ocpp_data->u8_fw_notify_pending = (U8_T)OCPP_FW_NOTIFY_PENDING_NONE;
if (do_rb != 0U) {
private_ocpp_data->u8_fw_reboot_pending = 0U;
v_sys_reboot();
}
}
return;
} else if (private_ocpp_data->u8_fw_notify_pending != (U8_T)OCPP_FW_NOTIFY_PENDING_NONE) {
private_ocpp_data->u8_fw_notify_pending = (U8_T)OCPP_FW_NOTIFY_PENDING_NONE;
}
//检查心跳周期是否到位
if(private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_Heartbeat == 1 || //召测标志
u8_ocpp_timer_ctrl(OCPP_TIMER_Heartbeat,GET_FLAG) == 1)
{
private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_Heartbeat = 0;
send_mag(GUN_NULL,OCPP_Heartbeat,NULL,0);
return;
}
/* 在线起充 → 离线 → 再上线:优先补发 Flash 中本笔充电的离线 MeterValues(PLN 离线充电数据) */
if (private_ocpp_data->s_unsettled_order_data == NULL) {
for (i = 0; i < GUN_MAX_CNT; i++) {
if (u8_ocpp_mv_offline_session_replay_step(i) != 0U) {
return;
}
}
}
//二级发送控制 -- 触发发送+周期发送
for (i = 0; i < GUN_MAX_CNT; i++)
{
/* 启动 / 停止 结果上送(PLN 问题1 修复)
* 原逻辑将 OCPP_START_RESULT_FLAG 与 OCPP_STOP_RESULT_FLAG 用 OR 绑在一起后一律发 StartTransaction
* 导致「充电停止边沿」仅置位 txFlag_stopResultbs_public_impl.c v_cheak_state_change)时也会误走 StartTransaction
* 日志表现为 gun x stop result 后出现 send cmd StartTransaction 与 idTag is null。
* 正确行为:仅 START 标志触发 StartTransactionSTOP 边沿只清标志,订单结束仍由 txFlag_tradUpload → StopTransaction 路径处理。 */
if(u8_charge_flag_ctrl(i,OCPP_STOP_RESULT_FLAG,GET_FLAG))
{
u8_charge_flag_ctrl(i,OCPP_STOP_RESULT_FLAG,CLEAR_FALG);
return;
}
if(u8_charge_flag_ctrl(i,OCPP_START_RESULT_FLAG,GET_FLAG))
{
u8_charge_flag_ctrl(i,OCPP_START_RESULT_FLAG,CLEAR_FALG);
send_mag(GUN_1+i,OCPP_StartTransaction,NULL,0); //发送状态帧
//private_ocpp_data->s_ctrl_flag[i].u8_MeterValues_type = 3; //交易开始数据上送
return;
}
//即插即充鉴权上送检查(与 v_ocpp_private_logic 中 v_ocpp_offline_local_authentication_check 联动:先发路径须先本地预鉴权,成功则不再发 Authorize)
if((u8_charge_flag_ctrl(i,OCPP_VIN_START_FLAG,GET_FLAG)==1 &&
u8_ocpp_timer_ctrl(OCPP_TIMER_Authorize_GUNA+i,GET_FLAG) == 1) /*||
(u8_get_pile_state(i) == 2) && //充电中,检查发送周期
u8_ocpp_timer_ctrl(OCPP_TIMER_Authorize_GUNA+i,GET_FLAG) == 1*/)
{
v_ocpp_offline_local_authentication_check(i);
if (u8_charge_flag_ctrl(i, OCPP_VIN_START_FLAG, GET_FLAG) != 1) {
/* 本地已通过:txFlag_autoStart 已清,本周期不上送 Authorize */
continue;
}
u8_ocpp_timer_ctrl(OCPP_TIMER_Authorize_GUNA+i,CLEAR_FALG); //清空周期上送计数
u8_charge_flag_ctrl(i,OCPP_VIN_START_FLAG,CLEAR_FALG); //清空上送标志
send_mag(GUN_1+i,OCPP_Authorize,NULL,0); //发送状态帧
return;
}
//订单上送检查
if(u8_charge_flag_ctrl(i,OCPP_TRADE_UPLOAD_FLAG,GET_FLAG))
{
if(u8_ocpp_timer_ctrl(OCPP_TIMER_StopTransaction_GUNA+i,GET_FLAG) == 1) //检查A枪上送周期是否到位
{
S_LOG_DATA stop_snap;
int txid = 0;
int32_t sm;
if (u8_ocpp_mv_offline_session_replay_step(i) != 0U) {
return;
}
memset(&stop_snap, 0, sizeof(stop_snap));
if (u8_plat_get_real_order(&stop_snap, i) != 0U) {
txid = atoi((const char *)stop_snap.u8_preTradeNo);
}
mSleep(50);
sm = send_mag(GUN_1 + i, OCPP_StopTransaction, NULL, 0);
if (sm > 0 && txid > 0) {
v_ocpp_mark_live_stop_tx_done(i, txid);
(void)v_clear_temp_chg_record_in_eeprom(i);
}
u8_charge_flag_ctrl(GUN_1+i,OCPP_TRADE_UPLOAD_FLAG,CLEAR_FALG);
v_bs_platform_settlement_confirm(GUN_1+i);
/* StopTx 已上送且本轮清了 txFlag:标记该枪订单上送结算完成 */
v_ocpp_reboot_mark_trade_uploaded((U8_T)i);
}
return;
}
//数据上送
if(private_ocpp_data->s_ctrl_flag[i].u8_get_Trigger_MeterValues == 1|| //召测标志
private_ocpp_data->s_ctrl_flag[i].u8_MeterValues_type != 0 ||
u8_ocpp_timer_ctrl(OCPP_TIMER_MeterValues_GUNA+i,GET_FLAG) == 1) //检查A枪电表状态上送周期是否到位
{
private_ocpp_data->s_ctrl_flag[i].u8_get_Trigger_MeterValues = 0;
private_ocpp_data->s_ctrl_flag[i].u8_MeterValues_type = 0;
u8_ocpp_timer_ctrl(OCPP_TIMER_MeterValues_GUNA+i,CLEAR_FALG); //清空周期上送计数
// if(private_ocpp_data->s_ctrl_flag[i].u8_MeterValues_type != 0)
// send_mag(GUN_1+i,OCPP_MeterValues,NULL,private_ocpp_data->s_ctrl_flag[i].u8_MeterValues_type-1); //空闲 周期上送
// else
if(u8_get_pile_state(i) == 2)
send_mag(GUN_1+i,OCPP_MeterValues,NULL,1); //充电中 周期上送
else
;//send_mag(GUN_1+i,OCPP_MeterValues,NULL,0); //空闲不上送 周期上送
return;
}
/* StatusNotification(1) 状态/故障相对上次成功上送变化
* (2) TriggerMessage 召测 (3) Boot Accepted/Pending 后每枪首报一次
* (4) 周期上送仅在为 Faulted 时:定时到且桩存在故障告警(间隔见 OCPP_TIMER_StatusNotification_GUNx,默认 20s */
if(private_ocpp_data->s_ctrl_flag[i].u8_get_Trigger_StatusNotification != 0 ||
private_ocpp_data->s_ctrl_flag[i].u8_need_status_after_login != 0 ||
#if CHARGE_TEST_FLAG
u8_charge_flag_ctrl(i,OCPP_STATE_CHANGE_FLAG,GET_FLAG) ||
#endif
u8_ocpp_StatusNotification_change_data(i,NULL,NULL,OCPP_STATUS_NOTIFY_MODE_NORMAL) ||
(u8_ocpp_timer_ctrl(OCPP_TIMER_StatusNotification_GUNA+i,GET_FLAG) == 1 &&
u8_ocpp_statusnotification_pile_faulted() != 0))
{
if(private_ocpp_data->s_ctrl_flag[i].u8_get_Trigger_StatusNotification !=0 && i == 0)
{
send_mag(GUN_1+i,OCPP_StatusNotification,NULL,0xFF); //召测:整桩 connectorId 0
mSleep(100);
}
private_ocpp_data->s_ctrl_flag[i].u8_get_Trigger_StatusNotification = 0;
private_ocpp_data->s_ctrl_flag[i].u8_need_status_after_login = 0;
u8_charge_flag_ctrl(i,OCPP_STATE_CHANGE_FLAG,CLEAR_FALG);
u8_ocpp_timer_ctrl(OCPP_TIMER_StatusNotification_GUNA+i,CLEAR_FALG); //周期节拍:任一次上送后重新计时
{
int32_t sn = send_mag(GUN_1+i,OCPP_StatusNotification,NULL,0); //连接器状态帧
if (sn > 0) {
u8_ocpp_StatusNotification_change_data(i,NULL,NULL,OCPP_STATUS_NOTIFY_MODE_SYNC_CACHE);
}
}
return;
}
}
//三级发送控制 -- 应答发送
for (i = 0; i < OCPP_ACK_STRUCT_NUM; i++)
{
if(private_ocpp_data->ack_ctrl[i] != NULL) //结构体指向存在 则说明需要应答
{
if (send_mag(private_ocpp_data->ack_ctrl[i]->gunNo,private_ocpp_data->ack_ctrl[i]->cmd ,private_ocpp_data->ack_ctrl[i],0) > 0) {
v_free_ack_data(i); //释放所有的应答结构体动态内存空间
}
return;
}
}
#if CHARGE_TEST_FLAG //裸机充电测试标志
if(u8_charge_test())
return;
#endif
}
#endif
#if 1 //接收
/** @brief 解析 BootNotification.conf:提取注册状态/心跳间隔/校时 */
static void BootNotification_res(OCPP_CMD cmd ,cJSON* root)
{
BootNotificationResponse data;
Comm_Time ctTime;
int ocpp_time_zone =0;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
//["Accepted","Pending","Rejected"]
if(strstr(data.status,"Accepted"))
private_ocpp_data->u8_register_flag = 1;
else if(strstr(data.status,"Pending"))
private_ocpp_data->u8_register_flag = 2;
else if(strstr(data.status,"Rejected"))
private_ocpp_data->u8_register_flag = 3;
/* 接入平台成功后每连接器补发一次当前状态(与 OCPP 上线流程一致;不依赖周期定时器) */
if(private_ocpp_data->u8_register_flag == 1 || private_ocpp_data->u8_register_flag == 2)
{
U8_T gi;
for(gi = 0; gi < GUN_MAX_CNT; gi++)
private_ocpp_data->s_ctrl_flag[gi].u8_need_status_after_login = 1;
/* 远程升级:复位前已写 SPI 升级头;Installed 须在 BootNotification 之后再上送 */
if (u8_ocpp_fw_eeprom_take_installed_after_boot() != 0U) {
v_ocpp_set_fw_notify_pending((U8_T)OCPP_FW_NOTIFY_PENDING_INSTALLED);
OCPP_PRINTF_LOG("FW: Installed FSN pending after BootNotification\r\n");
}
}
if(data.interval != 0 && data.interval < 0xFFFF)
{
private_ocpp_data->s_timerCnt[OCPP_TIMER_Heartbeat].u16_timeNUM = data.interval; //秒 心跳帧上送周期
s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_HeartbeatInterval = data.interval;
}
int ret = u8_ocpp_timestamp_to_tm(&ctTime,data.currentTime,1);
OCPP_PRINTF_LOG("Time parse=%d\n",ret);
if(ret == 0){
//校时
v_bs_set_time(&ctTime);
}
OCPP_PRINTF_LOG("Beijing time=%d-%d-%d ",
ctTime.iYear, ctTime.ucMonth, ctTime.ucDay);
OCPP_PRINTF_LOG("%d:%d:%d \n",
ctTime.ucHour, ctTime.ucMin, ctTime.ucSec);
}
/** @brief 解析 Heartbeat.conf:提取 currentTime 必要时校时 */
static void Heartbeat_res(OCPP_CMD cmd ,cJSON* root)
{
cJSON *mag;
Comm_Time ctTime, ctTime2;
mag = cJSON_GetObjectItem(root,"currentTime");
if(mag != NULL) return;
OCPP_PRINTF_LOG("HB currentTime=%s\n",mag->valuestring);
int ret = u8_ocpp_timestamp_to_tm(&ctTime,mag->valuestring,1);
if(ret == 0){
GetCurrentTime(&ctTime2); //获取当前时间
if(CalculateTimeDiffInMinutes(ctTime,ctTime2) > 10) //时间相差大于10分钟
{
//校时
v_bs_set_time(&ctTime);
OCPP_PRINTF_LOG("Time drift>10min, sync\n");
}
}
}
/** @brief 处理 StatusNotification.conf 应答 */
static void StatusNotification_res(OCPP_CMD cmd ,cJSON* root,U8_T gunNo)
{
OCPP_PRINTF_LOG("StatusNotif ack rx gun=%d\n",gunNo);
}
/** @brief 处理 MeterValues.conf 应答 */
static void MeterValues_res(OCPP_CMD cmd ,cJSON* root,U8_T gunNo)
{
OCPP_PRINTF_LOG("MeterValues ack rx gun=%d\n",gunNo);
}
/** @brief 解析 StartTransaction.conf:提取 transactionId 写入订单 */
static void StartTransaction_res(OCPP_CMD cmd ,cJSON* root,U8_T gunNo)
{
StartTransactionResponse data;
S_BS_CHAR_CTRL remote_data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
/* PLN 问题1(在线会话):StartTx 拒绝须停充,不得写 txId。
* 未结补单:即使 status=Blocked 且 transactionId=0,也写入 preTradeNo 并继续离线 MV + StopTx。 */
if(strcmp(data.idTagInfo.status,OCPP_STR_Blocked) == 0 ||
strcmp(data.idTagInfo.status,OCPP_STR_Invalid) == 0 ||
strcmp(data.idTagInfo.status,OCPP_STR_Expired) == 0 ||
strcmp(data.idTagInfo.status,OCPP_STR_ConcurrentTx) == 0)
{
if (private_ocpp_data->s_unsettled_order_data != NULL)
{
v_ocpp_unsettled_apply_starttx_txid(data.transactionId);
OCPP_PRINTF_LOG("Unsettled: StartTx status=%s txId=%d, continue MV/StopTx\r\n",
data.idTagInfo.status, data.transactionId);
return;
}
S_BS_CHAR_CTRL stop_data;
memset(&stop_data,0,sizeof(S_BS_CHAR_CTRL));
stop_data.u16_ctrl_res = E_FAULT_13064; //非法鉴权 / 平台拒绝
v_bs_charge_action_ctrl(gunNo,E_CHAR_CTRL_STOP,&stop_data);
u8_charge_flag_ctrl(gunNo,OCPP_START_RESULT_FLAG,CLEAR_FALG);
OCPP_PRINTF_LOG("StartTx rejected gun=%d status=%s txId=%d\n",gunNo,data.idTagInfo.status,data.transactionId);
return;
}
/* 未结补单:停充后 flow 常为 9/10 而非 0,不得用「空闲」作为写入 txId 条件 */
if (private_ocpp_data->s_unsettled_order_data != NULL)
{
v_ocpp_unsettled_apply_starttx_txid(data.transactionId);
OCPP_PRINTF_LOG("Unsettled: StartTx ok txId=%d preTrade=%s (gun=%u flow=%u)\r\n",
data.transactionId,
private_ocpp_data->s_unsettled_order_data->u8_preTradeNo,
(unsigned)private_ocpp_data->s_unsettled_order_data->u8_gunNo,
(unsigned)u8_get_pile_state(gunNo));
return;
}
memset(&remote_data,0xFF,sizeof(S_BS_CHAR_CTRL));
memset(remote_data.u8_preTradeNo , 0 ,sizeof(remote_data.u8_preTradeNo)); //除修改数据为 其余数据都为0xFF
sprintf((char *)remote_data.u8_preTradeNo,"%d",data.transactionId);
v_bs_charge_action_ctrl(gunNo,E_CHAR_CHANGE_LOG,&remote_data); //修改参数
OCPP_PRINTF_LOG("StartTx rsp parse gun=%d\n",gunNo);
#if CHARGE_TEST_FLAG //裸机充电测试标志
charge_test.transactionId = data.transactionId;
#endif
}
/** @brief 解析 StopTransaction.conf 应答 */
static void StopTransaction_res(OCPP_CMD cmd ,cJSON* root,U8_T gunNo)
{
StopTransactionResponse data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("StopTx rsp parse gun=%d\n",gunNo);
#if CHARGE_TEST_FLAG //裸机充电测试标志
charge_test.flag = CHARGE_TES_stop_ok;
#endif
}
/** @brief 解析 Authorize.conf:提取鉴权结果写入双平台槽位 */
static void Authorize_res(OCPP_CMD cmd ,cJSON* root,U8_T gunNo)
{
AuthorizeResponse data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
if (private_ocpp_data->s_unsettled_order_data != NULL
&& private_ocpp_data->u8_unsettled_auth_phase == OCPP_UNSETTLED_AUTH_WAIT) {
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_NONE;
}
return;
}
/* 未结补单:先发 Authorize,仅 Accepted 后才允许上送 StartTransaction;否则放弃本单内存上下文 */
if (private_ocpp_data->s_unsettled_order_data != NULL
&& private_ocpp_data->u8_unsettled_auth_phase == OCPP_UNSETTLED_AUTH_WAIT) {
if (strcmp(data.idTagInfo.status, OCPP_STR_Accepted) == 0) {
OCPP_PRINTF_LOG("unsettled Authorize Accepted, next: StartTransaction\r\n");
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_OK;
return;
}
if (u8_unsettled_authorize_bypass_to_starttx(data.idTagInfo.status) != 0U) {
OCPP_PRINTF_LOG("unsettled Authorize %s, bypass: StartTransaction for MV/StopTx\r\n",
data.idTagInfo.status);
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_OK;
return;
}
OCPP_PRINTF_LOG("unsettled Authorize reject status=%s, retry later (flash kept)\r\n",
data.idTagInfo.status);
private_ocpp_data->u8_unsettled_auth_phase = OCPP_UNSETTLED_AUTH_NONE;
return;
}
OCPP_PRINTF_LOG("Auth rsp parse=(%d) %s,%s\n",gunNo,data.idTagInfo.parentIdTag,data.idTagInfo.status);
{
U8_T bi;
U8_T v = (strcmp(data.idTagInfo.status, OCPP_STR_Accepted) == 0) ? 1U : 2U;
/* 双平台槽位均写入,避免 flow 读 gun 结果时仅扫到未激活的 s_bs_ctrl 槽导致长期「鉴权中」等满 30s */
for (bi = 0; bi < (U8_T)MAX_BS_NUM; bi++) {
s_bs_task_ctrl.s_bs_ctrl[bi].s_gundata[gunNo].txFlag_autoRwsult = v;
}
}
if (strcmp(data.idTagInfo.status, OCPP_STR_Accepted) != 0) {
/* PLN 问题1Authorize Blocked 等失败后禁止残留 txFlag_startResult,避免后续流程再次排队 StartTransaction */
u8_charge_flag_ctrl(gunNo, OCPP_START_RESULT_FLAG, CLEAR_FALG);
}
#if CHARGE_TEST_FLAG //裸机充电测试标志
if(strcmp(data.idTagInfo.status,OCPP_STR_Accepted) == 0)
{
charge_test.u8_vin_start = 1; //鉴权成功
}
#endif
}
/** @brief 解析 UnlockConnector.req:解枪锁指令,应答前执行停机+解锁 */
static void UnlockConnector_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
cJSON *mag;
int ack_id;
int connectorId;
U8_T ack_type = 0; /* UnlockConnectorResponse_stateCode: 0 Unlocked, 1 UnlockFailed, 2 NotSupported */
mag = cJSON_GetObjectItem(root,"connectorId");
if(mag == NULL)
return;
connectorId = mag->valueint;
if(connectorId < 1 || connectorId > GUN_MAX_CNT)
{
ack_type = 2;
OCPP_PRINTF_LOG("Unlock invalid connectorId=%d\n", connectorId);
}
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = (connectorId >= 1 && connectorId <= GUN_MAX_CNT) ? (U8_T)(connectorId - 1) : 0;
private_ocpp_data->ack_ctrl[ack_id]->type = ack_type;
OCPP_PRINTF_LOG("Unlock rx cid=%d ack_type=%u (stop/unlock on conf tx)\n", connectorId, (unsigned)ack_type);
}
/** @brief 解析 RemoteStartTransaction.req:远程启动充电指令 */
static void RemoteStartTransaction_unpack(OCPP_CMD cmd ,cJSON* root ,OCPP_DATA_RECV_ACK *ack_data)
{
RemoteStartTransactionRequest data;
Comm_Time ctTime;
int ack_id;
U8_T gunNo,start_flag ;
S_BS_CHAR_CTRL remote_data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
memset(&remote_data,0,sizeof(S_BS_CHAR_CTRL));
gunNo = data.connectorId-1;
if(s_ocpp_flash_cfg_data.s_ocpp_charge_data.s8_ChangeAvailability[gunNo] < 0) //不可用
{
start_flag = 1; //拒绝启动
}
else{
memcpy(remote_data.u8_user_id,data.idTag,strlen(data.idTag));
remote_data.u8_user_id[strlen(data.idTag)] = '\0';
sprintf((char *)remote_data.u8_preTradeNo,"%d",data.chargingProfile.transactionId);
remote_data.u16_ctrl_res = 3; //控制原因 启动时为启动原因 停止时为停止原因;启动原因0 无动作 1离线卡 2在线卡 3后台启动(tcu/积成)\扫码启动 4vin启动 5调试启动
v_bs_charge_action_ctrl(gunNo,E_CHAR_CTRL_START,&remote_data); //启动充电
start_flag = 0; //接受启动
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].s_gundata[gunNo].txFlag_autoRwsult = 1; //鉴权成功
}
//在接收中直接应答 可能会导致函数功能嵌套太多,且接收动态申请内存未释放 再进行发送内存申请可能会导致动态内存申请太多
//但是应答回复要做好多条同步回复的准备,例如同时收到3条指令需要回复,应答数组起码在5组以上,这会一直占用5组的静态内存;
//反正都使用json手动管理内存空间了,不如也动态内存实现了,重点需要做好动态内存的释放工作
//send_mag(data.connectorId-1,OCPP_RemoteStartTransaction,ack_data );
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = data.connectorId -1;
private_ocpp_data->ack_ctrl[ack_id]->type = start_flag; //应答内容
}
OCPP_PRINTF_LOG("RemoteStart rx\n");
}
/** @brief 解析 RemoteStopTransaction.req:远程停止充电指令 */
static void RemoteStopTransaction_unpack(OCPP_CMD cmd ,cJSON* root ,OCPP_DATA_RECV_ACK *ack_data)
{
RemoteStopTransactionRequest data;
Comm_Time ctTime;
int ack_id;
U8_T i;
char u8_str_data[BS_BUF_SIZE_64];
S_BS_CHAR_CTRL remote_data;
U8_T u8_gunCnt = u32_bs_get_sys_data(0,E_BS_GET_SYS_DATA_GUN_NUM,0); //枪数量
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
for (i = 0; i < u8_gunCnt; i++)
{
u8_get_str_data(i,E_STR_LOG_preTradeNo,0,u8_str_data,BS_BUF_SIZE_64); //获取流水号
if(atoi((char *)u8_str_data) == data.transactionId){
U8_T u8_flow_state = u32_bs_get_sys_data(i,E_BS_GET_SYS_DATA_GUN_FLOW_STATE,0); ////终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成
if(u8_flow_state == 0 || u8_flow_state == 10) //充电中或者充电停止状态才允许停止
{
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].s_gundata[i].txFlag_tradUpload = 1; //订单上送标志置一
}
else
{
memset(&remote_data,0,sizeof(S_BS_CHAR_CTRL));
remote_data.u16_ctrl_res = E_NORMAL_1002; //后天停止
v_bs_charge_action_ctrl(i,E_CHAR_CTRL_STOP,&remote_data); //控制停止
}
ack_data->type = 1;
break;
}
}
#if CHARGE_TEST_FLAG //裸机充电测试标志
if(data.transactionId == charge_test.transactionId)
charge_test.flag = CHARGE_TES_stop;
#endif
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->type = 0; //应答内容
}
OCPP_PRINTF_LOG("RemoteStop rx, txId=%d\n",data.transactionId);
}
/** @brief 解析 UpdateFirmware.req:固件升级指令,解析 FTP URL 并初始化升级流程 */
static void UpdateFirmware_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
U8_T parse_ok = 0;
UpdateFirmwareRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("FW upd url=%s\n",data.location);
if(parse_ftp_url(data.location,&t_ftp_update_info) != -1){
OCPP_PRINTF_LOG("FTP user=%s\n",t_ftp_update_info.u8_user);
OCPP_PRINTF_LOG("FTP pwd=%s\n",t_ftp_update_info.u8_pwd);
OCPP_PRINTF_LOG("FTP host=%s\n",t_ftp_update_info.u8_update_server_addr);
OCPP_PRINTF_LOG("FTP path=%s\n",t_ftp_update_info.u8_path);
OCPP_PRINTF_LOG("FTP file=%s\n",t_ftp_update_info.u8_file_name);
t_ftp_update_info.u8_netType = s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_netType;
/* 固件升级入口:强制切到升级模式,避免被日志上传残留标志影响 */
private_ocpp_data->u8_log_file_upload = 0;
t_ftp_update_info.u8_init_flag = 0; //下一轮重新初始化FTP状态机
OCPP_PRINTF_LOG("FW update prepare: clear log_upload flag, reset ftp init\n");
parse_ok = 1;
}
else
{
OCPP_PRINTF_LOG("parse_ftp_url fail, url=%s\r\n", data.location);
}
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = parse_ok; //内部控制:1解析成功可启动升级,0失败
}
}
/** @brief 解析 Reset.req:远程重启指令,区分 Hard/Soft 类型 */
static void reset_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
int ack_flag = 0;
ResetRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
if(u8_get_pile_state(0) == 2 || u8_get_pile_state(1) == 2) //充电中 不允许重启
{
OCPP_PRINTF_LOG("Chg active, reboot reject\n");
if(strcmp(data.type, "Hard") == 0) //硬重启
{
ack_flag = 1; //应答内容 1-拒绝重启
}
else if(strcmp(data.type, "Soft") == 0) //软重启
{
ack_flag = 2; //应答内容 2-软重启(充电中允许延后处理)
}
else
{
ack_flag = 1; //未知类型按拒绝处理
OCPP_PRINTF_LOG("Unknown reset type=%s, reject\n", data.type);
}
}
else
{
}
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = ack_flag; //应答内容
}
OCPP_PRINTF_LOG("Remote reboot=%s\n",data.type);
#if CHARGE_TEST_FLAG //裸机充电测试标志
charge_test.flag = CHARGE_TES_start;
#endif
}
/** @brief 解析 CancelReservation.req:取消预定指令 */
static void CancelReservation_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
CancelReservationRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("CancelReserve id=%d\n",data.reservationId);
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = 0; //应答内容
}
}
/** @brief 解析 ChangeAvailability.req:枪可用性设置指令 */
static void ChangeAvailability_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
int ack_flag = 0;
int i,set_flag[GUN_MAX_CNT] = {0};
ChangeAvailabilityRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("SetAvail cid=%d,%s\n",data.connectorId,data.type);
if(data.connectorId == 0) //整桩
{
for (i = 0; i < GUN_MAX_CNT; i++)
set_flag[i] = 1;
}
else if(data.connectorId > 0 && data.connectorId <= GUN_MAX_CNT) //枪号有效
{
set_flag[data.connectorId-1] = 1;
}
//检查状态更改 并赋值
for (i = 0; i < GUN_MAX_CNT; i++)
{
if(set_flag[i] == 1)
{
if(u8_get_pile_state(i) == 2) //充电中 不允许变为不可用
{
OCPP_PRINTF_LOG("Gun%d chg, avail chg reject\n",i+1);
ack_flag = 1; //应答失败
continue;
}
if(strcmp(data.type,"Inoperative") == 0)//"Inoperative","Operative"
s_ocpp_flash_cfg_data.s_ocpp_charge_data.s8_ChangeAvailability[i] = -1;
else
{
s_ocpp_flash_cfg_data.s_ocpp_charge_data.s8_ChangeAvailability[i] = 0;
v_ocpp_set_gun_flow_real_state_init(i); //状态变为可
}
}
}
v_flash_save_ocpp_cfg_data();
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = ack_flag; //应答内容
}
}
/** @brief 解析 ChangeConfiguration.req:参数配置变更指令 */
static void ChangeConfiguration_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
int res_data;
ChangeConfigurationRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("Cfg set %s,%s\n",data.key,data.value);
//{"key":"QRCodeTextOfConnector-1","value":"hlht://3201040001000301.222222222/"}]
//{"key":"AuthorizationCacheEnabled","value":"false"}]
//{"key":"MeterValueSampleInterval","value":"15"}]
//执行OCPP配置参数SET操作
U8_T result = u8_OCPP_config_operate(ENUM_TYPE_SET, data.key, data.value, strlen(data.value));
if(result == ENUM_Accepted)
{
v_flash_save_ocpp_cfg_data();
OCPP_PRINTF_LOG("Cfg set save flash,key=%s,result=%d\n", data.key, result);
/* MeterValueSampleInterval 写入 Flash 后立即同步运行计时器,下一拍即可按新周期上送 */
if(strcmp(data.key, KEY_METER_VALUE_SAMPLE_INTERVAL) == 0)
{
U16_T interval = s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval;
private_ocpp_data->s_timerCnt[OCPP_TIMER_MeterValues_GUNA].u16_timeNUM = interval;
private_ocpp_data->s_timerCnt[OCPP_TIMER_MeterValues_GUNB].u16_timeNUM = interval;
private_ocpp_data->s_timerCnt[OCPP_TIMER_MeterValues_GUNA].u16_timerCnt = interval;
private_ocpp_data->s_timerCnt[OCPP_TIMER_MeterValues_GUNB].u16_timerCnt = interval;
OCPP_PRINTF_LOG("Cfg: MeterValues interval apply=%u\n", (unsigned)interval);
}
}
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = result; //应答内容
}
}
/** @brief 解析 TriggerMessage.req:数据召测指令,置位对应 Trigger 标志 */
static void TriggerMessage_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
TriggerMessageRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("TriggerMsg cid=%d,%s\n",data.connectorId,data.requestedMessage);
//"BootNotification","DiagnosticsStatusNotification","FirmwareStatusNotification","Heartbeat","MeterValues","StatusNotification"
OCPP_CMD ocpp_cmd = e_get_recv_json_cmd(data.requestedMessage);
if(data.connectorId>0) data.connectorId -= 1; //抢号
else
data.connectorId = 0xFF;
if(ocpp_cmd == OCPP_BootNotification)
{
/* BootNotification 是整桩级消息,不按 connector 下标触发 */
private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_BootNotification = 1;
}
// else if(ocpp_cmd == OCPP_DiagnosticsStatusNotification)
// private_ocpp_data->s_ctrl_flag[data.connectorId].u8_get_Trigger_DiagnosticsStatusNotification =1;
// else if(ocpp_cmd == OCPP_FirmwareStatusNotification)
// private_ocpp_data->s_ctrl_flag[data.connectorId].u8_get_Trigger_FirmwareStatusNotification =1;
else if(ocpp_cmd == OCPP_Heartbeat)
{
if(data.connectorId == 0xFF)
{
private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_Heartbeat =1;
private_ocpp_data->s_ctrl_flag[1].u8_get_Trigger_Heartbeat =1;
}
else if(data.connectorId < GUN_MAX_CNT)
private_ocpp_data->s_ctrl_flag[data.connectorId].u8_get_Trigger_Heartbeat =1;
else
OCPP_PRINTF_LOG("TriggerMsg invalid connectorId=%d for Heartbeat\n", data.connectorId + 1);
}
else if(ocpp_cmd == OCPP_MeterValues)
{
if(data.connectorId == 0xFF)
{
private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_MeterValues =1;
private_ocpp_data->s_ctrl_flag[1].u8_get_Trigger_MeterValues =1;
}
else if(data.connectorId < GUN_MAX_CNT)
private_ocpp_data->s_ctrl_flag[data.connectorId].u8_get_Trigger_MeterValues =1;
else
OCPP_PRINTF_LOG("TriggerMsg invalid connectorId=%d for MeterValues\n", data.connectorId + 1);
}
else if(ocpp_cmd == OCPP_StatusNotification)
{
if(data.connectorId == 0xFF)
{
private_ocpp_data->s_ctrl_flag[0].u8_get_Trigger_StatusNotification =1;
private_ocpp_data->s_ctrl_flag[1].u8_get_Trigger_StatusNotification =1;
}
else if(data.connectorId < GUN_MAX_CNT)
private_ocpp_data->s_ctrl_flag[data.connectorId].u8_get_Trigger_StatusNotification =1;
else
OCPP_PRINTF_LOG("TriggerMsg invalid connectorId=%d for StatusNotification\n", data.connectorId + 1);
}
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = 0; //应答内容
}
}
/** @brief 解析 GetConfiguration.req:配置项查询指令 */
static void GetConfiguration_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
GetConfigurationRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("GetCfg rx\n");
//memset(&data,0,sizeof(GetConfigurationRequest));
/* 上次应答若已 OCPP_FREE 但未置 NULL,悬空指针会污染 unknownKey 并阻断 key:[] 全量回包 */
for(U8_T u = 0; u < GetConfiguration_NUM; u++)
{
if(unknown_tmpbuf[u] != NULL)
{
OCPP_FREE(unknown_tmpbuf[u]);
unknown_tmpbuf[u] = NULL;
}
}
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = 0; //应答内容
}
/*
//key内容为空 代表获取所有支持的参数配置 (此为可选功能,暂不开发)
if(strlen(data.key[0]) == 0)
{
private_ocpp_data->ack_ctrl[ack_id]->type = 1; //type为1 表示应答所有支持的参数配置
return;
}
*/
//将每个key对应的配置枚举号赋值到应答数据内容 ack_data【10】 1 4 6 (key)
for (U8_T i = 0; i < GetConfiguration_NUM; i++)
{
if(strlen(data.key[i]) == 0)
break;
private_ocpp_data->ack_ctrl[ack_id]->ack_data[i] = u8_find_OCPP_configID(data.key[i]); //保存键的枚举号
//未找到键
if(private_ocpp_data->ack_ctrl[ack_id]->ack_data[i] == ENUM_NULL)
{
//为unknown键动态申请内存
for(U8_T j = 0; j < GetConfiguration_NUM; j++)
{
if(unknown_tmpbuf[j] == NULL)
{
unknown_tmpbuf[j] = (char *)OCPP_MALLOC(OCPP_CiString50);
//将未知键赋值到unknown动态空间
if(unknown_tmpbuf[j] != NULL)
{
memcpy(unknown_tmpbuf[j] , data.key[i] ,strlen(data.key[i]));
break;
}
}
}
}
}
}
/** @brief 解析 ClearCache.req:清空缓存指令 */
static void ClearCache_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
OCPP_PRINTF_LOG("ClearCache rx\n");
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = 0; //应答内容
}
}
/** @brief 解析 SendLocalList.req:本地授权列表同步指令 */
static void SendLocalList_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id,i;
SendLocalListRequest data;
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
OCPP_PRINTF_LOG("SendLocalList rx %s %d\n",data.updateType,data.List_num);
s_ocpp_flash_cfg_data.s_ocpp_charge_data.card_ver = data.listVersion;
for (i = 0; i < data.List_num; i++)
{
if(strcmp(data.list[i].idTagInfo.status ,OCPP_STR_Accepted) ==0)
{
u8_card_info_add(data.list[i].idTag);
OCPP_PRINTF_LOG("Card add=%s\n",data.list[i].idTag);
}
}
if(data.List_num == 0)
{
v_card_info_clear();
s_ocpp_flash_cfg_data.s_ocpp_charge_data.card_ver = 0;
OCPP_PRINTF_LOG("Card clr\n");
}
if(data.list != NULL)
OCPP_FREE(data.list);
v_flash_save_ocpp_cfg_data();
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = 1; //应答内容
}
}
/** @brief 解析 GetLocalListVersion.req:本地列表版本查询 */
static void GetLocalListVersion_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
OCPP_PRINTF_LOG("CardList ver req\n");
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = 0; //应答内容
}
}
/** @brief 解析 GetDiagnostics.req:诊断日志上传指令 */
static void GetDiagnostics_mag(OCPP_CMD cmd ,cJSON* root,OCPP_DATA_RECV_ACK *ack_data)
{
int ack_id;
GetDiagnosticsRequest data;
OCPP_PRINTF_LOG("Diag log req\n");
if(get_ocpp_data(cmd , &data ,root) == 0){
OCPP_PRINTF_LOG("data fail %d,%s\n",cmd,s_get_ocpp_cmd_str(cmd));
return;
}
/* 先准备好本地日志文件名,确保 GetDiagnostics.conf 有效 */
v_ocpp_get_real_log_file_name((char *)t_ftp_update_info.u8_file_name,
sizeof(t_ftp_update_info.u8_file_name));
if(parse_ftp_url(data.location,&t_ftp_update_info) != -1){
OCPP_PRINTF_LOG("FTP user=%s\n",t_ftp_update_info.u8_user);
OCPP_PRINTF_LOG("FTP pwd=%s\n",t_ftp_update_info.u8_pwd);
OCPP_PRINTF_LOG("FTP host=%s\n",t_ftp_update_info.u8_update_server_addr);
OCPP_PRINTF_LOG("FTP path=%s\n",t_ftp_update_info.u8_path);
OCPP_PRINTF_LOG("FTP file=%s\n",t_ftp_update_info.u8_file_name);
t_ftp_update_info.u8_netType = s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_netType;
private_ocpp_data->u8_log_file_upload = 1; //上传日志文件标志置一
} else {
OCPP_PRINTF_LOG("Diag parse_ftp_url fail, use local file=%s\n", t_ftp_update_info.u8_file_name);
private_ocpp_data->u8_log_file_upload = 1; //即使解析失败也允许本地日志上传
}
ack_id = alloc_single_ackdata(); //动态申请 应答结构体
if(ack_id < 0){
return;
}
else{
memset(private_ocpp_data->ack_ctrl[ack_id] , 0 , sizeof(OCPP_DATA_RECV_ACK));
memcpy(private_ocpp_data->ack_ctrl[ack_id] , ack_data , sizeof(OCPP_DATA_RECV_ACK));
private_ocpp_data->ack_ctrl[ack_id]->cmd = cmd;
private_ocpp_data->ack_ctrl[ack_id]->gunNo = 0;
private_ocpp_data->ack_ctrl[ack_id]->type = 0; //应答内容
}
}
/** @brief OCPP 接收数据分发:根据 cmd 枚举路由到对应的解析函数 */
static void ocpp_analysis_data(OCPP_CMD cmd, cJSON* root , OCPP_DATA_RECV_ACK *ack_data)
{
switch (cmd)
{
//应答
case OCPP_BootNotification:
BootNotification_res(cmd , root);
break;
case OCPP_Heartbeat:
Heartbeat_res(cmd ,root);
break;
case OCPP_StatusNotification:
StatusNotification_res(cmd ,root,ack_data->gunNo);
break;
case OCPP_MeterValues:
MeterValues_res(cmd ,root,ack_data->gunNo);
break;
case OCPP_StartTransaction:
StartTransaction_res(cmd ,root,ack_data->gunNo);
break;
case OCPP_StopTransaction:
StopTransaction_res(cmd ,root,ack_data->gunNo);
break;
case OCPP_Authorize:
Authorize_res(cmd ,root,ack_data->gunNo);
break;
//平台下发
case OCPP_RemoteStartTransaction:
RemoteStartTransaction_unpack(cmd ,root ,ack_data);
break;
case OCPP_RemoteStopTransaction:
RemoteStopTransaction_unpack(cmd ,root ,ack_data);
break;
case OCPP_UnlockConnector:
UnlockConnector_mag(cmd ,root,ack_data);
break;
case OCPP_UpdateFirmware:
UpdateFirmware_mag(cmd ,root,ack_data);
break;
case OCPP_Reset:
reset_mag(cmd ,root,ack_data);
break;
case OCPP_CancelReservation:
CancelReservation_mag(cmd ,root,ack_data);
break;
case OCPP_ChangeAvailability: //设置枪可用状态 平台->桩
ChangeAvailability_mag(cmd ,root,ack_data);
break;
case OCPP_ChangeConfiguration: //更改参数配置 平台->桩
ChangeConfiguration_mag(cmd ,root,ack_data);
break;
case OCPP_TriggerMessage:
TriggerMessage_mag(cmd ,root,ack_data);
break;
case OCPP_GetConfiguration:
GetConfiguration_mag(cmd ,root,ack_data);
break;
case OCPP_ClearCache:
ClearCache_mag(cmd ,root,ack_data);
break;
case OCPP_SendLocalList:
SendLocalList_mag(cmd ,root,ack_data);
break;
case OCPP_GetLocalListVersion:
GetLocalListVersion_mag(cmd ,root,ack_data);
break;
case OCPP_GetDiagnostics:
GetDiagnostics_mag(cmd ,root,ack_data);
break;
default:
break;
}
}
int recv_mags(char* recv_mag)
{
int cmd,i;
cJSON* mag1;
cJSON* root = cJSON_Parse(recv_mag);
OCPP_CMD ocpp_cmd;
OCPP_DATA_RECV_ACK ack_data;
if(root == NULL)
{
OCPP_PRINTF_LOG("parse fail.\n");
return -1;
}
private_ocpp_data->ulong_timer = 0; //更新数据接收时间
mag1 = cJSON_GetArrayItem(root,0); //2 主动发送 3应答回复 4数据错误
cmd = mag1->valueint;
switch (cmd)
{
case 2:
{
mag1 = cJSON_GetArrayItem(root,1); //获取秘钥
if(mag1 == NULL) break;
memset(&ack_data,0,sizeof(OCPP_DATA_RECV_ACK));
memcpy(&ack_data.ack_key[0],mag1->valuestring,strlen(mag1->valuestring));
mag1 = cJSON_GetArrayItem(root,2); //获取命令字段码
if(mag1 == NULL) break;
ocpp_cmd = e_get_recv_json_cmd(mag1->valuestring);
OCPP_PRINTF_LOG("recv cmd str= %s ,%s\n",s_get_ocpp_cmd_str(ocpp_cmd),mag1->valuestring);
mag1 = cJSON_GetArrayItem(root,3); //获取数据
if(mag1 != NULL && ocpp_cmd != OCPP_NULL)
ocpp_analysis_data(ocpp_cmd,mag1,&ack_data);
break;
}
case 3:
{
mag1 = cJSON_GetArrayItem(root,1); //获取秘钥
if(mag1 == NULL || mag1->valuestring == NULL) break;
memset(&ack_data,0,sizeof(OCPP_DATA_RECV_ACK));
ocpp_cmd = get_json_cmd((int *)&ack_data.gunNo,mag1->valuestring);
mag1 = cJSON_GetArrayItem(root,2); //获取数据
if(mag1 == NULL) break;
// OCPP_PRINTF_LOG("recv ack cmd str= %s\n",s_get_ocpp_cmd_str(ocpp_cmd));
if(mag1 != NULL && ocpp_cmd != OCPP_NULL)
ocpp_analysis_data(ocpp_cmd,mag1,&ack_data);
break;
}
case 4:
OCPP_PRINTF_LOG("Bad data: ");
mag1 = cJSON_GetArrayItem(root,2); //获取秘钥
if(mag1 == NULL) break;
OCPP_PRINTF_LOG("%s ",mag1->valuestring);
mag1 = cJSON_GetArrayItem(root,3); //获取数据
if(mag1 == NULL) break;
OCPP_PRINTF_LOG("%s ,\n",mag1->valuestring);
break;
default:
break;
}
if(root != NULL)
cJSON_Delete(root);
return 1;
}
#endif
#if 1 //外部控制使用
/**
* @brief 联网通道恢复:释放应答动态内存、可选全量 4G 驻网、关闭 socket 并刷新重启计时
* @details 当 TCP 连接断开或需要重连时调用,比 v_ocpp_init_data 轻量,不重置 OCPP RAM 与定时器
*
* @param[in] reset_4g_modem - 1=全量 4G 驻网重连,0=仅清理 socket
* ************************************************************************/
void v_ocpp_init_data_ex(U8_T bs_flag, U8_T reset_4g_modem)
{
private_ocpp_data = &s_bs_task_ctrl.ocpp_data;
v_free_ack_data(-1);
if (reset_4g_modem != 0u) {
v_bs_4G_data_init();
}
s_bs_task_ctrl.s_bs_ctrl[bs_flag].fd = -1;
s_bs_task_ctrl.s_bs_ctrl[bs_flag].ulong_restart_timer = get_current_seconds();
private_ocpp_data->ulong_timer = 0;
{
U8_T gi;
for (gi = 0U; gi < GUN_MAX_CNT; gi++) {
private_ocpp_data->s_ctrl_flag[gi].s_ocpp_live_stop_tx_id = -1;
}
}
}
/**
* @brief 上电或 TCP 长时间失败全量初始化:离线 MV 门闩 → 清 OCPP RAM → 配置定时器 → 联网恢复
* @details 比 v_ocpp_init_data_ex 更彻底,重置全部 OCPP 上下文、加载 Flash 配置、初始化所有周期定时器
* ************************************************************************/
void v_ocpp_init_data(U8_T bs_flag)
{
v_ocpp_mv_offline_init();
private_ocpp_data = &s_bs_task_ctrl.ocpp_data;
//释放应答动态内存
v_free_ack_data(-1);
//清 OCPP RAM
memset(private_ocpp_data, 0, sizeof(OCPP_DATA_CTRL));
private_ocpp_data->id = bs_flag;
//配置与定时器
v_ocpp_init_config_data();
//设置定时器
private_ocpp_data->s_timerCnt[OCPP_TIMER_BootNotification].u16_timeNUM = 10;
private_ocpp_data->s_timerCnt[OCPP_TIMER_Heartbeat].u16_timeNUM =
s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_HeartbeatInterval;
private_ocpp_data->s_timerCnt[OCPP_TIMER_StatusNotification_GUNA].u16_timeNUM = 20;
private_ocpp_data->s_timerCnt[OCPP_TIMER_StatusNotification_GUNB].u16_timeNUM = 20;
//设置MeterValues定时器
private_ocpp_data->s_timerCnt[OCPP_TIMER_MeterValues_GUNA].u16_timeNUM =
s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval;
private_ocpp_data->s_timerCnt[OCPP_TIMER_MeterValues_GUNB].u16_timeNUM =
s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_MeterValueSampleInterval;
//设置StopTransaction定时器
private_ocpp_data->s_timerCnt[OCPP_TIMER_StopTransaction_GUNA].u16_timeNUM =
s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_TransactionMessageRetryInterval;
private_ocpp_data->s_timerCnt[OCPP_TIMER_StopTransaction_GUNB].u16_timeNUM =
s_ocpp_flash_cfg_data.s_ocpp_config_data.u16_TransactionMessageRetryInterval;
//设置Authorize定时器 默认5分钟
private_ocpp_data->s_timerCnt[OCPP_TIMER_Authorize_GUNA].u16_timeNUM = 60 * 5;
private_ocpp_data->s_timerCnt[OCPP_TIMER_Authorize_GUNB].u16_timeNUM = 60 * 5;
v_ocpp_init_data_ex(bs_flag, 1); //联网恢复
}
/**
* @brief 周期调用:推进秒计时、建立 TCP 连接、完成 WebSocket 握手
* @details 分三步:1)推进 ulong_timer 计时 2)建立 TCP4G/网线) 3)WebSocket 握手。含失败退避与超时重连策略
*
* ************************************************************************/
U8_T v_ocpp_tcp_connect()
{
U8_T u8_tcp = 0; // 1=TCP 已连通(底层),0=未连通
S8_T s8_ws = 0; // websocket_build1成功 0失败 -1为404
U8_T u8_result = 0; // 函数最终返回值
static ULONG s_ws_retry_after_sec = 0; // WS 失败后暂停重试截止秒
static U8_T s_last_tcp_ok = 0; // 记录上次TCP是否已连通,用于阶段切换时重置计时
/* 周期调用:先推进计时(ulong_timer 由这里累加),再尝试建链 */
v_ocpp_timer_add();
if (get_current_seconds() < s_ws_retry_after_sec) {
return 0U;
}
/* 1) 先建立 TCP4G/网口由 u8_tcp_connect 内部决定) */
u8_tcp = u8_tcp_connect(private_ocpp_data->id);
/* TCP阶段切换:一旦 TCP 状态变化(未连通↔已连通),重置计时基准 */
if ((u8_tcp ? 1U : 0U) != s_last_tcp_ok)
{
s_last_tcp_ok = (u8_tcp ? 1U : 0U);
private_ocpp_data->ulong_timer = 0;
}
/* 2) TCP 成功后再做 WebSocket 握手(若未启用 WS,则 TCP 成功即视为成功) */
if (u8_tcp == 1)
{
#if OCPP_WEBSOCKET_EN == 1
s8_ws = websocket_build();
if (s8_ws <= 0)
{
ULONG backoff_sec = (s8_ws < 0) ? 25U : 10U;
/* WS 握手失败:关闭传输层并清理连接标志,等待下次周期重试 */
if (s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_netType == 0) {
#if EC200A_4G_EN
#if !EC200A_TCP_USE_TRANSPARENT_MODE
/* 4G:须 AT+QSSLCLOSE 释放模组 SSL,否则平台侧残留会话会 404;下次走软重连 QSSLOPEN */
if (u8_ec200a_modem_tcp_stack_ok() != 0U) {
(void)s8_ec200a_tcp_socket_close();
}
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_4g_soft_tcp_reconnect = 1U;
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_4g_tcp_soft_fail_streak = 0U;
#endif
#endif
} else {
S32_T fd = s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].fd;
if (fd >= 0) {
lwip_tcp_close((int)fd);
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].fd = (S32_T)-1;
}
}
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_TCP_connect_flag = 0;
s_last_tcp_ok = 0U;
s_ws_retry_after_sec = get_current_seconds() + backoff_sec;
if (s8_ws < 0) {
printf("ws 404, retry after %lus\r\n", (unsigned long)backoff_sec);
} else {
printf("ws handshake fail, close socket and retry\r\n");
}
}
#else
s8_ws = 1;
#endif
}
/* 3) 统一用 if/else 链处理结果与超时策略 */
if (s8_ws == 1)
{
/* 成功:清空所有失败计时器 */
s_ws_retry_after_sec = 0U;
private_ocpp_data->ulong_timer = 0;
u8_result = 1;
}
else if (!u8_ocpp_block_rx_timeout_relink(private_ocpp_data->id)
&& u8_tcp == 0 && private_ocpp_data->ulong_timer > 5 * 60)
{
/* TCP未连通阶段:5分钟超时强制重连(与上电共用 v_ocpp_init_dataRAM 全量 + 4G + socket */
printf("TCP conn TO, relink[%d][%d]\n", u8_tcp, s8_ws);
v_set_bs_renew(private_ocpp_data->id);
v_ocpp_init_data(private_ocpp_data->id);
u8_result = 0;
}
else
{
u8_result = 0;
}
return u8_result;
}
/**
* @brief 周期调用:发送 BootNotification 并等待登录成功
*功能说明: 平台桩信息登录 -
*
* ************************************************************************/
U8_T v_ocpp_logo_on()
{
if(private_ocpp_data->u8_ping_ack)//需要回复ping包
{
ws_send(NULL, 0, 1, WDT_PONG);
private_ocpp_data->u8_ping_ack = 0;
return 0;
}else if(private_ocpp_data->u8_register_flag == 1)
{
OCPP_PRINTF_LOG("Login ok\n");
private_ocpp_data->ulong_timer = 0; //超时计数
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].s_gundata[0].txFlag_tradUpload = 0; //清空订单上送标志
s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].s_gundata[1].txFlag_tradUpload = 0;
return 1;
}
if (!u8_ocpp_block_rx_timeout_relink(private_ocpp_data->id)
&& private_ocpp_data->ulong_timer > 20 ) //登录连接超时 1分钟时间
{
OCPP_PRINTF_LOG("Login conn TO, relink[%d]\n",private_ocpp_data->u8_register_flag);
v_set_bs_renew(private_ocpp_data->id); //设置主函数 联网重连
v_ocpp_init_data_ex(private_ocpp_data->id, 0);
}
v_ocpp_timer_add(); //计时计数+1
if(u8_ocpp_timer_ctrl(OCPP_TIMER_BootNotification,0) == 0) //检查登录周期是否到位
return 0;
OCPP_PRINTF_LOG("Login tx\n");
send_mag(GUN_NULL,OCPP_BootNotification,NULL,0);
return 0;
}
/**
* @brief 周期调用:TCP 连接正常时接收并解析 OCPP 下行数据
*功能说明: 数据接收处理 -
*
* ************************************************************************/
void v_ocpp_recv_data()
{
// static U16_T u16_cnt = 0;
// if(u16_cnt++ < 10)
// return;
// u16_cnt = 0;
if(s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_TCP_connect_flag)
ocpp_recv();
}
/**
* @brief 周期调用:Ping/Pong 维持 + 状态变位检查 + OCPP 发送调度
*功能说明: sdk私有协议逻辑控制 -
*
* ************************************************************************/
void v_ocpp_send_ctrl()
{
if(private_ocpp_data->u8_ping_ack)//需要回复ping包
{
ws_send(NULL, 0, 1, WDT_PONG);
private_ocpp_data->u8_ping_ack = 0;
return;
}
else if(private_ocpp_data->u16_ocpp_ping_timer > 30) //主动发送ping包
{
ws_send(NULL, 0, 1, WDT_PING);
private_ocpp_data->u16_ocpp_ping_timer = 0;
return;
}
v_ocpp_timer_add(); //计时计数+1
v_cheak_state_change(); //状态改变检查
ocpp_send_mag_ctrl(); //发送控制
}
/**
* @brief 主逻辑轮询:按 未联网/联网未登录/已登录 三状态驱动 OCPP 业务
*功能说明: sdk私有协议逻辑控制 -
*
* ************************************************************************/
void v_ocpp_private_logic(U8_T bs_flag)
{
U8_T i,id = bs_flag;
if (s_bs_task_ctrl.s_bs_ctrl[id].u8_TCP_connect_flag == 0) //未联网
{
private_ocpp_data->u8_unsettled_order_flag = 0;
if (!u8_ocpp_block_rx_timeout_relink(id)
&& private_ocpp_data->ulong_timer > 2*60) //时间超时 60秒
{
OCPP_PRINTF_LOG("End rx TO, relink\n");
v_set_bs_renew(id); //设置主函数 联网重连
v_ocpp_init_data_ex(id, 0);
}
v_ocpp_mv_offline_bridge_private_logic(id);
}
else if(s_bs_task_ctrl.s_bs_ctrl[id].u8_log_on_flag ==0) //联网未登录
{
private_ocpp_data->u8_unsettled_order_flag = 0;
if (!u8_ocpp_block_rx_timeout_relink(id)
&& private_ocpp_data->ulong_timer > 2*60) //时间超时 60秒
{
OCPP_PRINTF_LOG("End rx TO, relink\n");
v_set_bs_renew(id); //设置主函数 联网重连
v_ocpp_init_data_ex(id, 0);
}
v_ocpp_mv_offline_bridge_private_logic(id);
}
else //已经登录
{
if (!u8_ocpp_block_rx_timeout_relink(id)
&& private_ocpp_data->ulong_timer > 2*60) //时间超时 60秒
{
OCPP_PRINTF_LOG("RX TO, relink\n");
v_set_bs_renew(id); //设置主函数 联网重连
v_ocpp_init_data_ex(id, 0);
}
if (private_ocpp_data->s_unsettled_order_data != NULL) {
/* 已有未结单在内存:每周期推进 Authorize/MV/StopTx,避免 StartTx 后空等 10s */
(void)s8_unsettled_order_chaek();
}
else if (private_ocpp_data->u8_unsettled_order_flag == 0)
{
if (private_ocpp_data->ulong_timer - private_ocpp_data->u32_timer > 10)
{
private_ocpp_data->u32_timer = private_ocpp_data->ulong_timer;
S8_T unsettledCount = s8_unsettled_order_chaek();
if (unsettledCount == 0) {
private_ocpp_data->u8_unsettled_order_flag = 1;
}
}
}
/* 在线 MeterValues 发送失败(如 QSSLSEND ERROR)时补写一条离线 MV 快照 */
for (i = 0; i < GUN_MAX_CNT; i++) {
if (private_ocpp_data->s_ctrl_flag[i].s_u8_mv_ws_fail_snap_req != 0U) {
private_ocpp_data->s_ctrl_flag[i].s_u8_mv_ws_fail_snap_req = 0U;
v_ocpp_mv_offline_snap_gun_now((U8_T)i);
}
}
/* 会话内离线 MV 补发仅在 ocpp_send_mag_ctrl 中推进,避免双路径重复发包 */
//充电中 鉴权检查
for (i = 0; i < GUN_MAX_CNT; i++)
{
v_ocpp_private_charge_authentication_cheak(i);
}
}
/* 本地鉴权缓存检查(与 ocpp_send_mag_ctrl 中 Authorize 前调用同一接口;此处保留:未在本周期走发送路径时仍可置位结果) */
for (i = 0; i < GUN_MAX_CNT; i++)
{
if(s_bs_task_ctrl.s_bs_ctrl[id].s_gundata[i].txFlag_autoStart == 1)
{
v_ocpp_offline_local_authentication_check(i);
}
}
if(s_bs_task_ctrl.s_bs_ctrl[private_ocpp_data->id].u8_updata_flag == 0)
{
if (private_ocpp_data->u8_log_file_upload != 0) {
/* 上传结束清除标志时同步清零空闲计时,避免上传期间累积的 ulong_timer 立刻触发 RX TO */
private_ocpp_data->ulong_timer = 0;
}
/* 勿在此清 u8_log_file_uploadGetDiagnostics 在 recv 置 1 后,CallResult/Uploading 发出前 u8_updata_flag 仍为 0
* 若此处清零,v_ftp_update_action 会误走 u8_ec200a_4g_ftp_update[FTP-UPG])而非日志上传。清零见 ec200a_4g_ftp 完成分支。 */
}
if(private_ocpp_data->u8_reboot_flag != 0)
{
v_ocpp_reboot_ctrl();
}
}
#if(MY_SHELL_EN)
/**
* @brief Shell:强制复位 OCPP TCP/WebSocket(与 RX TO relink 相同,不重启 4G 模组)
*/
void ocppTcpRenewFunc(void)
{
U8_T id = s_bs_task_ctrl.ocpp_data.id;
private_ocpp_data = &s_bs_task_ctrl.ocpp_data;
private_ocpp_data->u8_register_flag = 0;
private_ocpp_data->ulong_timer = 0;
v_set_bs_renew(id);
v_ocpp_init_data_ex(id, 0);
OCPP_PRINTF_LOG("Shell: OCPP TCP renew id=%u\r\n", (unsigned)id);
v_shell_print("ocppTcpRenew ok, bs=%u\r\n", (unsigned)id);
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
ocppTcpRenew, ocppTcpRenewFunc, myshlle-- ocpp tcp renew);
#endif /* MY_SHELL_EN */
#endif
#endif
/*
1. 充电桩上线流程
充电桩通电后与中央系统(Central System)建立通信链路并完成初始化。
步骤 方向 数据帧(操作) 说明
1. 连接建立 充电桩 → 平台 BootNotification 充电桩发送启动通知,包含厂商、型号、固件版本等信息。
2. 平台响应 平台 → 充电桩 BootNotification.conf 平台返回状态(Accepted/Pending)和心跳间隔(interval)。
3. 心跳维持 充电桩 → 平台 Heartbeat 按配置的间隔定期发送心跳包,维持长连接。
4. 心跳响应 平台 → 充电桩 Heartbeat.conf 返回当前平台时间(UTC),用于充电桩时间同步。
2. 平台远程启动充电流程
中央系统通过远程指令启动充电(如用户通过 App 发起充电)。
步骤 方向 数据帧(操作) 说明
1. 远程启动指令 平台 → 充电桩 RemoteStartTransaction 指定充电枪(connectorId)、用户标识(idTag)等。
2. 充电桩响应 充电桩 → 平台 RemoteStartTransaction.conf 返回是否接受请求(Accepted/Rejected)。
3. 开始充电 充电桩 → 平台 StartTransaction 充电桩启动充电,上报初始电表值(meterStart)和时间戳。
4. 平台确认 平台 → 充电桩 StartTransaction.conf 返回分配的 transactionId 和授权状态。
5. 充电中上报 充电桩 → 平台 MeterValues 周期性或事件触发上报实时电量(如每15分钟)。
6. 远程停止指令 平台 → 充电桩 RemoteStopTransaction 平台主动终止充电。
7. 停止充电 充电桩 → 平台 StopTransaction 上报最终电表值(meterStop)和充电时长。
3. 充电桩本地刷卡启动充电流程
用户通过 RFID 卡在充电桩本地启动充电。
步骤 方向 数据帧(操作) 说明
1. 刷卡授权 充电桩 → 平台 Authorize 上传用户卡号(idTag)请求授权。
2. 授权响应 平台 → 充电桩 Authorize.conf 返回授权状态(Accepted/Blocked)。
3. 开始充电 充电桩 → 平台 StartTransaction 充电桩启动充电,上报初始电表值。
4. 平台确认 平台 → 充电桩 StartTransaction.conf 返回 transactionId 和状态。
5. 充电中上报 充电桩 → 平台 MeterValues 周期性上报电量数据。
6. 停止充电 用户操作 → 充电桩 - 用户拔枪或刷卡停止。
7. 结束上报 充电桩 → 平台 StopTransaction 上报最终电表值和充电摘要。
*/