#include "meter_calculate/meter_calculate_impl.h" #include "publicdata/publicdata.h" #include "flowctrl/flow_interface.h" #include "bms/bms_interface.h" #include "meter_calculate/meter_coll_task.h" #include "flash_file_mgr/meter_calculate_flash_impl.h" #include "flash_file_mgr/unsettled_order_mng.h" #include "mylog/mylog.h" #include "plat_comm/impl/bs_public_impl.h" #include "app_rtc/app_rtc.h" #include "meter_calculate/calculate_task.h" #include #define METERFEE_LOG(fmt, ...) MYLOG_MSG(TASK_ID_Meterfee, (fmt), ##__VA_ARGS__) /* * 说明: * - CCU601E_D 的实现包含订单Flash/EEPROM持久化与平台结算逻辑; * - CCU621_M 当前工程目录未包含对应 flash_file_mgr/unsettled_order_mng 模块, * 因此此处先提供“计量计费核心数据获取 + 故障上报”功能,持久化接口留空实现, * 后续如引入存储模块再补齐。 */ extern S_METERING_BILLING_CTRL s_metering_ctrl; /** * ************************************************************************ * @brief - 电量计量任务 获取外部数据 * * @param[in] u8_gunNo - 枪号 0-N * @param[in] e_dataType - 数据类型 详看枚举定义 * @param[in] u16_dataPara - 数据参数 * * @return U32_T - 获取数据返回值 * * @author jiankalka * @date 2025-09-15 * ************************************************************************ */ U32_T u32_meterlog_get_sys_data(U8_T u8_gunNo , E_METERLOG_GET_DATA_TYPE e_dataType , U16_T u16_dataPara) { U32_T u32_ret = 0; (void)u16_dataPara; switch (e_dataType) { case E_METERLOG_GET_DATA_GUN_CNT: u32_ret = g_t_share_data.t_sys_fix_cfg.u8_gunCnt; break; case E_METERLOG_GET_DATA_FEE_RATE_DATA: //费率电费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元 /* 费率数据结构若未初始化,返回 0 */ u32_ret = 0; if (u8_gunNo < FEE_MODEL_CNT) { U8_T u8_type = (U8_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.u8_fee_type[u8_gunNo]); if (u8_type > 0) { u8_type = (U8_T)(u8_type - 1U); } if (u8_type >= FEE_MODEL_TYPE_CNT) { u8_type = 0; } u32_ret = (U32_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.s_fee_data[u8_type].u32_rate/10U); } break; case E_METERLOG_GET_DATA_FEE_SERVER_DATA: //费率服务费信息 抢号代表时段位置 0-95;参数0代表充电费率 1代表放电费率 精度 0.0001元 u32_ret = 0; if (u8_gunNo < FEE_MODEL_CNT) { U8_T u8_type = (U8_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.u8_fee_type[u8_gunNo]); if (u8_type > 0) { u8_type = (U8_T)(u8_type - 1U); } if (u8_type >= FEE_MODEL_TYPE_CNT) { u8_type = 0; } u32_ret = (U32_T)(g_t_share_data.t_sys_var_cfg.s_fee_model.s_fee_data[u8_type].u32_sevice/10U); } break; case E_METERLOG_GET_DATA_GUN_SOC: //当前soc值 u32_ret = (U32_T)u16_bms_put_data(u8_gunNo, E_BMS_PUT_DATA_GUN_SOC, 0); break; case E_METERLOG_GET_DATA_GUN_PUT_VOL: //当前充电电压 精度 0.1V u32_ret = (U32_T)(u64_get_meter_data(u8_gunNo, 0, E_METER_DATA_VOL) / 1000U); break; case E_METERLOG_GET_DATA_GUN_PUT_CUR: //当前充电电流 精度 0.1A u32_ret = (U32_T)(u64_get_meter_data(u8_gunNo, 0, E_METER_DATA_CUR) / 1000U); break; case E_METERLOG_GET_DATA_GUN_FLOW_STATE: //终端充电状态 0空闲 1充电启动 2绝缘启动 3绝缘计算完成 4绝缘检测模块停止 5开始预充 6预充完成 7充电中 8BMS通讯超时 9充电停止 10充电停止完成 u32_ret = (U32_T)u16_flow_put_data(u8_gunNo, E_FLOW_PUT_DATA_GUN_REAL_STATE, 0); break; default: break; } return u32_ret; } /** * ************************************************************************ * @brief - 获取枪电表电量值 * * @param[in] u8_gunNo - 枪号 0-n * @param[in] u8_type - 数据类型 0组合有功电量 1正向有功电量 2反向有功电量 * * @return U64_T - * * @author jiankalka * @date 2025-09-15 * ************************************************************************ */ U64_T u64_meterlog_get_meter_data(U8_T u8_gunNo, U8_T u8_type) { U64_T u64_ret = 0; /* 当前工程仅提供组合/正向有功电量:统一取 E_METER_DATA_POWER */ (void)u8_type; u64_ret = u64_get_meter_data(u8_gunNo, 0, E_METER_DATA_POWER); return u64_ret; } /** * ************************************************************************ * @brief - 设置电量计量异常故障 用于外部触发故障告警 * * @param[in] u8_flag - 故障类型 0电表跳变故障 1电量长时间未更新故障 2对时时间不合法故障 * * * @author jiankalka * @date 2025-08-07 * ************************************************************************ */ void v_meterlog_set_fault(U8_T u8_gunNo, U8_T u8_flag,U8_T u8_fault_flag,U16_T u16_data1,U16_T u16_data2) { E_FAULT_TYPE e_fault_status = (u8_fault_flag != 0U) ? E_FAULT : E_RES; if (u8_flag == 0U) { v_set_fault_data((E_FAULT_SITE)((U16_T)E_GUN + (U16_T)u8_gunNo), e_fault_status, E_FAULT_17007, 0U, u16_data1, u16_data2); } else if (u8_flag == 1U) { v_set_fault_data((E_FAULT_SITE)((U16_T)E_GUN + (U16_T)u8_gunNo), e_fault_status, E_FAULT_17008, 0U, u16_data1, u16_data2); } else if (u8_flag == 2U) { v_set_fault_data(E_WHOLE_HOST, e_fault_status, E_FAULT_17009, 0U, u16_data1, u16_data2); } else { /* ignore */ } } #if 1 //对外提供接口 /** * ************************************************************************ * @brief - 启动充电时 赋值订单信息 * * @param[in] u8_gunNo - 枪号 0-N * @param[in] s_log_start_data - 启动充电订单数据信息 * * * @author jiankalka * @date 2025-09-12 * ************************************************************************ */ void v_meterlog_set_start_char_log_info(U8_T u8_gunNo,S_CHAR_LOG_START_DATA *s_log_start_data) { if(u8_gunNo >= GUN_MAX_CNT ) return; S_LOG_DATA *s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo]; Comm_Time time; GetCurrentTime(&time); memset(s_gun_log , 0 ,sizeof(S_LOG_DATA)); //赋值订单信息 s_gun_log->e_start_reason = s_log_start_data->e_start_reason ; //启动充电原因 s_gun_log->e_sdk_startType = s_log_start_data->e_sdk_startType; //启动方式 sdk订单上传使用 s_gun_log->e_user_type = s_log_start_data->e_user_type ; //用户类型 --积成平台使用 s_gun_log->u32_balance_begin = s_log_start_data->u32_balance_begin; ////启动充电前金额 0.0001元 memcpy(s_gun_log->u8_preTradeNo ,s_log_start_data->u8_preTradeNo, EVS_MAX_TRADE_LEN); //平台交易流水号:8位桩地址+2位枪号+12位时间+补0(ascii码后面补0,相当于结束符) memcpy(s_gun_log->u8_tradeNo ,s_log_start_data->u8_tradeNo, EVS_MAX_TRADE_LEN); //设备交易流水号 memcpy(s_gun_log->u8_stop_cmd ,s_log_start_data->u8_stop_cmd, 4); //停机码 4位 memcpy(s_gun_log->u8_user_id ,s_log_start_data->u8_user_id, 32); //用户标识 memcpy(s_gun_log->u8_vinCode ,s_log_start_data->u8_vinCode, 18); //车辆vin //获取初始值 memcpy(&s_gun_log->StartChargeTime, &time,sizeof(Comm_Time)); //充电开始时间 s_gun_log->u64_startKWh = u64_meterlog_get_meter_data(u8_gunNo,0); //当前电表数据 s_gun_log->u8_gunNo = u8_gunNo; //抢号信息 METERFEE_LOG("[%d]Gun(0-N) start order info set\n", u8_gunNo); } /** * ************************************************************************ * @brief - 停止充电时 赋值订单信息 * * @param[in] u8_gunNo - 枪号 0-N * @param[in] e_stop_reason - 故障原因 * * * @author jiankalka * @date 2025-09-12 * ************************************************************************ */ void v_meterlog_set_stop_char_log_info(U8_T u8_gunNo,E_FAULT_INFO e_stop_reason) { if(u8_gunNo >= GUN_MAX_CNT ) return; S_LOG_DATA *s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo]; Comm_Time time; U8_T i,u8_soc; GetCurrentTime(&time); for (i = 0; i < NUM_5; i++) { if(s_gun_log->e_stop_reason[i] == 0) { s_gun_log->e_stop_reason[i] = e_stop_reason; break; } } //获取车辆VIN码 v_bms_put_vin_str(u8_gunNo,s_gun_log->u8_vinCode,18); //获取结束时间值 memcpy(&s_gun_log->EndChargeTime, &time,sizeof(Comm_Time)); //充电开始时间 s_gun_log->u64_endKWh = u64_meterlog_get_meter_data(u8_gunNo,0); //当前电表数据 u8_soc = u32_meterlog_get_sys_data(u8_gunNo,E_METERLOG_GET_DATA_GUN_SOC,0); if(u8_soc != 0) s_gun_log->u8_endSoc = u8_soc; //SOC METERFEE_LOG("[%d]Gun(0-N) stop order info set, stopRsn=%d\n", u8_gunNo, e_stop_reason); } /** * ************************************************************************ * @brief - 对外获取充电订单信息 * * @param[in] u8_gunNo - 抢号 0-N * @param[in] s_log - 订单结构体指针 * * * @author jiankalka * @date 2025-09-22 * ************************************************************************ */ void v_meterlog_put_char_log_data(U8_T u8_gunNo,S_LOG_DATA *s_log) { memset(s_log , 0, sizeof(S_LOG_DATA)); if(u8_gunNo > GUN_MAX_CNT) return; memcpy(s_log , &s_metering_ctrl.s_gun_log[u8_gunNo],sizeof(S_LOG_DATA)); } /** * ************************************************************************ * @brief - 设置充电订单信息 * * @param[in] u8_gunNo - 抢号 0-N * @param[in] s_log_start_data - 设置订单信息 其中数组数据设置NULL代表无需改动 * * * @author jiankalka * @date 2025-09-22 * ************************************************************************ */ void v_meterlog_set_char_log_info(U8_T u8_gunNo, E_METERLOG_SET_LOG_INFO_TYPE e_type , U32_T *u32_data, U8_T *u8_str) { S_LOG_DATA *s_gun_log; if (u8_gunNo >= GUN_MAX_CNT) { return; } s_gun_log = &s_metering_ctrl.s_gun_log[u8_gunNo]; switch (e_type) { case E_METERLOG_SET_LOG_INFO_u32_start_reason : //启动充电原因 if (u32_data != NULL) s_gun_log->e_start_reason = (START_REASON)(*u32_data); break; case E_METERLOG_SET_LOG_INFO_u32_sdk_startType: //启动方式 sdk订单上传使用 if (u32_data != NULL) s_gun_log->e_sdk_startType = (E_SDK_START_TYPE)(*u32_data); break; case E_METERLOG_SET_LOG_INFO_u32_user_type: //用户类型 --积成平台使用 if (u32_data != NULL) s_gun_log->e_user_type = (USER_TYPE_E)(*u32_data); break; case E_METERLOG_SET_LOG_INFO_u32_balance_begin: //启动充电前金额 0.0001元 if (u32_data != NULL) s_gun_log->u32_balance_begin = *u32_data; break; case E_METERLOG_SET_LOG_INFO_char_vinCode: //车辆vin if (u8_str != NULL) strncpy((char *)s_gun_log->u8_vinCode, (char *)u8_str, sizeof(s_gun_log->u8_vinCode)); break; case E_METERLOG_SET_LOG_INFO_char_user_id: //用户标识 if (u8_str != NULL) strncpy((char *)s_gun_log->u8_user_id, (char *)u8_str, sizeof(s_gun_log->u8_user_id)); break; case E_METERLOG_SET_LOG_INFO_char_stop_cmd: //停机码 4位 if (u8_str != NULL) strncpy((char *)s_gun_log->u8_stop_cmd, (char *)u8_str, sizeof(s_gun_log->u8_stop_cmd)); break; case E_METERLOG_SET_LOG_INFO_char_tradeNo: //设备交易流水号 if (u8_str != NULL) strncpy((char *)s_gun_log->u8_tradeNo, (char *)u8_str, sizeof(s_gun_log->u8_tradeNo)); break; case E_METERLOG_SET_LOG_INFO_char_preTradeNo: //平台交易流水号: if (u8_str != NULL) strncpy((char *)s_gun_log->u8_preTradeNo, (char *)u8_str, sizeof(s_gun_log->u8_preTradeNo)); /* StartTransaction 取得 txId 后立即落 EEPROM,避免掉电前 60s 周期未到而丢失流水号 */ if (s_metering_ctrl.s_char_data[u8_gunNo].u8_billing_flag != 0U) { (void)v_save_temp_chg_record_to_eeprom(u8_gunNo, s_gun_log, TEMP_CHG_FLAG_UNSETTLED); } break; default: break; } } /** * ************************************************************************ * @brief - 设置充电订单信息上送 * * @param[in] u8_gunNo - 抢号 0-N * * * @author jiankalka * @date 2025-09-25 * ************************************************************************ */ void v_set_plat_log_flag(U8_T u8_gunNo) { v_bs_log_txFlag_tradUpload(u8_gunNo); } /** * ************************************************************************ * @brief -输出订单中指定数据 * * @param[in] u8_gunNo -枪号 * @param[in] e_typeData -数据枚举 * * @return U32_T -读取的数据 * * @author YZG * @date 2025-09-29 * ************************************************************************ */ U32_T u32_meterlog_put_data(U8_T u8_gunNo,E_METERLOG_PUT_LOG_INFO_TYPE e_typeData ,U8_T *u8_str , U16_T u16_len) { U32_T u32_ret = 0; if(u8_str != NULL) memset(u8_str , 0 , u16_len); switch (e_typeData) { case E_METERLOG_PUT_LOG_INFO_USED_MONEY: //以充电费金额,精度0.0001元 { u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u32_used_money; break; } case E_METERLOG_PUT_LOG_INFO_USED_SERVICE: //以充服务费金额,精度0.0001元 { u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u32_used_service; break; } case E_METERLOG_PUT_LOG_INFO_USED_ENERGY: //以充电量,精度0.0001kWh { u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u32_used_energy; break; } case E_METERLOG_PUT_LOG_INFO_START_TYPE: //启动充电方式 START_REASON { u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].e_start_reason; break; } case E_METERLOG_PUT_LOG_INFO_USER_ID: //用户ID { if(u8_str == NULL) break; strncpy((char *)u8_str,(char *)s_metering_ctrl.s_gun_log[u8_gunNo].u8_user_id,u16_len); } case E_METERLOG_PUT_LOG_INFO_CHAR_TIME: //充电开始时间 { u32_ret = s_metering_ctrl.s_gun_log[u8_gunNo].u16_ChargedTime; break; } default: break; } return u32_ret; } /** * ************************************************************************ * @brief -计算下一循环的金额 * * @param[in] u8_gunNo -枪号 * * @return U32_T -下一循环金额 * * @author YZG * @date 2025-10-09 * ************************************************************************ */ U32_T u32_meterlog_next_cycle_money(U8_T u8_gunNo) { Comm_Time time; U32_T u32_next_cycle_money = 0,u32_out_vol = 0, u32_out_curr = 0; //下个周期金额 精度0.0001元,输出电压 精度0.1V,输出电流 精度0.1A U32_T u32_power = 0,u32_energy = 0; //充电功率,下周期充电电量 S_REAL_FEE_DATA *s_real_fee_data = &s_metering_ctrl.s_real_fee_data[s_metering_ctrl.s_char_data[u8_gunNo].u8_discharge_flag]; //获取当前费率信息 默认已经更新正确 GetCurrentTime(&time); u32_out_vol = u64_get_meter_data(u8_gunNo,0,E_METER_DATA_VOL)/1000; //输出电压,精度0.1 u32_out_curr = u64_get_meter_data(u8_gunNo,0,E_METER_DATA_CUR)/1000; //输出电流,精度0.1 u32_power = u32_out_vol * u32_out_curr /10; //充电功率 精度 0.0001kw(0.01W /10 = 0.0001kw) u32_energy = u32_power/ 3600.0 * 10;//10S充电电能,单位0.0001kwh //下一周期金额 精度0.0001元 u32_next_cycle_money = (U32_T)(u32_energy *(s_real_fee_data->u32_money_rate + s_real_fee_data->u32_money_service) / 10000); //METERFEE_LOG("充电功率:%d,充电电量:%d,预判金额:%d\n",u32_power,u32_energy,u32_next_cycle_money); return u32_next_cycle_money; } /** * ************************************************************************ * @brief - 存储订单操作函数 * * @param[in] u8_flag - 0 为数据更新 1为订单结束存储 * @param[in] u8_gunNo - 枪号 0-1 * * * @author jiankalka * @date 2026-01-19 * ************************************************************************ */ static U8_T u8_meterlog_pre_trade_no_valid(const U8_T *pre) { if (pre == NULL || pre[0] == '\0' || pre[0] == (U8_T)0xFF) { return 0U; } if (atoi((const char *)pre) <= 0) { return 0U; } return 1U; } /** 订单结束写 Flash 前:RAM 被复位时从 EEPROM 临时记录补全 txId 等字段 */ static void v_meterlog_merge_temp_into_log(U8_T u8_gunNo, S_LOG_DATA *s_gun_log) { S_LOG_DATA temp; if (s_gun_log == NULL || u8_meterlog_pre_trade_no_valid(s_gun_log->u8_preTradeNo) != 0U) { return; } if (v_check_temp_chg_record_valid(u8_gunNo) != 1U) { return; } if (v_read_temp_chg_record_from_eeprom(u8_gunNo, &temp, NULL) != 0U) { return; } if (u8_meterlog_pre_trade_no_valid(temp.u8_preTradeNo) == 0U) { return; } memcpy(s_gun_log->u8_preTradeNo, temp.u8_preTradeNo, EVS_MAX_TRADE_LEN); if (s_gun_log->u8_user_id[0] == '\0' && temp.u8_user_id[0] != '\0') { memcpy(s_gun_log->u8_user_id, temp.u8_user_id, sizeof(s_gun_log->u8_user_id)); } if (temp.StartChargeTime.iYear != 0 && s_gun_log->StartChargeTime.iYear == 0) { memcpy(&s_gun_log->StartChargeTime, &temp.StartChargeTime, sizeof(Comm_Time)); } if (s_gun_log->u64_startKWh == 0U && temp.u64_startKWh != 0U) { s_gun_log->u64_startKWh = temp.u64_startKWh; } METERFEE_LOG("[%d]Gun merge EEPROM temp txId=%s\r\n", u8_gunNo, (char *)s_gun_log->u8_preTradeNo); } void v_meterlog_restore_temp_from_eeprom(void) { U8_T gun; for (gun = 0U; gun < GUN_MAX_CNT; gun++) { if (v_check_temp_chg_record_valid(gun) != 1U) { continue; } if (v_read_temp_chg_record_from_eeprom(gun, &s_metering_ctrl.s_gun_log[gun], NULL) != 0U) { continue; } METERFEE_LOG("[%d]Gun restore order from EEPROM temp txId=%s\r\n", gun, (char *)s_metering_ctrl.s_gun_log[gun].u8_preTradeNo); } } void v_meterlog_flash_save_log(U8_T u8_flag , U8_T u8_gunNo) { if(u8_flag == 1) //订单结束存储 { v_meterlog_merge_temp_into_log(u8_gunNo, &s_metering_ctrl.s_gun_log[u8_gunNo]); // 同时保存到Flash存储(双备份) U32_T orderIndex = u32_chg_order_save_record(&s_metering_ctrl.s_gun_log[u8_gunNo]); s_metering_ctrl.s_gun_log[u8_gunNo].u32_index = orderIndex; // 将订单索引添加到未结算订单列表(默认未收到平台结算) if (orderIndex != 0) { u8_add_unsettled_order(orderIndex); METERFEE_LOG("[%d]Gun(0-N) order add to unsettled, idx=%u\r\n", u8_gunNo, orderIndex); } // 清除EEPROM中的临时充电记录(设置为已结算状态) v_clear_temp_chg_record_in_eeprom(u8_gunNo); #if FATFS_ENABLE_ORDER // 可选:同时保存到文件系统缓存 cache_add_order(&s_metering_ctrl.s_gun_log[u8_gunNo]); METERFEE_LOG("[%d]Gun(0-N) order saved Flash/EEPROM + FS cache, add unsettled\r\n", u8_gunNo); #else METERFEE_LOG("[%d]Gun(0-N) order saved Flash/EEPROM, add unsettled\r\n", u8_gunNo); #endif } else //订单数据更新(充电中周期更新) { // 更新EEPROM中的临时充电记录(设置为未结算状态) v_save_temp_chg_record_to_eeprom(u8_gunNo, &s_metering_ctrl.s_gun_log[u8_gunNo], TEMP_CHG_FLAG_UNSETTLED); //METERFEE_LOG("[%d]枪(0-N)充电订单数据已更新到EEPROM临时记录(未结算状态)\r\n", u8_gunNo); } } /** * ************************************************************************ * @brief - 平台结算确认接口 * * 当平台确认订单结算后调用此函数,从未结算订单列表中移除对应的订单索引 * * @param[in] orderIndex - 订单索引(由u32_chg_order_save_record返回) * @return U8_T - 0-成功,1-失败(未找到该索引) * * @author jiankalka * @date 2026-01-19 * ************************************************************************ */ U8_T u8_platform_settlement_confirm(U32_T orderIndex) { U8_T ret = u8_remove_unsettled_order(orderIndex); if (ret == 0) { METERFEE_LOG("Plat settle confirm OK, rm unsettled idx=%u\r\n", orderIndex); } else { METERFEE_LOG("Plat settle confirm fail, idx=%u NF\r\n", orderIndex); } return ret; } /** * ************************************************************************ * @brief - 查询订单结算状态接口 * * 查询指定订单是否已结算 * * @param[in] orderIndex - 订单索引(由u32_chg_order_save_record返回) * @return U8_T - 0-已结算,1-未结算 * * @author jiankalka * @date 2026-01-19 * ************************************************************************ */ U8_T u8_check_order_settlement_status(U32_T orderIndex) { return u8_check_order_settled(orderIndex); } #endif