Update FreeRTOS configuration and enhance upper computer AI documentation
- Increased total heap size in FreeRTOSConfig.h from 10000 to 18000. - Modified stack sizes for various tasks in freertos.c to improve performance: - initTask: 128 to 256 - CarCtrlTask: 256 to 1024 - timerTask: 512 to 1024 - sr04Task: 128 to 512 - rc522Task: 128 to 512 - Added comprehensive upper computer AI guide for TCP client implementation, detailing protocol contracts, command sets, telemetry, architecture requirements, and error handling strategies. - Introduced a Python web upper computer AI guide with a recommended tech stack and project structure. - Created a Chinese version of the upper computer AI guide for local developers. - Updated STM32 project configuration to reflect new task stack sizes and heap settings. - Adjusted configuration files for various tools to ensure compatibility with the new project structure.
This commit is contained in:
@@ -21,8 +21,11 @@ HAL_StatusTypeDef ESP12F_TCP_SendMessage(const char *data) {
|
|||||||
return HAL_ERROR; // 数据为空
|
return HAL_ERROR; // 数据为空
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动 DMA 传输
|
/*
|
||||||
return HAL_UART_Transmit_DMA(&huart1, (uint8_t *)data, size);
|
* 这里使用阻塞发送而不是 DMA:协议帧很短,且发送缓冲区通常来自局部变量。
|
||||||
|
* 使用 DMA 会引入发送缓冲区生命周期问题,容易导致反馈丢失或数据不稳定。
|
||||||
|
*/
|
||||||
|
return HAL_UART_Transmit(&huart1, (uint8_t *)data, size, 50U);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* External DMA handle */
|
/* External DMA handle */
|
||||||
|
|||||||
@@ -40,6 +40,9 @@
|
|||||||
#define OBSTACLE_STOP_DISTANCE_CM 18.0f
|
#define OBSTACLE_STOP_DISTANCE_CM 18.0f
|
||||||
#define OBSTACLE_RECOVER_DISTANCE_CM 22.0f
|
#define OBSTACLE_RECOVER_DISTANCE_CM 22.0f
|
||||||
|
|
||||||
|
/* 手动控制方向斜向速度缩放,避免斜向合速度过大 */
|
||||||
|
#define MANUAL_DIAG_SCALE 0.7071f
|
||||||
|
|
||||||
/* 电机位置定义 (基于用户规定) */
|
/* 电机位置定义 (基于用户规定) */
|
||||||
#define MOTOR_LR MOTOR_1 // 左后 (Left Rear)
|
#define MOTOR_LR MOTOR_1 // 左后 (Left Rear)
|
||||||
#define MOTOR_LF MOTOR_2 // 左前 (Left Front)
|
#define MOTOR_LF MOTOR_2 // 左前 (Left Front)
|
||||||
@@ -59,12 +62,75 @@ static uint8_t obstacle_locked = 0; // 避障锁(1=有障碍锁定)
|
|||||||
static track_ir_ctrl_output_t track_output = {0};
|
static track_ir_ctrl_output_t track_output = {0};
|
||||||
static uint8_t track_line_mask = 0;
|
static uint8_t track_line_mask = 0;
|
||||||
|
|
||||||
|
static void CarCtrl_StopAll(void);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CTRL_MODE_AUTO = 0,
|
||||||
|
CTRL_MODE_MANUAL
|
||||||
|
} ctrl_mode_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MAN_DIR_STOP = 0,
|
||||||
|
MAN_DIR_FWD,
|
||||||
|
MAN_DIR_BWD,
|
||||||
|
MAN_DIR_LEFT,
|
||||||
|
MAN_DIR_RIGHT,
|
||||||
|
MAN_DIR_LF,
|
||||||
|
MAN_DIR_RF,
|
||||||
|
MAN_DIR_LB,
|
||||||
|
MAN_DIR_RB,
|
||||||
|
MAN_DIR_CW,
|
||||||
|
MAN_DIR_CCW
|
||||||
|
} manual_dir_t;
|
||||||
|
|
||||||
|
static ctrl_mode_t car_ctrl_mode = CTRL_MODE_AUTO;
|
||||||
|
static manual_dir_t manual_dir = MAN_DIR_STOP;
|
||||||
|
|
||||||
#define CAR_STATION_MIN ((uint16_t)STATION_1)
|
#define CAR_STATION_MIN ((uint16_t)STATION_1)
|
||||||
#define CAR_STATION_MAX ((uint16_t)STATION_2)
|
#define CAR_STATION_MAX ((uint16_t)STATION_2)
|
||||||
|
|
||||||
/* 4个电机的 PID 控制器 */
|
/* 4个电机的 PID 控制器 */
|
||||||
static PID_TypeDef motor_pid[MOTOR_COUNT];
|
static PID_TypeDef motor_pid[MOTOR_COUNT];
|
||||||
|
|
||||||
|
static const char *CarCtrl_ModeToString(ctrl_mode_t mode)
|
||||||
|
{
|
||||||
|
return (mode == CTRL_MODE_MANUAL) ? "MAN" : "AUTO";
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *CarCtrl_ManualDirToString(manual_dir_t dir)
|
||||||
|
{
|
||||||
|
switch (dir) {
|
||||||
|
case MAN_DIR_FWD: return "FWD";
|
||||||
|
case MAN_DIR_BWD: return "BWD";
|
||||||
|
case MAN_DIR_LEFT: return "LEFT";
|
||||||
|
case MAN_DIR_RIGHT:return "RIGHT";
|
||||||
|
case MAN_DIR_LF: return "LF";
|
||||||
|
case MAN_DIR_RF: return "RF";
|
||||||
|
case MAN_DIR_LB: return "LB";
|
||||||
|
case MAN_DIR_RB: return "RB";
|
||||||
|
case MAN_DIR_CW: return "CW";
|
||||||
|
case MAN_DIR_CCW: return "CCW";
|
||||||
|
case MAN_DIR_STOP:
|
||||||
|
default:
|
||||||
|
return "STOP";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CarCtrl_SetManualDirection(manual_dir_t dir)
|
||||||
|
{
|
||||||
|
manual_dir = dir;
|
||||||
|
if (dir == MAN_DIR_STOP) {
|
||||||
|
car_running = 0U;
|
||||||
|
target_v_x = 0.0f;
|
||||||
|
target_v_y = 0.0f;
|
||||||
|
target_v_w = 0.0f;
|
||||||
|
CarCtrl_StopAll();
|
||||||
|
} else {
|
||||||
|
car_running = 1U;
|
||||||
|
car_target_reached = 0U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化闭环控制系统
|
* @brief 初始化闭环控制系统
|
||||||
*/
|
*/
|
||||||
@@ -77,6 +143,8 @@ void CarCtrl_InitClosedLoop(void)
|
|||||||
|
|
||||||
track_ir_algo_init();
|
track_ir_algo_init();
|
||||||
obstacle_locked = 0U;
|
obstacle_locked = 0U;
|
||||||
|
car_ctrl_mode = CTRL_MODE_AUTO;
|
||||||
|
manual_dir = MAN_DIR_STOP;
|
||||||
BEEP_Off();
|
BEEP_Off();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +181,10 @@ static void CarCtrl_CheckTargetStation(void)
|
|||||||
rc522_card_info_t card;
|
rc522_card_info_t card;
|
||||||
station_id_t station;
|
station_id_t station;
|
||||||
|
|
||||||
|
if (car_ctrl_mode != CTRL_MODE_AUTO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (car_running == 0U || car_target_reached != 0U || car_target_station == 0U) {
|
if (car_running == 0U || car_target_reached != 0U || car_target_station == 0U) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -168,8 +240,66 @@ void CarCtrl_UpdateClosedLoop(void)
|
|||||||
|
|
||||||
BEEP_Off();
|
BEEP_Off();
|
||||||
|
|
||||||
track_line_mask = track_ir_get_line_mask_filtered();
|
if (car_ctrl_mode == CTRL_MODE_AUTO) {
|
||||||
track_ir_algo_step(track_line_mask, &track_output);
|
track_line_mask = track_ir_get_line_mask_filtered();
|
||||||
|
track_ir_algo_step(track_line_mask, &track_output);
|
||||||
|
|
||||||
|
// 从前进速度分量计算基础速度(自动循迹)
|
||||||
|
target_v_x = (float)car_speed_percent * TARGET_MAX_RPM / 100.0f;
|
||||||
|
target_v_x = target_v_x * (float)track_output.speed_scale_percent / 100.0f;
|
||||||
|
target_v_y = 0.0f;
|
||||||
|
target_v_w = track_output.yaw_correction_rpm;
|
||||||
|
} else {
|
||||||
|
float base_rpm = (float)car_speed_percent * TARGET_MAX_RPM / 100.0f;
|
||||||
|
track_line_mask = 0U;
|
||||||
|
memset(&track_output, 0, sizeof(track_output));
|
||||||
|
|
||||||
|
/* 手动模式下,依据上位机方向指令生成 Vx/Vy/Vw */
|
||||||
|
switch (manual_dir) {
|
||||||
|
case MAN_DIR_FWD:
|
||||||
|
target_v_x = base_rpm; target_v_y = 0.0f; target_v_w = 0.0f;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_BWD:
|
||||||
|
target_v_x = -base_rpm; target_v_y = 0.0f; target_v_w = 0.0f;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_LEFT:
|
||||||
|
target_v_x = 0.0f; target_v_y = base_rpm; target_v_w = 0.0f; // 修正:左移Vy为正
|
||||||
|
break;
|
||||||
|
case MAN_DIR_RIGHT:
|
||||||
|
target_v_x = 0.0f; target_v_y = -base_rpm; target_v_w = 0.0f; // 修正:右移Vy为负
|
||||||
|
break;
|
||||||
|
case MAN_DIR_LF:
|
||||||
|
target_v_x = base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_y = -base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_w = 0.0f;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_RF:
|
||||||
|
target_v_x = base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_y = base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_w = 0.0f;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_LB:
|
||||||
|
target_v_x = -base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_y = -base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_w = 0.0f;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_RB:
|
||||||
|
target_v_x = -base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_y = base_rpm * MANUAL_DIAG_SCALE;
|
||||||
|
target_v_w = 0.0f;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_CW:
|
||||||
|
target_v_x = 0.0f; target_v_y = 0.0f; target_v_w = base_rpm;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_CCW:
|
||||||
|
target_v_x = 0.0f; target_v_y = 0.0f; target_v_w = -base_rpm;
|
||||||
|
break;
|
||||||
|
case MAN_DIR_STOP:
|
||||||
|
default:
|
||||||
|
target_v_x = 0.0f; target_v_y = 0.0f; target_v_w = 0.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 麦克纳姆轮运动解算 (RPM单位)
|
/* 麦克纳姆轮运动解算 (RPM单位)
|
||||||
* 根据你的电机位置规定:
|
* 根据你的电机位置规定:
|
||||||
@@ -177,12 +307,6 @@ void CarCtrl_UpdateClosedLoop(void)
|
|||||||
*/
|
*/
|
||||||
float target_rpms[MOTOR_COUNT];
|
float target_rpms[MOTOR_COUNT];
|
||||||
|
|
||||||
// 从前进速度分量计算基础速度
|
|
||||||
target_v_x = (float)car_speed_percent * TARGET_MAX_RPM / 100.0f;
|
|
||||||
target_v_x = target_v_x * (float)track_output.speed_scale_percent / 100.0f;
|
|
||||||
target_v_y = 0.0f;
|
|
||||||
target_v_w = track_output.yaw_correction_rpm;
|
|
||||||
|
|
||||||
// 麦轮全向解算公式:
|
// 麦轮全向解算公式:
|
||||||
// LF = Vx + Vy - Vw
|
// LF = Vx + Vy - Vw
|
||||||
// RF = Vx - Vy + Vw
|
// RF = Vx - Vy + Vw
|
||||||
@@ -332,6 +456,72 @@ static void CarCtrl_HandleCommand(const char *cmd_payload)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 控制模式切换:MD:AUTO / MD:MAN
|
||||||
|
if (strncmp(cmd_payload, "MD", 2) == 0) {
|
||||||
|
if (strcmp(arg + 1, "AUTO") == 0) {
|
||||||
|
car_ctrl_mode = CTRL_MODE_AUTO;
|
||||||
|
manual_dir = MAN_DIR_STOP;
|
||||||
|
car_running = 0U;
|
||||||
|
obstacle_locked = 0U;
|
||||||
|
track_ir_algo_reset();
|
||||||
|
CarCtrl_StopAll();
|
||||||
|
elog_i(Protocol_TAG, "已切换为自动循迹模式(AUTO)");
|
||||||
|
Protocol_SendFeedback("MD", 1);
|
||||||
|
} else if (strcmp(arg + 1, "MAN") == 0) {
|
||||||
|
car_ctrl_mode = CTRL_MODE_MANUAL;
|
||||||
|
obstacle_locked = 0U;
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_STOP);
|
||||||
|
elog_i(Protocol_TAG, "已切换为手动麦轮模式(MAN)");
|
||||||
|
Protocol_SendFeedback("MD", 1);
|
||||||
|
} else {
|
||||||
|
elog_w(Protocol_TAG, "未知模式命令: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("MD", 0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动运动方向命令:MV:FWD/BWD/LEFT/RIGHT/LF/RF/LB/RB/CW/CCW/STOP
|
||||||
|
if (strncmp(cmd_payload, "MV", 2) == 0) {
|
||||||
|
if (car_ctrl_mode != CTRL_MODE_MANUAL) {
|
||||||
|
elog_w(Protocol_TAG, "当前非手动模式,拒绝执行MV。请先下发 MD:MAN");
|
||||||
|
Protocol_SendFeedback("MV", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(arg + 1, "FWD") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_FWD);
|
||||||
|
} else if (strcmp(arg + 1, "BWD") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_BWD);
|
||||||
|
} else if (strcmp(arg + 1, "LEFT") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_LEFT);
|
||||||
|
} else if (strcmp(arg + 1, "RIGHT") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_RIGHT);
|
||||||
|
} else if (strcmp(arg + 1, "LF") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_LF);
|
||||||
|
} else if (strcmp(arg + 1, "RF") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_RF);
|
||||||
|
} else if (strcmp(arg + 1, "LB") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_LB);
|
||||||
|
} else if (strcmp(arg + 1, "RB") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_RB);
|
||||||
|
} else if (strcmp(arg + 1, "CW") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_CW);
|
||||||
|
} else if (strcmp(arg + 1, "CCW") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_CCW);
|
||||||
|
} else if (strcmp(arg + 1, "STOP") == 0) {
|
||||||
|
CarCtrl_SetManualDirection(MAN_DIR_STOP);
|
||||||
|
} else {
|
||||||
|
elog_w(Protocol_TAG, "未知手动方向命令: %s", cmd_payload);
|
||||||
|
Protocol_SendFeedback("MV", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elog_i(Protocol_TAG, "手动方向=%s, speed=%u%%", CarCtrl_ManualDirToString(manual_dir),
|
||||||
|
car_speed_percent);
|
||||||
|
Protocol_SendFeedback("MV", 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 未知命令类型
|
// 未知命令类型
|
||||||
elog_w(Protocol_TAG, "未支持的控制命令: %s", cmd_payload);
|
elog_w(Protocol_TAG, "未支持的控制命令: %s", cmd_payload);
|
||||||
}
|
}
|
||||||
@@ -354,7 +544,7 @@ static void Protocol_SendPacket(const char *payload) {
|
|||||||
size_t current_len = strlen(packet);
|
size_t current_len = strlen(packet);
|
||||||
snprintf(packet + current_len, sizeof(packet) - current_len, ":%02X#", cs);
|
snprintf(packet + current_len, sizeof(packet) - current_len, ":%02X#", cs);
|
||||||
|
|
||||||
// 4. 发送
|
// 4. 直接发送
|
||||||
ESP12F_TCP_SendMessage(packet);
|
ESP12F_TCP_SendMessage(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +553,7 @@ static void Protocol_SendPacket(const char *payload) {
|
|||||||
* 格式示例: STAT:SP:080,STA:001,RUN:1,DIS:12.5,TRK:0010,RPM:25:25:25:25
|
* 格式示例: STAT:SP:080,STA:001,RUN:1,DIS:12.5,TRK:0010,RPM:25:25:25:25
|
||||||
*/
|
*/
|
||||||
void Protocol_SendStatusReport(void) {
|
void Protocol_SendStatusReport(void) {
|
||||||
char payload[128] = {0};
|
char payload[160] = {0};
|
||||||
float dist = sr04_get_distance();
|
float dist = sr04_get_distance();
|
||||||
uint8_t trk_mask = track_line_mask;
|
uint8_t trk_mask = track_line_mask;
|
||||||
|
|
||||||
@@ -371,8 +561,11 @@ void Protocol_SendStatusReport(void) {
|
|||||||
// TRK 此时上报 4 位二进制掩码 (H4 H3 H2 H1)
|
// TRK 此时上报 4 位二进制掩码 (H4 H3 H2 H1)
|
||||||
// RPM 为各个电机的实际 RPM,保留整数
|
// RPM 为各个电机的实际 RPM,保留整数
|
||||||
snprintf(payload, sizeof(payload),
|
snprintf(payload, sizeof(payload),
|
||||||
"STAT:SP:%03u,STA:%03u,RUN:%u,DIS:%.1f,TRK:%u%u%u%u,DEV:%d,OBS:%u,RPM:%d:%d:%d:%d",
|
"STAT:SP:%03u,STA:%03u,RUN:%u,MODE:%s,MAN:%s,DIS:%.1f,TRK:%u%u%u%u,DEV:%d,OBS:%u,RPM:%d:%d:%d:%d",
|
||||||
car_speed_percent, car_target_station, car_running, dist,
|
car_speed_percent, car_target_station, car_running,
|
||||||
|
CarCtrl_ModeToString(car_ctrl_mode),
|
||||||
|
CarCtrl_ManualDirToString(manual_dir),
|
||||||
|
dist,
|
||||||
(trk_mask & TRACK_IR_H4_BIT) ? 1 : 0,
|
(trk_mask & TRACK_IR_H4_BIT) ? 1 : 0,
|
||||||
(trk_mask & TRACK_IR_H3_BIT) ? 1 : 0,
|
(trk_mask & TRACK_IR_H3_BIT) ? 1 : 0,
|
||||||
(trk_mask & TRACK_IR_H2_BIT) ? 1 : 0,
|
(trk_mask & TRACK_IR_H2_BIT) ? 1 : 0,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
#define configTICK_RATE_HZ ((TickType_t)1000)
|
#define configTICK_RATE_HZ ((TickType_t)1000)
|
||||||
#define configMAX_PRIORITIES ( 56 )
|
#define configMAX_PRIORITIES ( 56 )
|
||||||
#define configMINIMAL_STACK_SIZE ((uint16_t)128)
|
#define configMINIMAL_STACK_SIZE ((uint16_t)128)
|
||||||
#define configTOTAL_HEAP_SIZE ((size_t)10000)
|
#define configTOTAL_HEAP_SIZE ((size_t)18000)
|
||||||
#define configMAX_TASK_NAME_LEN ( 16 )
|
#define configMAX_TASK_NAME_LEN ( 16 )
|
||||||
#define configUSE_TRACE_FACILITY 1
|
#define configUSE_TRACE_FACILITY 1
|
||||||
#define configUSE_16_BIT_TICKS 0
|
#define configUSE_16_BIT_TICKS 0
|
||||||
|
|||||||
@@ -60,35 +60,35 @@
|
|||||||
osThreadId_t initTaskHandle;
|
osThreadId_t initTaskHandle;
|
||||||
const osThreadAttr_t initTask_attributes = {
|
const osThreadAttr_t initTask_attributes = {
|
||||||
.name = "initTask",
|
.name = "initTask",
|
||||||
.stack_size = 128 * 4,
|
.stack_size = 256 * 4,
|
||||||
.priority = (osPriority_t) osPriorityNormal,
|
.priority = (osPriority_t) osPriorityNormal,
|
||||||
};
|
};
|
||||||
/* Definitions for CarCtrlTask */
|
/* Definitions for CarCtrlTask */
|
||||||
osThreadId_t CarCtrlTaskHandle;
|
osThreadId_t CarCtrlTaskHandle;
|
||||||
const osThreadAttr_t CarCtrlTask_attributes = {
|
const osThreadAttr_t CarCtrlTask_attributes = {
|
||||||
.name = "CarCtrlTask",
|
.name = "CarCtrlTask",
|
||||||
.stack_size = 256 * 4,
|
.stack_size = 1024 * 4,
|
||||||
.priority = (osPriority_t) osPriorityNormal,
|
.priority = (osPriority_t) osPriorityNormal,
|
||||||
};
|
};
|
||||||
/* Definitions for timerTask */
|
/* Definitions for timerTask */
|
||||||
osThreadId_t timerTaskHandle;
|
osThreadId_t timerTaskHandle;
|
||||||
const osThreadAttr_t timerTask_attributes = {
|
const osThreadAttr_t timerTask_attributes = {
|
||||||
.name = "timerTask",
|
.name = "timerTask",
|
||||||
.stack_size = 512 * 4,
|
.stack_size = 1024 * 4,
|
||||||
.priority = (osPriority_t) osPriorityBelowNormal,
|
.priority = (osPriority_t) osPriorityBelowNormal,
|
||||||
};
|
};
|
||||||
/* Definitions for sr04Task */
|
/* Definitions for sr04Task */
|
||||||
osThreadId_t sr04TaskHandle;
|
osThreadId_t sr04TaskHandle;
|
||||||
const osThreadAttr_t sr04Task_attributes = {
|
const osThreadAttr_t sr04Task_attributes = {
|
||||||
.name = "sr04Task",
|
.name = "sr04Task",
|
||||||
.stack_size = 128 * 4,
|
.stack_size = 512 * 4,
|
||||||
.priority = (osPriority_t) osPriorityLow,
|
.priority = (osPriority_t) osPriorityLow,
|
||||||
};
|
};
|
||||||
/* Definitions for rc522Task */
|
/* Definitions for rc522Task */
|
||||||
osThreadId_t rc522TaskHandle;
|
osThreadId_t rc522TaskHandle;
|
||||||
const osThreadAttr_t rc522Task_attributes = {
|
const osThreadAttr_t rc522Task_attributes = {
|
||||||
.name = "rc522Task",
|
.name = "rc522Task",
|
||||||
.stack_size = 128 * 4,
|
.stack_size = 512 * 4,
|
||||||
.priority = (osPriority_t) osPriorityBelowNormal,
|
.priority = (osPriority_t) osPriorityBelowNormal,
|
||||||
};
|
};
|
||||||
/* Definitions for CmdQueue */
|
/* Definitions for CmdQueue */
|
||||||
|
|||||||
331
docs/upper_computer_ai_guide.md
Normal file
331
docs/upper_computer_ai_guide.md
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
# Logistics Car Upper Computer AI Guide
|
||||||
|
|
||||||
|
## 1. Purpose
|
||||||
|
This document is written for an AI agent that will implement the upper computer application.
|
||||||
|
Goal: build a stable TCP client for the logistics car, with both auto mode and manual mecanum mode, based on the embedded protocol implemented in this repository.
|
||||||
|
|
||||||
|
Target users:
|
||||||
|
- AI coding agents (Copilot-like, AutoGen-like, etc.)
|
||||||
|
- Human developers who review AI-generated upper computer code
|
||||||
|
|
||||||
|
Out of scope:
|
||||||
|
- Modifying embedded firmware behavior
|
||||||
|
- Replacing transport protocol
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Must-Follow Protocol Contract
|
||||||
|
|
||||||
|
### 2.1 Frame format
|
||||||
|
All commands and telemetry use ASCII text:
|
||||||
|
|
||||||
|
LOGI:<PAYLOAD>:<CS>#
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- LOGI: fixed header
|
||||||
|
- <PAYLOAD>: command or telemetry body
|
||||||
|
- <CS>: 2-digit uppercase hex checksum
|
||||||
|
- #: fixed frame tail
|
||||||
|
|
||||||
|
### 2.2 Checksum algorithm
|
||||||
|
Checksum is the low 8 bits of ASCII sum of all bytes from L in LOGI to the byte before the final colon before CS.
|
||||||
|
|
||||||
|
Pseudo:
|
||||||
|
1. base = "LOGI:" + payload
|
||||||
|
2. cs = sum(base bytes as ASCII) & 0xFF
|
||||||
|
3. frame = base + ":" + toHex2Upper(cs) + "#"
|
||||||
|
|
||||||
|
Python reference:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def build_frame(payload: str) -> str:
|
||||||
|
base = f"LOGI:{payload}"
|
||||||
|
cs = sum(base.encode("ascii")) & 0xFF
|
||||||
|
return f"{base}:{cs:02X}#"
|
||||||
|
```
|
||||||
|
|
||||||
|
C# reference:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
static string BuildFrame(string payload)
|
||||||
|
{
|
||||||
|
string baseStr = $"LOGI:{payload}";
|
||||||
|
int sum = 0;
|
||||||
|
foreach (byte b in System.Text.Encoding.ASCII.GetBytes(baseStr))
|
||||||
|
sum = (sum + b) & 0xFF;
|
||||||
|
return $"{baseStr}:{sum:X2}#";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Important:
|
||||||
|
- Must use uppercase hex (for consistency)
|
||||||
|
- Do not append CRLF unless your socket tool requires it
|
||||||
|
- One frame can be sent as raw ASCII bytes directly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Command Set (PC -> Car)
|
||||||
|
|
||||||
|
### 3.1 Common control
|
||||||
|
- SP:xxx set speed 000..100
|
||||||
|
- ST:RUN start auto run
|
||||||
|
- ST:STOP stop run
|
||||||
|
- GS:xxx set target station (current firmware supports 001, 002)
|
||||||
|
|
||||||
|
### 3.2 Mode switch (new)
|
||||||
|
- MD:MAN switch to manual mecanum mode
|
||||||
|
- MD:AUTO switch to auto tracking mode
|
||||||
|
|
||||||
|
### 3.3 Manual movement (new, only valid in MAN mode)
|
||||||
|
- MV:FWD forward
|
||||||
|
- MV:BWD backward
|
||||||
|
- MV:LEFT strafe left
|
||||||
|
- MV:RIGHT strafe right
|
||||||
|
- MV:LF forward-left diagonal
|
||||||
|
- MV:RF forward-right diagonal
|
||||||
|
- MV:LB backward-left diagonal
|
||||||
|
- MV:RB backward-right diagonal
|
||||||
|
- MV:CW rotate clockwise
|
||||||
|
- MV:CCW rotate counterclockwise
|
||||||
|
- MV:STOP stop motion
|
||||||
|
|
||||||
|
If current mode is AUTO and you send MV:*:
|
||||||
|
- car returns FB:MV:0 (rejected)
|
||||||
|
- upper computer must first send MD:MAN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Telemetry and Feedback (Car -> PC)
|
||||||
|
|
||||||
|
### 4.1 Periodic status (every ~500ms)
|
||||||
|
Payload format:
|
||||||
|
|
||||||
|
STAT:SP:xxx,STA:xxx,RUN:x,MODE:mode,MAN:dir,DIS:d,TRK:b4b3b2b1,DEV:n,OBS:x,RPM:m1:m2:m3:m4
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- SP: speed percent
|
||||||
|
- STA: target station
|
||||||
|
- RUN: 1 running, 0 stopped
|
||||||
|
- MODE: AUTO or MAN
|
||||||
|
- MAN: STOP/FWD/BWD/LEFT/RIGHT/LF/RF/LB/RB/CW/CCW
|
||||||
|
- DIS: ultrasonic distance in cm
|
||||||
|
- TRK: 4-channel line mask, order H4 H3 H2 H1
|
||||||
|
- DEV: track deviation (auto mode meaningful)
|
||||||
|
- OBS: obstacle lock (1 blocked, 0 clear)
|
||||||
|
- RPM: 4 motor RPM, order M1:M2:M3:M4 (LR:LF:RF:RR)
|
||||||
|
|
||||||
|
### 4.2 Command feedback (immediate)
|
||||||
|
Payload format:
|
||||||
|
|
||||||
|
FB:<CMD>:<0|1>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- FB:SP:1 speed accepted
|
||||||
|
- FB:GS:0 station invalid or parse fail
|
||||||
|
- FB:MV:0 movement rejected (for example mode mismatch)
|
||||||
|
|
||||||
|
Upper computer rule:
|
||||||
|
- Every button operation should wait for feedback timeout window
|
||||||
|
- If no feedback in timeout, mark command uncertain and retry policy applies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Upper Computer Architecture Requirements
|
||||||
|
|
||||||
|
AI must implement the upper computer with these modules.
|
||||||
|
|
||||||
|
### 5.1 Network module
|
||||||
|
Responsibilities:
|
||||||
|
- Maintain TCP connection lifecycle
|
||||||
|
- Auto reconnect with backoff
|
||||||
|
- Byte stream receive and frame extraction
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
- Reconnect delay: 1s, 2s, 3s, then fixed 3s
|
||||||
|
- On reconnect success, UI state set to Connected
|
||||||
|
- On disconnect, all movement buttons should be disabled
|
||||||
|
|
||||||
|
### 5.2 Protocol module
|
||||||
|
Responsibilities:
|
||||||
|
- Build frames from payload
|
||||||
|
- Parse incoming frames
|
||||||
|
- Verify checksum before dispatch
|
||||||
|
|
||||||
|
Required behavior:
|
||||||
|
- Drop frame if checksum invalid
|
||||||
|
- Log invalid frame raw text for diagnostics
|
||||||
|
- Expose strongly typed events: StatusEvent, FeedbackEvent
|
||||||
|
|
||||||
|
### 5.3 Command dispatcher
|
||||||
|
Responsibilities:
|
||||||
|
- Serialize outbound commands (single queue)
|
||||||
|
- Attach request id locally (not in protocol) for UI correlation
|
||||||
|
- Wait feedback with timeout
|
||||||
|
|
||||||
|
Recommended defaults:
|
||||||
|
- Feedback timeout: 800 ms
|
||||||
|
- Max retries for idempotent commands (SP, MD, MV:STOP): 2
|
||||||
|
- No auto-retry for ST:RUN and GS:xxx unless user confirms
|
||||||
|
|
||||||
|
### 5.4 UI module
|
||||||
|
Required controls:
|
||||||
|
- Connection: IP, port, connect/disconnect
|
||||||
|
- Mode: AUTO / MAN toggle
|
||||||
|
- Speed: slider 0..100 with send button
|
||||||
|
- Auto controls: station selector (001/002), RUN, STOP
|
||||||
|
- Manual controls: directional keypad + rotation + STOP
|
||||||
|
- Status panel: parsed telemetry fields
|
||||||
|
- Logs panel: TX/RX raw frames + parsed result
|
||||||
|
|
||||||
|
### 5.5 Safety guard module
|
||||||
|
Required safety logic:
|
||||||
|
- If UI in MAN mode and mouse/touch release on direction button, auto send MV:STOP
|
||||||
|
- If no fresh status for >2s, show stale warning
|
||||||
|
- Emergency stop hotkey sends MV:STOP first; if in AUTO also send ST:STOP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Exact Button-to-Command Mapping
|
||||||
|
|
||||||
|
All below are complete frames with checksum, directly sendable.
|
||||||
|
|
||||||
|
### 6.1 Mode
|
||||||
|
- Switch to manual: LOGI:MD:MAN:0C#
|
||||||
|
- Switch to auto: LOGI:MD:AUTO:69#
|
||||||
|
|
||||||
|
### 6.2 Speed presets
|
||||||
|
- 30%: LOGI:SP:030:D5#
|
||||||
|
- 50%: LOGI:SP:050:D7#
|
||||||
|
- 80%: LOGI:SP:080:DA#
|
||||||
|
|
||||||
|
### 6.3 Auto mode
|
||||||
|
- Station 001: LOGI:GS:001:CA#
|
||||||
|
- Station 002: LOGI:GS:002:CB#
|
||||||
|
- Auto run: LOGI:ST:RUN:3B#
|
||||||
|
- Auto stop: LOGI:ST:STOP:8C#
|
||||||
|
|
||||||
|
### 6.4 Manual movement
|
||||||
|
- Forward: LOGI:MV:FWD:23#
|
||||||
|
- Backward: LOGI:MV:BWD:1F#
|
||||||
|
- Left strafe: LOGI:MV:LEFT:6D#
|
||||||
|
- Right strafe: LOGI:MV:RIGHT:C0#
|
||||||
|
- Forward-left: LOGI:MV:LF:D4#
|
||||||
|
- Forward-right: LOGI:MV:RF:DA#
|
||||||
|
- Backward-left: LOGI:MV:LB:D0#
|
||||||
|
- Backward-right: LOGI:MV:RB:D6#
|
||||||
|
- Rotate CW: LOGI:MV:CW:DC#
|
||||||
|
- Rotate CCW: LOGI:MV:CCW:1F#
|
||||||
|
- Stop: LOGI:MV:STOP:88#
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Recommended Operation Sequences
|
||||||
|
|
||||||
|
### 7.1 Manual driving flow
|
||||||
|
1. Connect TCP
|
||||||
|
2. Send SP:xxx
|
||||||
|
3. Send MD:MAN
|
||||||
|
4. Hold direction button -> send MV:* repeatedly or once per state change
|
||||||
|
5. Release button -> send MV:STOP
|
||||||
|
|
||||||
|
Recommended send style for manual:
|
||||||
|
- Edge-triggered (on press send once, on release send stop) is preferred
|
||||||
|
- If network jitter is high, add keepalive send of same MV every 300 ms
|
||||||
|
|
||||||
|
### 7.2 Auto transport flow
|
||||||
|
1. Connect TCP
|
||||||
|
2. Send SP:xxx
|
||||||
|
3. Send MD:AUTO
|
||||||
|
4. Send GS:001 or GS:002
|
||||||
|
5. Send ST:RUN
|
||||||
|
6. Observe RUN and feedback/status until arrival
|
||||||
|
|
||||||
|
Arrival behavior in firmware:
|
||||||
|
- Car auto-stops on target RFID
|
||||||
|
- RUN becomes 0
|
||||||
|
- Next run requires sending GS again, then ST:RUN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Frame Parsing Strategy (Stream-safe)
|
||||||
|
|
||||||
|
TCP is stream-based, not message-based. AI must implement frame extraction correctly.
|
||||||
|
|
||||||
|
Required parser behavior:
|
||||||
|
- Keep a receive buffer string/byte array
|
||||||
|
- Search for header "LOGI:"
|
||||||
|
- Search for tail '#'
|
||||||
|
- Extract candidate frame from first valid header to first following '#'
|
||||||
|
- Validate checksum
|
||||||
|
- Dispatch valid frame; remove consumed bytes
|
||||||
|
- If garbage before header, discard garbage
|
||||||
|
|
||||||
|
Do not assume one recv equals one frame.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Error Handling and Recovery
|
||||||
|
|
||||||
|
### 9.1 Feedback timeout
|
||||||
|
If command has no FB within timeout:
|
||||||
|
- Mark as timeout in UI
|
||||||
|
- For safe idempotent commands, retry once or twice
|
||||||
|
- For movement commands, prefer fail-safe by sending STOP
|
||||||
|
|
||||||
|
### 9.2 Checksum mismatch in received data
|
||||||
|
- Drop frame
|
||||||
|
- Log as warning with raw text
|
||||||
|
- Keep connection alive (do not disconnect directly)
|
||||||
|
|
||||||
|
### 9.3 Mode conflict
|
||||||
|
If receiving FB:MV:0 after sending MV command:
|
||||||
|
- UI should hint: "Not in manual mode, switching to MAN"
|
||||||
|
- Auto-send MD:MAN then retry once
|
||||||
|
|
||||||
|
### 9.4 Connection loss during motion
|
||||||
|
On disconnect event:
|
||||||
|
- UI enters safe state
|
||||||
|
- disable movement controls
|
||||||
|
- after reconnect, require operator to re-confirm mode and speed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. AI Implementation Checklist (Definition of Done)
|
||||||
|
|
||||||
|
Functional:
|
||||||
|
- Can connect/disconnect TCP manually
|
||||||
|
- Can parse status and feedback reliably
|
||||||
|
- All buttons send correct command frames
|
||||||
|
- Manual control works in all listed directions
|
||||||
|
- Auto mode flow works (GS + RUN + STOP)
|
||||||
|
|
||||||
|
Robustness:
|
||||||
|
- Stream parser handles sticky packets and split packets
|
||||||
|
- Reconnect works automatically
|
||||||
|
- Command timeout/retry policy implemented
|
||||||
|
- Logs include TX/RX and parse errors
|
||||||
|
|
||||||
|
Usability:
|
||||||
|
- Telemetry fields visible and updating
|
||||||
|
- Mode and direction state clearly shown
|
||||||
|
- Emergency stop is always accessible
|
||||||
|
|
||||||
|
Testing:
|
||||||
|
- Unit test for checksum build/verify
|
||||||
|
- Unit test for parser with mixed/fragmented frames
|
||||||
|
- Integration test with mock TCP server replaying status/feedback
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Suggested Prompt for Coding AI
|
||||||
|
Use this prompt when asking another coding AI to generate the upper computer:
|
||||||
|
|
||||||
|
"Implement a production-ready TCP upper computer client for an STM32 logistics car protocol. Use ASCII frame format LOGI:<PAYLOAD>:<CS># with checksum as sum(base bytes) & 0xFF where base is LOGI:<PAYLOAD>. Support commands SP, ST, GS, MD, MV. Parse status payload STAT with fields SP, STA, RUN, MODE, MAN, DIS, TRK, DEV, OBS, RPM. Parse feedback payload FB:<CMD>:<0|1>. Build a stream-safe parser for sticky/fragmented TCP packets. Include reconnect, timeout/retry, and a UI with manual mecanum directional controls and auto mode controls."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Notes for Future Extension
|
||||||
|
If firmware adds fields later:
|
||||||
|
- Keep parser tolerant: unknown key-value pairs should not crash parser
|
||||||
|
- Preserve raw payload in logs for forward compatibility
|
||||||
|
- Version your app-side parser module (for example v1_2)
|
||||||
392
docs/upper_computer_ai_guide_python_web_zh.md
Normal file
392
docs/upper_computer_ai_guide_python_web_zh.md
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
# Python Web 上位机实施文档(给开发AI直接执行)
|
||||||
|
|
||||||
|
## 1. 目标与技术选型
|
||||||
|
|
||||||
|
目标:实现一个可运行、可联调、可扩展的上位机系统,满足以下要求:
|
||||||
|
- 与小车通过 TCP 协议通信
|
||||||
|
- 浏览器可视化控制(自动模式 + 手动麦轮模式)
|
||||||
|
- 实时状态显示(500ms级)
|
||||||
|
- 日志、重连、超时、错误恢复完整
|
||||||
|
|
||||||
|
推荐技术栈:
|
||||||
|
- 后端:Python 3.11+
|
||||||
|
- Web框架:FastAPI
|
||||||
|
- 实时推送:WebSocket
|
||||||
|
- 异步:asyncio
|
||||||
|
- 前端:Vue3 或 React(都可以,本文给中立接口规范)
|
||||||
|
- 部署:Windows 本地运行(开发阶段)
|
||||||
|
|
||||||
|
建议架构:
|
||||||
|
- Browser UI <-> WebSocket/HTTP <-> Python Backend <-> TCP Socket <-> Car
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 协议契约(必须严格一致)
|
||||||
|
|
||||||
|
帧格式:
|
||||||
|
LOGI:<PAYLOAD>:<CS>#
|
||||||
|
|
||||||
|
校验和:
|
||||||
|
- 对字符串 LOGI:<PAYLOAD> 做 ASCII 累加
|
||||||
|
- 取低8位
|
||||||
|
- 转 2位大写十六进制
|
||||||
|
|
||||||
|
Python函数(必须实现并复用):
|
||||||
|
|
||||||
|
```python
|
||||||
|
def build_frame(payload: str) -> str:
|
||||||
|
base = f"LOGI:{payload}"
|
||||||
|
cs = sum(base.encode("ascii")) & 0xFF
|
||||||
|
return f"{base}:{cs:02X}#"
|
||||||
|
```
|
||||||
|
|
||||||
|
已验证可直接发送的关键命令:
|
||||||
|
- LOGI:MD:MAN:0C#
|
||||||
|
- LOGI:MD:AUTO:69#
|
||||||
|
- LOGI:MV:FWD:23#
|
||||||
|
- LOGI:MV:BWD:1F#
|
||||||
|
- LOGI:MV:LEFT:6D#
|
||||||
|
- LOGI:MV:RIGHT:C0#
|
||||||
|
- LOGI:MV:LF:D4#
|
||||||
|
- LOGI:MV:RF:DA#
|
||||||
|
- LOGI:MV:LB:D0#
|
||||||
|
- LOGI:MV:RB:D6#
|
||||||
|
- LOGI:MV:CW:DC#
|
||||||
|
- LOGI:MV:CCW:1F#
|
||||||
|
- LOGI:MV:STOP:88#
|
||||||
|
- LOGI:SP:030:D5#
|
||||||
|
- LOGI:SP:050:D7#
|
||||||
|
- LOGI:SP:080:DA#
|
||||||
|
- LOGI:GS:001:CA#
|
||||||
|
- LOGI:GS:002:CB#
|
||||||
|
- LOGI:ST:RUN:3B#
|
||||||
|
- LOGI:ST:STOP:8C#
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 项目目录(AI生成代码必须遵循)
|
||||||
|
|
||||||
|
```text
|
||||||
|
upper-computer-python-web/
|
||||||
|
backend/
|
||||||
|
app/
|
||||||
|
main.py
|
||||||
|
config.py
|
||||||
|
models.py
|
||||||
|
protocol.py
|
||||||
|
tcp_client.py
|
||||||
|
command_service.py
|
||||||
|
state_store.py
|
||||||
|
ws_hub.py
|
||||||
|
parser.py
|
||||||
|
logger.py
|
||||||
|
api/
|
||||||
|
http_routes.py
|
||||||
|
ws_routes.py
|
||||||
|
requirements.txt
|
||||||
|
frontend/
|
||||||
|
src/
|
||||||
|
main.ts
|
||||||
|
api.ts
|
||||||
|
ws.ts
|
||||||
|
store.ts
|
||||||
|
components/
|
||||||
|
ConnectionPanel.vue
|
||||||
|
ModePanel.vue
|
||||||
|
SpeedPanel.vue
|
||||||
|
AutoPanel.vue
|
||||||
|
ManualPad.vue
|
||||||
|
StatusPanel.vue
|
||||||
|
LogPanel.vue
|
||||||
|
package.json
|
||||||
|
README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 后端职责与实现要求
|
||||||
|
|
||||||
|
### 4.1 TCP客户端(tcp_client.py)
|
||||||
|
|
||||||
|
必须实现:
|
||||||
|
- connect(host, port)
|
||||||
|
- disconnect()
|
||||||
|
- send_frame(frame)
|
||||||
|
- background_read_loop()
|
||||||
|
- auto_reconnect_loop()
|
||||||
|
|
||||||
|
重连策略:
|
||||||
|
- 1s -> 2s -> 3s -> 3s循环
|
||||||
|
|
||||||
|
连接状态事件:
|
||||||
|
- connected
|
||||||
|
- disconnected
|
||||||
|
- reconnecting
|
||||||
|
- connect_failed
|
||||||
|
|
||||||
|
### 4.2 流式解析器(parser.py)
|
||||||
|
|
||||||
|
TCP是流,必须做粘包拆包。
|
||||||
|
|
||||||
|
规则:
|
||||||
|
1. 维护 bytearray 缓冲区
|
||||||
|
2. 找 LOGI: 起点
|
||||||
|
3. 找 # 终点
|
||||||
|
4. 抽取候选帧
|
||||||
|
5. 校验和正确才分发
|
||||||
|
6. 错帧丢弃并记录日志
|
||||||
|
|
||||||
|
必须支持:
|
||||||
|
- 一次收多帧
|
||||||
|
- 一帧分多次收
|
||||||
|
- 帧前垃圾字节
|
||||||
|
|
||||||
|
### 4.3 协议服务(protocol.py)
|
||||||
|
|
||||||
|
必须提供:
|
||||||
|
- build_frame(payload)
|
||||||
|
- parse_frame(frame) -> dict
|
||||||
|
- verify_checksum(frame) -> bool
|
||||||
|
|
||||||
|
parse_frame 输出建议:
|
||||||
|
- type: status / feedback / unknown
|
||||||
|
- payload_raw
|
||||||
|
- fields: dict
|
||||||
|
|
||||||
|
### 4.4 命令服务(command_service.py)
|
||||||
|
|
||||||
|
必须实现命令队列与反馈匹配:
|
||||||
|
- send_command(payload, wait_feedback=True)
|
||||||
|
- 超时默认 800ms
|
||||||
|
- 可重试命令:SP、MD、MV:STOP(最多2次)
|
||||||
|
- 非幂等命令 ST:RUN、GS 不自动重试
|
||||||
|
|
||||||
|
反馈格式:
|
||||||
|
- FB:<CMD>:<0|1>
|
||||||
|
|
||||||
|
### 4.5 状态存储(state_store.py)
|
||||||
|
|
||||||
|
维护全局状态对象:
|
||||||
|
- connection
|
||||||
|
- speed
|
||||||
|
- station
|
||||||
|
- run
|
||||||
|
- mode
|
||||||
|
- manual_dir
|
||||||
|
- distance
|
||||||
|
- trk
|
||||||
|
- dev
|
||||||
|
- obs
|
||||||
|
- rpm[4]
|
||||||
|
- last_status_ts
|
||||||
|
|
||||||
|
超过2秒无状态包:
|
||||||
|
- 标记 stale = true
|
||||||
|
- 推送前端告警
|
||||||
|
|
||||||
|
### 4.6 WebSocket中枢(ws_hub.py)
|
||||||
|
|
||||||
|
前端订阅以下事件:
|
||||||
|
- conn_update
|
||||||
|
- status_update
|
||||||
|
- feedback_update
|
||||||
|
- tx_log
|
||||||
|
- rx_log
|
||||||
|
- warn
|
||||||
|
- error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. HTTP 与 WebSocket 接口规范
|
||||||
|
|
||||||
|
### 5.1 HTTP
|
||||||
|
|
||||||
|
1) POST /api/connect
|
||||||
|
- body: {"host":"192.168.1.50","port":3456}
|
||||||
|
- resp: {"ok":true}
|
||||||
|
|
||||||
|
2) POST /api/disconnect
|
||||||
|
- resp: {"ok":true}
|
||||||
|
|
||||||
|
3) POST /api/cmd
|
||||||
|
- body: {"payload":"MV:FWD","waitFeedback":true}
|
||||||
|
- resp: {"ok":true,"feedback":{"cmd":"MV","status":1}}
|
||||||
|
|
||||||
|
4) GET /api/state
|
||||||
|
- resp: 全量状态JSON
|
||||||
|
|
||||||
|
5) GET /api/health
|
||||||
|
- resp: {"ok":true}
|
||||||
|
|
||||||
|
### 5.2 WebSocket
|
||||||
|
|
||||||
|
- ws://host:port/ws
|
||||||
|
|
||||||
|
消息统一格式:
|
||||||
|
- {"event":"status_update","data":{...}}
|
||||||
|
- {"event":"feedback_update","data":{...}}
|
||||||
|
- {"event":"tx_log","data":{"frame":"..."}}
|
||||||
|
- {"event":"rx_log","data":{"frame":"..."}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 前端页面与按钮行为
|
||||||
|
|
||||||
|
页面最少包含:
|
||||||
|
- 连接面板:IP、端口、连接/断开
|
||||||
|
- 模式面板:AUTO / MAN
|
||||||
|
- 速度面板:0-100滑条 + 发送按钮
|
||||||
|
- 自动面板:站点001/002、RUN、STOP
|
||||||
|
- 手动面板:8方向 + CW/CCW + STOP
|
||||||
|
- 状态面板:SP/STA/RUN/MODE/MAN/DIS/TRK/DEV/OBS/RPM
|
||||||
|
- 日志面板:TX/RX
|
||||||
|
|
||||||
|
按钮映射要求:
|
||||||
|
|
||||||
|
1) 切手动
|
||||||
|
- 发送 payload: MD:MAN
|
||||||
|
|
||||||
|
2) 切自动
|
||||||
|
- 发送 payload: MD:AUTO
|
||||||
|
|
||||||
|
3) 速度
|
||||||
|
- 发送 payload: SP:xxx(补零3位)
|
||||||
|
|
||||||
|
4) 自动站点
|
||||||
|
- 001 -> GS:001
|
||||||
|
- 002 -> GS:002
|
||||||
|
|
||||||
|
5) 自动运行控制
|
||||||
|
- RUN -> ST:RUN
|
||||||
|
- STOP -> ST:STOP
|
||||||
|
|
||||||
|
6) 手动方向
|
||||||
|
- 前进 MV:FWD
|
||||||
|
- 后退 MV:BWD
|
||||||
|
- 左移 MV:LEFT
|
||||||
|
- 右移 MV:RIGHT
|
||||||
|
- 左前 MV:LF
|
||||||
|
- 右前 MV:RF
|
||||||
|
- 左后 MV:LB
|
||||||
|
- 右后 MV:RB
|
||||||
|
- 顺时针 MV:CW
|
||||||
|
- 逆时针 MV:CCW
|
||||||
|
- 停止 MV:STOP
|
||||||
|
|
||||||
|
手动安全行为(必须):
|
||||||
|
- 按下方向键:发对应MV
|
||||||
|
- 松开方向键:立即发 MV:STOP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 关键业务逻辑(必须实现)
|
||||||
|
|
||||||
|
### 7.1 模式冲突恢复
|
||||||
|
如果发送 MV:* 收到 FB:MV:0:
|
||||||
|
1. 自动提示“当前不在手动模式”
|
||||||
|
2. 自动发送 MD:MAN
|
||||||
|
3. 重试一次原MV命令
|
||||||
|
|
||||||
|
### 7.2 断线保护
|
||||||
|
断线时:
|
||||||
|
- 禁用全部运动按钮
|
||||||
|
- UI状态置灰
|
||||||
|
- 重连成功后,不自动恢复运动,必须人工再次点方向
|
||||||
|
|
||||||
|
### 7.3 急停策略
|
||||||
|
急停按钮逻辑:
|
||||||
|
1. 先发 MV:STOP
|
||||||
|
2. 若当前模式是AUTO,再发 ST:STOP
|
||||||
|
|
||||||
|
### 7.4 自动流程约束
|
||||||
|
自动流程推荐顺序:
|
||||||
|
- SP -> MD:AUTO -> GS -> ST:RUN
|
||||||
|
|
||||||
|
到站后固件会锁存,下一次运行前应:
|
||||||
|
- 重发 GS
|
||||||
|
- 再发 ST:RUN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 后端参考代码骨架(最小可运行)
|
||||||
|
|
||||||
|
main.py 示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from fastapi import FastAPI, WebSocket
|
||||||
|
from app.api.http_routes import router as http_router
|
||||||
|
from app.api.ws_routes import router as ws_router
|
||||||
|
|
||||||
|
app = FastAPI(title="Car Upper Computer")
|
||||||
|
app.include_router(http_router, prefix="/api")
|
||||||
|
app.include_router(ws_router)
|
||||||
|
|
||||||
|
@app.get("/api/health")
|
||||||
|
async def health():
|
||||||
|
return {"ok": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
command_service.py 的命令发送入口示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def send_payload(payload: str, wait_feedback: bool = True):
|
||||||
|
frame = build_frame(payload)
|
||||||
|
await tcp_client.send_frame(frame)
|
||||||
|
await ws_hub.broadcast("tx_log", {"frame": frame})
|
||||||
|
if not wait_feedback:
|
||||||
|
return {"ok": True}
|
||||||
|
fb = await feedback_waiter.wait(payload.split(":", 1)[0], timeout=0.8)
|
||||||
|
return {"ok": fb is not None, "feedback": fb}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 联调步骤(按此执行)
|
||||||
|
|
||||||
|
1. 启动后端
|
||||||
|
- uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
2. 启动前端
|
||||||
|
- npm install
|
||||||
|
- npm run dev
|
||||||
|
|
||||||
|
3. 页面连接到小车TCP
|
||||||
|
- 输入IP和端口
|
||||||
|
- 点击连接
|
||||||
|
|
||||||
|
4. 先测基础命令
|
||||||
|
- SP:050
|
||||||
|
- MD:MAN
|
||||||
|
- MV:FWD
|
||||||
|
- MV:STOP
|
||||||
|
|
||||||
|
5. 再测自动流程
|
||||||
|
- MD:AUTO
|
||||||
|
- GS:001
|
||||||
|
- ST:RUN
|
||||||
|
|
||||||
|
6. 检查状态面板字段是否持续刷新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 测试清单(必须通过)
|
||||||
|
|
||||||
|
单元测试:
|
||||||
|
- build_frame校验值正确
|
||||||
|
- verify_checksum正确拦截坏包
|
||||||
|
- parser支持粘包拆包
|
||||||
|
|
||||||
|
集成测试:
|
||||||
|
- 命令发送后能收到FB
|
||||||
|
- 状态包断流时 stale 告警出现
|
||||||
|
- 断线后自动重连并恢复状态展示
|
||||||
|
|
||||||
|
交互测试:
|
||||||
|
- 手动按钮按下/松开逻辑正确
|
||||||
|
- 急停在任意状态都可触发
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 给开发AI的最终任务提示词
|
||||||
|
|
||||||
|
请实现一个 Python Web 上位机系统,后端用 FastAPI + asyncio TCP + WebSocket,前端用 Vue3 或 React。协议为 LOGI:<PAYLOAD>:<CS>#,CS 为 LOGI:<PAYLOAD> 的 ASCII 累加低8位。实现 SP/ST/GS/MD/MV 命令,解析 STAT 与 FB。支持手动麦轮方向控制(8方向+旋转+STOP)和自动模式控制。必须实现 TCP 粘包拆包解析、命令反馈超时处理、自动重连、状态实时推送、日志面板与安全急停逻辑。
|
||||||
311
docs/upper_computer_ai_guide_zh.md
Normal file
311
docs/upper_computer_ai_guide_zh.md
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
# 上位机开发AI执行文档(物流小车 TCP 协议)
|
||||||
|
|
||||||
|
## 1. 文档目标
|
||||||
|
这份文档是写给“负责开发上位机的AI”的,不是普通说明书。
|
||||||
|
目标是让AI按本文档直接产出可运行的上位机程序(TCP客户端),并且与当前固件协议完全兼容。
|
||||||
|
|
||||||
|
适用范围:
|
||||||
|
- Windows上位机(Python / C# / C++ / Electron均可)
|
||||||
|
- 控制模式:自动循迹 + 手动麦克纳姆
|
||||||
|
- 通信方式:TCP,ASCII协议
|
||||||
|
|
||||||
|
不做的事情:
|
||||||
|
- 不修改单片机固件协议
|
||||||
|
- 不更换通信栈
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 协议总规范(必须严格遵守)
|
||||||
|
|
||||||
|
### 2.1 帧格式
|
||||||
|
所有收发数据都使用:
|
||||||
|
|
||||||
|
LOGI:<PAYLOAD>:<CS>#
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
- LOGI: 固定帧头
|
||||||
|
- <PAYLOAD>: 有效载荷
|
||||||
|
- <CS>: 2位大写十六进制校验和
|
||||||
|
- #: 固定帧尾
|
||||||
|
|
||||||
|
### 2.2 校验和算法
|
||||||
|
计算范围是 LOGI:PAYLOAD 这一段(不含最后 :CS#)。
|
||||||
|
算法:ASCII逐字节累加,取低8位。
|
||||||
|
|
||||||
|
示例代码(Python):
|
||||||
|
|
||||||
|
```python
|
||||||
|
def build_frame(payload: str) -> str:
|
||||||
|
base = f"LOGI:{payload}"
|
||||||
|
cs = sum(base.encode("ascii")) & 0xFF
|
||||||
|
return f"{base}:{cs:02X}#"
|
||||||
|
```
|
||||||
|
|
||||||
|
示例代码(C#):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
static string BuildFrame(string payload)
|
||||||
|
{
|
||||||
|
string baseStr = $"LOGI:{payload}";
|
||||||
|
int sum = 0;
|
||||||
|
foreach (byte b in System.Text.Encoding.ASCII.GetBytes(baseStr))
|
||||||
|
sum = (sum + b) & 0xFF;
|
||||||
|
return $"{baseStr}:{sum:X2}#";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
硬性要求:
|
||||||
|
- 十六进制统一大写
|
||||||
|
- 不要额外拼接换行(除非你的网络调试工具强制)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 下行指令(上位机 -> 小车)
|
||||||
|
|
||||||
|
### 3.1 通用控制
|
||||||
|
- SP:xxx 设置速度(000~100)
|
||||||
|
- ST:RUN 自动启动
|
||||||
|
- ST:STOP 自动停止
|
||||||
|
- GS:xxx 设置目标站点(当前固件支持001/002)
|
||||||
|
|
||||||
|
### 3.2 模式切换(新增)
|
||||||
|
- MD:MAN 切换手动麦轮模式
|
||||||
|
- MD:AUTO 切换自动循迹模式
|
||||||
|
|
||||||
|
### 3.3 手动方向(新增,仅MAN模式有效)
|
||||||
|
- MV:FWD 前进
|
||||||
|
- MV:BWD 后退
|
||||||
|
- MV:LEFT 左平移
|
||||||
|
- MV:RIGHT 右平移
|
||||||
|
- MV:LF 左前
|
||||||
|
- MV:RF 右前
|
||||||
|
- MV:LB 左后
|
||||||
|
- MV:RB 右后
|
||||||
|
- MV:CW 原地顺时针
|
||||||
|
- MV:CCW 原地逆时针
|
||||||
|
- MV:STOP 停止
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 若处于AUTO模式直接发MV:*,固件会返回 FB:MV:0
|
||||||
|
- 上位机应先发 MD:MAN 再发 MV:*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 上行数据(小车 -> 上位机)
|
||||||
|
|
||||||
|
### 4.1 状态上报(约500ms)
|
||||||
|
PAYLOAD格式:
|
||||||
|
|
||||||
|
STAT:SP:xxx,STA:xxx,RUN:x,MODE:mode,MAN:dir,DIS:d,TRK:b4b3b2b1,DEV:n,OBS:x,RPM:m1:m2:m3:m4
|
||||||
|
|
||||||
|
字段含义:
|
||||||
|
- SP: 当前速度百分比
|
||||||
|
- STA: 当前目标站点
|
||||||
|
- RUN: 1运行,0停止
|
||||||
|
- MODE: AUTO 或 MAN
|
||||||
|
- MAN: STOP/FWD/BWD/LEFT/RIGHT/LF/RF/LB/RB/CW/CCW
|
||||||
|
- DIS: 超声距离(cm)
|
||||||
|
- TRK: 循迹4位掩码(H4 H3 H2 H1)
|
||||||
|
- DEV: 偏差值(自动模式更有意义)
|
||||||
|
- OBS: 避障锁(1有障碍,0无)
|
||||||
|
- RPM: 4路电机转速,顺序 M1:M2:M3:M4(LR:LF:RF:RR)
|
||||||
|
|
||||||
|
### 4.2 命令反馈(即时)
|
||||||
|
PAYLOAD格式:
|
||||||
|
|
||||||
|
FB:<CMD>:<0|1>
|
||||||
|
|
||||||
|
示例:
|
||||||
|
- FB:SP:1 表示设置速度成功
|
||||||
|
- FB:MV:0 表示手动命令被拒绝(如模式不对)
|
||||||
|
|
||||||
|
上位机处理规则:
|
||||||
|
- 每个按钮操作都要等待反馈超时窗口
|
||||||
|
- 无反馈应标记为超时,按策略重试或提示用户
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 上位机程序架构要求(AI必须实现)
|
||||||
|
|
||||||
|
### 5.1 网络层
|
||||||
|
功能:
|
||||||
|
- TCP连接/断开
|
||||||
|
- 自动重连
|
||||||
|
- 接收字节流
|
||||||
|
|
||||||
|
要求:
|
||||||
|
- 重连退避:1s -> 2s -> 3s -> 3s...
|
||||||
|
- 断线后禁用运动按钮
|
||||||
|
- 重连成功后状态切到已连接
|
||||||
|
|
||||||
|
### 5.2 协议层
|
||||||
|
功能:
|
||||||
|
- 组帧(payload -> frame)
|
||||||
|
- 解帧(frame -> typed message)
|
||||||
|
- 校验和验证
|
||||||
|
|
||||||
|
要求:
|
||||||
|
- 校验失败帧丢弃并记录日志
|
||||||
|
- 提供结构化事件:StatusEvent / FeedbackEvent
|
||||||
|
|
||||||
|
### 5.3 命令调度层
|
||||||
|
功能:
|
||||||
|
- 发送队列串行化
|
||||||
|
- 等待反馈
|
||||||
|
- 超时重试
|
||||||
|
|
||||||
|
建议默认:
|
||||||
|
- 反馈超时 800ms
|
||||||
|
- 可重试命令:SP、MD、MV:STOP(最多2次)
|
||||||
|
- ST:RUN、GS:xxx不自动重试,避免误动作
|
||||||
|
|
||||||
|
### 5.4 UI层
|
||||||
|
必须具备控件:
|
||||||
|
- 网络连接区:IP、端口、连接/断开
|
||||||
|
- 模式区:AUTO/MAN
|
||||||
|
- 速度区:滑条+发送
|
||||||
|
- 自动区:站点选择、RUN、STOP
|
||||||
|
- 手动区:方向盘(8方向)+旋转+STOP
|
||||||
|
- 状态区:实时显示SP/STA/RUN/MODE/MAN/DIS/TRK/DEV/OBS/RPM
|
||||||
|
- 日志区:TX/RX原始帧 + 解析结果
|
||||||
|
|
||||||
|
### 5.5 安全保护
|
||||||
|
必须实现:
|
||||||
|
- 手动方向按钮松开时自动发送 MV:STOP
|
||||||
|
- 2秒无状态包,UI提示“状态超时”
|
||||||
|
- 急停键:优先发 MV:STOP;若在AUTO,再发 ST:STOP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 按钮与命令映射(可直接发送,含校验)
|
||||||
|
|
||||||
|
### 6.1 模式
|
||||||
|
- 手动模式:LOGI:MD:MAN:0C#
|
||||||
|
- 自动模式:LOGI:MD:AUTO:69#
|
||||||
|
|
||||||
|
### 6.2 速度快捷键
|
||||||
|
- 30%:LOGI:SP:030:D5#
|
||||||
|
- 50%:LOGI:SP:050:D7#
|
||||||
|
- 80%:LOGI:SP:080:DA#
|
||||||
|
|
||||||
|
### 6.3 自动流程按钮
|
||||||
|
- 站点001:LOGI:GS:001:CA#
|
||||||
|
- 站点002:LOGI:GS:002:CB#
|
||||||
|
- 启动:LOGI:ST:RUN:3B#
|
||||||
|
- 停止:LOGI:ST:STOP:8C#
|
||||||
|
|
||||||
|
### 6.4 手动方向按钮
|
||||||
|
- 前进:LOGI:MV:FWD:23#
|
||||||
|
- 后退:LOGI:MV:BWD:1F#
|
||||||
|
- 左移:LOGI:MV:LEFT:6D#
|
||||||
|
- 右移:LOGI:MV:RIGHT:C0#
|
||||||
|
- 左前:LOGI:MV:LF:D4#
|
||||||
|
- 右前:LOGI:MV:RF:DA#
|
||||||
|
- 左后:LOGI:MV:LB:D0#
|
||||||
|
- 右后:LOGI:MV:RB:D6#
|
||||||
|
- 顺时针:LOGI:MV:CW:DC#
|
||||||
|
- 逆时针:LOGI:MV:CCW:1F#
|
||||||
|
- 停止:LOGI:MV:STOP:88#
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 操作时序(AI实现时必须按此流程)
|
||||||
|
|
||||||
|
### 7.1 手动控制时序
|
||||||
|
1. 连接TCP
|
||||||
|
2. 发速度 SP:xxx
|
||||||
|
3. 发 MD:MAN
|
||||||
|
4. 按下方向键发 MV:*
|
||||||
|
5. 松开方向键发 MV:STOP
|
||||||
|
|
||||||
|
建议:
|
||||||
|
- 优先“边沿发送”(按下发一次,松开发STOP)
|
||||||
|
- 若网络抖动明显,可每300ms补发一次当前MV,松开必发STOP
|
||||||
|
|
||||||
|
### 7.2 自动运输时序
|
||||||
|
1. 连接TCP
|
||||||
|
2. 发速度 SP:xxx
|
||||||
|
3. 发 MD:AUTO
|
||||||
|
4. 发 GS:001 或 GS:002
|
||||||
|
5. 发 ST:RUN
|
||||||
|
6. 监控状态包直到到站
|
||||||
|
|
||||||
|
固件行为:
|
||||||
|
- 到站会自动停
|
||||||
|
- RUN变0
|
||||||
|
- 再次启动前需先重发GS,再发ST:RUN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. TCP粘包/拆包解析要求
|
||||||
|
|
||||||
|
TCP是流,不能假设一次recv就是一帧。
|
||||||
|
|
||||||
|
必须实现:
|
||||||
|
- 维护接收缓存
|
||||||
|
- 搜索帧头 LOGI:
|
||||||
|
- 搜索帧尾 #
|
||||||
|
- 从有效头到尾提取候选帧
|
||||||
|
- 校验通过才分发
|
||||||
|
- 头前垃圾数据丢弃
|
||||||
|
|
||||||
|
如果不做这一层,上位机会出现随机解析错误。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 错误处理策略
|
||||||
|
|
||||||
|
### 9.1 超时
|
||||||
|
- 指令超时无反馈:UI标红并记录日志
|
||||||
|
- 安全指令可重试
|
||||||
|
- 运动指令超时优先补发STOP
|
||||||
|
|
||||||
|
### 9.2 校验失败
|
||||||
|
- 丢弃该帧
|
||||||
|
- 记录原始帧日志
|
||||||
|
- 不要直接断线
|
||||||
|
|
||||||
|
### 9.3 模式冲突
|
||||||
|
收到 FB:MV:0 时:
|
||||||
|
1. 自动提示“当前非手动模式”
|
||||||
|
2. 自动发 MD:MAN
|
||||||
|
3. 重试一次原MV命令
|
||||||
|
|
||||||
|
### 9.4 断线
|
||||||
|
- 立刻禁用控制按钮
|
||||||
|
- 显示离线
|
||||||
|
- 重连成功后要求用户确认模式和速度后再控车
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. AI交付验收清单(Definition of Done)
|
||||||
|
|
||||||
|
功能通过:
|
||||||
|
- 能连接/断开TCP
|
||||||
|
- 能正确收发并解析状态和反馈
|
||||||
|
- 全部按钮命令可用
|
||||||
|
- 手动10方向可控
|
||||||
|
- 自动流程可用(GS+RUN+STOP)
|
||||||
|
|
||||||
|
稳定性通过:
|
||||||
|
- 粘包拆包处理正确
|
||||||
|
- 自动重连生效
|
||||||
|
- 超时重试策略生效
|
||||||
|
- 日志可追溯
|
||||||
|
|
||||||
|
可用性通过:
|
||||||
|
- 状态字段实时刷新
|
||||||
|
- 当前模式和方向显示清晰
|
||||||
|
- 急停入口明显且可用
|
||||||
|
|
||||||
|
测试通过:
|
||||||
|
- 校验和单元测试
|
||||||
|
- 解析器单元测试(粘包/拆包/坏包)
|
||||||
|
- 联调测试(与真实小车或Mock服务)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 给“上位机开发AI”的可直接任务描述
|
||||||
|
请将下面这段作为任务输入给编码AI:
|
||||||
|
|
||||||
|
实现一个可发布的物流小车TCP上位机客户端。协议是ASCII帧 LOGI:<PAYLOAD>:<CS>#,校验算法为 sum(LOGI:<PAYLOAD>的ASCII字节) & 0xFF。实现命令 SP/ST/GS/MD/MV,解析状态STAT和反馈FB。支持自动模式和手动麦克纳姆模式(8方向+旋转+停止)。必须实现TCP流式粘包拆包解析、自动重连、命令超时重试、原始日志和结构化状态显示。UI必须包含连接区、模式区、速度区、自动区、手动方向区、状态区和日志区。
|
||||||
@@ -33,11 +33,11 @@ Dma.USART2_TX.2.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
|
|||||||
Dma.USART2_TX.2.PeriphInc=DMA_PINC_DISABLE
|
Dma.USART2_TX.2.PeriphInc=DMA_PINC_DISABLE
|
||||||
Dma.USART2_TX.2.Priority=DMA_PRIORITY_LOW
|
Dma.USART2_TX.2.Priority=DMA_PRIORITY_LOW
|
||||||
Dma.USART2_TX.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority
|
Dma.USART2_TX.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority
|
||||||
FREERTOS.FootprintOK=true
|
FREERTOS.FootprintOK=false
|
||||||
FREERTOS.IPParameters=Tasks01,FootprintOK,Queues01,configTOTAL_HEAP_SIZE
|
FREERTOS.IPParameters=Tasks01,FootprintOK,Queues01,configTOTAL_HEAP_SIZE
|
||||||
FREERTOS.Queues01=CmdQueue,16,16,1,Dynamic,NULL,NULL
|
FREERTOS.Queues01=CmdQueue,16,16,1,Dynamic,NULL,NULL
|
||||||
FREERTOS.Tasks01=initTask,24,128,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;CarCtrlTask,24,256,CarCtrl_Task,As weak,NULL,Dynamic,NULL,NULL;timerTask,16,512,speed_get,As weak,NULL,Dynamic,NULL,NULL;sr04Task,8,128,sr04_task,Default,NULL,Dynamic,NULL,NULL;rc522Task,16,128,rc522_task,Default,NULL,Dynamic,NULL,NULL
|
FREERTOS.Tasks01=initTask,24,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;CarCtrlTask,24,1024,CarCtrl_Task,As weak,NULL,Dynamic,NULL,NULL;timerTask,16,1024,speed_get,As weak,NULL,Dynamic,NULL,NULL;sr04Task,8,512,sr04_task,Default,NULL,Dynamic,NULL,NULL;rc522Task,16,512,rc522_task,Default,NULL,Dynamic,NULL,NULL
|
||||||
FREERTOS.configTOTAL_HEAP_SIZE=10000
|
FREERTOS.configTOTAL_HEAP_SIZE=18000
|
||||||
File.Version=6
|
File.Version=6
|
||||||
GPIO.groupedBy=Group By Peripherals
|
GPIO.groupedBy=Group By Peripherals
|
||||||
KeepUserPlacement=false
|
KeepUserPlacement=false
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<add key="super_send23hex_checked" value="0" />
|
<add key="super_send23hex_checked" value="0" />
|
||||||
<add key="super_send24hex_checked" value="0" />
|
<add key="super_send24hex_checked" value="0" />
|
||||||
<add key="super_send25hex_checked" value="0" />
|
<add key="super_send25hex_checked" value="0" />
|
||||||
<add key="super_text_send" value="你好啊啊" />
|
<add key="super_text_send" value="AT+RST #复位" />
|
||||||
<add key="super_text_send1" value="AT #测试" />
|
<add key="super_text_send1" value="AT #测试" />
|
||||||
<add key="super_text_send2" value="AT+CWMODE=1 #设置工作模式为STA" />
|
<add key="super_text_send2" value="AT+CWMODE=1 #设置工作模式为STA" />
|
||||||
<add key="super_text_send3" value="AT+CWJAP="ssid","password" #配置WIFI" />
|
<add key="super_text_send3" value="AT+CWJAP="ssid","password" #配置WIFI" />
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<add key="super_text_send25" value="" />
|
<add key="super_text_send25" value="" />
|
||||||
<add key="super_num_time1" value="800" />
|
<add key="super_num_time1" value="800" />
|
||||||
<add key="super_num_time2" value="500" />
|
<add key="super_num_time2" value="500" />
|
||||||
<add key="super_rate" value="6" />
|
<add key="super_rate" value="5" />
|
||||||
<add key="super_parity" value="0" />
|
<add key="super_parity" value="0" />
|
||||||
<add key="super_databits" value="1" />
|
<add key="super_databits" value="1" />
|
||||||
<add key="super_stopbit" value="0" />
|
<add key="super_stopbit" value="0" />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ SendHex=0
|
|||||||
[DlgCreateServer]
|
[DlgCreateServer]
|
||||||
DlgServerPort=3456
|
DlgServerPort=3456
|
||||||
[Update]
|
[Update]
|
||||||
Time=1775660879
|
Time=1776317344
|
||||||
[SysOptions]
|
[SysOptions]
|
||||||
SendBlSZforFile=10240
|
SendBlSZforFile=10240
|
||||||
SendBlITforFile=1
|
SendBlITforFile=1
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ file_sel13 = 0
|
|||||||
file_path13 =
|
file_path13 =
|
||||||
file_flag13 = False
|
file_flag13 = False
|
||||||
file_offset13 =
|
file_offset13 =
|
||||||
default_path = E:\My_Workpace\ÎïÁ÷С³µ\esp_12f\flash_download_tool_3.9.7
|
default_path = E:\My_Workpace\f103_car\ÎïÁ÷С³µ\esp_12f\flash_download_tool_3.9.7
|
||||||
|
|
||||||
[FLASH_CRYSTAL]
|
[FLASH_CRYSTAL]
|
||||||
spicfgdis = 1
|
spicfgdis = 1
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ multi_col = 2
|
|||||||
|
|
||||||
[DOWNLOAD PATH]
|
[DOWNLOAD PATH]
|
||||||
file_sel0 = 1
|
file_sel0 = 1
|
||||||
file_path0 = E:\My_Workpace\ÎïÁ÷С³µ\esp_12f\flash_download_tool_3.9.7\£¨1112£©ESP8266-4M.bin
|
file_path0 = E:\My_Workpace\f103_car\ÎïÁ÷С³µ\esp_12f\£¨1112£©ESP8266-4M.bin
|
||||||
file_flag0 = False
|
file_flag0 = False
|
||||||
file_offset0 = 0
|
file_offset0 = 0
|
||||||
file_sel1 = 0
|
file_sel1 = 0
|
||||||
@@ -62,7 +62,7 @@ file_sel13 = 0
|
|||||||
file_path13 =
|
file_path13 =
|
||||||
file_flag13 = False
|
file_flag13 = False
|
||||||
file_offset13 =
|
file_offset13 =
|
||||||
default_path = E:\My_Workpace\ÎïÁ÷С³µ\esp_12f\flash_download_tool_3.9.7
|
default_path = E:\My_Workpace\f103_car\ÎïÁ÷С³µ\esp_12f
|
||||||
|
|
||||||
[FLASH_CRYSTAL]
|
[FLASH_CRYSTAL]
|
||||||
spicfgdis = 1
|
spicfgdis = 1
|
||||||
|
|||||||
BIN
物流小车/web/app.exe
BIN
物流小车/web/app.exe
Binary file not shown.
Reference in New Issue
Block a user