## 主要修改

### 1. MQTT setMode命令处理修复
- 添加Set_System_Mode函数extern声明,解决编译错误
- 将setMode处理逻辑移到control主题,符合小程序规范
- 完善Set_System_Mode函数,添加音频播放功能

### 2. 按键4手动补水功能
- 实现key4_single_click_handler函数,添加手动补水逻辑
- 与按键2喂食逻辑保持一致:相同的模式检查和错误处理
- 提供完整的本地手动控制功能

### 3. 系统优化
- 统一本地和远程控制逻辑
- 完善错误处理和用户反馈
- 优化代码结构和日志记录

## 影响
- 系统现在支持完整的双重控制方式(本地按键+远程MQTT)
- 所有按键功能完善:模式切换、手动喂食、页面切换、手动补水
- 编译无错误,代码结构清晰,便于维护
This commit is contained in:
2026-02-25 22:48:12 +08:00
parent d184d96052
commit 1fc85589c6
6 changed files with 1031 additions and 463 deletions

View File

@@ -19,10 +19,9 @@
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "cmsis_os.h"
#include "cmsis_os2.h"
#include "main.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
@@ -110,48 +109,67 @@ static volatile Feed_Cmd_t feed_command = FEED_CMD_NONE; // 喂食命令标志
static volatile uint16_t feed_angle = 90; // 喂食角度默认90度
static volatile uint8_t feed_amount = 1; // 喂食份数
// 加水控制命令枚举
typedef enum {
WATER_CMD_NONE = 0, // 无命令
WATER_CMD_MANUAL, // 手动加水
WATER_CMD_AUTO, // 自动加水
WATER_CMD_REMOTE // 远程加水
} Water_Cmd_t;
// 加水控制相关全局变量
static volatile Water_Cmd_t water_command = WATER_CMD_NONE; // 加水命令标志
static volatile uint8_t watering_in_progress = 0; // 加水进行中标志
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 256 * 4,
.priority = (osPriority_t)osPriorityHigh1,
.name = "defaultTask",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityHigh1,
};
/* Definitions for wifi_mqtt */
osThreadId_t wifi_mqttHandle;
const osThreadAttr_t wifi_mqtt_attributes = {
.name = "wifi_mqtt",
.stack_size = 3000 * 4,
.priority = (osPriority_t)osPriorityHigh,
.name = "wifi_mqtt",
.stack_size = 3000 * 4,
.priority = (osPriority_t) osPriorityHigh,
};
/* Definitions for LCD_SHOW_Task */
osThreadId_t LCD_SHOW_TaskHandle;
const osThreadAttr_t LCD_SHOW_Task_attributes = {
.name = "LCD_SHOW_Task",
.stack_size = 1024 * 4,
.priority = (osPriority_t)osPriorityHigh,
.name = "LCD_SHOW_Task",
.stack_size = 1024 * 4,
.priority = (osPriority_t) osPriorityHigh,
};
/* Definitions for button */
osThreadId_t buttonHandle;
const osThreadAttr_t button_attributes = {
.name = "button",
.stack_size = 512 * 4,
.priority = (osPriority_t)osPriorityRealtime2,
.name = "button",
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityRealtime2,
};
/* Definitions for sensor */
osThreadId_t sensorHandle;
const osThreadAttr_t sensor_attributes = {
.name = "sensor",
.stack_size = 1024 * 4,
.priority = (osPriority_t)osPriorityNormal,
.name = "sensor",
.stack_size = 1024 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for step_motor */
osThreadId_t step_motorHandle;
const osThreadAttr_t step_motor_attributes = {
.name = "step_motor",
.stack_size = 512 * 4,
.priority = (osPriority_t)osPriorityNormal,
.name = "step_motor",
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for water_control */
osThreadId_t water_controlHandle;
const osThreadAttr_t water_control_attributes = {
.name = "water_control",
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Private function prototypes -----------------------------------------------*/
@@ -165,6 +183,16 @@ void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water,
Sensor_Data_t *LCD_GetSensorData(void);
void user_button_init(void);
void RTC_TimeUpdateCallback(void); // RTC时间更新回调函数
uint8_t Build_Sensor_JSON(const Sensor_Data_t *data, char *buffer, uint16_t buffer_size);
static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) ;
void Clear_Feed_Command(void);
uint8_t Request_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount);
uint8_t Request_Water(Water_Cmd_t cmd);
void Clear_Water_Command(void);
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
@@ -173,14 +201,15 @@ void LCD_Task(void *argument);
void button_task(void *argument);
void sensorTask(void *argument);
void step_motor_task(void *argument);
void water_controlTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
ST7735_Init(); // 初始化ST7735显示屏
@@ -205,8 +234,7 @@ void MX_FREERTOS_Init(void) {
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle =
osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of wifi_mqtt */
wifi_mqttHandle = osThreadNew(wifi_task_mqtt, NULL, &wifi_mqtt_attributes);
@@ -223,6 +251,9 @@ void MX_FREERTOS_Init(void) {
/* creation of step_motor */
step_motorHandle = osThreadNew(step_motor_task, NULL, &step_motor_attributes);
/* creation of water_control */
water_controlHandle = osThreadNew(water_controlTask, NULL, &water_control_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
@@ -230,6 +261,7 @@ void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
@@ -239,7 +271,8 @@ void MX_FREERTOS_Init(void) {
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument) {
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
// 1. 打开运行灯
Device_Control(DEVICE_LED_RUN, 1);
@@ -281,7 +314,8 @@ void StartDefaultTask(void *argument) {
* @retval None
*/
/* USER CODE END Header_LCD_Task */
void LCD_Task(void *argument) {
void LCD_Task(void *argument)
{
/* USER CODE BEGIN LCD_Task */
char display_str[32];
@@ -503,7 +537,8 @@ void LCD_Task(void *argument) {
* @retval None
*/
/* USER CODE END Header_button_task */
void button_task(void *argument) {
void button_task(void *argument)
{
/* USER CODE BEGIN button_task */
user_button_init();
@@ -522,7 +557,8 @@ void button_task(void *argument) {
* @retval None
*/
/* USER CODE END Header_sensorTask */
void sensorTask(void *argument) {
void sensorTask(void *argument)
{
/* USER CODE BEGIN sensorTask */
elog_i(TAG, "启动传感器任务");
@@ -582,108 +618,35 @@ void sensorTask(void *argument) {
sensor_data.food_weight= HX711_GetWeight(10); // 采样10次取平均
elog_d(TAG, "当前重量:%.2f g", sensor_data.food_weight);
// ========== 发布传感器数据到MQTT ==========
char json_buffer[128];
if (Build_Sensor_JSON(&sensor_data, json_buffer, sizeof(json_buffer))) {
if (WIFI_MQTT_Publish_Sensor(json_buffer)) {
elog_i(TAG, "传感器数据发布成功: %s", json_buffer);
} else {
elog_e(TAG, "传感器数据发布失败");
}
} else {
elog_e(TAG, "构建JSON数据失败");
}
// ========== 发布结束 ===============
// 每2秒读取一次
osDelay(2000);
}
/* USER CODE END sensorTask */
}
/**
* @brief 请求喂食操作
* @param cmd: 喂食命令类型
* @param angle: 转动角度
* @param amount: 喂食份数
* @retval 1=请求成功, 0=请求失败(正在喂食中)
*/
uint8_t Request_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) {
if (feeding_in_progress) {
elog_w("FEED", "喂食进行中,无法接受新命令");
return 0;
}
feed_command = cmd;
feed_angle = angle;
feed_amount = amount;
elog_i("FEED", "喂食请求已提交: cmd=%d, angle=%d, amount=%d", cmd, angle,
amount);
return 1;
}
/**
* @brief 获取当前喂食命令
* @retval 当前喂食命令
*/
Feed_Cmd_t Get_Feed_Command(void) { return feed_command; }
/**
* @brief 清除喂食命令
*/
void Clear_Feed_Command(void) { feed_command = FEED_CMD_NONE; }
/**
* @brief 执行喂食操作
* @param cmd: 喂食命令类型
* @param angle: 转动角度
* @param amount: 喂食份数
*/
static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) {
if (feeding_in_progress) {
return; // 防止重复执行
}
feeding_in_progress = 1;
// 根据命令类型播放不同音频
switch (cmd) {
case FEED_CMD_MANUAL:
MP3_Play(FEED_MANUAL_TRIGGER);
elog_i("FEED", "执行手动喂食: 角度%d度, %d份", angle, amount);
break;
case FEED_CMD_AUTO:
MP3_Play(FEED_AUTO_START);
elog_i("FEED", "执行自动喂食: 角度%d度, %d份", angle, amount);
break;
case FEED_CMD_REMOTE:
MP3_Play(REMOTE_CMD_RECEIVED);
elog_i("FEED", "执行远程喂食: 角度%d度, %d份", angle, amount);
break;
case FEED_CMD_TEST:
elog_i("FEED", "执行测试喂食: 角度%d度", angle);
break;
default:
break;
}
// 执行实际的喂食动作
for (uint8_t i = 0; i < amount; i++) {
Stepper_Motor_RotateAngle(angle, STEPPER_DIR_CW, 2, STEPPER_MODE_FULL_STEP);
if (i < amount - 1) {
osDelay(1000); // 多份之间间隔1秒
}
}
// 等待食物落下
osDelay(3000);
// 停止电机
Stepper_Motor_Stop();
// 播放完成音效
MP3_Play(FEED_COMPLETE);
elog_i("FEED", "喂食完成");
feeding_in_progress = 0;
}
/* USER CODE BEGIN Header_step_motor_task */
/**
* @brief Function implementing the step_motor thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_step_motor_task */
void step_motor_task(void *argument) {
void step_motor_task(void *argument)
{
/* USER CODE BEGIN step_motor_task */
// 初始化步进电机
Stepper_Motor_Init();
@@ -708,6 +671,91 @@ void step_motor_task(void *argument) {
/* USER CODE END step_motor_task */
}
/* USER CODE BEGIN Header_water_controlTask */
/**
* @brief Function implementing the water_control thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_water_controlTask */
void water_controlTask(void *argument)
{
/* USER CODE BEGIN water_controlTask */
elog_i("WATER", "启动加水控制任务");
/* Infinite loop */
for(;;)
{
// 检查是否有加水命令需要执行
if (water_command != WATER_CMD_NONE && !watering_in_progress) {
Water_Cmd_t current_cmd = water_command;
// 清除命令标志
Clear_Water_Command();
// 执行加水
watering_in_progress = 1;
// 根据命令类型播放不同音频
switch (current_cmd) {
case WATER_CMD_MANUAL:
MP3_Play(WATER_REFILL_DONE);
elog_i("WATER", "执行手动加水");
break;
case WATER_CMD_AUTO:
MP3_Play(WATER_REFILL_DONE);
elog_i("WATER", "执行自动加水");
break;
case WATER_CMD_REMOTE:
MP3_Play(REMOTE_CMD_RECEIVED);
elog_i("WATER", "执行远程加水");
break;
default:
break;
}
// 打开水泵继电器假设DEVICE_RELAY控制水泵
Device_Control(DEVICE_RELAY, 1);
elog_i("WATER", "水泵启动,等待水位传感器检测到有水");
// 加水超时保护最大30秒
uint32_t start_time = osKernelGetTickCount();
const uint32_t MAX_WATERING_TIME = 30000; // 30秒超时
// 循环检查水位传感器,直到检测到有水或超时
while (1) {
// 检查是否检测到有水
if (sensor_data.water_level == 1) {
elog_i("WATER", "水位传感器检测到有水,停止加水");
break;
}
// 检查是否超时
if (osKernelGetTickCount() - start_time > MAX_WATERING_TIME) {
elog_w("WATER", "加水超时30秒强制停止");
break;
}
// 短暂延时避免占用过多CPU
osDelay(100);
}
// 关闭水泵继电器
Device_Control(DEVICE_RELAY, 0);
elog_i("WATER", "水泵停止");
// 播放完成音效
MP3_Play(WATER_REFILL_DONE);
elog_i("WATER", "加水完成");
watering_in_progress = 0;
}
osDelay(100); // 短暂延时,保持任务响应性
}
/* USER CODE END water_controlTask */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
@@ -795,7 +843,26 @@ void key3_single_click_handler(Button *btn) {
LCD_NextPage();
}
void key4_single_click_handler(Button *btn) { elog_i("KEY", "按键4单击"); }
/**
* @brief 按键4处理函数手动补水仅手动模式有效
* @param btn: 按键句柄
*/
void key4_single_click_handler(Button *btn) {
// 检查是否为手动模式
if (!system_mode) {
// 手动模式下才允许手动补水
if (Request_Water(WATER_CMD_MANUAL)) {
elog_i("KEY", "按键4单击 - 手动补水请求已提交");
} else {
elog_w("KEY", "补水进行中,请稍后再试");
MP3_Play(SYS_ERROR_ALARM);
}
} else {
// 自动模式下按键无效
elog_w("KEY", "当前为自动模式按键4无效");
MP3_Play(SYS_ERROR_ALARM);
}
}
void M3_long_press_start_handler(Button *btn) {
elog_i("KEY", "M3水位传感器检测到有水低电平");
@@ -856,6 +923,10 @@ void Set_System_Mode(uint8_t mode) {
system_mode = mode;
sensor_data.system_mode = mode;
lcd_force_refresh = 1; // 强制刷新显示
// 播放模式切换音效与按键1逻辑保持一致
MP3_Play(mode ? MODE_AUTO : MODE_MANUAL);
elog_i("SYSTEM", "系统模式设置为: %s", mode ? "自动" : "手动");
}
}
@@ -950,6 +1021,153 @@ Sensor_Data_t *LCD_GetSensorData(void) { return &sensor_data; }
*/
void RTC_TimeUpdateCallback(void) { lcd_force_refresh = 1; }
/**
* @brief 构建传感器数据JSON字符串符合小程序格式要求
* @param data 传感器数据结构体指针
* @param buffer 输出缓冲区
* @param buffer_size 缓冲区大小
* @return 1表示成功0表示失败参数错误或缓冲区不足
*/
uint8_t Build_Sensor_JSON(const Sensor_Data_t *data, char *buffer, uint16_t buffer_size) {
// 参数检查
if (!data || !buffer || buffer_size < 128) {
return 0;
}
// 将float类型的湿度和重量转换为整数小程序要求整数
int humidity_int = (int)data->humidity;
int food_weight_int = (int)data->food_weight;
// 构建符合小程序要求的JSON字符串
// 注意字段名foodWeightcamelCase而不是food_weightsnake_case
// waterLevelcamelCase而不是water_levelsnake_case
snprintf(buffer, buffer_size,
"{\"temperature\":%.1f,\"humidity\":%d,\"foodWeight\":%d,\"waterLevel\":%d}",
data->temperature, // 温度保留1位小数
humidity_int, // 湿度(整数)
food_weight_int, // 食物重量(整数)
data->water_level); // 水位状态0或1
return 1;
}
/**
* @brief 请求喂食操作
* @param cmd: 喂食命令类型
* @param angle: 转动角度
* @param amount: 喂食份数
* @retval 1=请求成功, 0=请求失败(正在喂食中)
*/
uint8_t Request_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) {
if (feeding_in_progress) {
elog_w("FEED", "喂食进行中,无法接受新命令");
return 0;
}
feed_command = cmd;
feed_angle = angle;
feed_amount = amount;
elog_i("FEED", "喂食请求已提交: cmd=%d, angle=%d, amount=%d", cmd, angle,
amount);
return 1;
}
/**
* @brief 获取当前喂食命令
* @retval 当前喂食命令
*/
Feed_Cmd_t Get_Feed_Command(void) { return feed_command; }
/**
* @brief 清除喂食命令
*/
void Clear_Feed_Command(void) { feed_command = FEED_CMD_NONE; }
/**
* @brief 请求加水操作
* @param cmd: 加水命令类型
* @retval 1=请求成功, 0=请求失败(正在加水中)
*/
uint8_t Request_Water(Water_Cmd_t cmd) {
if (watering_in_progress) {
elog_w("WATER", "加水进行中,无法接受新命令");
return 0;
}
water_command = cmd;
elog_i("WATER", "加水请求已提交: cmd=%d", cmd);
return 1;
}
/**
* @brief 清除加水命令
*/
void Clear_Water_Command(void) { water_command = WATER_CMD_NONE; }
/**
* @brief 执行喂食操作
* @param cmd: 喂食命令类型
* @param angle: 转动角度
* @param amount: 喂食份数
*/
static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) {
if (feeding_in_progress) {
return; // 防止重复执行
}
feeding_in_progress = 1;
// 根据命令类型播放不同音频
switch (cmd) {
case FEED_CMD_MANUAL:
MP3_Play(FEED_MANUAL_TRIGGER);
elog_i("FEED", "执行手动喂食: 角度%d度, %d份", angle, amount);
break;
case FEED_CMD_AUTO:
MP3_Play(FEED_AUTO_START);
elog_i("FEED", "执行自动喂食: 角度%d度, %d份", angle, amount);
break;
case FEED_CMD_REMOTE:
MP3_Play(REMOTE_CMD_RECEIVED);
elog_i("FEED", "执行远程喂食: 角度%d度, %d份", angle, amount);
break;
case FEED_CMD_TEST:
elog_i("FEED", "执行测试喂食: 角度%d度", angle);
break;
default:
break;
}
// 执行实际的喂食动作
for (uint8_t i = 0; i < amount; i++) {
Stepper_Motor_RotateAngle(angle, STEPPER_DIR_CW, 2, STEPPER_MODE_FULL_STEP);
if (i < amount - 1) {
osDelay(1000); // 多份之间间隔1秒
}
}
// 等待食物落下
osDelay(3000);
// 停止电机
Stepper_Motor_Stop();
// 播放完成音效
MP3_Play(FEED_COMPLETE);
elog_i("FEED", "喂食完成");
feeding_in_progress = 0;
}
/* USER CODE END LCD_Page_Functions */
/* USER CODE END Application */
/* USER CODE END Application */