fix:修复UDP通讯长数据丢包问题

This commit is contained in:
2026-05-06 15:22:21 +08:00
parent e8987abe14
commit f858a672a4
9 changed files with 273 additions and 60 deletions

View File

@@ -4,10 +4,9 @@
local log = require "log" local log = require "log"
local sys = require "sys" local sys = require "sys"
local socket = require "socket" local socket = require "socket"
-- 服务器配置 -- 服务器配置
local SERVER_IP = "121.43.69.62" local SERVER_IP = "129.211.170.245"
local SERVER_PORT = 8767 local SERVER_PORT = 9002
local MAX_CLIENTS = 6 local MAX_CLIENTS = 6
-- 客户端状态表 -- 客户端状态表

View File

@@ -61,7 +61,7 @@ void MX_LWIP_Init(void)
IP_ADDRESS[0] = 10; IP_ADDRESS[0] = 10;
IP_ADDRESS[1] = 12; IP_ADDRESS[1] = 12;
IP_ADDRESS[2] = 19; IP_ADDRESS[2] = 19;
IP_ADDRESS[3] = 252; IP_ADDRESS[3] = 100;
NETMASK_ADDRESS[0] = 255; NETMASK_ADDRESS[0] = 255;
NETMASK_ADDRESS[1] = 255; NETMASK_ADDRESS[1] = 255;
NETMASK_ADDRESS[2] = 255; NETMASK_ADDRESS[2] = 255;

View File

@@ -58,7 +58,7 @@
/*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/ /*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
#define MEM_ALIGNMENT 4 #define MEM_ALIGNMENT 4
/*----- Default Value for MEM_SIZE: 1600 ---*/ /*----- Default Value for MEM_SIZE: 1600 ---*/
#define MEM_SIZE 1024*10 #define MEM_SIZE 1024*20
/*----- Default Value for H7 devices: 0x30044000 -----*/ /*----- Default Value for H7 devices: 0x30044000 -----*/
#define LWIP_RAM_HEAP_POINTER 0x30004000 #define LWIP_RAM_HEAP_POINTER 0x30004000
/*----- Default Value for MEMP_NUM_PBUF: 16 ---*/ /*----- Default Value for MEMP_NUM_PBUF: 16 ---*/
@@ -80,11 +80,11 @@
/*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/ /*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/
#define LWIP_NETIF_LINK_CALLBACK 1 #define LWIP_NETIF_LINK_CALLBACK 1
/*----- Value in opt.h for TCPIP_THREAD_STACKSIZE: 0 -----*/ /*----- Value in opt.h for TCPIP_THREAD_STACKSIZE: 0 -----*/
#define TCPIP_THREAD_STACKSIZE 1024 #define TCPIP_THREAD_STACKSIZE 2048
/*----- Value in opt.h for TCPIP_THREAD_PRIO: 1 -----*/ /*----- Value in opt.h for TCPIP_THREAD_PRIO: 1 -----*/
#define TCPIP_THREAD_PRIO osPriorityNormal #define TCPIP_THREAD_PRIO osPriorityNormal
/*----- Value in opt.h for TCPIP_MBOX_SIZE: 0 -----*/ /*----- Value in opt.h for TCPIP_MBOX_SIZE: 0 -----*/
#define TCPIP_MBOX_SIZE 6 #define TCPIP_MBOX_SIZE 32
/*----- Value in opt.h for SLIPIF_THREAD_STACKSIZE: 0 -----*/ /*----- Value in opt.h for SLIPIF_THREAD_STACKSIZE: 0 -----*/
#define SLIPIF_THREAD_STACKSIZE 1024 #define SLIPIF_THREAD_STACKSIZE 1024
/*----- Value in opt.h for SLIPIF_THREAD_PRIO: 1 -----*/ /*----- Value in opt.h for SLIPIF_THREAD_PRIO: 1 -----*/
@@ -94,9 +94,9 @@
/*----- Value in opt.h for DEFAULT_THREAD_PRIO: 1 -----*/ /*----- Value in opt.h for DEFAULT_THREAD_PRIO: 1 -----*/
#define DEFAULT_THREAD_PRIO 3 #define DEFAULT_THREAD_PRIO 3
/*----- Value in opt.h for DEFAULT_UDP_RECVMBOX_SIZE: 0 -----*/ /*----- Value in opt.h for DEFAULT_UDP_RECVMBOX_SIZE: 0 -----*/
#define DEFAULT_UDP_RECVMBOX_SIZE 6 #define DEFAULT_UDP_RECVMBOX_SIZE 20
/*----- Value in opt.h for DEFAULT_TCP_RECVMBOX_SIZE: 0 -----*/ /*----- Value in opt.h for DEFAULT_TCP_RECVMBOX_SIZE: 0 -----*/
#define DEFAULT_TCP_RECVMBOX_SIZE 6 #define DEFAULT_TCP_RECVMBOX_SIZE 20
/*----- Value in opt.h for DEFAULT_ACCEPTMBOX_SIZE: 0 -----*/ /*----- Value in opt.h for DEFAULT_ACCEPTMBOX_SIZE: 0 -----*/
#define DEFAULT_ACCEPTMBOX_SIZE 6 #define DEFAULT_ACCEPTMBOX_SIZE 6
/*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/ /*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/
@@ -107,7 +107,7 @@
#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 #define LWIP_CHECKSUM_CTRL_PER_NETIF 1
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */ /* USER CODE BEGIN 1 */
#define MEMP_NUM_NETBUF 16 #define MEMP_NUM_NETBUF 48
/* USER CODE END 1 */ /* USER CODE END 1 */

View File

@@ -8,6 +8,7 @@ virtualFolder:
name: <virtual_root> name: <virtual_root>
files: files:
- path: ../README.md - path: ../README.md
- path: ../../Doc/云快充平台协议V1.6.pdf
folders: folders:
- name: Application - name: Application
files: [] files: []

View File

@@ -61,7 +61,7 @@ void on_cmd_frame_type_0X0A(uint8_t stake_index, SERVER_PACK *pack)
printf("谷电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.valley_fee_ratio, g_charger_manager.fee_model_global.valley_service_ratio); printf("谷电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.valley_fee_ratio, g_charger_manager.fee_model_global.valley_service_ratio);
printf("计损比例: %d%% \r\n", g_charger_manager.fee_model_global.loss_ratio); printf("计损比例: %d%% \r\n", g_charger_manager.fee_model_global.loss_ratio);
printf("\n合并后的费率时间段:\r\n"); printf("\n合并后的费率时间段 :\r\n");
printf("================================================================\r\n"); printf("================================================================\r\n");
int i = 0; int i = 0;
@@ -74,19 +74,19 @@ void on_cmd_frame_type_0X0A(uint8_t stake_index, SERVER_PACK *pack)
switch (current_type) switch (current_type)
{ {
case 0x00: case 0x00:
fee_name = "尖时"; fee_name = " 尖时 ";
break; break;
case 0x01: case 0x01:
fee_name = "峰时"; fee_name = " 峰时 ";
break; break;
case 0x02: case 0x02:
fee_name = "平时"; fee_name = " 平时 ";
break; break;
case 0x03: case 0x03:
fee_name = "谷时"; fee_name = " 谷时 ";
break; break;
default: default:
fee_name = "未知"; fee_name = " 未知 ";
break; break;
} }

View File

@@ -135,7 +135,7 @@ void air724_callback_fun()
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(Air724_Message_Queue, uart1_rx_buffer, &xHigherPriorityTaskWoken); xQueueSendFromISR(Air724_Message_Queue, uart1_rx_buffer, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
memset(uart1_rx_buffer, 0, UART1_RX_BUFFER_SIZE); memset(uart1_rx_buffer, 0, UART1_RX_BUFFER_SIZE);
} }
void rs485_callback_fun() void rs485_callback_fun()

View File

@@ -26,37 +26,31 @@ ip4_addr_t stake_ip_6 = IPADDR4_INIT_BYTES(10, 12, 19, 106); // 桩 6 IP地址
err_t udp_send_response(uint8_t stake_index, uint8_t *data, u16_t len) err_t udp_send_response(uint8_t stake_index, uint8_t *data, u16_t len)
{ {
struct netbuf *buf = netbuf_new(); struct netbuf *buf = netbuf_new();
if (!buf) if (!buf) {
{
printf("[%s] %d\n", __func__, __LINE__);
return ERR_MEM; return ERR_MEM;
} }
ip4_addr_t *dst_ip = NULL; ip4_addr_t *dst_ip = NULL;
switch (stake_index) switch (stake_index) {
{ case 1: dst_ip = &stake_ip_1; break;
case 1: case 2: dst_ip = &stake_ip_2; break;
dst_ip = &stake_ip_1; case 3: dst_ip = &stake_ip_3; break;
break; case 4: dst_ip = &stake_ip_4; break;
case 2: case 5: dst_ip = &stake_ip_5; break;
dst_ip = &stake_ip_2; case 6: dst_ip = &stake_ip_6; break;
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: default:
printf("Invalid stake index\r\n"); printf("Invalid stake index\r\n");
return; netbuf_delete(buf);
return ERR_VAL; // ← 修复 return 无返回值
} }
netbuf_ref(buf, data, len);
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); err_t err = netconn_sendto(datalink_conn, buf, dst_ip, LINK_STAKE_PORT);
netbuf_delete(buf); netbuf_delete(buf);
return err; return err;
@@ -148,7 +142,7 @@ void UDPTask_Function(void const *argument)
// 队列满,释放数据内存 // 队列满,释放数据内存
if (xQueueSend(UDP_Message_Queue, &msg, 0) != pdPASS) if (xQueueSend(UDP_Message_Queue, &msg, 0) != pdPASS)
{ {
vPortFree(msg.data); vPortFree(msg.data);
} }
} }
} }
@@ -213,36 +207,36 @@ void local_on_cmd_callback_heartbeat_response(uint8_t stake_index, cJSON *json_p
{ {
return; return;
} }
//心跳unpack // 心跳unpack
cJSON *gun_array = cJSON_GetObjectItem(json_pack, "gun"); cJSON *gun_array = cJSON_GetObjectItem(json_pack, "gun");
// 直接判断 type 字段 // 直接判断 type 字段
if (gun_array == NULL || gun_array->type != cJSON_Array) if (gun_array == NULL || gun_array->type != cJSON_Array)
{ {
printf(" └── [error] 缺少 gun 数组\r\n"); printf(" └── [error] 缺少 gun 数组\r\n");
return; return;
} }
int gun_count = cJSON_GetArraySize(gun_array); int gun_count = cJSON_GetArraySize(gun_array);
for (int i = 0; i < gun_count; i++) for (int i = 0; i < gun_count; i++)
{ {
cJSON *gun = cJSON_GetArrayItem(gun_array, i); cJSON *gun = cJSON_GetArrayItem(gun_array, i);
if (!gun) continue; if (!gun)
continue;
cJSON *id = cJSON_GetObjectItem(gun, "id"); cJSON *id = cJSON_GetObjectItem(gun, "id");
cJSON *state = cJSON_GetObjectItem(gun, "state"); cJSON *state = cJSON_GetObjectItem(gun, "state");
if (!id || !state) continue; if (!id || !state)
continue;
// if (id->valueint == 1) pile->gun1_state = state->valueint;
// if (id->valueint == 2) pile->gun2_state = state->valueint; // 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); printf(" └── [info] 桩%d 枪%d state=%d\r\n", stake_index, id->valueint, state->valueint);
} }
//心跳回复组包 // 心跳回复组包
cJSON *root = NULL; cJSON *root = NULL;
char *str = NULL; char *str = NULL;
root = cJSON_CreateObject(); root = cJSON_CreateObject();
@@ -263,8 +257,6 @@ void local_on_cmd_callback_heartbeat_response(uint8_t stake_index, cJSON *json_p
cJSON_Delete(root); cJSON_Delete(root);
printf("南向:对电桩 %d 心跳回复成功\r\n", stake_index); printf("南向:对电桩 %d 心跳回复成功\r\n", stake_index);
} }
void handle_udp_downlink(uint8_t id, const char *cmd, cJSON *json_pack) void handle_udp_downlink(uint8_t id, const char *cmd, cJSON *json_pack)
@@ -287,4 +279,222 @@ void handle_udp_downlink(uint8_t id, const char *cmd, cJSON *json_pack)
{ {
printf("Unknown CMD: '%s' from ID %d\r\n", cmd, id); 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;
// 转换交易ID16位数组转字符串
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));
} }

View File

@@ -22,6 +22,7 @@ typedef struct {
//void Charger_Task_Init(void); //void Charger_Task_Init(void);
void handle_udp_downlink(uint8_t id, const char *cmd, cJSON *json_pack); void handle_udp_downlink(uint8_t id, const char *cmd, cJSON *json_pack);
void local_on_cmd_send_start_charging(uint8_t stake_index,uint8_t gun_id);

View File

@@ -11,6 +11,7 @@
/* Includes ------------------------------------------------------------------*/ /* Includes ------------------------------------------------------------------*/
#include "YkcTask.h" #include "YkcTask.h"
#include "ChargerTask.h"
#include "lwip/opt.h" #include "lwip/opt.h"
#include "lwip/api.h" #include "lwip/api.h"
#include "lwip/sys.h" #include "lwip/sys.h"
@@ -83,7 +84,8 @@ void YkcTask_Function(void const *argument)
case 3: case 3:
{ {
charger_to_server_0X13(1, 1); // 上传状态 charger_to_server_0X13(1, 1); // 上传状态
osDelay(15000); local_on_cmd_send_start_charging(1,1);
osDelay(3000);
} }
break; break;
default: default: