9ceb218f80
Co-authored-by: Cursor <cursoragent@cursor.com>
5408 lines
240 KiB
C
5408 lines
240 KiB
C
#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_timer(WebSocket 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_autoStart(VIN 鉴权请求上送标志)
|
||
*
|
||
* 三种操作模式:
|
||
* - 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 Charge,0~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,当前SOC;type=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,当前SOC;type=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,当前SOC;type=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 / 10(Wh)
|
||
* - 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 Charge,0~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,当前SOC;type=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_E(Downloading … 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=须上送 Installed,0=无需上送
|
||
*
|
||
* @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 != 0(FTP 升级进行中)
|
||
* - 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=绕过授权继续 StartTx,0=需重试或放弃
|
||
*
|
||
* @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");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 未结订单且尚无有效 transactionId:Authorize(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;
|
||
}
|
||
|
||
/* 尚无 txId:Authorize → 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,当前SOC;type=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,当前SOC;type=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,当前SOC;type=1,启动时SOC;type=2,结束时SOC
|
||
U8_T u8_stop_soc; //SOC 数据 type=0,当前SOC;type=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,当前SOC;type=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,当前SOC;type=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:从当前枪订单取 idTag;1:从 `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 DiagnosticsStatusNotification:status 与 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 FirmwareStatusNotification:status 与 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;
|
||
}
|
||
|
||
/*
|
||
* FirmwareStatusNotification(FSN):与 Diagnostics 并列 pending,Diag 优先。
|
||
* 每周期最多发一条;sm<=0(4G 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_stopResult(bs_public_impl.c v_cheak_state_change)时也会误走 StartTransaction,
|
||
* 日志表现为 gun x stop result 后出现 send cmd StartTransaction 与 idTag is null。
|
||
* 正确行为:仅 START 标志触发 StartTransaction;STOP 边沿只清标志,订单结束仍由 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 问题1:Authorize 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)建立 TCP(4G/网线) 3)WebSocket 握手。含失败退避与超时重连策略
|
||
*
|
||
* ************************************************************************/
|
||
U8_T v_ocpp_tcp_connect()
|
||
{
|
||
U8_T u8_tcp = 0; // 1=TCP 已连通(底层),0=未连通
|
||
S8_T s8_ws = 0; // websocket_build:1成功 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) 先建立 TCP(4G/网口由 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_data:RAM 全量 + 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_upload:GetDiagnostics 在 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 上报最终电表值和充电摘要。
|
||
|
||
|
||
*/ |