#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 #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;i32767)里 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;i20)//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 上报最终电表值和充电摘要。 */