/***************************************************************************** * @copyright 2024-202, . POWER SUPPLY CO., LTD. * @file logic_peakvalley.c * @brief 削峰填谷控制策略 * @author Gary * @date 2024/09/21 * @remark 初修订 *****************************************************************************/ #include "logic_comm.h" // 削峰填谷参数数据库配置 peakvalley_zone_tab_t zone_tab_obj = {0}; // 定义一个结构体变量 peakvalley_zone_tab_t* zone_tab = &zone_tab_obj; // 使指针指向该变量 /***************************************************************************** * @brief 将日期字符串转换为月份和日的整数表示 * @param[in] char* dateStr 日期字符串,例如 "MM-DD" * @param[out] int* month 指向月份的指针 * @param[out] int* day 指向日期的指针 * @return 0: 成功 其它值:失败 *****************************************************************************/ static int parseDate(const char* dateStr, int* month, int* day) { int result = sscanf(dateStr, "%d-%d", month, day); // 解析字符串获取月份和日期 return (result == 2) ? 0 : -1; // 返回0表示成功,其他值表示失败 } /***************************************************************************** * @brief 获取削峰平谷实时运行参数接口 * @param[in] pv_time_config_t *ret_params(功率计划时间表) u_int16_t slotTabLen (实际表长) * @return 目标功率 *****************************************************************************/ static float_t getPeakValleyPower(pv_time_config_t* ret_params, u_int16_t slotTabLen) { // 获取当前时间 time_t current_time; struct tm* timeinfo; time(¤t_time); // 获取当前时间戳 timeinfo = localtime(¤t_time); // 将时间戳转换为当地时间 uint32_t now_seconds = timeinfo->tm_hour * 3600 + timeinfo->tm_min * 60 + timeinfo->tm_sec; // 计算当前秒数 // EMS要求最小的时间间隔不得小于15分钟,换算之后一天不可大于96个时段 if (slotTabLen > MAX_TIME_SEGMENTS) { // 检查传入的时间段表长度是否合法 KITLOG(LOG_LOGIC_EN, WARN_EN, "获取时段表的长度非法 len:%d\n", slotTabLen); return 0; // 返回0表示未能获取有效功率 } for (int loop = 0; loop < slotTabLen; loop++) { // 跳过开始时间和结束时间相同的时间段 if (ret_params[loop].startTime == ret_params[loop].endTime) { continue; } // 检查当前时间是否在时间段内 if (now_seconds >= ret_params[loop].startTime && now_seconds <= ret_params[loop].endTime) { return ret_params[loop].power; // 返回当前时间段的功率值 } } // 未命中任意时段则停机,返回0 return 0; } /***************************************************************************** * @brief 判断日期是否在给定时间段内 * @param[in] int dateMonth(当前月) * @param[in] int dateDay(当前日) * @param[in] const char* startDateStr(开始日期字符串) * @param[in] const char* endDateStr(结束日期字符串) * @return 1 在时间段内 0 不在时间段内 -1参数异常解析失败 *****************************************************************************/ static int isDateInRange(int dateMonth, int dateDay, const char* startDateStr, const char* endDateStr) { if (NULL == startDateStr || NULL == endDateStr) { // 检查开始和结束日期指针是否为空 KITLOG(LOG_LOGIC_EN, ERROR_EN, "入参指针为空"); return -1; // 返回-1表示参数异常 } int startMonth, startDay, endMonth, endDay; if (0 != parseDate(startDateStr, &startMonth, &startDay)) { // 解析开始日期字符串失败 KITLOG(LOG_LOGIC_EN, ERROR_EN, "开始日期字符串匹配失败"); return -1; // 返回-1表示参数解析失败 } if (0 != parseDate(endDateStr, &endMonth, &endDay)) { // 解析结束日期字符串失败 KITLOG(LOG_LOGIC_EN, ERROR_EN, "结束日期字符串匹配失败"); return -1; // 返回-1表示参数解析失败 } // 日期保护,确保开始日期在结束日期之前 if (startMonth > endMonth || (startMonth == endMonth && startDay > endDay)) { return 0; // 返回0表示不在范围内 } // 判断是否在日期内 if (dateMonth > startMonth || (dateMonth == startMonth && dateDay >= startDay)) { if (dateMonth < endMonth || (dateMonth == endMonth && dateDay <= endDay)) { return 1; // 返回1表示在时间段内 } } return 0; // 返回0表示不在范围内 } /***************************************************************************** * @brief 更新时区时段表 * @return 0 更新成功 -1 失败 *****************************************************************************/ int logic_peakValleyUpdate() { UT_array* pvDateConfigs; // 清空之前的时段表 if (zone_tab->peakItem != NULL) { for (int j = 0; j < zone_tab->zoneTabLen; ++j) { if (zone_tab->peakItem[j].timeCfgTab != NULL) free(zone_tab->peakItem[j].timeCfgTab); zone_tab->peakItem[j].timeCfgTab = NULL; } free(zone_tab->peakItem); zone_tab->peakItem = NULL; } // 从数据库获取新的削峰填谷时段表数据 if (kit_get_pv_date_cfg_db_data(&pvDateConfigs) != 0) { KITLOG(LOG_LOGIC_EN, ERROR_EN, "获取削峰填谷时段表失败"); return 1; // 返回错误码 } // 获取数据个数 size_t numConfigs = utarray_len(pvDateConfigs); // 检查时段表长度是否合法 if (MAX_DATA_SEGMENTS < numConfigs || numConfigs <= 0) { KITLOG(LOG_LOGIC_EN, ERROR_EN, "时区表表长异常: %zu", numConfigs); utarray_free(pvDateConfigs); // 释放资源 return 1; // 返回错误码 } // 分配空间 zone_tab->peakItem = (pv_date_config_t*)calloc(numConfigs, sizeof(pv_date_config_t)); if (zone_tab->peakItem == NULL) { KITLOG(LOG_LOGIC_EN, ERROR_EN, "内存分配失败"); utarray_free(pvDateConfigs); // 释放资源 return 1; // 返回错误码 } // 更新日期配置个数 zone_tab->zoneTabLen = numConfigs; // 遍历并复制数据 使用 memcpy 整体复制结构体 for (int i = 0; i < numConfigs; ++i) { pv_date_config_t* srcConfig = (pv_date_config_t*)utarray_eltptr(pvDateConfigs, i); pv_date_config_t* dstConfig = &zone_tab->peakItem[i]; // 直接访问数组元素 // 复制 timeCfgTab 前先分配内存 dstConfig->timeCfgTab = (pv_time_config_t*)calloc(srcConfig->timeCfgLen, sizeof(pv_time_config_t)); if (dstConfig->timeCfgTab == NULL) { KITLOG(LOG_LOGIC_EN, ERROR_EN, "内存分配失败"); // 释放已分配的内存 for (int j = 0; j < i; ++j) { free(zone_tab->peakItem[j].timeCfgTab); } free(zone_tab->peakItem); utarray_free(pvDateConfigs); // 释放资源 return 1; // 返回错误码 } memcpy(dstConfig, srcConfig, sizeof(pv_date_config_t)); // 整体复制结构体 memcpy(dstConfig->timeCfgTab, srcConfig->timeCfgTab, srcConfig->timeCfgLen * sizeof(pv_time_config_t)); } utarray_free(pvDateConfigs); // 释放 UT_array 资源 return 0; // 返回成功 } /***************************************************************************** * @brief 获取削峰平谷实时运行参数接口 * @param[in] bool sign 时间表是否更新标志 * @return 0 成功 -1 失败 *****************************************************************************/ int logicFun_peakValley() { // 获取任务参数 peakvalley_params_t* task_params = &stlogic.para_delivery.task_params.pkvly; time_t current_time; struct tm* timeinfo; time(¤t_time); // 获取当前时间 timeinfo = localtime(¤t_time); // 转换为当地时间 // 获取日期 int month = timeinfo->tm_mon + 1; // 月份从0开始,需要加1 int day = timeinfo->tm_mday; // 天 if (zone_tab == NULL) { // 检查时段表指针是否为空 KITLOG(LOG_LOGIC_EN, ERROR_EN, "入参指针为空"); return -1; // 返回-1表示失败 } for (int loop = 0; loop < zone_tab->zoneTabLen; loop++) { // 检查当前日期是否在时段表中 if (1 == isDateInRange(month, day, (char*)zone_tab->peakItem[loop].startDate, (char*)zone_tab->peakItem[loop].endDate)) { // 获取对应时间段的功率配置 task_params->power = getPeakValleyPower(zone_tab->peakItem[loop].timeCfgTab, zone_tab->peakItem[loop].timeCfgLen); return 0; // 返回0表示成功 } } // 没找到合适的日期,返回0 task_params->power = 0; return 0; }