Release:5.20灰测

This commit is contained in:
2026-05-21 10:01:28 +08:00
parent 8a5a32b139
commit fd65e9c6a2
68 changed files with 4329 additions and 1489 deletions

View File

@@ -1,20 +1,17 @@
/**
******************************************************************************
* @file user\task\DatalinkTask.c
* @author luhuaishuai
* @version v0.1
* @date 2026-1-12
* @brief
******************************************************************************
* @file udp_manager.c
* @brief UDP管理模块
* @details UDP通信
*
* @date 2025-01-09 14:32:15
* @version 1.0.0
* @copyright Copyright (c) 2026
*/
/* Includes ------------------------------------------------------------------*/
#include "DataLinkTask.h"
#include "YkcTask.h"
#include "lwip/opt.h"
#include "lwip/api.h"
#include "lwip/sys.h"
#include "httpd.h"
#include "task_air724.h"
#include "ykc_router.h"
#include "drv_air724.h"
/* typedef --------------------------------------------------------------------*/
@@ -23,7 +20,7 @@
/* code -----------------------------------------------------------------------*/
/**
* @brief DownLinkTask_Function
* @brief tcp_recv_task_function TCP
*
* @note none
*
@@ -32,11 +29,10 @@
* @retval runtime :
*/
void DownLinkTask_Function(void const *argument)
void air724_recv_task_function(void const *argument)
{
uint8_t air724_rx_msg[UART1_RX_BUFFER_SIZE];
uint8_t rs485_rx_msg[UART3_RX_BUFFER_SIZE];
uint8_t *ykc_downlink_frame = NULL;
/* 等待 datalink_conn 初始化完成 */
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
@@ -56,7 +52,7 @@ void DownLinkTask_Function(void const *argument)
// 主指令解析
switch (air724_rx_msg[2])
{
// 云快下行解析
// 云快下行解析
case 0x01:
{
uint8_t charger_index = air724_rx_msg[3];
@@ -73,39 +69,22 @@ void DownLinkTask_Function(void const *argument)
memcpy(frame.data, ykc_downlink_frame + HEADER_LENGTH, frame.len - 4);
frame.crc = (ykc_downlink_frame[HEADER_LENGTH + frame.len] << 8) | ykc_downlink_frame[HEADER_LENGTH + frame.len + 1];
// printf("Raw Bytes: ");
// for (int i = 0; i < ykc_downlink_frame_len; i++)
// {
// printf("%02X ", ykc_downlink_frame[i]);
// if ((i + 1) % 16 == 0) // 每16字节换行
// {
// printf("\r");
// }
// }
// printf("\r");
handle_ykc_downlink(charger_index, &frame);
ykc_route_dispatch(charger_index, &frame); // 云快充路由分发
vPortFree(frame.data);
}
break;
case 0x83:
case 0x84:
case 0x85:
case 0x86:
case 0x87:
{
if (air724_rx_msg[3] == 0x01)
printf("4GSocket %d 通道连接成功\r\n", air724_rx_msg[4]);
if (air724_rx_msg[3] == 0x02)
printf("4GSocket %d 通道断开成功\r\n", air724_rx_msg[4]);
drv_air724_parse_response(air724_rx_msg, UART1_RX_BUFFER_SIZE);
}
break;
}
}
}
if (xQueueReceive(RS485_Message_Queue, &rs485_rx_msg, 10) == pdPASS)
{
#ifdef DEBUG
printf("rs-485_rx_msg: %s", rs485_rx_msg);
#endif
}
}
}

View File

@@ -1,6 +1,6 @@
#ifndef __DATALINKTASK_H
#define __DATALINKTASK_H
#ifndef __AIR724_RECV_TASK_H
#define __AIR724_RECV_TASK_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
@@ -10,9 +10,7 @@
/* struct ------------------------------------------------------------------------------------------------*/
void DataLink_Task_Init(void);
#endif /* __DATALINKTASK_H */
#endif /* __AIR724_RECV_TASK_H */

View File

@@ -9,7 +9,7 @@
*/
/* Includes ------------------------------------------------------------------*/
#include "HeartBeatTask.h"
#include "task_sys.h"
void HeartBeat_Sign(void);
@@ -20,7 +20,7 @@ void HeartBeat_Sign(void);
* @param : argument
* @retval: void
*/
void HeartbeatTask_Function(void const *argument)
void sys_task_function(void const *argument)
{
while (1)
{
@@ -58,14 +58,14 @@ void HeartBeat_Sign(void)
if ((now - last_heartbeat_tick[i]) >= 8000)
{
last_heartbeat_tick[i] = now;
charger_to_server_0X03(i + 1, 1, 0);
charger_to_server_0X03(i + 1, 1);
}
}
}
if (any_online)
{
RUN_EVERY(100, tick_B, {
RUN_EVERY(50, tick_B, {
System_Mode_Led_Toggle();
});
}

View File

@@ -1,5 +1,5 @@
#ifndef __HEATBEATTASK_H
#define __HEATBEATTASK_H
#ifndef __TASK_SYS_H
#define __TASK_SYS_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
@@ -19,4 +19,4 @@
} \
} while(0)
#endif /* __HEATBEATTASK_H */
#endif /* __TASK_SYS_H */

123
Core/User/App/task_udp.c Normal file
View File

@@ -0,0 +1,123 @@
/**
* @file task_udp.c
* @brief UDP通信任务处理模块
* @details 处理充电桩与服务器之间的UDP通信协议包括UDP数据接收、数据解析和路由分发等功能。
* 支持JSON格式的数据交互通过FreeRTOS任务和队列机制实现异步数据处理。
* 包含两个主要任务UDP数据接收任务(udp_recv_task_function)和UDP数据解析任务(udp_parse_task_function)。
* @date 2025-01-09 14:32:15
* @version 1.0.0
* @copyright Copyright (c) 2026
*/
#include "task_udp.h"
/**
* @brief udp_recv_taskUDP 数据接受任务
*
* @note none
*
* @param taskID : 任务ID
*
* @retval runtime : 任务周期
*/
void udp_recv_task_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(Air724_ParseTaskHandle);
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 UDP_ParseTask_FunctionUDP 数据解析任务
*
* @note none
*
* @param taskID : 任务ID
*
* @retval runtime : 任务周期
*/
void udp_parse_task_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;
udp_route_dispatch((uint8_t)id->valueint, cmd->valuestring, root); //送入路由处理
cJSON_Delete(root);
}
else
{
printf("Missing 'code' field from \r\n");
cJSON_Delete(root);
}
vPortFree(msg.data);
msg.data = NULL;
}
}
}

25
Core/User/App/task_udp.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef __UDP_TASK_H
#define __UDP_TASK_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
#include "board_config.h"
#include "udp_router.h"
#include "udp_manager.h"
#include "lwip/opt.h"
#include "lwip/api.h"
#include "lwip/sys.h"
#include "httpd.h"
// 定义UDP消息体
typedef struct {
ip4_addr_t src_ip; // 来源IP
uint16_t src_port; // 来源端口
uint16_t len; // 数据长度
char *data; // 数据指针(动态分配)
} UdpMsg_t;
#endif /* __UDP_TASK_H */

197
Core/User/App/task_ykc.c Normal file
View File

@@ -0,0 +1,197 @@
/**
******************************************************************************
* @file user\task\HeartBeatTask.c
* @author luhuaishuai
* @version v0.1
* @date 2026-1-12
* @brief Briefly describe the function of your function
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "task_ykc.h"
#include "lwip/opt.h"
#include "lwip/api.h"
#include "lwip/sys.h"
#include "usbd_cdc_if.h"
/**
* @funNm : ykc_task_function
* @brief : 云快充平台交互主任务
* @param : argument
* @retval: void
*/
void ykc_task_function(void const *argument)
{
uint32_t now;
init_chargers();
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
osDelay(5000);
while (1)
{
TaskRunTimeStat.YkcTask.threads_runtime = GetTask_RunTime(YkcTaskID);
TaskRunTimeStat.YkcTask.threads_counter = GetTask_Beatcnt(YkcTaskID);
TaskRunTimeStat.YkcTask.threads_freestack = Get_Free_Stack(YkcTaskID);
now = HAL_GetTick();
for (int i = 0; i < g_charger_manager.charger_count; i++)
{
ChargerPile *pile = &g_charger_manager.charger_piles[i];
/* ========== 桩层初始化桩0~2通用只做一次 ========== */
if (pile->step < 3)
{
if (now < pile->step_tick)
continue;
switch (pile->step)
{
case 0: // 等待桩上电 + 连接本桩4G通道
{
// if (pile->is_udp_online)
if(1)
{
if (!g_air724_info.socket_connected[i])
{
drv_air724_connect_channel(i + 1);
pile->step_tick = now + 3000;
}
else
{
pile->step = 1;
pile->step_tick = now;
}
}
else
{
USB_Printf("网关:等待桩%d上电指令\r\n", i + 1);
drv_air724_query_iccid();
pile->step_tick = now + 1000;
}
}
break;
case 1: // 云快充登录认证
{
if (!pile->is_online)
{
charger_to_server_0X01(i + 1);
pile->step_tick = now + 5000;
}
else
{
pile->step = 2;
charger_to_server_0X05(1, i + 1);
pile->step_tick = now;
}
}
break;
case 2: // 云快充计费模型请求
{
if (!pile->get_model)
{
charger_to_server_0X09(i + 1);
pile->step_tick = now + 5000;
}
else
{
pile->step = 3;
pile->step_tick = now;
}
}
break;
}
continue;
}
/* ========== 枪层独立运行(每把枪独立状态机) ========== */
for (int g = 0; g < MAX_GUN_PER_CHARGER; g++)
{
ChargerGun *gun = &pile->guns[g];
if (now < gun->gun_step_tick)
continue;
switch (gun->gun_step)
{
case 0: // 运行时:周期上传实时数据 + 账单处理
{
charger_to_server_0X13(i + 1, g + 1);
host_computer_report_data(i + 1, g + 1);
if (gun->is_get_bill)
{
charger_to_server_0X3B(i + 1, g + 1);
charger_to_server_0X3B(i + 1, g + 1);
gun->is_get_bill = 0;
memset(gun->real_time_data.trade_serial, 0,
sizeof(gun->real_time_data.trade_serial));
}
gun->gun_step_tick = now + 5000;
}
break;
default:
break;
}
}
}
osDelay(10);
}
}
/**
* @brief 云快充平台状态检查回调
* @note 每 500ms 触发一次,遍历 1-6 号桩检查状态
* @param xTimer 定时器句柄
* @retval none
*/
void ykc_timer_callback_function(TimerHandle_t xTimer)
{
for (int i = 0; i < MAX_CHARGER_COUNT; i++)
{
for (int g = 0; g < MAX_GUN_PER_CHARGER; g++)
{
ChargerGun *gun = &g_charger_manager.charger_piles[i].guns[g];
if (gun->charger_state == REDAY_CHARGER_START_STATE)
{
USB_Printf("状态机:[info] 桩%d 枪%d 查询中,等待启动回复.... \r\n", i + 1, g + 1);
}
else if (gun->charger_state == SUCCESS_CHARGER_STATE)
{
USB_Printf("状态机:[success] 桩%d 枪%d 启动成功.... \r\n", i + 1, g + 1);
gun->charger_state = CHARGER_STATE_CHARGING;
}
else if (gun->charger_state == FAIL_CHARGER_STATE)
{
USB_Printf("状态机:[error] 桩%d 枪%d 启动失败.... \r\n", i + 1, g + 1);
}
else if (gun->charger_state == CHARGER_STATE_CHARGING)
{
USB_Printf("状态机:[info] 桩%d 枪%d 充电中.... \r\n", i + 1, g + 1);
}
else if (gun->charger_state == REDAY_CHARGER_STOP_STATE)
{
USB_Printf("状态机:[info] 桩%d 枪%d 查询中,等待停止回复.... \r\n", i + 1, g + 1);
}
else if (gun->charger_state == SUCCESS_CHARGER_STOP_STATE)
{
USB_Printf("状态机:[success] 桩%d 枪%d 停止成功.... \r\n", i + 1, g + 1);
}
else if (gun->charger_state == FAIL_CHARGER_STOP_STATE)
{
USB_Printf("状态机:[error] 桩%d 枪%d 停止失败.... \r\n", i + 1, g + 1);
}
else if (gun->charger_state == CHARGER_STATE_CHARGE_DONE)
{
printf("状态机:[success] 桩%d 枪%d 计费数据已发送,充电完成.... \r\n", i + 1, g + 1);
}
}
}
}

11
Core/User/App/task_ykc.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef __TASK_YKC_H
#define __TASK_YKC_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
#include "drv_air724.h"
void ykc_task_function(void const *argument);
void ykc_timer_callback_function(TimerHandle_t xTimer);
#endif /* __TASK_YKC_H */

View File

@@ -0,0 +1,204 @@
/**
* @file drv_air724.c
* @brief 4G模块(AIR724)驱动
* @details 提供通道连接/断开、设置服务器地址、查询SIM/信号/链路状态的接口,
* 以及统一解析4G模块上行响应帧的入口。
*
* 协议55 AA mainCmd subCmd [payload] AA 55
*
* STM32 → 4G:
* 0x03 01 channel 连接请求
* 0x03 02 channel 断开请求
* 0x04 00 ip+port 设置服务器
* 0x05 01|02|03 查询ICCID/IMSI/IMEI
* 0x06 00 查询信号
* 0x07 00 查询链路
*
* 4G → STM32 (0x83):
* 0x83 01 channel 00 已连接
* 0x83 02 channel 00 已断开
* 0x83 03 channel 01 连接失败
*/
#include "drv_air724.h"
#include "drv_usart.h"
Air724_Info_t g_air724_info = {0};
/* ── 内部:构造并发送命令帧 ──────────────────────────── */
static void drv_air724_send_cmd(uint8_t main_cmd, uint8_t sub_cmd,
const uint8_t *payload, uint16_t payload_len)
{
uint8_t frame[256];
uint16_t idx = 0;
frame[idx++] = 0x55;
frame[idx++] = 0xAA;
frame[idx++] = main_cmd;
frame[idx++] = sub_cmd;
if (payload && payload_len > 0) {
memcpy(&frame[idx], payload, payload_len);
idx += payload_len;
}
frame[idx++] = 0xAA;
frame[idx++] = 0x55;
Air724_Message_Send(frame, idx);
}
/* ── 发送函数 ──────────────────────────────────────── */
void drv_air724_set_server(const char *ip, uint16_t port)
{
if (!ip) return;
uint8_t payload[128];
uint8_t ip_len = (uint8_t)strlen(ip);
if (ip_len > 100) ip_len = 100;
payload[0] = ip_len;
memcpy(&payload[1], ip, ip_len);
payload[1 + ip_len] = (uint8_t)((port >> 8) & 0xFF);
payload[2 + ip_len] = (uint8_t)(port & 0xFF);
drv_air724_send_cmd(0x04, 0, payload, 3 + ip_len);
}
void drv_air724_connect_channel(uint8_t channel)
{
if (channel < 1 || channel > AIR724_SOCKET_MAX) {
printf("4G: 通道 %d 无效\r\n", channel);
return;
}
drv_air724_send_cmd(0x03, 0x01, &channel, 1);
printf("4G: 通道 %d 连接请求已发送\r\n", channel);
}
void drv_air724_disconnect_channel(uint8_t channel)
{
if (channel < 1 || channel > AIR724_SOCKET_MAX) {
printf("4G: 通道 %d 无效\r\n", channel);
return;
}
drv_air724_send_cmd(0x03, 0x02, &channel, 1);
printf("4G: 通道 %d 断开请求已发送\r\n", channel);
}
void drv_air724_query_iccid(void)
{
drv_air724_send_cmd(0x05, 0x01, NULL, 0);
}
void drv_air724_query_imsi(void)
{
drv_air724_send_cmd(0x05, 0x02, NULL, 0);
}
void drv_air724_query_imei(void)
{
drv_air724_send_cmd(0x05, 0x03, NULL, 0);
}
void drv_air724_query_signal(void)
{
drv_air724_send_cmd(0x06, 0x00, NULL, 0);
}
void drv_air724_query_link_status(void)
{
drv_air724_send_cmd(0x07, 0x00, NULL, 0);
}
/* ── 统一解析入口 ───────────────────────────────────── */
void drv_air724_parse_response(const uint8_t *frame, uint16_t len)
{
if (!frame || len < 7) return;
uint8_t main_cmd = frame[2];
uint8_t sub_cmd = frame[3];
switch (main_cmd) {
/* ── 0x83: 连接/断开/失败通知 ── */
case 0x83:
if (len >= 7) {
uint8_t socket_id = frame[4];
if (socket_id < 1 || socket_id > AIR724_SOCKET_MAX) break;
switch (sub_cmd) {
case 0x01:
g_air724_info.socket_connected[socket_id - 1] = 1;
printf("4G: 通道 %d 连接成功\r\n", socket_id);
break;
case 0x02:
g_air724_info.socket_connected[socket_id - 1] = 0;
printf("4G: 通道 %d 已断开\r\n", socket_id);
break;
case 0x03:
g_air724_info.socket_connected[socket_id - 1] = 0;
printf("4G: 通道 %d 连接失败\r\n", socket_id);
break;
}
}
break;
/* ── 0x84: SIM信息响应 ── */
case 0x84: {
if (len < 5) break;
uint8_t data_len = frame[4];
if (data_len == 0 || len < (uint16_t)(5 + data_len)) break;
switch (sub_cmd) {
case 0x01:
memcpy(g_air724_info.iccid, &frame[5], data_len < 23 ? data_len : 23);
g_air724_info.iccid[data_len < 23 ? data_len : 23] = '\0';
printf("4G: ICCID=%s\r\n", g_air724_info.iccid);
break;
case 0x02:
memcpy(g_air724_info.imsi, &frame[5], data_len < 15 ? data_len : 15);
g_air724_info.imsi[data_len < 15 ? data_len : 15] = '\0';
printf("4G: IMSI=%s\r\n", g_air724_info.imsi);
break;
case 0x03:
memcpy(g_air724_info.imei, &frame[5], data_len < 15 ? data_len : 15);
g_air724_info.imei[data_len < 15 ? data_len : 15] = '\0';
printf("4G: IMEI=%s\r\n", g_air724_info.imei);
break;
}
break;
}
/* ── 0x85: 信号强度响应 ── */
case 0x85:
if (len >= 7) {
g_air724_info.csq = frame[4];
g_air724_info.ber = frame[5];
printf("4G: CSQ=%d, BER=%d\r\n", g_air724_info.csq, g_air724_info.ber);
}
break;
/* ── 0x86: 链路状态响应 ── */
case 0x86: {
uint8_t count = (len - 6) < AIR724_SOCKET_MAX ? (len - 6) : AIR724_SOCKET_MAX;
for (uint8_t i = 0; i < count; i++) {
g_air724_info.socket_connected[i] = frame[4 + i];
}
printf("4G: 链路[");
for (uint8_t i = 0; i < AIR724_SOCKET_MAX; i++) {
printf("%d", g_air724_info.socket_connected[i]);
}
printf("]\r\n");
break;
}
/* ── 0x87: 服务器配置结果 ── */
case 0x87:
if (len >= 6) {
printf("4G: 服务器配置%s\r\n", frame[4] == 0 ? "成功" : "失败");
}
break;
}
}

View File

@@ -0,0 +1,39 @@
#ifndef __DRVAIR724_H
#define __DRVAIR724_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
/* macro ------------------------------------------------------------------------------------------------*/
#define AIR724_SOCKET_MAX 6
/* struct -----------------------------------------------------------------------------------------------*/
typedef struct {
char iccid[24];
char imsi[16];
char imei[16];
uint8_t csq;
uint8_t ber;
uint8_t socket_connected[AIR724_SOCKET_MAX];
} Air724_Info_t;
/* Exported functions prototypes ------------------------------------------------------------------------*/
void drv_all_init(void);
/* ── 查询发送函数STM32 → 4G── */
void drv_air724_set_server(const char *ip, uint16_t port);
void drv_air724_connect_channel(uint8_t channel);
void drv_air724_disconnect_channel(uint8_t channel);
void drv_air724_query_iccid(void);
void drv_air724_query_imsi(void);
void drv_air724_query_imei(void);
void drv_air724_query_signal(void);
void drv_air724_query_link_status(void);
/* ── 统一解析入口4G → STM32── */
void drv_air724_parse_response(const uint8_t *frame, uint16_t len);
/* Exported constants -----------------------------------------------------------------------------------*/
extern Air724_Info_t g_air724_info;
#endif /* __DRVAIR724_H */

View File

@@ -13,31 +13,6 @@
#include "flash_config.h"
/* code -----------------------------------------------------------------------*/
void send_cmd_to_air724(uint8_t *cmd, uint16_t len)
{
Air724_Message_Send(cmd, len);
}
void send_server_address_to_air724(void)
{
char ip[] = YKC_SERVER_IP;
char port[6];
sprintf(port, "%d", YKC_SERVER_PORT);
uint8_t len = strlen(ip) + 1 + strlen(port);
uint8_t config_cmd[256] = {0x55, 0xAA, 0x04, 0x00, len};
uint8_t pos = 5;
memcpy(&config_cmd[pos], ip, strlen(ip));
pos += strlen(ip);
config_cmd[pos++] = 0x00;
memcpy(&config_cmd[pos], port, strlen(port));
pos += strlen(port);
config_cmd[pos++] = 0xAA;
config_cmd[pos++] = 0x55;
send_cmd_to_air724(config_cmd, pos);
pos = 0;
memset(config_cmd, 0, sizeof(config_cmd));
}
/**
* @brief drv_all_Init所有传感器、外设芯片、外部设备初始化
@@ -49,8 +24,9 @@ void send_server_address_to_air724(void)
* @retval none
*/
void drv_all_Init(void)
void drv_all_init(void)
{
AIR724_RESET(); /* AIR724 复位 */
stm_flash_init();/* 初始化flash */
drv_usart_init();/* 初始化所有串口 */
stm_flash_init();/* 初始化flash管理器 */
}

View File

@@ -8,7 +8,7 @@
/* Exported functions prototypes ------------------------------------------------------------------------*/
void drv_all_Init(void);
void drv_all_init(void);
#endif /* __DRVINIT_H */

View File

@@ -9,7 +9,7 @@
*/
/* Includes -------------------------------------------------------------------*/
#include "_hal_usart.h"
#include "drv_usart.h"
/* variables ------------------------------------------------------------------*/
uint8_t uart1_rx_buffer[UART1_RX_BUFFER_SIZE];
@@ -70,7 +70,7 @@ void Rs485_Message_Send(uint8_t *data, uint16_t len)
}
/**
* @brief _hal_usart_Init
* @brief drv_usart_init
*
* @note none
*
@@ -79,7 +79,7 @@ void Rs485_Message_Send(uint8_t *data, uint16_t len)
* @retval none
*/
void hal_usart_Init(void)
void drv_usart_init(void)
{
air724_tx_mutex = xSemaphoreCreateMutex();

View File

@@ -1,6 +1,6 @@
#ifndef __HALUSART_H
#define __HALUSART_H
#ifndef __DRV_USART_H
#define __DRV_USART_H
/* Suppress warning messages */
#if defined(__CC_ARM)
@@ -27,9 +27,9 @@ extern uint8_t uart1_tx_buffer[UART1_TX_BUFFER_SIZE];
extern uint8_t uart3_rx_buffer[UART3_RX_BUFFER_SIZE];
extern uint8_t uart3_tx_buffer[UART3_TX_BUFFER_SIZE];
/* function prototype ------------------------------------------------------------------------------------*/
extern void hal_usart_init(void);
extern void air724_callback_fun(void);
extern void rs485_callback_fun(void);
extern void Air724_Message_Send(uint8_t *data, uint16_t len);
extern void Rs485_Message_Send(uint8_t *data, uint16_t len);
#endif /* __HALUSART_H */
extern void drv_usart_init(void); // 初始化所有串口
extern void air724_callback_fun(void); // 4G数据接收回调函数
extern void rs485_callback_fun(void); // RS485数据接收回调函数
extern void Air724_Message_Send(uint8_t *data, uint16_t len); // 4G数据发送函数
extern void Rs485_Message_Send(uint8_t *data, uint16_t len); // RS485数据发送函数
#endif /* __DRV_USART_H */

View File

@@ -102,19 +102,8 @@ uint8_t flash_sector_erase(uint32_t addr)
return 1;
}
flash_manage_t stm_flash_manage = {0};
/* demo */
// stm_flash_read(data[0], 1);
// stm_flash_read(data[1], 0);
// stm_flash_read(data[0], 1);
// stm_flash_read(data[1], 0);
// stm_flash_write("12345678912345678912345678912345678912345", 41, 1);
// stm_flash_write("abcdef", 6, 0);
// stm_flash_write("12345678912345678912345678912345678912345", 41, 1);
// stm_flash_write("abcdef", 6, 0);
void stm_flash_init(void)
{
stm_flash_manage.align_num = 32;

View File

@@ -0,0 +1,19 @@
/**
******************************************************************************
* @file user\global\board_config.h
* @author luhuaishuai
* @version v0.1
* @date 2023-10-11
* @brief Briefly describe the function of your function
******************************************************************************
*/
#ifndef __BOARD_CONFIG_H
#define __BOARD_CONFIG_H
/* Private includes ----------------------------------------------------------*/
#define LINK_SERVER_PORT 6001 // 网关UDP服务端口
#define LINK_APP_PORT 6002 // 桩通讯端口
#define LINK_STAKE_PORT 6001 // 桩通讯端口
#endif /* __BOARD_CONFIG_H */

View File

@@ -5,42 +5,46 @@ ChargerManager g_charger_manager = {0};
/*充电桩序列号*/
const uint8_t piles_serial[6][7] = {
// {0x88, 0x26, 0x01, 0x13, 0x12, 0x00, 0x01},
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x45},
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x44},
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x43},
{0x32, 0x01, 0x06, 0x01, 0x16, 0x92, 0x42},
{0x32, 0x01, 0x06, 0x01, 0x11, 0x15, 0x58},
{0x32, 0x01, 0x06, 0x01, 0x11, 0x16, 0x54},
{0x88, 0x26, 0x01, 0x13, 0x12, 0x00, 0x01},
{0x88, 0x26, 0x01, 0x13, 0x12, 0x00, 0x02},
{0x88, 0x26, 0x01, 0x13, 0x12, 0x00, 0x03},
{0x88, 0x26, 0x01, 0x13, 0x12, 0x00, 0x04},
{0x88, 0x26, 0x01, 0x13, 0x12, 0x00, 0x05},
{0x32, 0x01, 0x06, 0x01, 0x11, 0x15, 0x54},
};
/**
* @brief 初始化充电桩管理器
* @note 初始化充电桩管理器,设置充电桩数量和每个充电桩的初始状态
*/
void init_chargers(void) {
void init_chargers(void)
{
g_charger_manager.charger_count = MAX_CHARGER_COUNT;
for (int i = 0; i < g_charger_manager.charger_count; i++) {
for (int i = 0; i < g_charger_manager.charger_count; i++)
{
ChargerPile *ctx = &g_charger_manager.charger_piles[i];
memcpy(ctx->login_info.charger_serial, piles_serial[i], 7);
memcpy(ctx->charger_serial, piles_serial[i], CHARGER_SERIAL_LENGTH);
memcpy(ctx->login_info.charger_serial, piles_serial[i], CHARGER_SERIAL_LENGTH);
ctx->get_model = false;
ctx->is_udp_online = false;
ctx->step = 0;
ctx->step_tick = 0;
ctx->login_info.charger_type = CHARGER_TYPE_DC;
ctx->login_info.gun_num = MAX_GUN_PER_CHARGER;
ctx->login_info.protocol_ver = 0x10; // V1.6
strcpy((char*)ctx->login_info.software_ver, "V4.1.50");
strcpy((char *)ctx->login_info.software_ver, "V4.1.50");
ctx->login_info.net_conn_type = 0; // SIM
memset(ctx->login_info.sim, 0, 10);
ctx->login_info.tele_factory = 0x00; // 移动
// 初始化枪
for (int g = 0; g < ctx->login_info.gun_num; g++) {
for (int g = 0; g < ctx->login_info.gun_num; g++)
{
ctx->guns[g].gun_index = g + 1;
ctx->guns[g].gun_step = 0;
ctx->guns[g].gun_step_tick = 0;
ctx->guns[g].real_time_data.status = 0; // 离线
}
}

View File

@@ -8,37 +8,61 @@
#define MAX_CHARGER_COUNT 2 // 充电桩数量
#define MAX_GUN_PER_CHARGER 2 // 每个充电桩最多枪数
enum CHARGER_STATE
{
IDLE_CHARGER_STATE = 0, // 空闲状态
REDAY_CHARGER_START_STATE = 1, // 等待桩启动回应
SUCCESS_CHARGER_STATE = 2, // 桩启动成功
FAIL_CHARGER_STATE = 3, // 桩启动失败
CHARGER_STATE_CHARGING = 4, // 桩充电中
REDAY_CHARGER_STOP_STATE = 5, // 等待桩停止回应
SUCCESS_CHARGER_STOP_STATE = 6, // 桩停止成功
FAIL_CHARGER_STOP_STATE = 7, // 桩停止失败
CHARGER_STATE_CHARGE_DONE = 8, // 桩充电完成
CHARGER_STATE_STOPPED = 9, // 桩主动停止
};
/* 充电枪结构体*/
typedef struct
{
uint8_t gun_index; // 枪索引
uint8_t gun_index; // 枪索引
uint8_t charger_state; // 枪充电流程状态
uint8_t gun_step; // 枪独立状态步进
uint32_t gun_step_tick; // 枪非阻塞延时时间戳(ms)
uint8_t is_get_bill; // 是否获取账单数据
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
PACK_DATA_0X33 charging_feedback; // 远程启机反馈数据
PACK_DATA_0X13 real_time_data; // 实时数据
PACK_DATA_0X03 heartbeat_status_data; // 心跳状态数据
PACK_DATA_0X3B fee_data; // 计费数据
PACK_DATA_0X23 bms_demand; // 充电过程BMS需求、充电机输出
PACK_DATA_0X25 bms_info; // 充电过程BMS信息
PACK_DATA_0X13 real_time_data; // 实时数据
} ChargerGun;
/* 单个充电桩结构体*/
typedef struct
{
PACK_DATA_0X01 login_info;
bool is_online; //云快充是否连接
bool is_udp_online; //是否本地在线
bool get_model;
uint16_t last_heartbeat_time; // 最后一次心跳时间
ChargerGun guns[MAX_GUN_PER_CHARGER]; // 充电枪数组
bool is_online; // 云快充是否连接
bool is_udp_online; // 是否本地在线
bool get_model; // 是否获取计费模型
uint8_t step; // 桩初始化/运行状态步进
uint32_t step_tick; // 非阻塞延时时间戳(ms)
PACK_DATA_0X01 login_info; // 登录信息数据
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
ChargerGun guns[MAX_GUN_PER_CHARGER]; // 充电枪数组
} ChargerPile;
/*全局充电桩管理结构体*/
typedef struct
{
uint8_t charger_count; // 桩索引
uint8_t charger_count; // 桩索引
ChargerPile charger_piles[MAX_CHARGER_COUNT]; // 充电桩数组
FEE_MODEL fee_model_global ; //全局计费模型
FEE_MODEL fee_model_global; // 全局计费模型
} ChargerManager;
extern ChargerManager g_charger_manager;
void init_chargers(void); //初始化全局充电桩
void init_chargers(void); // 初始化全局充电桩
#endif /* __DC_PILE_H */

View File

@@ -12,7 +12,6 @@
/* Includes -------------------------------------------------------------------*/
#include "drv_init.h"
#include "_hal_init.h"
@@ -20,9 +19,9 @@
* @brief 初始化全局系统
* @note 初始化全局系统包括HAL层和驱动层的初始化
*/
void g_Init(void)
void g_init(void)
{
_hal_all_Init();
drv_all_Init();
}
drv_all_init();
os_init();
}

View File

@@ -5,7 +5,7 @@
/* Exported functions prototypes ------------------------------------------------------------------------*/
void g_Init(void);
void g_init(void);

View File

@@ -36,6 +36,7 @@
#include "semphr.h"
#include "event_groups.h"
#include "stream_buffer.h"
#include "timers.h"
/* Os */
#include "os_task.h"
@@ -47,13 +48,13 @@
#include "server_to_charger.h"
#include "g_dcpile.h"
/* _hal */
#include "_hal_usart.h"
/* driver */
#include "cJSON.h"
#include "server_common.h"
#include "charger_to_server.h"
#include "drv_usart.h"
/* macro ------------------------------------------------------------------------------------------------*/
#define ON 1
@@ -73,8 +74,6 @@
#define DEBUG 1 // 调试模式
#define YKC_SERVER_IP "121.43.69.62" // YKC 服务器 IP 地址
#define YKC_SERVER_PORT 8767 // YKC 服务器端口号
/*- I/O 输出-*/

View File

@@ -1,33 +0,0 @@
/**
******************************************************************************
* @file user\hal\_hal_init.c
* @author luhuaishuai
* @version v0.1
* @date 2026-1-12
* @brief Briefly describe the function of your function
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "_hal_init.h"
/* code -----------------------------------------------------------------------*/
/**
* @funNm
* @brief
* @param
*/
/**
* @brief _hal_all_Init所有外设接口初始化
*
* @note none
*
* @param none
*
* @retval none
*/
void _hal_all_Init(void)
{
hal_usart_Init();
}

View File

@@ -1,13 +0,0 @@
#ifndef __HALINIT_H
#define __HALINIT_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
/* Exported functions prototypes ------------------------------------------------------------------------*/
void _hal_all_Init(void);
#endif /* __HALINIT_H */

View File

@@ -0,0 +1,97 @@
/**
* @file udp_manager.c
* @brief UDP管理模块
* @details 处理充电桩与服务器之间的UDP通信提供数据发送功能。
* 支持向指定充电桩发送数据以及向上位机服务器发送数据。
* @date 2025-01-09 14:32:15
* @version 1.0.0
* @copyright Copyright (c) 2026
*/
#include "udp_manager.h"
struct netconn *datalink_conn; // 数据链路句柄
/**
* @brief 桩IP地址
* @note 桩IP地址从10.12.19.101开始递增
*/
static ip4_addr_t s_point_ip[6] = {
IPADDR4_INIT_BYTES(10, 12, 19, 101), /* 桩1 */
IPADDR4_INIT_BYTES(10, 12, 19, 102), /* 桩2 */
IPADDR4_INIT_BYTES(10, 12, 19, 103), /* 桩3 */
IPADDR4_INIT_BYTES(10, 12, 19, 104), /* 桩4 */
IPADDR4_INIT_BYTES(10, 12, 19, 105), /* 桩5 */
IPADDR4_INIT_BYTES(10, 12, 19, 106), /* 桩6 */
};
static ip4_addr_t s_server_ip = IPADDR4_INIT_BYTES(10, 12, 19, 107); /* 上位机 */
/**
* @brief UDP发送
* @note 发送数据到指定桩
* @param point_id 目标桩ID
* @param data 数据指针
* @param len 数据长度
* @return err_t 错误码
*/
err_t udp_send_to_point(uint8_t point_id, uint8_t *data, u16_t len)
{
if (!data || len == 0)
{
return ERR_VAL;
}
if (!datalink_conn)
{
return ERR_CLSD;
}
if (point_id < 1 || point_id > 6)
{
return ERR_VAL;
}
struct netbuf *buf = netbuf_new();
if (!buf)
{
return ERR_MEM;
}
void *buf_data = netbuf_alloc(buf, len);
if (!buf_data)
{
netbuf_delete(buf);
return ERR_MEM;
}
memcpy(buf_data, data, len);
err_t err = netconn_sendto(datalink_conn, buf, &s_point_ip[point_id - 1], LINK_STAKE_PORT);
netbuf_delete(buf);
return err;
}
err_t udp_send_to_server(uint8_t *data, uint16_t len)
{
struct netbuf *buf = netbuf_new();
if (!buf)
{
return ERR_MEM;
}
if (!datalink_conn)
{
return ERR_CLSD;
}
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, &s_server_ip, LINK_APP_PORT);
netbuf_delete(buf);
return err;
}

View File

@@ -0,0 +1,41 @@
/**
******************************************************************************
* @file user\network\udp_manager.h
* @author luhuaishuai
* @version v0.1
* @date 2023-10-11
* @brief Briefly describe the function of your function
******************************************************************************
*/
#ifndef __UDP_MANAGER_H
#define __UDP_MANAGER_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
#include "lwip/opt.h"
#include "lwip/api.h"
#include "lwip/sys.h"
#include "board_config.h"
/* Private includes ----------------------------------------------------------*/
extern struct netconn *datalink_conn; // 数据链路句柄
/**
* @brief 发送数据到充电桩
* @param point_index 桩编号 1~6
* @param data 数据指针
* @param len 数据长度
*/
err_t udp_send_to_point(uint8_t point_index, uint8_t *data, u16_t len);
/**
* @brief 发送数据到上位机
* @param data 数据指针
* @param len 数据长度
*/
err_t udp_send_to_server(uint8_t *data, uint16_t len);
#endif /* __UDP_MANAGER_H */

View File

@@ -0,0 +1,56 @@
/**
* @file udp_router.c
* @brief UDP路由分发模块
* @details 处理充电桩与服务器之间的UDP通信路由分发根据指令字符串匹配对应的处理函数。
* 支持充电桩南向通信和上位机控制两类路由,通过静态路由表实现高效的指令分发。
* @date 2025-01-09 14:32:15
* @version 1.0.0
* @copyright Copyright (c) 2026
*/
#include "udp_router.h"
/**
* @brief UDP 路由表
*/
static const route_entry_t ROUTE_TABLE[] = {
/* ── 充电桩南向路由 ── */
{"online", point_callback_power_on, " 上电 "},
{"heartbeat", point_callback_heartbeat, " 心跳 "},
{"start charging", point_callback_start_charging, " 开始充电反馈 "},
{"end charging", point_callback_end_charging, " 停止充电反馈 "},
{"realtime data", point_callback_realtime_data, " 实时数据 "},
{"settlement bill", point_callback_settlement_bill, " 结算账单 "},
{"proactive end charging", point_callback_proactive_end_charging, " 桩主动停止充电反馈 "},
{"charge process real", point_callback_charge_process, " BMS实时需求 "},
{"bms info real", point_callback_bms_info, " BMS实时信息 "},
/* ── 上位机路由 ── */
{"server_login", (cmd_handler_t)host_computer_on_login, " 上位机登录 "},
{"server_get_status", (cmd_handler_t)host_computer_on_get_status, " 获取状态 "},
{"server_reboot", (cmd_handler_t)host_computer_on_reboot, " 重启设备 "},
};
#define ROUTE_TABLE_SIZE (sizeof(ROUTE_TABLE) / sizeof(ROUTE_TABLE[0]))
/**
* @brief 路由分发入口
* @param id 指令发送的充电桩ID
* @param cmd 指令字符串
* @param json_pack JSON格式的指令参数
*/
void udp_route_dispatch(uint8_t id, const char *cmd, cJSON *json_pack)
{
if (!cmd)
return;
for (size_t i = 0; i < ROUTE_TABLE_SIZE; i++)
{
if (strcmp(cmd, ROUTE_TABLE[i].cmd) == 0)
{
printf("[ UDP 接收路由 ] 指令: %-26s │ 桩ID: %d │ %s \r\n",cmd,id, ROUTE_TABLE[i].desc);
ROUTE_TABLE[i].handler(id, json_pack);
return;
}
}
printf("[ UDP 接收路由 ] = 未知指令: '%s' 桩ID: %d \r\n", cmd, id);
}

View File

@@ -0,0 +1,23 @@
#ifndef __UDP_ROUTER_H
#define __UDP_ROUTER_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
#include "board_config.h"
#include "point_protocol.h"
#include "host_computer_protocol.h"
typedef void (*cmd_handler_t)(uint8_t id, cJSON *json);
typedef struct
{
const char *cmd;
cmd_handler_t handler;
const char *desc;
} route_entry_t;
void udp_route_dispatch(uint8_t id, const char *cmd, cJSON *json_pack);//UDP路由分发入口
#endif /* __UDP_ROUTER_H */

View File

@@ -23,10 +23,11 @@
*
* @retval none
*/
void Os_Init(void)
void os_init(void)
{
Os_Semaphore_Init();
Os_Task_Init();
Os_Semaphore_Init();/* 初始化信号量 */
Os_Task_Init();/* 初始化任务 */
SwTimer_Init();/* 初始化软件定时器 */
}

View File

@@ -7,7 +7,7 @@
/* Exported functions prototypes ------------------------------------------------------------------------*/
void Os_Init(void);
void os_init(void);
/* Exported constants --------------------------------------------------------*/

View File

@@ -10,7 +10,7 @@
/* Includes -------------------------------------------------------------------*/
#include "os_queue.h"
#include "ChargerTask.h"
#include "task_udp.h"
/*-内核对象句柄-队列-*/
QueueHandle_t Air724_Message_Queue = NULL; /* 4G数据接收队列 */
@@ -21,7 +21,7 @@ QueueHandle_t UDP_Message_Queue = NULL; /* UDP数据接收队列 */
void Air724_Message_Queue_Init(void)
{
Air724_Message_Queue = xQueueCreate(5, UART1_RX_BUFFER_SIZE);
Air724_Message_Queue = xQueueCreate(20, UART1_RX_BUFFER_SIZE);
if (Air724_Message_Queue == NULL)
{
printf("Air724_Message_Queue_Init Failed\r\n");
@@ -43,7 +43,7 @@ void RS485_Message_Queue_Init(void)
void UDP_Message_Queue_Init(void)
{
UDP_Message_Queue = xQueueCreate(5, sizeof(UdpMsg_t));
UDP_Message_Queue = xQueueCreate(20, sizeof(UdpMsg_t));
if (UDP_Message_Queue == NULL)
{
// 创建失败处理

View File

@@ -12,26 +12,26 @@
#include "os_task.h"
/*-任务句柄-*/
osThreadId HeartbeatTaskHandle; /* 心跳任务句柄 */
osThreadId SysTaskHandle; /* 系统任务句柄 */
osThreadId UDPTaskHandle; /* UDP 消息队列接受任务句柄 */
osThreadId UDP_ParseTaskHandle; /* UDP 消息队列解析任务句柄 */
osThreadId DownLinkTaskHandle; /* 4G接收消息任务句柄 */
osThreadId Air724_ParseTaskHandle; /* 4G消息队列解析任务句柄 */
osThreadId YkcTaskHandle; /* 云快充平台交互任务句柄 */
/*-函数声明-*/
void HeartbeatTask_Function(void const *argument); // 心跳任务
void sys_task_function(void const *argument); // 系统任务
void UDPTask_Function(void const *argument); // UDP 接受任务
void udp_recv_task_function(void const *argument); // UDP 接受任务
void UDP_ParseTask_Function(void const *argument); //UDP消息解析任务
void udp_parse_task_function(void const *argument); //UDP消息解析任务
void YkcTask_Function(void const *argument); // 云快充平台交互任务
void ykc_task_function(void const *argument); // 云快充平台交互任务
void DownLinkTask_Function(void const *argument); // 4G接收消息任务
void air724_recv_task_function(void const *argument); // 云快充 TCP 消息解析任务
/* code -----------------------------------------------------------------------*/
@@ -49,20 +49,16 @@ void Os_Task_Init(void) /*任务入口函数、任务名字、任务栈大小、
{
BaseType_t xReturn = pdPASS;
/* 心跳任务 */
xReturn = xTaskCreate((TaskFunction_t)HeartbeatTask_Function, "HeartbeatTask", 128, NULL, (UBaseType_t)osPriorityLow, &HeartbeatTaskHandle);
/* 4G消息解析任务 */
xReturn = xTaskCreate((TaskFunction_t)DownLinkTask_Function, "DownLinkTask", 1024, NULL, osPriorityAboveNormal, &DownLinkTaskHandle);
/* 系统任务 */
xReturn = xTaskCreate((TaskFunction_t)sys_task_function, "SysTask", 128, NULL, (UBaseType_t)osPriorityLow, &SysTaskHandle);
/* 云快充 TCP 消息解析任务 */
xReturn = xTaskCreate((TaskFunction_t)air724_recv_task_function, "Air724RecvTask", 1024, NULL, osPriorityAboveNormal, &Air724_ParseTaskHandle);
/* UDP 消息队列接受任务 */
xReturn = xTaskCreate((TaskFunction_t)UDPTask_Function, "UDPTask", 1536, NULL, osPriorityHigh, &UDPTaskHandle);
xReturn = xTaskCreate((TaskFunction_t)udp_recv_task_function, "UDPRecvTask", 1536, NULL, osPriorityHigh, &UDPTaskHandle);
/* UDP 消息解析任务 */
xReturn = xTaskCreate((TaskFunction_t)UDP_ParseTask_Function, "UDPParseTask", 2048, NULL, osPriorityAboveNormal, &UDP_ParseTaskHandle);
xReturn = xTaskCreate((TaskFunction_t)udp_parse_task_function, "UDPParseTask", 2048, NULL, osPriorityAboveNormal, &UDP_ParseTaskHandle);
/* 云快充平台交互任务 */
xReturn = xTaskCreate((TaskFunction_t)YkcTask_Function, "YKCTask", 1024, NULL, osPriorityAboveNormal, &YkcTaskHandle);
xReturn = xTaskCreate((TaskFunction_t)ykc_task_function, "YKC_Task", 1024, NULL, osPriorityAboveNormal, &YkcTaskHandle);
if (xReturn == pdPASS)
{

View File

@@ -9,13 +9,13 @@
void Os_Task_Init(void);
/* Exported constants --------------------------------------------------------*/
extern osThreadId HeartbeatTaskHandle; /* 心跳任务句柄 */
extern osThreadId SysTaskHandle; /* 系统任务句柄 */
extern osThreadId UDPTaskHandle; /* UDP 消息队列接受任务句柄 */
extern osThreadId UDP_ParseTaskHandle; /* UDP 消息队列解析任务句柄 */
extern osThreadId DownLinkTaskHandle; /* 消息任务句柄 */
extern osThreadId Air724_ParseTaskHandle; /* 4G消息队列解析任务句柄 */
extern osThreadId YkcTaskHandle; /* 云快充平台交互任务句柄 */

147
Core/User/Os/os_timer.c Normal file
View File

@@ -0,0 +1,147 @@
/**
******************************************************************************
* @file user\os\os_timer.c
* @author luhuaishuai
* @version v0.1
* @date 2026-1-12
* @brief 软件定时器管理
******************************************************************************
*/
/* Includes -------------------------------------------------------------------*/
#include "os_timer.h"
#include "task_ykc.h"
/* variables ------------------------------------------------------------------*/
TimerHandle_t YkcTimerHandle = NULL;
/* code -----------------------------------------------------------------------*/
/**
* @brief 软件定时器初始化
* @note 需要在 FreeRTOS 启动前调用
* @param none
* @retval none
*/
void SwTimer_Init(void)
{
SwTimer_YkcTimer_Init();
}
/**
* @brief 创建软件定时器
* @param name 定时器名称
* @param period_ms 定时周期(ms)
* @param auto_reload true:周期模式 false:单次模式
* @param callback 定时器回调函数
* @retval TimerHandle_t 定时器句柄NULL 表示创建失败
*/
TimerHandle_t SwTimer_Create(const char *name,
uint32_t period_ms,
bool auto_reload,
SwTimerCallback_t callback)
{
UBaseType_t reload = auto_reload ? pdTRUE : pdFALSE;
TimerHandle_t xTimer = xTimerCreate(name,
pdMS_TO_TICKS(period_ms),
reload,
NULL,
callback);
if (xTimer == NULL)
{
printf("SwTimer_Create Failed: %s\r\n", name);
}
return xTimer;
}
/**
* @brief 启动软件定时器
* @param xTimer 定时器句柄
* @retval none
*/
void SwTimer_Start(TimerHandle_t xTimer)
{
if (xTimer != NULL)
{
if (xTimerStart(xTimer, 0) != pdPASS)
{
printf("SwTimer_Start Failed\r\n");
}
}
}
/**
* @brief 停止软件定时器
* @param xTimer 定时器句柄
* @retval none
*/
void SwTimer_Stop(TimerHandle_t xTimer)
{
if (xTimer != NULL)
{
if (xTimerStop(xTimer, 0) != pdPASS)
{
printf("SwTimer_Stop Failed\r\n");
}
}
}
/**
* @brief 复位软件定时器
* @param xTimer 定时器句柄
* @retval none
*/
void SwTimer_Reset(TimerHandle_t xTimer)
{
if (xTimer != NULL)
{
if (xTimerReset(xTimer, 0) != pdPASS)
{
printf("SwTimer_Reset Failed\r\n");
}
}
}
/**
* @brief 创建充电桩状态检查定时器
* @note 500ms 周期,自动重载,遍历检查 1-6 号桩状态
* @param none
* @retval none
*/
void SwTimer_YkcTimer_Init(void)
{
YkcTimerHandle = SwTimer_Create("YkcTimer",
2500,
true,
ykc_timer_callback_function);
if (YkcTimerHandle != NULL)
{
SwTimer_Start(YkcTimerHandle);
printf("YkcTimer_Init\r\n");
}
else
{
printf("YkcTimer_Init Failed\r\n");
}
}
/**
* @brief 修改定时器周期
* @param xTimer 定时器句柄
* @param period_ms 新周期(ms)
* @retval none
*/
void SwTimer_ChangePeriod(TimerHandle_t xTimer, uint32_t period_ms)
{
if (xTimer != NULL)
{
if (xTimerChangePeriod(xTimer, pdMS_TO_TICKS(period_ms), 0) != pdPASS)
{
printf("SwTimer_ChangePeriod Failed\r\n");
}
}
}

29
Core/User/Os/os_timer.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef __OSTIMER_H
#define __OSTIMER_H
/* Private includes ----------------------------------------------------------*/
#include "global.h"
/* Exported types ------------------------------------------------------------*/
typedef void (*SwTimerCallback_t)(TimerHandle_t xTimer);
/* Exported functions prototypes ---------------------------------------------*/
void SwTimer_Init(void);
TimerHandle_t SwTimer_Create(const char *name,
uint32_t period_ms,
bool auto_reload,
SwTimerCallback_t callback);
void SwTimer_Start(TimerHandle_t xTimer);
void SwTimer_Stop(TimerHandle_t xTimer);
void SwTimer_Reset(TimerHandle_t xTimer);
void SwTimer_ChangePeriod(TimerHandle_t xTimer, uint32_t period_ms);
void SwTimer_YkcTimer_Init(void);
/* Exported constants --------------------------------------------------------*/
extern TimerHandle_t YkcTimerHandle;
#endif

View File

@@ -0,0 +1,279 @@
/**
* @file host_computer_protocol.c
* @brief 上位机通信协议处理模块
* @details 处理上位机与充电桩之间的通信协议,包括登录验证、状态查询、
* 数据上报等功能。支持JSON格式的数据交互通过UDP协议与服务器通信。
* @date 2025-01-09 14:32:15
* @version 1.0.0
* @copyright Copyright (c) 2026
*/
#include "host_computer_protocol.h"
#include "point_protocol.h"
void host_computer_on_login(uint8_t id, cJSON *json_pack)
{
(void) id;
cJSON *request_id = cJSON_GetObjectItem(json_pack, "request_id");
cJSON *username = cJSON_GetObjectItem(json_pack, "username");
cJSON *password = cJSON_GetObjectItem(json_pack, "password");
cJSON *root = NULL;
char *str = NULL;
root = cJSON_CreateObject();
if (root == NULL)
{
printf("Failed to create JSON object for server login\r\n");
return;
}
/* 添加一条字符串类型的JSON数据(添加一个链表节点) */
cJSON_AddStringToObject(root, "request_id", request_id->valuestring);
if (strcmp(username->valuestring, "admin") != 0 || strcmp(password->valuestring, "123456") != 0)
{
cJSON_AddStringToObject(root, "error", "用户或密码错误 ");
cJSON_AddBoolToObject(root, "success", cJSON_False);
printf("登录失败\r\n");
}
else
{
cJSON_AddBoolToObject(root, "success", cJSON_True);
printf("登录成功\r\n");
}
str = cJSON_Print(root);
udp_send_to_server(str, strlen(str));
free(str);
cJSON_Delete(root);
}
// 处理获取状态查询指令
void host_computer_on_get_status(uint8_t id, cJSON *json_pack)
{
(void) id;
cJSON *request_id = cJSON_GetObjectItem(json_pack, "request_id");
cJSON *root = NULL;
cJSON *piles = NULL;
cJSON *pile = NULL;
cJSON *guns = NULL;
cJSON *gun = NULL;
char *str = NULL;
root = cJSON_CreateObject();
if (root == NULL)
{
printf("Failed to create JSON object for get_status\r");
return;
}
cJSON_AddStringToObject(root, "request_id", request_id->valuestring);
cJSON_AddBoolToObject(root, "success", cJSON_True);
/* ── piles 数组 ── */
piles = cJSON_CreateArray();
/* 根据MAX_CHARGER_COUNT动态生成桩数据 */
for (int i = 0; i < MAX_CHARGER_COUNT; i++)
{
ChargerPile *pile_data = &g_charger_manager.charger_piles[i];
pile = cJSON_CreateObject();
if (pile == NULL)
{
continue;
}
/* 添加桩序列号 */
char serial_str[15] = {0};
for (int j = 0; j < 7; j++)
{
sprintf(&serial_str[j * 2], "%02X", pile_data->login_info.charger_serial[j]);
}
cJSON_AddStringToObject(pile, "serial", serial_str);
/* 添加在线状态 */
cJSON_AddBoolToObject(pile, "is_online", pile_data->is_udp_online ? cJSON_True : cJSON_False);
/* 创建guns数组 */
guns = cJSON_CreateArray();
if (guns != NULL)
{
for (int g = 0; g < MAX_GUN_PER_CHARGER; g++)
{
gun = cJSON_CreateObject();
if (gun != NULL)
{
/* 添加枪状态 */
cJSON_AddNumberToObject(gun, "status", pile_data->guns[g].real_time_data.status);
cJSON_AddItemToArray(guns, gun);
}
}
cJSON_AddItemToObject(pile, "guns", guns);
}
cJSON_AddItemToArray(piles, pile);
}
cJSON_AddItemToObject(root, "piles", piles);
/* ── 发送 ── */
str = cJSON_Print(root);
udp_send_to_server(str, strlen(str));
free(str);
cJSON_Delete(root);
printf("get_status 回复已发送 \r\n");
}
void host_computer_on_reboot(uint8_t id, cJSON *json)
{
(void) id;
printf("host_computer_on_reboot\r\n");
__ASM volatile("cpsid i");
HAL_NVIC_SystemReset();
}
/**
* @brief 主动上报充电桩数据
* @note 模拟并上报充电桩实时数据,符合协议格式要求
* @param stake_index 桩索引 (1~6)
* @param gun_id 枪编码 (1~N)
*/
void host_computer_report_data(uint8_t stake_index, uint8_t gun_id)
{
if (stake_index > MAX_CHARGER_COUNT || 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;
}
cJSON *root = NULL;
cJSON *piles_array = NULL;
cJSON *pile_obj = NULL;
cJSON *guns_array = NULL;
cJSON *gun_obj = NULL;
char *str = NULL;
root = cJSON_CreateObject();
if (root == NULL)
{
printf("Failed to create JSON object for report_data\r\n");
return;
}
/* 添加命令字段 */
cJSON_AddStringToObject(root, "cmd", "report_data");
/* 创建piles数组 */
piles_array = cJSON_CreateArray();
if (piles_array == NULL)
{
printf("Failed to create piles array\r\n");
cJSON_Delete(root);
return;
}
/* 创建桩数据对象 */
pile_obj = cJSON_CreateObject();
if (pile_obj == NULL)
{
printf("Failed to create pile object\r\n");
cJSON_Delete(root);
return;
}
/* 从结构体获取真实数据 */
ChargerGun *gun = &g_charger_manager.charger_piles[stake_index - 1].guns[gun_id - 1];
FEE_MODEL *model = &g_charger_manager.fee_model_global;
/* 存入结构体前放大过10倍发送时缩小回去 */
float current = gun->real_time_data.out_current / 10.0f;
float voltage = gun->real_time_data.out_voltage / 10.0f;
float power = (voltage * current) / 1000.0f;
/* 电量(0.01kWh/单位) 金额(0.01元/单位) */
float energy = gun->real_time_data.charge_energy / 10000.0f;
float total_amount = gun->real_time_data.charge_money / 10000.0f;
/* 根据计费模型拆分电费/服务费 */
uint32_t fee_ratio_sum = model->flat_fee_ratio + model->flat_service_ratio;
float electricity_fee = (fee_ratio_sum > 0) ? total_amount * model->flat_fee_ratio / fee_ratio_sum : 0;
float service_fee = total_amount - electricity_fee;
int status = gun->real_time_data.status;
int cumulative_time = gun->real_time_data.charge_time;
int remaining_time = gun->real_time_data.remain_time;
uint8_t soc = gun->real_time_data.soc;
/* 交易流水号 → 十六进制字符串 */
char order_no[33] = {0};
trade_serial_to_string(gun->real_time_data.trade_serial, order_no);
/* 模拟桩数据 */
cJSON_AddNumberToObject(pile_obj, "index", stake_index);
/* 创建guns数组 */
guns_array = cJSON_CreateArray();
if (guns_array == NULL)
{
printf("Failed to create guns array\r\n");
cJSON_Delete(pile_obj);
cJSON_Delete(root);
return;
}
/* 创建枪数据对象 */
gun_obj = cJSON_CreateObject();
if (gun_obj == NULL)
{
printf("Failed to create gun object\r\n");
cJSON_Delete(guns_array);
cJSON_Delete(pile_obj);
cJSON_Delete(root);
return;
}
/* 枪数据 */
cJSON_AddNumberToObject(gun_obj, "gun", gun_id);
cJSON_AddNumberToObject(gun_obj, "current", current); // 电流(随机变化)
cJSON_AddNumberToObject(gun_obj, "voltage", voltage); // 电压(随机变化)
cJSON_AddNumberToObject(gun_obj, "power", power); // 功率(随机变化)
cJSON_AddNumberToObject(gun_obj, "energy", energy); // 电量(随机变化)
cJSON_AddNumberToObject(gun_obj, "service_fee", service_fee); // 服务费(随机变化)
cJSON_AddNumberToObject(gun_obj, "electricity_fee", electricity_fee); // 电费(随机变化)
cJSON_AddNumberToObject(gun_obj, "total_amount", total_amount); // 总金额(随机变化)
cJSON_AddNumberToObject(gun_obj, "status", status); // 状态
cJSON_AddStringToObject(gun_obj, "order_no", order_no);
cJSON_AddNumberToObject(gun_obj, "soc", soc); // SOC随机变化
cJSON_AddNumberToObject(gun_obj, "cumulative_time", cumulative_time); // 累计充电时间(随机变化)
cJSON_AddNumberToObject(gun_obj, "remaining_time", remaining_time); // 剩余时间(随机变化)
cJSON_AddItemToArray(guns_array, gun_obj);
cJSON_AddItemToObject(pile_obj, "guns", guns_array);
cJSON_AddItemToArray(piles_array, pile_obj);
cJSON_AddItemToObject(root, "piles", piles_array);
/* 转换为JSON字符串并发送 */
str = cJSON_Print(root);
if (str != NULL)
{
udp_send_to_server(str, strlen(str));
free(str);
printf("南向:主动上报桩 %d 枪 %d 主动上报数据成功 \r\n", stake_index, gun_id);
}
else
{
printf("Failed to print JSON for report_data\r\n");
}
cJSON_Delete(root);
}

View File

@@ -0,0 +1,16 @@
#ifndef HOST_COMPUTER_HANDLER_H
#define HOST_COMPUTER_HANDLER_H
#include "global.h"
#include "cJSON.h"
#include <stdint.h>
#include "udp_manager.h"
/* ── 路由表注册接口(供 UdpRouter 调用)─────────────── */
void host_computer_on_login (uint8_t id, cJSON *json);
void host_computer_on_get_status(uint8_t id, cJSON *json);
void host_computer_on_reboot (uint8_t id, cJSON *json);
/* ── 主动上报接口(供业务层调用)────────────────────── */
void host_computer_report_data(uint8_t stake_index, uint8_t gun_id);
#endif /* HOST_COMPUTER_HANDLER_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
#ifndef POINT_HANDLER_H
#define POINT_HANDLER_H
#include "global.h"
#include "cJSON.h"
#include <stdint.h>
#include "udp_manager.h"
/* ── 路由表注册接口─────────────── */
void point_callback_power_on(uint8_t id, cJSON *json); // 上电
void point_callback_heartbeat(uint8_t id, cJSON *json); // 心跳包
void point_callback_start_charging(uint8_t stake_index, cJSON *json_pack); // 开始充电反馈
void point_callback_end_charging(uint8_t stake_index, cJSON *json_pack); // 停止充电反馈
void point_callback_realtime_data(uint8_t stake_index, cJSON *json); // 实时数据
void point_callback_settlement_bill(uint8_t stake_index, cJSON *json); // 结算单
void point_callback_proactive_end(uint8_t stake_index, cJSON *json); // 主动结束
void point_callback_charge_process(uint8_t stake_index, cJSON *json); // 实时信息
void point_callback_bms_info(uint8_t stake_index, cJSON *json); // BMS 信息
void point_callback_proactive_end_charging(uint8_t stake_index, cJSON *json_pack); // 主动结束充电
/* ── 主动下发接口(供业务层调用)────────────────────── */
void point_send_start_charging(uint8_t stake_index, uint8_t gun_id); // 开始充电
void point_send_stop_charging(uint8_t stake_index, uint8_t gun_id); // 停止充电
/* ── 工具函数 ─────────────────────────────────────────── */
void trade_serial_to_string(uint8_t *trade_serial, char *output_str); // 转换交易序列号为字符串
#endif /* POINT_HANDLER_H */

View File

@@ -0,0 +1,187 @@
/**
* @file charger_to_server.c
* @brief 充电桩通信协议处理模块 上行数据
* @details 处理充电桩与服务器之间的通信协议,包括登录验证、心跳请求、计费模型验证请求、
* 数据上报等功能。支持字节码格式的数据交互,通过串口协议与服务器通信。
* @date 2025-01-09 14:32:15
* @version 1.0.0
* @copyright Copyright (c) 2026
*/
#include "global.h"
#include "charger_to_server.h"
uint16_t pack_serial = 0;
// 充电桩登录认证
void charger_to_server_0X01(uint8_t stake_index)
{
PACK_DATA_0X01 data = {0};
ChargerPile *point = &g_charger_manager.charger_piles[stake_index - 1];
load_charger_serial(stake_index, data.charger_serial); // 加载充电桩序列号
data.charger_type = g_charger_manager.charger_piles[stake_index - 1].login_info.charger_type;
data.gun_num = g_charger_manager.charger_piles[stake_index - 1].login_info.gun_num;
data.protocol_ver = g_charger_manager.charger_piles[stake_index - 1].login_info.protocol_ver;
memcpy(data.software_ver, g_charger_manager.charger_piles[stake_index - 1].login_info.software_ver, 8);
data.net_conn_type = g_charger_manager.charger_piles[stake_index - 1].login_info.net_conn_type;
data.tele_factory = g_charger_manager.charger_piles[stake_index - 1].login_info.tele_factory;
memcpy(data.sim, g_charger_manager.charger_piles[stake_index - 1].login_info.sim, 10);
printf("北向:对电桩 %d 发送登录认证,序列号:%s\r\n", stake_index, data.charger_serial);
pack_and_send_server_data(FRAME_TYPE_0X01, (uint8_t *)&data, sizeof(PACK_DATA_0X01), stake_index);
}
void charger_to_server_0X03(uint8_t stake_index, uint8_t gun_index)
{
PACK_DATA_0X03 data = {0};
load_charger_serial(stake_index, data.charger_serial); // 加载充电桩序列号
data.gun_index = ((gun_index / 10) << 4) | (gun_index % 10);
data.gun_status = g_charger_manager.charger_piles[stake_index - 1].guns[gun_index - 1].heartbeat_status_data.gun_status;
printf("北向:对电桩 %d 发送心跳请求,枪号:%d状态%d\r\n", stake_index, gun_index, data.gun_status);
pack_and_send_server_data(FRAME_TYPE_0X03, (uint8_t *)&data, sizeof(PACK_DATA_0X03), stake_index);
}
// 计费模型验证请求
void charger_to_server_0X05(uint16_t num, uint8_t stake_index)
{
PACK_DATA_0X05 data = {0};
load_charger_serial(stake_index, data.charger_serial); // 加载充电桩序列号
data.fee_model_no = num;
printf("北向:对电桩 %d 计费模型验证请求,模型号:%d\r\n", stake_index, num);
pack_and_send_server_data(FRAME_TYPE_0X05, (uint8_t *)&data, sizeof(PACK_DATA_0X05), stake_index);
}
// 充电桩计费模型请求
void charger_to_server_0X09(uint8_t stake_index)
{
PACK_DATA_0X09 data = {0};
load_charger_serial(stake_index, data.charger_serial); // 加载充电桩序列号
printf("北向:对电桩 %d 计费模型请求,序列号:%s\r\n", stake_index, data.charger_serial);
pack_and_send_server_data(FRAME_TYPE_0X09, (uint8_t *)&data, sizeof(PACK_DATA_0X09), stake_index);
}
// 上传实时监测数据
void charger_to_server_0X13(uint8_t stake_index, uint8_t gun_index)
{
PACK_DATA_0X13 data = {0};
ChargerGun *gun = &g_charger_manager.charger_piles[stake_index - 1].guns[gun_index - 1]; // 获取枪指针
data = gun->real_time_data; // 复制结构体
// 覆盖需要重新加载的字段
load_charger_serial(stake_index, data.charger_serial);
load_trade_serial(stake_index, gun_index, data.trade_serial);
data.gun_index = ((gun_index / 10) << 4) | (gun_index % 10);
// 根据状态清零特定字段
if (data.status == 0x01 || data.status == 0x02 || data.status == 0x00)
{
memset(data.trade_serial, 0, sizeof(data.trade_serial));
data.out_voltage = 0;
data.out_current = 0;
data.charge_energy = 0;
data.charge_time = 0;
data.charge_money = 0;
data.hard_fault = 0;
}
pack_and_send_server_data(FRAME_TYPE_0X13, (uint8_t *)&data, sizeof(PACK_DATA_0X13), stake_index);
}
// 充电结束上报
void charger_to_server_0X19(uint8_t stake_index, uint8_t gun_index)
{
PACK_DATA_0X19 data = {0};
load_charger_serial(stake_index, data.charger_serial);
load_trade_serial(stake_index, gun_index, data.trade_serial);
pack_and_send_server_data(FRAME_TYPE_0X19, (uint8_t *)&data, sizeof(PACK_DATA_0X19), stake_index);
}
// 平台启动回复
void charger_to_server_0X33(uint8_t stake_index, uint8_t gun_index, uint8_t result, uint8_t err_code)
{
PACK_DATA_0X33 data = {0};
load_charger_serial(stake_index, data.charger_serial);
load_trade_serial(stake_index, gun_index, data.trade_serial);
data.gun_index = ((gun_index / 10) << 4) | (gun_index % 10);
data.result = result;
data.err_code = err_code;
printf(" └── 网关向云快充发送 [%s] 电桩 %d 枪 %d的平台启动回复结果%d错误码%d\r\n",result == 0 ? "error" : "success", stake_index, gun_index, result, err_code);
pack_and_send_server_data(FRAME_TYPE_0X33, (uint8_t *)&data, sizeof(PACK_DATA_0X33), stake_index);
}
// 平台停机回复
void charger_to_server_0X35(uint8_t stake_index, uint8_t gun_index, uint8_t result, uint8_t err_code)
{
PACK_DATA_0X35 data = {0};
load_charger_serial(stake_index, data.charger_serial);
data.gun_index = ((gun_index / 10) << 4) | (gun_index % 10);
data.result = result;
data.err_code = err_code;
printf("北向:对电桩 %d 的平台停机回复,结果:%d错误码%d\r\n", stake_index, result, err_code);
pack_and_send_server_data(FRAME_TYPE_0X35, (uint8_t *)&data, sizeof(PACK_DATA_0X35), stake_index);
}
// 上传交易记录
void charger_to_server_0X3B(uint8_t stake_index, uint8_t gun_index)
{
PACK_DATA_0X3B data = {0};
ChargerGun *gun = &g_charger_manager.charger_piles[stake_index - 1].guns[gun_index - 1];
load_charger_serial(stake_index, data.charger_serial); // 加载电桩编号
load_trade_serial(stake_index, gun_index, data.trade_serial); // 加载交易记录
data.gun_index = ((gun_index / 10) << 4) | (gun_index % 10); // 枪索引
memcpy(data.start_time, gun->fee_data.start_time, 7); // 充电开始时间戳
memcpy(data.end_time, gun->fee_data.end_time, 7); // 充电结束时间戳
data.shark_price = gun->fee_data.shark_price; // 尖单价
data.shark_energy = gun->fee_data.shark_energy; // 尖电量
data.loss_shark_energy = gun->fee_data.loss_shark_energy; // 尖损失电量
data.shark_money = gun->fee_data.shark_money; // 尖金额
data.peak_price = gun->fee_data.peak_price; // 峰单价
data.peak_energy = gun->fee_data.peak_energy; // 峰电量
data.loss_peak_energy = gun->fee_data.loss_peak_energy; // 峰损失电量
data.peak_money = gun->fee_data.peak_money; // 峰金额
data.flat_price = gun->fee_data.flat_price; // 平单价
data.flat_energy = gun->fee_data.flat_energy; // 平电量
data.loss_flat_energy = gun->fee_data.loss_flat_energy; // 平损失电量
data.flat_money = gun->fee_data.flat_money; // 平金额
data.valley_price = gun->fee_data.valley_price; // 谷单价
data.valley_energy = gun->fee_data.valley_energy; // 谷电量
data.loss_valley_energy = gun->fee_data.loss_valley_energy; // 谷损失电量
data.valley_money = gun->fee_data.valley_money; // 谷金额
memcpy(data.meter_start_value, gun->fee_data.meter_start_value, 4); // 电表充电开始值
memcpy(data.meter_end_value, gun->fee_data.meter_end_value, 4); // 电表充电结束值
data.total_energy = gun->fee_data.total_energy; // 充电电量
data.loss_total_energy = gun->fee_data.loss_total_energy; // 损失电量
data.consumption = gun->fee_data.consumption; // 金额
data.trade_flag = 0x01;
data.stop_reason = gun->fee_data.stop_reason; // 停机原因
memcpy(data.trade_time, gun->fee_data.trade_time, 7); // 交易时间
memcpy(data.vin, gun->fee_data.vin, 17); // VIN码
memcpy(data.phy_cardid, gun->fee_data.phy_cardid, 8); // 物理卡号
printf("========================================\r\n");
printf("北向:对电桩 %d 枪 %d 上传交易记录(0x3B),电量:%.2f,金额:%.2f\r\n",
stake_index, gun_index, data.total_energy / 10000.0f, data.consumption / 10000.0f);
printf("\r\n========================================\r\n");
pack_and_send_server_data(FRAME_TYPE_0X3B, (uint8_t *)&data, sizeof(PACK_DATA_0X3B), stake_index);
}
// 计费模型主动更新回复
void charger_to_server_0x57(uint8_t stake_index)
{
PACK_DATA_0X57 data = {0};
load_charger_serial(stake_index, data.charger_serial);
data.result = 0x01;
printf("北向:对电桩 %d 的计费模型主动更新回复\r\n", stake_index);
pack_and_send_server_data(FRAME_TYPE_0X57, (uint8_t *)&data, sizeof(PACK_DATA_0X57), stake_index);
}

View File

@@ -0,0 +1,56 @@
#ifndef __CHARGER_TO_SERVER_H__
#define __CHARGER_TO_SERVER_H__
#include <stdint.h>
#include "server_common.h"
extern uint16_t pack_serial;
uint32_t get_current_rtc_sec();
//充电桩登录认证请求
extern void charger_to_server_0X01(uint8_t stake_index);
//充电桩心跳包
extern void charger_to_server_0X03(uint8_t stake_index,uint8_t gun_index);
//计费模型验证请求
extern void charger_to_server_0X05(uint16_t num,uint8_t stake_index);
//充电桩计费模型请求
extern void charger_to_server_0X09(uint8_t stake_index);
//上传实时监测数据
extern void charger_to_server_0X13(uint8_t stake_index,uint8_t gun_index);
// 充电结束
extern void charger_to_server_0X19(uint8_t stake_index,uint8_t gun_index);
// 报文被改为A5A8
extern void charger_to_server_0X33(uint8_t stake_index,uint8_t gun_index, uint8_t result, uint8_t err_code);
extern void charger_to_server_0X35(uint8_t stake_index, uint8_t gun_index, uint8_t result, uint8_t err_code);
extern void charger_to_server_0X3B(uint8_t stake_index, uint8_t gun_index);
//余额更新应答
void charger_to_server_0X41(uint8_t * card_id, uint8_t result) ;
//对时设置应答
extern void charger_to_server_0X55(uint32_t time);
//计费模型应答
extern void charger_to_server_0X57(uint8_t stake_index);
extern uint8_t get_real_status_for_server(uint8_t idx);
//远程启机命令回复
extern void charger_to_server_0XA7(uint8_t gun_index,uint8_t start_result,uint8_t fail_reson);
//交易记录
void charger_to_server_0x3D(uint8_t gun_index,uint32_t card_id,uint8_t trade_flag,uint8_t stop_reason);
//远程重启应答
void charger_to_server_0X91(uint8_t result);
#endif

View File

@@ -0,0 +1,327 @@
#if 1
#include "global.h"
#include "server_common.h"
#include "time.h"
#include <string.h>
uint8_t trade_serial[2][TRADE_SERIAL_LENGTH] = {0};
static FEE_MODEL current_setting_fee_model = {0};
typedef struct
{
uint32_t phy_num;
uint32_t logic_num;
} CARD_NUM;
static CARD_NUM card_num[2] = {0};
void set_card_phy_logic_num(uint8_t idx, uint8_t type, uint32_t num)
{
idx = idx - 1;
if (idx >= 2)
{
return;
}
if (type == 0)
{
card_num[idx].phy_num = num;
}
else if (type == 1)
{
card_num[idx].logic_num = num;
}
}
uint32_t get_card_phy_num(uint8_t idx)
{
idx = idx - 1;
if (idx >= 2)
{
return 0;
}
return card_num[idx].phy_num;
}
uint32_t get_card_logic_num(uint8_t idx)
{
if (idx >= 2)
{
return 0;
}
return card_num[idx].logic_num;
}
void load_charger_serial(uint8_t stake_mark, uint8_t *serial)
{
if (stake_mark > MAX_CHARGER_COUNT)
{
return;
}
memcpy(serial, g_charger_manager.charger_piles[stake_mark - 1].charger_serial, CHARGER_SERIAL_LENGTH);
}
void set_trade_serial(uint8_t stake_mark, uint8_t idx, uint8_t *serial)
{
if (stake_mark > MAX_CHARGER_COUNT)
{
return;
}
if (idx > MAX_GUN_PER_CHARGER)
{
return;
}
memcpy(g_charger_manager.charger_piles[stake_mark - 1].guns[idx - 1].real_time_data.trade_serial, serial, TRADE_SERIAL_LENGTH);
}
void load_trade_serial(uint8_t stake_mark, uint8_t idx, uint8_t *serial)
{
if (stake_mark > MAX_CHARGER_COUNT)
{
return;
}
if (idx > MAX_GUN_PER_CHARGER)
{
return;
}
memcpy(serial, g_charger_manager.charger_piles[stake_mark - 1].guns[idx - 1].real_time_data.trade_serial, TRADE_SERIAL_LENGTH);
}
uint8_t is_my_charger_serial(uint8_t stake_mark, uint8_t *serial)
{
uint8_t ret = 0;
if (!memcmp(&g_charger_manager.charger_piles[stake_mark - 1].login_info.charger_serial, serial, CHARGER_SERIAL_LENGTH))
{
ret = 1;
}
return ret;
}
// CRC 计算
const uint8_t gabyCRCHi[] =
{
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40,
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41,
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40,
0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40,
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40,
0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40,
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40,
0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0, 0x80, 0x41, 0x00, 0xc1,
0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
0x80, 0x41, 0x00, 0xc1, 0x81, 0x40};
const uint16_t gabyCRCLo[] =
{
0x00, 0xc0, 0xc1, 0x01, 0xc3, 0x03, 0x02, 0xc2, 0xc6, 0x06,
0x07, 0xc7, 0x05, 0xc5, 0xc4, 0x04, 0xcc, 0x0c, 0x0d, 0xcd,
0x0f, 0xcf, 0xce, 0x0e, 0x0a, 0xca, 0xcb, 0x0b, 0xc9, 0x09,
0x08, 0xc8, 0xd8, 0x18, 0x19, 0xd9, 0x1b, 0xdb, 0xda, 0x1a,
0x1e, 0xde, 0xdf, 0x1f, 0xdd, 0x1d, 0x1c, 0xdc, 0x14, 0xd4,
0xd5, 0x15, 0xd7, 0x17, 0x16, 0xd6, 0xd2, 0x12, 0x13, 0xd3,
0x11, 0xd1, 0xd0, 0x10, 0xf0, 0x30, 0x31, 0xf1, 0x33, 0xf3,
0xf2, 0x32, 0x36, 0xf6, 0xf7, 0x37, 0xf5, 0x35, 0x34, 0xf4,
0x3c, 0xfc, 0xfd, 0x3d, 0xff, 0x3f, 0x3e, 0xfe, 0xfa, 0x3a,
0x3b, 0xfb, 0x39, 0xf9, 0xf8, 0x38, 0x28, 0xe8, 0xe9, 0x29,
0xeb, 0x2b, 0x2a, 0xea, 0xee, 0x2e, 0x2f, 0xef, 0x2d, 0xed,
0xec, 0x2c, 0xe4, 0x24, 0x25, 0xe5, 0x27, 0xe7, 0xe6, 0x26,
0x22, 0xe2, 0xe3, 0x23, 0xe1, 0x21, 0x20, 0xe0, 0xa0, 0x60,
0x61, 0xa1, 0x63, 0xa3, 0xa2, 0x62, 0x66, 0xa6, 0xa7, 0x67,
0xa5, 0x65, 0x64, 0xa4, 0x6c, 0xac, 0xad, 0x6d, 0xaf, 0x6f,
0x6e, 0xae, 0xaa, 0x6a, 0x6b, 0xab, 0x69, 0xa9, 0xa8, 0x68,
0x78, 0xb8, 0xb9, 0x79, 0xbb, 0x7b, 0x7a, 0xba, 0xbe, 0x7e,
0x7f, 0xbf, 0x7d, 0xbd, 0xbc, 0x7c, 0xb4, 0x74, 0x75, 0xb5,
0x77, 0xb7, 0xb6, 0x76, 0x72, 0xb2, 0xb3, 0x73, 0xb1, 0x71,
0x70, 0xb0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9c, 0x5c,
0x5d, 0x9d, 0x5f, 0x9f, 0x9e, 0x5e, 0x5a, 0x9a, 0x9b, 0x5b,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4b, 0x8b,
0x8a, 0x4a, 0x4e, 0x8e, 0x8f, 0x4f, 0x8d, 0x4d, 0x4c, 0x8c,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40};
uint16_t calc_crc16(uint8_t *pData, uint8_t len)
{
uint8_t byCRCHi = 0xff;
uint8_t byCRCLo = 0xff;
uint8_t byIdx;
uint16_t crc;
while (len--)
{
byIdx = byCRCHi ^ *pData++;
byCRCHi = byCRCLo ^ gabyCRCHi[byIdx];
byCRCLo = gabyCRCLo[byIdx];
}
crc = byCRCHi;
crc <<= 8;
crc += byCRCLo;
return crc;
}
uint8_t unpack_server_data(uint8_t *buf, uint8_t len, SERVER_PACK *pack)
{
uint8_t ret = 0;
uint16_t calc_crc = 0;
if (!buf || len < SERVER_PACK_MIN_LEN)
{
return ret;
}
if (buf[0] == SERVER_PACK_START_FLAG)
{
pack->start_flag = buf[0];
pack->len = buf[1];
pack->serial = buf[2] + (buf[3] << 8);
pack->encrypt_flag = buf[4];
pack->frame_type = buf[5];
if (pack->len > 4)
{
pack->data = &buf[6];
memcpy(pack->data, &buf[6], pack->len - 4);
}
pack->crc = (buf[len - 2] << 8) + buf[len - 1];
calc_crc = calc_crc16(&buf[2], pack->len);
if (pack->crc == calc_crc)
{
ret = 1;
}
}
return ret;
}
void pack_and_send_server_data(uint8_t type, uint8_t *pdata, uint8_t len, uint8_t stake_mark)
{
uint8_t index = 0;
uint16_t crc = 0;
uint16_t current_serial = 0;
uint8_t *buf = NULL;
buf = (uint8_t *)pvPortMalloc(len + 14);
if (!buf)
{
return;
}
/* 原子获取全局包序号,避免多任务竞争 */
taskENTER_CRITICAL();
current_serial = pack_serial;
pack_serial++;
taskEXIT_CRITICAL();
buf[index++] = 0x55;
buf[index++] = 0xAA;
buf[index++] = 0x01;
buf[index++] = stake_mark;
buf[index++] = SERVER_PACK_START_FLAG;
buf[index++] = len + 4;
buf[index++] = (current_serial >> 8) & 0xFF;
buf[index++] = current_serial & 0xFF;
buf[index++] = 0x00;
buf[index++] = type;
if ((len > 0) && (pdata))
{
memcpy(&buf[index], pdata, len);
}
index = index + len;
crc = calc_crc16(&buf[6], len + 4);
buf[index++] = crc & 0xFF;
buf[index++] = (crc >> 8) & 0xFF;
buf[index++] = 0xAA;
buf[index++] = 0x55;
if (type == FRAME_TYPE_0X3B)
{
printf("北向0x3B 原始数据(%d字节): ", index);
for (uint16_t i = 0; i < index; i++)
printf("%02X ", buf[i]);
printf("\r\n");
}
Air724_Message_Send(buf, index);
vPortFree(buf);
}
int get_num_from_string(const char *str, int *num)
{
int len = strlen(str);
int index = 0;
int t = 0;
int flag = 0;
int i;
for (i = 0; i < len; i++)
{
flag = 0;
while ((str[i] >= '0' && str[i] <= '9') && (i < len))
{
t = str[i] - '0';
num[index] = num[index] * 10 + t;
i++;
flag = 1;
}
if (flag)
{
index++;
}
}
return index;
}
void get_cp56time_from_sec(uint8_t *buff, uint32_t sec)
{
struct tm timeinfo;
st_cp56time2a cp56time = {0};
time_t rawtime = sec;
localtime_r(&rawtime, &timeinfo);
cp56time.Compts.year = timeinfo.tm_year % 100; // 获取年份的最后两位数
cp56time.Compts.month = timeinfo.tm_mon + 1; // tm_mon是从0开始的所以需要加1
cp56time.Compts.mday = timeinfo.tm_mday;
cp56time.Compts.hour = timeinfo.tm_hour;
cp56time.Compts.min = timeinfo.tm_min;
cp56time.Compts.msec = timeinfo.tm_sec * 1000;
buff[0] = cp56time.time & 0xFF;
buff[1] = (cp56time.time >> 8) & 0xFF;
buff[2] = (cp56time.time >> 16) & 0xFF;
buff[3] = (cp56time.time >> 24) & 0xFF;
buff[4] = (cp56time.time >> 32) & 0xFF;
buff[5] = (cp56time.time >> 40) & 0xFF;
buff[6] = (cp56time.time >> 48) & 0xFF;
}
void update_current_fee_model(FEE_MODEL *model)
{
if (!model)
{
return;
}
memcpy(&current_setting_fee_model, model, sizeof(FEE_MODEL));
}
FEE_MODEL *get_setting_fee_model(void)
{
return &current_setting_fee_model;
}
uint16_t htons(uint16_t val)
{
return (val << 8) | (val >> 8);
}
#endif

View File

@@ -0,0 +1,505 @@
#ifndef __SERVER_COMMON_H__
#define __SERVER_COMMON_H__
#include <stdint.h>
#define SERVER_PACK_MAX_LEN 255
#define SERVER_PACK_MIN_LEN 8
#define SERVER_PACK_START_FLAG 0x68
#define CHARGER_SERIAL_LENGTH 7
#define TRADE_SERIAL_LENGTH 16
#define RSA_LENGTH 88
#define CHARGER_TYPE_DC 0
#define CHARGER_TYPE_AC 1
#define HEADER_LENGTH 6 // 消息体前的头部数据长度
#define FRAME_TYPE_0X01 0x01 // 充电桩登录认证
#define FRAME_TYPE_0X02 0x02
#define FRAME_TYPE_0X03 0x03 // 充电桩心跳包
#define FRAME_TYPE_0X04 0x04
#define FRAME_TYPE_0X05 0x05 // 计费模型验证请求
#define FRAME_TYPE_0X06 0x06
#define FRAME_TYPE_0X09 0x09 // 充电桩计费模型请求
#define FRAME_TYPE_0X0A 0x0A
#define FRAME_TYPE_0X12 0x12 // 读取实时监测数据
// charger ---> server
#define FRAME_TYPE_0X13 0x13 // 离线监测数据
#define FRAME_TYPE_0X15 0x15 // 充电握手
#define FRAME_TYPE_0X17 0x17 // 参数配置
#define FRAME_TYPE_0X19 0x19 // 充电结束
#define FRAME_TYPE_0X1B 0x1B // 错误报文
#define FRAME_TYPE_0X1D 0x1D // 充电阶段BMS中止
#define FRAME_TYPE_0X21 0x21 // 充电阶段充电机中止
#define FRAME_TYPE_0X23 0x23 // 充电过程BMS需求、充电机输出
#define FRAME_TYPE_0X25 0x25 // 充电过程BMS信息
//---------------------------------------
#define FRAME_TYPE_0X31 0x31 // 充电桩主动申请启动充电
#define FRAME_TYPE_0X32 0x32 // 运营平台确认启动充电
#define FRAME_TYPE_0X33 0x33 // 远程启动命令回复
#define FRAME_TYPE_0X34 0x34 // 运营平台远程控制启动
#define FRAME_TYPE_0X35 0x35 // 远程停机命令回复
#define FRAME_TYPE_0X36 0x36 // 运营平台远程停机
//---------------------------------------
#define FRAME_TYPE_0X3B 0x3B // 交易记录
#define FRAME_TYPE_0X40 0x40 // 交易记录确认
#define FRAME_TYPE_0X41 0x41 // 交易更新应答
#define FRAME_TYPE_0X42 0x42 // 远程账户余额更新
#define FRAME_TYPE_0X43 0x43 // 卡数据同步
#define FRAME_TYPE_0X44 0x44 // 离线卡数据同步
#define FRAME_TYPE_0X45 0x45 // 离线卡数据清除
#define FRAME_TYPE_0X46 0x46 // 离线卡数据清除应答
#define FRAME_TYPE_0X47 0x47 // 离线卡数据清除
#define FRAME_TYPE_0X48 0x48 // 离线卡数据清除应答
#define FRAME_TYPE_0X51 0x51 // 充电桩工作参数设置应答
#define FRAME_TYPE_0X52 0x52 // 充电桩工作参数设置
#define FRAME_TYPE_0X55 0x55 // 对时设置应答
#define FRAME_TYPE_0X56 0x56 // 对时设置
#define FRAME_TYPE_0X57 0x57 // 计费模型应答
#define FRAME_TYPE_0X58 0x58 // 计算模型设置
#define FRAME_TYPE_0X61 0x61 // 地锁数据上送
#define FRAME_TYPE_0X62 0x62 // 遥控地锁升锁与降锁
#define FRAME_TYPE_0X63 0x63 // 充电桩返回数据
#define FRAME_TYPE_0X91 0x91 // 远程启动应答
#define FRAME_TYPE_0X92 0x92 // 远程启动
#define FRAME_TYPE_0X93 0x93 // 远程更新应答
#define FRAME_TYPE_0X94 0x94 // 远程更新
#define FRAME_TYPE_0XA1 0xA1 // 充电桩主动申请并充充电
#define FRAME_TYPE_0XA2 0xA2 // 运营平台确认并充启动充电
#define FRAME_TYPE_0XA3 0xA3 // 远程并充启机命令回复
#define FRAME_TYPE_0XA4 0xA4 // 运营平台远程控制并充启机
#define FRAME_TYPE_0XA7 0xA7
#define FRAME_TYPE_0X91 0x91
#define FRAME_TYPE_0X92 0x92
#define FRAME_TYPE_MAX 0xFF
#define CAHRGE_START_MODE_APP 0x01
#define CAHRGE_START_MODE_CARD 0x02
#define CAHRGE_START_MODE_OFFLINE_CARD 0x04
#define CAHRGE_START_MODE_VIN 0x05
#define CAHRGE_START_MODE_IC_CARD 0xA0
#define CAHRGE_START_MODE_PASSWORD 0xA1
#define STOP_CHARGE_AS_APP 0x40
#define STOP_CHARGE_AS_FULL 0x41
#define STOP_CHARGE_AS_ENERGY_REACH 0x42
#define STOP_CHARGE_AS_MONEY_REACH 0x43
#define STOP_CHARGE_AS_TIME_REACH 0x44
#define STOP_CHARGE_AS_MANUAL 0x45
#define STOP_CHARGE_AS_NO_MONEY 0x6E
#define STOP_CHARGE_AS_EMERGY_STOP 0x72
#define STOP_CHARGE_AS_POWEROFF 0x83
typedef enum
{
STOP_REASON_APP = 0x40,
STOP_REASON_FULL = 0x41,
STOP_REASON_MANUAL = 0x45,
STOP_REASON_INSUFFICIENT_BALANCE = 0x6E,
STOP_REASON_EMERGENCY_STOP = 0x72,
STOP_REASON_OTHER // 其他原因可以添加在这里
} StopReason;
typedef struct
{
uint8_t start_flag;
uint8_t len;
uint16_t serial;
uint8_t encrypt_flag;
uint8_t frame_type;
uint8_t *data;
uint16_t crc;
} SERVER_PACK;
typedef struct
{
float voltage; // 电压 (单位: V)
float charge_energy; // 充电能量 (单位: kWh)
float current; // 电流 (单位: A)
unsigned long charge_time; // 充电时间 (单位: 秒)
float charge_money; // 充电费用 (单位: 元)
} CHARGING_GUN_CTRL;
typedef struct
{
uint16_t fee_model_no; // 计费模型编号
uint32_t shark_fee_ratio; // 尖费电费费率
uint32_t shark_service_ratio; // 尖服务费费率
uint32_t peak_fee_ratio; // 峰电费费率
uint32_t peak_service_ratio; // 峰服务费费率
uint32_t flat_fee_ratio; // 平电费费率
uint32_t flat_service_ratio; // 平服务费费率
uint32_t valley_fee_ratio; // 谷电费费率
uint32_t valley_service_ratio; // 谷服务费费率
uint8_t loss_ratio; // 计损比例
uint8_t fee_num[48]; // 0x00尖费率 0x01峰费率 0x02平费率 0x03谷费率
} __attribute__((packed)) FEE_MODEL;
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint8_t charger_type; // 桩类型,0--DC,1--AC
uint8_t gun_num; // 充电枪个数
uint8_t protocol_ver; // 协议版本
uint8_t software_ver[8]; // 程序版本
uint8_t net_conn_type; // 网络链接类型,0SIM卡,1:LAN,2WAN,03:others
uint8_t sim[10]; // SIM卡
uint8_t tele_factory; // 0x00,移动,02:电信,03:联通,04:其他
} __attribute__((packed)) PACK_DATA_0X01; // 0x01 充电桩登陆认证
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint8_t result; // 登陆结果
} __attribute__((packed)) PACK_DATA_0X02; // 0x02 充电桩登陆认证应答;
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint8_t gun_index; // 枪号
uint8_t gun_status; // 枪状态,0:正常,1:异常
} __attribute__((packed)) PACK_DATA_0X03; // 0x03
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint8_t gun_index; // 枪号
uint8_t ack; // 0
} __attribute__((packed)) PACK_DATA_0X04; // 0x04
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint16_t fee_model_no; // 计费模型编号
} __attribute__((packed)) PACK_DATA_0X05; // 05 计费模型验证请求
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint16_t fee_model_no; // 计费模型编号
uint8_t result; // 0x00 桩计费模型与平台一致,0x01 桩计费模型与平台不一致
} __attribute__((packed)) PACK_DATA_0X06; // 06 计费模型验证请求
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
} __attribute__((packed)) PACK_DATA_0X09; // 0x09 计费模型请求
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
FEE_MODEL model;
} __attribute__((packed)) PACK_DATA_0X0A; // 0x0A
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t status; // 状态
uint8_t gun_back; // 枪是否归位
uint8_t gun_is_insert; // 是否插枪
uint16_t out_voltage; // 输出电压,小数点后一位,待机置0
uint16_t out_current; // 输出电流,小数点后一位,待机置0
uint8_t gun_line_temp; // 枪线温度
uint8_t gun_line_num[8]; // 枪线编码
uint8_t soc; // 待机置零;交流桩置零
uint8_t battery_temp; // 电池组最高温度.整形,偏移量-50 oC待机置零交流桩置零
uint16_t charge_time; // 累计充电时间
uint16_t remain_time; // 剩余时间
uint32_t charge_energy; // 充电度数
uint32_t loss_energy; // 计损充电度数
uint32_t charge_money; // 已充金额
uint16_t hard_fault; // 硬件故障
} __attribute__((packed)) PACK_DATA_0X13; // 0x13
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t bms_soc; //
uint16_t bms_vol_low; //
uint16_t bms_vol_high;
uint8_t bms_temp_low; //
uint8_t bms_temp_high;
uint16_t charge_time; // 电桩累计充电时间
uint16_t out_energy; // 电桩输出能量
uint32_t charger_motor_no; // 充电桩电机编号
} __attribute__((packed)) PACK_DATA_0X19; // 0x19
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪号
uint16_t bms_vol_demand; // BMS电压需求 0.1V/位
uint16_t bms_cur_demand; // BMS电流需求 0.1A/位, -400A偏移
uint8_t bms_charge_mode; // BMS充电模式 0x01:恒压 0x02:恒流
uint16_t bms_vol_measure; // BMS充电电压测量值 0.1V/位
uint16_t bms_cur_measure; // BMS充电电流测量值 0.1A/位, -400A偏移
uint16_t bms_max_cell_vol; // BMS最高单体电压及组号 低12位:0.01V/位 高4位:组号
uint8_t bms_soc; // BMS当前SOC 1%/位 0~100%
uint16_t bms_remain_time; // BMS估算剩余充电时间 1min/位 0~600min
uint16_t charger_vol_output; // 电桩电压输出值 0.1V/位
uint16_t charger_cur_output; // 电桩电流输出值 0.1A/位, -400A偏移
uint16_t charge_time; // 累计充电时间 1min/位 0~600min
} __attribute__((packed)) PACK_DATA_0X23; // 0x23
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪号
uint8_t bms_max_cell_vol_num; // BMS最高单体电压所在编号 1/位,1偏移 1~256
uint8_t bms_max_temp; // BMS最高动力蓄电池温度 1°C/位,-50°C偏移
uint8_t max_temp_point_num; // 最高温度检测点编号 1/位,1偏移 1~128
uint8_t bms_min_temp; // 最低动力蓄电池温度 1°C/位,-50°C偏移
uint8_t min_temp_point_num; // 最低温度检测点编号 1/位,1偏移 1~128
uint8_t bms_alarm_1; // 位0-1:单体电压过高/过低 位2-3:SOC过高/过低 位4-5:充电过流 位6-7:温度过高 (<00>:正常 <01>:过高 <10>:过低)
uint8_t bms_alarm_2; // 位0-1:绝缘状态 位2-3:输出连接器状态 位4-5:充电禁止 位6-7:预留 (<00>:正常 <01>:异常 <10>:不可信)
} __attribute__((packed)) PACK_DATA_0X25; // 0x25
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t start_mode; // 启动方式,0x1刷卡启动,0x02,账号启动,0x03VIN码启动
uint8_t password_flag; // 是否需要密码
uint8_t card_no[8]; // 账号或物理卡号
uint8_t password[16]; // 对用户输入的密码进行16位MD5加密,小写上传
uint8_t vin[17]; // VIN码,非VIN码方式为0
} __attribute__((packed)) PACK_DATA_0X31; // 0x31
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t logic_card_no[8]; // 逻辑卡号
uint32_t remain_money; // 账户余额
uint8_t result; // 鉴权成功标志,0失败,1成功
uint8_t err_code; // 失败原因,0x01账户不存在,0x02账户冻结,0x03余额不足,0x04未结账记录,0x05桩停用
// 0x06账户不能在此桩充电,0x07密码错误,0x08电站电容不足,0x09VIN码不存在,
// 0x0A该桩存在未结账记录,0x0B该桩不支持刷卡
} __attribute__((packed)) PACK_DATA_0X32; // 0x32
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t result; // 启动结果
uint8_t err_code; // 失败原因
} __attribute__((packed)) PACK_DATA_0X33; // 0x33
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t logic_cardid[8]; // 逻辑卡号
uint8_t phy_cardid[8]; // 物理卡号
uint32_t remain_money; // 账户余额,0.01元
uint16_t max_power; // 最大功率
} __attribute__((packed)) PACK_DATA_0X34; // 0x34
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t result; // 停止结果,0x00失败 0x01成功
uint8_t err_code; // 失败原因,0x0无
} __attribute__((packed)) PACK_DATA_0X35; // 0x35
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
} __attribute__((packed)) PACK_DATA_0X36; // 0x36
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t start_time[7]; // 开始时间
uint8_t end_time[7]; // 结束时间
uint32_t shark_price; // 尖单价
uint32_t shark_energy; // 尖电量
uint32_t loss_shark_energy; // 计损尖电量
uint32_t shark_money; // 尖金额
uint32_t peak_price; // 峰单价
uint32_t peak_energy; // 峰电量
uint32_t loss_peak_energy; // 计损峰电量
uint32_t peak_money; // 峰金额
uint32_t flat_price; // 平单价
uint32_t flat_energy; // 平电量
uint32_t loss_flat_energy; // 计损平电量
uint32_t flat_money; // 平金额
uint32_t valley_price; // 谷单价
uint32_t valley_energy; // 谷电量
uint32_t loss_valley_energy; // 计损谷电量
uint32_t valley_money; // 谷金额
uint8_t meter_start_value[5];
uint8_t meter_end_value[5];
uint32_t total_energy; // 总电量
uint32_t loss_total_energy; // 计损总电量
uint32_t consumption; // 消费金额
uint8_t vin[17]; // 电动汽车唯一标识
uint8_t trade_flag; // 交易标识
uint8_t trade_time[7]; // 交易时间
uint8_t stop_reason; // 停止原因
uint8_t phy_cardid[8]; // 物理卡号
} __attribute__((packed)) PACK_DATA_0X3B; // 0x3B
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t logical_card_number[8]; // 逻辑卡号
uint8_t physical_card_number[8]; // 物理卡号
uint8_t balance[4]; // 账户余额
uint8_t max_power[2]; // 最大功率
} __attribute__((packed)) PACK_DATA_0XA8;
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t result[1];
uint8_t reson[1];
} __attribute__((packed)) PACK_DATA_0XA7;
typedef struct
{
uint8_t trade_serial[TRADE_SERIAL_LENGTH]; // 交易流水号
uint8_t result;
} __attribute__((packed)) PACK_DATA_0X40;
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t phy_cardid[8]; // 物理卡号
uint8_t result; // 0:修改成功,1:设备编码错误,2:卡号错误
} __attribute__((packed)) PACK_DATA_0X41;
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t gun_index; // 枪编号
uint8_t phy_cardid[8]; // 物理卡号
uint8_t remain_money[4]; // 修复后的余额
} __attribute__((packed)) PACK_DATA_0X42;
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t current_time[7];
} __attribute__((packed)) PACK_DATA_0X55;
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编号
uint8_t current_time[7];
} __attribute__((packed)) PACK_DATA_0X56;
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint8_t result; // 设置结果
} __attribute__((packed)) PACK_DATA_0X57; // 0x57
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
FEE_MODEL model;
} __attribute__((packed)) PACK_DATA_0X58; // 0x58
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint8_t control_mode;
} __attribute__((packed)) PACK_DATA_0X92; // 0x92
typedef struct
{
uint8_t charger_serial[CHARGER_SERIAL_LENGTH]; // 桩编码
uint8_t result;
} __attribute__((packed)) PACK_DATA_0X91; // 0x91
#pragma pack(1)
typedef volatile union
{
uint64_t time; // 低7字节是七个八位位组二进制时间
struct
{
uint16_t msec;
uint8_t min : 6;
uint8_t res1 : 1;
uint8_t iv : 1;
uint8_t hour : 5;
uint8_t res2 : 2;
uint8_t su : 1;
uint8_t mday : 5;
uint8_t wday : 3;
uint8_t month : 4;
uint8_t res3 : 4;
uint8_t year : 7;
uint8_t res4 : 1;
} Compts;
} st_cp56time2a;
#pragma pack()
struct QueueItem
{
uint8_t *data;
uint16_t length;
};
extern void load_charger_serial(uint8_t stake_mark, uint8_t *serial);
extern void set_charger_serial_for_server(uint8_t value, uint8_t offset);
extern uint8_t is_my_charger_serial(uint8_t stake_mark, uint8_t *serial);
extern void load_trade_serial(uint8_t stake_mark, uint8_t idx, uint8_t *serial);
extern void set_trade_serial(uint8_t stake_mark, uint8_t idx, uint8_t *serial);
extern void increase_heart_beat_cnt(void);
extern void reset_heart_beat_cnt(void);
extern uint32_t get_heart_beat_cnt(void);
extern uint8_t unpack_server_data(uint8_t *buf, uint8_t len, SERVER_PACK *pack);
extern void pack_and_send_server_data(uint8_t type, uint8_t *data, uint8_t len, uint8_t stake_mark);
extern int get_num_from_string(const char *str, int *num);
extern uint32_t get_card_logic_num(uint8_t idx);
extern uint32_t get_card_phy_num(uint8_t idx);
extern void set_card_phy_logic_num(uint8_t idx, uint8_t type, uint32_t num);
extern void get_cp56time_from_sec(uint8_t *buff, uint32_t sec);
extern void update_current_fee_model(FEE_MODEL *model);
extern FEE_MODEL *get_setting_fee_model(void);
extern void generateTransactionID(uint8_t *buffer, uint16_t bufferSize, uint8_t gun_num);
extern void printLocalTime();
extern void sendFailureReasonToQueue(StopReason reason);
uint16_t htons(uint16_t val);
#endif

View File

@@ -0,0 +1,279 @@
/**
* @file server_to_charger.c
* @brief 充电桩通信协议处理模块 下行数据
* @details 处理充电桩与服务器之间的通信协议,包括登录认证应答、心跳应答、计费模型验证应答、
* 等功能。支持字节码格式的数据交互,通过串口协议与服务器通信。
* @date 2025-01-09 14:32:15
* @version 1.0.0
* @copyright Copyright (c) 2026
*/
#include "global.h"
#include "server_to_charger.h"
#include "point_protocol.h"
#include "host_computer_protocol.h"
// 登录认证应答
void on_cmd_frame_type_0X02(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X02 data;
memcpy(&data, pack->data, sizeof(PACK_DATA_0X02));
if (is_my_charger_serial(stake_index, data.charger_serial))
{
if (data.result == 0)
{
g_charger_manager.charger_piles[stake_index - 1].is_online = 1;
#ifdef DEBUG
printf("北向桩ID %d 登陆成功!\r\n", stake_index);
#endif
}
}
}
// 心跳应答
void on_cmd_frame_type_0X04(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X04 data;
memcpy(&data, pack->data, sizeof(PACK_DATA_0X04));
#ifdef DEBUG
printf("北向桩ID %d 收到心跳应答!\r\n", stake_index);
#endif
}
// 计费模型验证应答
void on_cmd_frame_type_0X06(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X06 data;
memcpy(&data, pack->data, sizeof(PACK_DATA_0X06));
if (is_my_charger_serial(stake_index, data.charger_serial))
{
#ifdef DEBUG
printf("北向接收到计费模型验证应答模型ID%d, 验证结果:%d\r\n", data.fee_model_no, data.result);
#endif
}
}
// 获取计费模型
void on_cmd_frame_type_0X0A(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X0A data;
memcpy(&data, pack->data, sizeof(PACK_DATA_0X0A));
if (is_my_charger_serial(stake_index, data.charger_serial))
{
g_charger_manager.fee_model_global = data.model;
g_charger_manager.charger_piles[stake_index - 1].get_model = true;
#ifdef DEBUG
printf("北向:接收到计费模型应答,模型ID:%d\r\n", g_charger_manager.fee_model_global.fee_model_no);
printf("尖电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.shark_fee_ratio, g_charger_manager.fee_model_global.shark_service_ratio);
printf("峰电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.peak_fee_ratio, g_charger_manager.fee_model_global.peak_service_ratio);
printf("平电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.flat_fee_ratio, g_charger_manager.fee_model_global.flat_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("\n合并后的费率时间段 :\r\n");
printf("================================================================\r\n");
int i = 0;
while (i < 48)
{
uint8_t current_type = g_charger_manager.fee_model_global.fee_num[i];
const char *fee_name;
switch (current_type)
{
case 0x00:
fee_name = " 尖时 ";
break;
case 0x01:
fee_name = " 峰时 ";
break;
case 0x02:
fee_name = " 平时 ";
break;
case 0x03:
fee_name = " 谷时 ";
break;
default:
fee_name = " 未知 ";
break;
}
// 起始时间(第 i 个半小时段)
int start_hour = i / 2;
int start_min = (i % 2) * 30;
// 向后查找连续相同费率的结束位置
int j = i;
while (j < 48 && g_charger_manager.fee_model_global.fee_num[j] == current_type)
{
j++;
}
// j 是第一个不等于 current_type 的位置,所以结束段是 j-1
int end_idx = j - 1;
int end_hour = (end_idx + 1) / 2; // 结束时间是 (end_idx+1) 对应的半小时起点
int end_min = ((end_idx + 1) % 2) * 30;
// 处理跨天24:00 显示为 00:00
if (end_hour == 24)
{
end_hour = 0;
}
printf("%02d:%02d-%02d:%02d 为 %s 费率\r\n",
start_hour, start_min,
end_hour, end_min,
fee_name);
i = j; // 跳到下一个不同费率段
}
printf("================================================================\r\n");
#endif
}
}
void on_cmd_frame_type_0X58(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X58 data;
memcpy(&data, pack->data, sizeof(PACK_DATA_0X58));
if (is_my_charger_serial(stake_index, data.charger_serial))
{
g_charger_manager.fee_model_global = data.model;
g_charger_manager.charger_piles[stake_index - 1].get_model = true;
#ifdef DEBUG
printf("北向:接收到计费模型应答,模型ID:%d\r\n", g_charger_manager.fee_model_global.fee_model_no);
printf("尖电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.shark_fee_ratio, g_charger_manager.fee_model_global.shark_service_ratio);
printf("峰电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.peak_fee_ratio, g_charger_manager.fee_model_global.peak_service_ratio);
printf("平电费率:%d 服务费:%d\r\n", g_charger_manager.fee_model_global.flat_fee_ratio, g_charger_manager.fee_model_global.flat_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("\n合并后的费率时间段 :\r\n");
printf("================================================================\r\n");
int i = 0;
while (i < 48)
{
uint8_t current_type = g_charger_manager.fee_model_global.fee_num[i];
const char *fee_name;
switch (current_type)
{
case 0x00:
fee_name = " 尖时 ";
break;
case 0x01:
fee_name = " 峰时 ";
break;
case 0x02:
fee_name = " 平时 ";
break;
case 0x03:
fee_name = " 谷时 ";
break;
default:
fee_name = " 未知 ";
break;
}
// 起始时间(第 i 个半小时段)
int start_hour = i / 2;
int start_min = (i % 2) * 30;
// 向后查找连续相同费率的结束位置
int j = i;
while (j < 48 && g_charger_manager.fee_model_global.fee_num[j] == current_type)
{
j++;
}
// j 是第一个不等于 current_type 的位置,所以结束段是 j-1
int end_idx = j - 1;
int end_hour = (end_idx + 1) / 2; // 结束时间是 (end_idx+1) 对应的半小时起点
int end_min = ((end_idx + 1) % 2) * 30;
// 处理跨天24:00 显示为 00:00
if (end_hour == 24)
{
end_hour = 0;
}
printf("%02d:%02d-%02d:%02d 为 %s 费率\r\n",
start_hour, start_min,
end_hour, end_min,
fee_name);
i = j; // 跳到下一个不同费率段
}
printf("================================================================\r\n");
#endif
charger_to_server_0x57(stake_index);
}
}
// 平台控制充电
void on_cmd_frame_type_0X34(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X34 data;
uint8_t result = TRUE; // 默认启动结果成功
uint8_t err_code = 0; // 默认错误码为0
memcpy(&data, pack->data, sizeof(PACK_DATA_0X34));
set_trade_serial(stake_index, data.gun_index, data.trade_serial);
printf(" └── [info] 桩ID:%d, 枪ID:%d, 金额:%d\r\n", stake_index, data.gun_index, data.remain_money);
if (is_my_charger_serial(stake_index, data.charger_serial) == FALSE)
{
result = 0;
err_code = 0x1; // 桩编号不匹配
charger_to_server_0X33(stake_index, data.gun_index, result, err_code);
g_charger_manager.charger_piles[stake_index - 1].guns[data.gun_index - 1].charger_state = FAIL_CHARGER_STATE;
return;
}
point_send_start_charging(stake_index, data.gun_index);
g_charger_manager.charger_piles[stake_index - 1].guns[data.gun_index - 1].charger_state = REDAY_CHARGER_START_STATE;
printf(" └── [info] 等待桩启动回应...\r\n");
}
// 平台控制停止充电
void on_cmd_frame_type_0X36(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X36 data;
uint8_t result = TRUE; // 默认停止结果成功
uint8_t err_code = 0; // 默认错误码为0
memcpy(&data, pack->data, sizeof(PACK_DATA_0X36));
printf("北向:平台控制停止充电,桩ID:%d, 枪ID:%d\r\n", stake_index, data.gun_index);
if (!is_my_charger_serial(stake_index, data.charger_serial))
{
result = 0;
err_code = 0x1; // 桩编号不匹配
}
else if (g_charger_manager.charger_piles[stake_index - 1].guns[data.gun_index - 1].real_time_data.status != 3)
{
result = 0;
err_code = 0x2; // 枪未在使用
}
charger_to_server_0X35(stake_index, data.gun_index, result, err_code);
point_send_stop_charging(stake_index, data.gun_index);
}
// 平台接收到交易记录
void on_cmd_frame_type_0X40(uint8_t stake_index, SERVER_PACK *pack)
{
PACK_DATA_0X40 data;
memcpy(&data, pack->data, sizeof(PACK_DATA_0X40));
printf("北向:平台接收到交易记录,桩ID:%d, 交易记录:%s\r\n", stake_index, data.trade_serial);
if (data.result == 0)
{
printf("1234567890987654324567898765434567890-98765165165165*************************************成功\r\n");
return;
}
}

View File

@@ -0,0 +1,16 @@
#ifndef __SERVER_TO_CHARGER_H__
#define __SERVER_TO_CHARGER_H__
#include <stdint.h>
#include "server_common.h"
void on_cmd_frame_type_0X02(uint8_t stake_index,SERVER_PACK *pack);
void on_cmd_frame_type_0X04(uint8_t stake_index,SERVER_PACK *pack);
void on_cmd_frame_type_0X0A(uint8_t stake_index,SERVER_PACK *pack);
void on_cmd_frame_type_0X06(uint8_t stake_index,SERVER_PACK *pack);
void on_cmd_frame_type_0X58(uint8_t stake_index,SERVER_PACK *pack);
void on_cmd_frame_type_0X34(uint8_t stake_index,SERVER_PACK *pack);
void on_cmd_frame_type_0X36(uint8_t stake_index,SERVER_PACK *pack);
void on_cmd_frame_type_0X40(uint8_t stake_index,SERVER_PACK *pack);
#endif

View File

@@ -0,0 +1,34 @@
#include "ykc_router.h"
static const ykc_route_entry_t YKC_ROUTE_TABLE[] = {
{FRAME_TYPE_0X02, on_cmd_frame_type_0X02, " 登录认证应答 "},
{FRAME_TYPE_0X04, on_cmd_frame_type_0X04, " 心跳应答 "},
{FRAME_TYPE_0X06, on_cmd_frame_type_0X06, " 计费模型验证应答 "},
{FRAME_TYPE_0X0A, on_cmd_frame_type_0X0A, " 获取计费模型 "},
{FRAME_TYPE_0X58, on_cmd_frame_type_0X58, " 计费模型设置 "},
{FRAME_TYPE_0X34, on_cmd_frame_type_0X34, " 平台启动充电 "},
{FRAME_TYPE_0X36, on_cmd_frame_type_0X36, " 平台控制停止充电 "},
{FRAME_TYPE_0X40, on_cmd_frame_type_0X40, " 平台接收到交易记录 "},
};
#define YKC_ROUTE_TABLE_SIZE (sizeof(YKC_ROUTE_TABLE) / sizeof(YKC_ROUTE_TABLE[0]))
void ykc_route_dispatch(uint8_t stake_mark, SERVER_PACK *pack)
{
if (!pack)
return;
for (size_t i = 0; i < YKC_ROUTE_TABLE_SIZE; i++)
{
if (pack->frame_type == YKC_ROUTE_TABLE[i].frame_type)
{
printf("[YKC Router] frame=0x%02X │ stake=%d │ %s\r\n",
pack->frame_type, stake_mark, YKC_ROUTE_TABLE[i].desc);
YKC_ROUTE_TABLE[i].handler(stake_mark, pack);
return;
}
}
printf("[YKC Router] 未知帧类型: 0x%02X from stake %d\r\n",
pack->frame_type, stake_mark);
}

View File

@@ -0,0 +1,19 @@
#ifndef __YKC_ROUTER_H
#define __YKC_ROUTER_H
#include "global.h"
#include "server_common.h"
#include "server_to_charger.h"
typedef void (*ykc_handler_t)(uint8_t stake_index, SERVER_PACK *pack);
typedef struct
{
uint8_t frame_type; /* 帧类型(匹配键) */
ykc_handler_t handler; /* 处理函数 */
const char *desc; /* 描述(日志用) */
} ykc_route_entry_t;
void ykc_route_dispatch(uint8_t stake_mark, SERVER_PACK *pack);
#endif

View File

@@ -1,793 +0,0 @@
#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地址
ip4_addr_t server_ip = IPADDR4_INIT_BYTES(10, 12, 19, 107); // 上位机管理地址
/**
* @brief 将十六进制字符串转换为字节数组
* @note 输入字符串长度必须为偶数
*/
int hex_string_to_bytes(const char *hex_str, uint8_t *bytes, int len)
{
for (int i = 0; i < len; i++)
{
unsigned int val;
sscanf(hex_str + i * 2, "%02x", &val);
bytes[i] = (uint8_t)val;
}
return 0;
}
/**
* @brief 将字节数组转换为十六进制字符串
* @note 输出字符串长度必须为32
*/
void bytes_to_hex_string(const uint8_t *bytes, char *hex_str)
{
for (int i = 0; i < 16; i++)
{
sprintf(hex_str + i * 2, "%02X", bytes[i]);
}
}
/**
* @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;
case 7:
dst_ip = &server_ip;
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 > MAX_CHARGER_COUNT)
{
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 > MAX_CHARGER_COUNT)
{
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);
}
/**
* @brief 解析充电桩实时数据指令
* @note 回复实时数据应答
* @param stake_index 桩索引
* @param json_pack json数据包
*/
void local_on_cmd_callback_realtime_data_response(uint8_t stake_index, cJSON *json_pack)
{
if (stake_index > MAX_CHARGER_COUNT)
{
return;
}
cJSON *transaction_id = cJSON_GetObjectItem(json_pack, "transaction_id"); // 提取交易序列号
cJSON *gun_id = cJSON_GetObjectItem(json_pack, "gun_id"); // 提取枪号
cJSON *state = cJSON_GetObjectItem(json_pack, "state"); // 提取枪状态
cJSON *gun_back = cJSON_GetObjectItem(json_pack, "gun_back"); // 提取枪归位状态
cJSON *gun_insert = cJSON_GetObjectItem(json_pack, "gun_insert"); // 提取枪插入状态
cJSON *voltage = cJSON_GetObjectItem(json_pack, "voltage"); // 提取电压
cJSON *current = cJSON_GetObjectItem(json_pack, "current"); // 提取电流
cJSON *cable_temp = cJSON_GetObjectItem(json_pack, "cable_temp"); // 提取电缆温度
cJSON *cable_code = cJSON_GetObjectItem(json_pack, "cable_code"); // 提取电缆编码
cJSON *soc = cJSON_GetObjectItem(json_pack, "soc"); // 提取SOC
cJSON *battery_temp = cJSON_GetObjectItem(json_pack, "battery_temp"); // 提取电池温度
cJSON *charge_time = cJSON_GetObjectItem(json_pack, "charge_time"); // 提取充电时间
cJSON *remain_time = cJSON_GetObjectItem(json_pack, "remain_time"); // 提取剩余时间
cJSON *charge_kwh = cJSON_GetObjectItem(json_pack, "charge_kwh"); // 提取充电量
cJSON *loss_kwh = cJSON_GetObjectItem(json_pack, "loss_kwh"); // 提取损失量
cJSON *charge_amount = cJSON_GetObjectItem(json_pack, "charge_amount"); // 提取充电金额
cJSON *fault = cJSON_GetObjectItem(json_pack, "fault"); // 提取故障状态
// 检查交易序列号、枪号、状态字段是否存在
if (!transaction_id || !gun_id || !state)
{
printf(" └── [error] 缺少必要字段 transaction_id/gun_id/state\r\n");
return;
}
// 检查交易序列号长度是否为32位
if (strlen(transaction_id->valuestring) != 32)
{
printf(" └── [error] transaction_id长度错误: %d\r\n", (int)strlen(transaction_id->valuestring));
return;
}
// 检查枪号是否有效
uint8_t gun_idx = (uint8_t)gun_id->valueint;
if (gun_idx == 0 || gun_idx > MAX_GUN_PER_CHARGER)
{
printf(" └── [error] 无效的gun_id: %d\r\n", gun_idx);
return;
}
ChargerPile *pile = &g_charger_manager.charger_piles[stake_index - 1];
ChargerGun *gun = &pile->guns[gun_idx - 1];
// 更新枪状态
gun->real_time_data.status = (uint8_t)state->valueint;
// 更新枪归位状态
gun->real_time_data.gun_back = (uint8_t)gun_back->valueint;
// 更新枪插入状态
gun->real_time_data.gun_is_insert = (uint8_t)gun_insert->valueint;
// 更新电压
gun->real_time_data.out_voltage = (uint16_t)(voltage->valuedouble * 10);
// 更新电流
gun->real_time_data.out_current = (uint16_t)(current->valuedouble * 10);
// 更新电缆温度
gun->real_time_data.gun_line_temp = (uint8_t)cable_temp->valueint;
// 更新SOC
gun->real_time_data.soc = (uint8_t)soc->valueint;
// 更新电池温度
gun->real_time_data.battery_temp = (uint8_t)battery_temp->valueint;
// 更新累计充电时间
gun->real_time_data.charge_time = (uint16_t)charge_time->valueint;
// 更新剩余时间
gun->real_time_data.remain_time = (uint16_t)remain_time->valueint;
// 更新充电量
gun->real_time_data.charge_energy = (uint32_t)(charge_kwh->valuedouble * 10000);
// 更新损失量
gun->real_time_data.loss_energy = (uint32_t)(loss_kwh->valuedouble * 10000);
// 更新充电金额
gun->real_time_data.charge_money = (uint32_t)(charge_amount->valuedouble * 10000);
// 更新故障状态
gun->real_time_data.hard_fault = (uint16_t)fault->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", "realtime data");
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 桩索引 (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", 00.00);
/* 创建计费模型数组 */
billing_array = cJSON_CreateArray();
if (billing_array == NULL)
{
printf("Failed to create billing array\r\n");
cJSON_Delete(root);
return;
}
/* 计费时段:生成简化格式 [start_time, end_time, electricity_fee, service_fee] */
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++;
}
// 计算起始时间(小时.半小时6.0=6:00, 6.5=6:30
float start_time = i * 0.5f;
float end_time = j * 0.5f;
// 跨天处理24.0显示为0.0
if (end_time == 24.0f)
{
end_time = 0.0f;
}
// 根据费率类型设置电费和服务费
float electricity_fee = 0.0f;
float service_fee = 0.0f;
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;
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;
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;
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;
break;
default:
electricity_fee = 0.0f;
service_fee = 0.0f;
break;
}
// 创建计费时段数组 [start_time, end_time, electricity_fee, service_fee]
if (electricity_fee > 0 || service_fee > 0)
{
billing_item = cJSON_CreateArray();
if (billing_item != NULL)
{
cJSON_AddNumberToObject(billing_item, NULL, start_time);
cJSON_AddNumberToObject(billing_item, NULL, end_time);
cJSON_AddNumberToObject(billing_item, NULL, electricity_fee);
cJSON_AddNumberToObject(billing_item, NULL, service_fee);
cJSON_AddItemToArray(billing_array, billing_item);
}
}
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));
}
void local_on_cmd_callback_server_login_response(cJSON *json_pack)
{
cJSON *request_id = cJSON_GetObjectItem(json_pack, "request_id");
cJSON *username = cJSON_GetObjectItem(json_pack, "username");
cJSON *password = cJSON_GetObjectItem(json_pack, "password");
cJSON *root = NULL;
char *str = NULL;
root = cJSON_CreateObject();
if (root == NULL)
{
printf("Failed to create JSON object for server login\r\n");
return;
}
/* 添加一条字符串类型的JSON数据(添加一个链表节点) */
cJSON_AddStringToObject(root, "request_id", request_id->valuestring);
if (strcmp(username->valuestring, "admin") != 0 || strcmp(password->valuestring, "123456") != 0)
{
cJSON_AddStringToObject(root, "error", "用户或密码错误 ");
cJSON_AddBoolToObject(root, "success", cJSON_False);
printf("登录失败\r\n");
}
else
{
cJSON_AddBoolToObject(root, "success", cJSON_True);
printf("登录成功\r\n");
}
str = cJSON_Print(root);
udp_send_response(7, str, strlen(str));
free(str);
cJSON_Delete(root);
}
// 处理获取状态查询指令
void local_on_cmd_callback_server_get_status_response(cJSON *json_pack)
{
cJSON *request_id = cJSON_GetObjectItem(json_pack, "request_id");
cJSON *root = NULL;
cJSON *piles = NULL;
cJSON *pile = NULL;
cJSON *guns = NULL;
cJSON *gun = NULL;
char *str = NULL;
root = cJSON_CreateObject();
if (root == NULL)
{
printf("Failed to create JSON object for get_status\r");
return;
}
cJSON_AddStringToObject(root, "request_id", request_id->valuestring);
cJSON_AddBoolToObject(root, "success", cJSON_True);
/* ── piles 数组 ── */
piles = cJSON_CreateArray();
/* 桩 1 */
pile = cJSON_CreateObject();
cJSON_AddStringToObject(pile, "serial", "32010601111558");
cJSON_AddBoolToObject(pile, "is_online", cJSON_True);
guns = cJSON_CreateArray();
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 1);
cJSON_AddItemToArray(guns, gun);
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
cJSON_AddItemToObject(pile, "guns", guns);
cJSON_AddItemToArray(piles, pile);
/* 桩 2 */
pile = cJSON_CreateObject();
cJSON_AddStringToObject(pile, "serial", "32010601111559");
cJSON_AddBoolToObject(pile, "is_online", cJSON_True);
guns = cJSON_CreateArray();
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 1);
cJSON_AddItemToArray(guns, gun);
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 1);
cJSON_AddItemToArray(guns, gun);
cJSON_AddItemToObject(pile, "guns", guns);
cJSON_AddItemToArray(piles, pile);
/* 桩 3 */
pile = cJSON_CreateObject();
cJSON_AddStringToObject(pile, "serial", "32010601111560");
cJSON_AddBoolToObject(pile, "is_online", cJSON_True);
guns = cJSON_CreateArray();
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
cJSON_AddItemToObject(pile, "guns", guns);
cJSON_AddItemToArray(piles, pile);
/* 桩 4 */
pile = cJSON_CreateObject();
cJSON_AddStringToObject(pile, "serial", "32010601111561");
cJSON_AddBoolToObject(pile, "is_online", cJSON_False);
guns = cJSON_CreateArray();
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
cJSON_AddItemToObject(pile, "guns", guns);
cJSON_AddItemToArray(piles, pile);
/* 桩 5 */
pile = cJSON_CreateObject();
cJSON_AddStringToObject(pile, "serial", "32010601111562");
cJSON_AddBoolToObject(pile, "is_online", cJSON_True);
guns = cJSON_CreateArray();
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 1);
cJSON_AddItemToArray(guns, gun);
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
cJSON_AddItemToObject(pile, "guns", guns);
cJSON_AddItemToArray(piles, pile);
/* 桩 6 */
pile = cJSON_CreateObject();
cJSON_AddStringToObject(pile, "serial", "32010601111563");
cJSON_AddBoolToObject(pile, "is_online", cJSON_False);
guns = cJSON_CreateArray();
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
gun = cJSON_CreateObject();
cJSON_AddNumberToObject(gun, "status", 0);
cJSON_AddItemToArray(guns, gun);
cJSON_AddItemToObject(pile, "guns", guns);
cJSON_AddItemToArray(piles, pile);
cJSON_AddItemToObject(root, "piles", piles);
/* ── 发送 ── */
str = cJSON_Print(root);
udp_send_response(7, str, strlen(str));
free(str);
cJSON_Delete(root);
printf("get_status 回复已发送 \r\n");
}
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 if (strcmp(cmd, "realtime data") == 0)
{
printf("南向:收到电桩 %d 实时数据指令 \r\n", id);
local_on_cmd_callback_realtime_data_response(id, json_pack);
}
/*处理上位机指令 */
// 处理登录指令
else if (strcmp(cmd, "server_login") == 0)
{
printf("上位机:收到登录指令 \r\n");
local_on_cmd_callback_server_login_response(json_pack);
}
// 处理获取状态查询指令
else if (strcmp(cmd, "server_get_status") == 0)
{
printf("上位机:收到获取状态查询指令 \r\n");
local_on_cmd_callback_server_get_status_response(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';
}

View File

@@ -1,31 +0,0 @@
#ifndef __CHARGERTASK_H
#define __CHARGERTASK_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
#include "lwip/opt.h"
#include "lwip/api.h"
#include "lwip/sys.h"
/* macro ------------------------------------------------------------------------------------------------*/
/* struct ------------------------------------------------------------------------------------------------*/
// 定义UDP消息体
typedef struct {
ip4_addr_t src_ip; // 来源IP
uint16_t src_port; // 来源端口
uint16_t len; // 数据长度
char *data; // 数据指针(动态分配)
} UdpMsg_t;
//void Charger_Task_Init(void);
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);
#endif /* __CHARGERTASK_H */

View File

@@ -1,131 +0,0 @@
/**
******************************************************************************
* @file user\task\HeartBeatTask.c
* @author luhuaishuai
* @version v0.1
* @date 2026-1-12
* @brief Briefly describe the function of your function
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "YkcTask.h"
#include "ChargerTask.h"
#include "lwip/opt.h"
#include "lwip/api.h"
#include "lwip/sys.h"
/**
* @funNm : YkcTask_Function
* @brief : 云快充平台交互任务
* @param : argument
* @retval: void
*/
uint8_t SETP = 0;
void YkcTask_Function(void const *argument)
{
init_chargers(); /* 初始化桩结构体*/
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* 等待桩通讯协议层完成*/
osDelay(5000);
// send_server_address_to_air724();
while (1)
{
TaskRunTimeStat.YkcTask.threads_runtime = GetTask_RunTime(YkcTaskID);
TaskRunTimeStat.YkcTask.threads_counter = GetTask_Beatcnt(YkcTaskID);
TaskRunTimeStat.YkcTask.threads_freestack = Get_Free_Stack(YkcTaskID);
switch (SETP)
{
case 0:
{
// 等待桩上电
if (!g_charger_manager.charger_piles[1 - 1].is_udp_online)
{
uint8_t open_cmd[] = {0x55, 0xAA, 0x3, 0x01, 0x01, 0xAA, 0x55};
Air724_Message_Send(open_cmd, sizeof(open_cmd));
osDelay(3000);
SETP = 1;
}
else
{
printf("网关:等待桩上电指令\r\n");
osDelay(1000);
SETP = 0;
}
}
break;
// 云快充登录认证
case 1:
{
if (!g_charger_manager.charger_piles[1 - 1].is_online)
{
charger_to_server_0X01(1);
}
else
{
SETP = 2;
charger_to_server_0X05(1,1);
}
osDelay(5000);
}
break;
// 云快充计费模型请求
case 2:
{
if (!g_charger_manager.charger_piles[1 - 1].get_model)
charger_to_server_0X09(1); // 桩1计费模型请求
else
SETP = 3;
osDelay(3000);
}
break;
case 3:
{
charger_to_server_0X13(1, 1); // 上传状态
// local_on_cmd_send_start_charging(1, 1);
osDelay(3000);
}
break;
default:
break;
}
// osDelay(2000);
}
}
/**
* @brief 解析云快充平台下行数据
* @note 解析云快充平台下行数据,根据不同的帧类型调用相应的处理函数
* @param stake_mark 桩编号
* @param pack 指向SERVER_PACK结构体的指针包含下行数据
*/
void handle_ykc_downlink(uint8_t stake_mark, SERVER_PACK *pack)
{
switch (pack->frame_type)
{
case FRAME_TYPE_0X02:
on_cmd_frame_type_0X02(stake_mark, pack);
break;
case FRAME_TYPE_0X04:
on_cmd_frame_type_0X04(stake_mark, pack);
break;
case FRAME_TYPE_0X06:
on_cmd_frame_type_0X06(stake_mark, pack);
break;
case FRAME_TYPE_0X0A:
on_cmd_frame_type_0X0A(stake_mark, pack);
break;
case FRAME_TYPE_0X58:
on_cmd_frame_type_0X58(stake_mark, pack);
break;
case FRAME_TYPE_0X34:
on_cmd_frame_type_0X34(stake_mark, pack);
break;
}
}

View File

@@ -1,11 +0,0 @@
#ifndef __YKCTASK_H
#define __YKCTASK_H
/* includes ----------------------------------------------------------------------------------------------*/
#include "global.h"
void handle_ykc_downlink(uint8_t stake_mark, SERVER_PACK *pack);
#endif /* __YKCTASK_H */