#include "ChargerTask.h" static struct netconn *datalink_conn; // 数据链路句柄 #define LINK_SERVER_PORT 6001 // 网关UDP服务端口 #define LINK_STAKE_PORT 6001 // 桩通讯端口 /** * @brief 桩IP地址 * @note 桩IP地址从10.12.19.101开始递增 */ ip4_addr_t stake_ip_1 = IPADDR4_INIT_BYTES(10, 12, 19, 101); // 桩 1 IP地址 ip4_addr_t stake_ip_2 = IPADDR4_INIT_BYTES(10, 12, 19, 102); // 桩 2 IP地址 ip4_addr_t stake_ip_3 = IPADDR4_INIT_BYTES(10, 12, 19, 103); // 桩 3 IP地址 ip4_addr_t stake_ip_4 = IPADDR4_INIT_BYTES(10, 12, 19, 104); // 桩 4 IP地址 ip4_addr_t stake_ip_5 = IPADDR4_INIT_BYTES(10, 12, 19, 105); // 桩 5 IP地址 ip4_addr_t stake_ip_6 = IPADDR4_INIT_BYTES(10, 12, 19, 106); // 桩 6 IP地址 /** * @brief UDP发送 * @note 发送数据到指定桩 * @param stake_index 目标桩ID * @param data 数据指针 * @param len 数据长度 * @return err_t 错误码 */ err_t udp_send_response(uint8_t stake_index, uint8_t *data, u16_t len) { struct netbuf *buf = netbuf_new(); if (!buf) { return ERR_MEM; } ip4_addr_t *dst_ip = NULL; switch (stake_index) { case 1: dst_ip = &stake_ip_1; break; case 2: dst_ip = &stake_ip_2; break; case 3: dst_ip = &stake_ip_3; break; case 4: dst_ip = &stake_ip_4; break; case 5: dst_ip = &stake_ip_5; break; case 6: dst_ip = &stake_ip_6; break; default: printf("Invalid stake index\r\n"); netbuf_delete(buf); return ERR_VAL; // ← 修复 return 无返回值 } void *buf_data = netbuf_alloc(buf, len); // ← 在 netbuf 内部分配内存 if (!buf_data) { netbuf_delete(buf); return ERR_MEM; } memcpy(buf_data, data, len); // ← 拷贝数据,不再依赖外部指针 err_t err = netconn_sendto(datalink_conn, buf, dst_ip, LINK_STAKE_PORT); netbuf_delete(buf); return err; } void UDP_ParseTask_Function(void const *argument) { UdpMsg_t msg; cJSON *root = NULL, *cmd = NULL, *id = NULL; ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* 等待桩通讯协议层完成*/ while (1) { if (xQueueReceive(UDP_Message_Queue, &msg, portMAX_DELAY) == pdPASS) { root = cJSON_Parse((const char *)msg.data); if (root == NULL) { printf("JSON Parse Failed: %s\r\n", msg.data); vPortFree(msg.data); continue; } id = cJSON_GetObjectItem(root, "id"); cmd = cJSON_GetObjectItem(root, "cmd"); if (cmd != NULL && id != NULL) { const char *cmd_str = cmd->valuestring; handle_udp_downlink((uint8_t)id->valueint, cmd_str, root); cJSON_Delete(root); } else { printf("Missing 'code' field from \r\n"); cJSON_Delete(root); } vPortFree(msg.data); msg.data = NULL; } } } /** * @brief UPLinkTask_Function:桩通讯任务 * * @note none * * @param taskID : 任务ID * * @retval runtime : 任务周期 */ void UDPTask_Function(void const *argument) { err_t recv_err; struct netbuf *datalink_buf = NULL; datalink_conn = netconn_new(NETCONN_UDP); netconn_bind(datalink_conn, IP_ADDR_ANY, LINK_SERVER_PORT); UDP_Message_Queue_Init(); // 初始化UDP接收队列 /*桩UDP通讯初始化完成 发送云快充任务通知*/ xTaskNotifyGive(YkcTaskHandle); xTaskNotifyGive(DownLinkTaskHandle); xTaskNotifyGive(UDP_ParseTaskHandle); while (1) { /*获取任务运行状态*/ TaskRunTimeStat.UPLinkTask.threads_runtime = GetTask_RunTime(UPLinkTaskID); TaskRunTimeStat.UPLinkTask.threads_counter = GetTask_Beatcnt(UPLinkTaskID); TaskRunTimeStat.UPLinkTask.threads_freestack = Get_Free_Stack(UPLinkTaskID); recv_err = netconn_recv(datalink_conn, &datalink_buf); if (recv_err == ERR_OK && datalink_buf != NULL) { uint8_t *playload; uint16_t playload_len; netbuf_data(datalink_buf, (void *)&playload, &playload_len); if (playload_len > 0) { UdpMsg_t msg; ip_addr_copy(msg.src_ip, *netbuf_fromaddr(datalink_buf)); // 获取UDP源IP msg.src_port = netbuf_fromport(datalink_buf); // 获取UDP源端口 msg.len = playload_len; msg.data = (char *)pvPortMalloc(playload_len + 1); if (msg.data != NULL) { memcpy(msg.data, playload, playload_len); msg.data[playload_len] = '\0'; // 队列满,释放数据内存 if (xQueueSend(UDP_Message_Queue, &msg, 0) != pdPASS) { vPortFree(msg.data); } } } netbuf_delete(datalink_buf); // 释放UDP网络缓冲区 } else { if (recv_err != ERR_TIMEOUT && recv_err != ERR_WOULDBLOCK) { printf("datalink netconn_recv err: %d\r\n", recv_err); } } } } /** * @brief 解析充电桩上电指令 * @note 设置桩为本地在线状态,发送上电应答 * @param stake_index 桩索引 * @param json_pack json数据包 */ void local_on_cmd_callback_power_on(uint8_t stake_index, cJSON *json_pack) { if (stake_index > 6) { return; } g_charger_manager.charger_piles[stake_index - 1].is_udp_online = true; // 设置桩为本地在线状态 cJSON *root = NULL; uint8_t *str = NULL; root = cJSON_CreateObject(); if (root == NULL) { printf("Failed to create JSON object for stake %d\r\n", stake_index); return; } /* 添加一条字符串类型的JSON数据(添加一个链表节点) */ cJSON_AddNumberToObject(root, "id", stake_index); cJSON_AddStringToObject(root, "cmd", "online"); cJSON_AddStringToObject(root, "type", "response"); str = cJSON_Print(root); udp_send_response(stake_index, str, strlen(str)); free(str); cJSON_Delete(root); printf("南向:对电桩 %d 上电回复成功\r\n", stake_index); } /** * @brief 解析充电桩心跳指令 * @note 回复心跳应答 * @param stake_index 桩索引 * @param json_pack json数据包 */ void local_on_cmd_callback_heartbeat_response(uint8_t stake_index, cJSON *json_pack) { if (stake_index > 6) { return; } // 心跳unpack cJSON *gun_array = cJSON_GetObjectItem(json_pack, "gun"); // 直接判断 type 字段 if (gun_array == NULL || gun_array->type != cJSON_Array) { printf(" └── [error] 缺少 gun 数组\r\n"); return; } int gun_count = cJSON_GetArraySize(gun_array); for (int i = 0; i < gun_count; i++) { cJSON *gun = cJSON_GetArrayItem(gun_array, i); if (!gun) continue; cJSON *id = cJSON_GetObjectItem(gun, "id"); cJSON *state = cJSON_GetObjectItem(gun, "state"); if (!id || !state) continue; // if (id->valueint == 1) pile->gun1_state = state->valueint; // if (id->valueint == 2) pile->gun2_state = state->valueint; printf(" └── [info] 桩%d 枪%d state=%d\r\n", stake_index, id->valueint, state->valueint); } // 心跳回复组包 cJSON *root = NULL; char *str = NULL; root = cJSON_CreateObject(); if (root == NULL) { printf("Failed to create JSON object for stake %d\r\n", stake_index); return; } /* 添加一条字符串类型的JSON数据(添加一个链表节点) */ cJSON_AddNumberToObject(root, "id", stake_index); cJSON_AddStringToObject(root, "cmd", "heartbeat"); cJSON_AddStringToObject(root, "type", "response"); str = cJSON_Print(root); udp_send_response(stake_index, str, strlen(str)); free(str); cJSON_Delete(root); printf("南向:对电桩 %d 心跳回复成功\r\n", stake_index); } void handle_udp_downlink(uint8_t id, const char *cmd, cJSON *json_pack) { if (cmd == NULL) return; // 处理上电指令 if (strcmp(cmd, "online") == 0) { printf("南向:收到电桩 %d 上电指令\r\n", id); local_on_cmd_callback_power_on(id, json_pack); } // 处理心跳指令 else if (strcmp(cmd, "heartbeat") == 0) { printf("南向:收到电桩 %d 心跳指令\r\n", id); local_on_cmd_callback_heartbeat_response(id, json_pack); } else { printf("Unknown CMD: '%s' from ID %d\r\n", cmd, id); } } /** * @brief 将16字节的交易序列号转换为32位16进制字符串 * @param trade_serial 16字节数组 * @param output_str 输出缓冲区(至少33字节) */ void trade_serial_to_string(uint8_t *trade_serial, char *output_str) { if (trade_serial == NULL || output_str == NULL) { return; } for (int i = 0; i < 16; i++) { sprintf(&output_str[i * 2], "%02X", trade_serial[i]); } output_str[32] = '\0'; } /** * @brief 主动下发启动指令 * @note 发送启动指令到充电桩,符合协议格式要求 * @param stake_index 桩索引 (1~6) * @param gun_id 枪编码 (1~N) */ void local_on_cmd_send_start_charging(uint8_t stake_index, uint8_t gun_id) { if (stake_index > 6 || stake_index == 0) { printf("Invalid stake index: %d\r\n", stake_index); return; } if (gun_id == 0 || gun_id > MAX_GUN_PER_CHARGER) { printf("Invalid gun id: %d\r\n", gun_id); return; } // 检查是否已获取费率模型 if (!g_charger_manager.charger_piles[stake_index - 1].get_model) { printf("Stake %d fee model not ready\r\n", stake_index); return; } cJSON *root = NULL; cJSON *billing_array = NULL; cJSON *billing_item = NULL; char *str = NULL; // 转换交易ID:16位数组转字符串 char transaction_id_str[33] = {0}; // 32位 + 结束符 uint8_t *trade_serial = g_charger_manager.charger_piles[stake_index - 1].guns[gun_id - 1].real_time_data.trade_serial; trade_serial_to_string(trade_serial, transaction_id_str); root = cJSON_CreateObject(); if (root == NULL) { printf("Failed to create JSON object for stake %d\r\n", stake_index); return; } /* 添加基本字段 */ cJSON_AddNumberToObject(root, "id", stake_index); cJSON_AddStringToObject(root, "cmd", "start charging"); cJSON_AddStringToObject(root, "transaction_id", transaction_id_str); // 使用转换后的32位字符串 cJSON_AddNumberToObject(root, "gun_id", gun_id); cJSON_AddStringToObject(root, "type", "request"); /* 添加限制金额(根据实际业务设置)*/ cJSON_AddNumberToObject(root, "limit_amount", 30.00); /* 创建计费模型数组 */ billing_array = cJSON_CreateArray(); if (billing_array == NULL) { printf("Failed to create billing array\r\n"); cJSON_Delete(root); return; } /* 计费时段:根据费率表生成 period_id 1:尖 2:峰 3:平 4:谷 */ int period_id = 1; int i = 0; while (i < 48) // 48个半小时时段 { uint8_t current_type = g_charger_manager.fee_model_global.fee_num[i]; // 跳过无效费率 if (current_type == 0xFF) { i++; continue; } // 查找连续相同费率的结束位置 int j = i; while (j < 48 && g_charger_manager.fee_model_global.fee_num[j] == current_type) { j++; } // 计算起始时间(第i个半小时段) int start_hour = i / 2; int start_min = (i % 2) * 30; // 计算结束时间(第j个半小时段的起始时间) int end_hour = j / 2; int end_min = (j % 2) * 30; // 格式化时间字符串 "HH:MM" char start_time[6]; char end_time[6]; sprintf(start_time, "%02d:%02d", start_hour, start_min); if (end_hour == 24 && end_min == 0) { sprintf(end_time, "00:00"); // 跨天显示为00:00 } else { sprintf(end_time, "%02d:%02d", end_hour, end_min); } // 根据费率类型设置电费和服务费 float electricity_fee = 0.0f; float service_fee = 0.0f; int mapped_period_id = 0; // 映射到协议要求的period_id switch (current_type) { case 0x00: // 尖时 electricity_fee = g_charger_manager.fee_model_global.shark_fee_ratio / 100000.0f; service_fee = g_charger_manager.fee_model_global.shark_service_ratio / 100000.0f; mapped_period_id = 1; // 1:尖 break; case 0x01: // 峰时 electricity_fee = g_charger_manager.fee_model_global.peak_fee_ratio / 100000.0f; service_fee = g_charger_manager.fee_model_global.peak_service_ratio / 100000.0f; mapped_period_id = 2; // 2:峰 break; case 0x02: // 平时 electricity_fee = g_charger_manager.fee_model_global.flat_fee_ratio / 100000.0f; service_fee = g_charger_manager.fee_model_global.flat_service_ratio / 100000.0f; mapped_period_id = 3; // 3:平 break; case 0x03: // 谷时 electricity_fee = g_charger_manager.fee_model_global.valley_fee_ratio / 100000.0f; service_fee = g_charger_manager.fee_model_global.valley_service_ratio / 100000.0f; mapped_period_id = 4; // 4:谷 break; default: electricity_fee = 0.0f; service_fee = 0.0f; mapped_period_id = 0; break; } // 创建计费时段对象(只有有效费率类型才添加) if (mapped_period_id > 0) { billing_item = cJSON_CreateObject(); if (billing_item != NULL) { cJSON_AddNumberToObject(billing_item, "period_id", mapped_period_id); cJSON_AddStringToObject(billing_item, "start_time", start_time); cJSON_AddStringToObject(billing_item, "end_time", end_time); cJSON_AddNumberToObject(billing_item, "electricity_fee", electricity_fee); cJSON_AddNumberToObject(billing_item, "service_fee", service_fee); cJSON_AddItemToArray(billing_array, billing_item); // 调试打印 #ifdef DEBUG const char *period_name = ""; switch(mapped_period_id) { case 1: period_name = "Sharp"; break; case 2: period_name = "Peak"; break; case 3: period_name = "Flat"; break; case 4: period_name = "Valley"; break; } printf("Period %d: %s-%s [%s] electricity:%.3f service:%.3f\r\n", mapped_period_id, start_time, end_time, period_name, electricity_fee, service_fee); #endif } } i = j; // 跳到下一个不同费率段 } // 将数组添加到根对象 cJSON_AddItemToObject(root, "billing_model", billing_array); // 转换为JSON字符串并发送 str = cJSON_Print(root); if (str != NULL) { #ifdef DEBUG printf("Send start command JSON: %s\r\n", str); #endif udp_send_response(stake_index, str, strlen(str)); free(str); } else { printf("Failed to print JSON for stake %d\r\n", stake_index); } cJSON_Delete(root); printf("Southbound: Start command sent to stake %d, gun %d, total %d billing periods\r\n", stake_index, gun_id, (int)cJSON_GetArraySize(billing_array)); }