不干了!!!收
This commit is contained in:
@@ -111,22 +111,41 @@ static volatile uint8_t feed_amount = 1; // 喂食份数
|
||||
|
||||
// 加水控制命令枚举
|
||||
typedef enum {
|
||||
WATER_CMD_NONE = 0, // 无命令
|
||||
WATER_CMD_MANUAL, // 手动加水
|
||||
WATER_CMD_AUTO, // 自动加水
|
||||
WATER_CMD_REMOTE // 远程加水
|
||||
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; // 加水进行中标志
|
||||
|
||||
// 自动补水控制变量
|
||||
static volatile uint8_t auto_water_enabled = 1; // 自动补水使能标志(默认开启)
|
||||
static volatile uint8_t water_level_threshold = 1; // 水位阈值(0=无水,1=有水)
|
||||
static volatile uint8_t pir_trigger_water = 0; // PIR传感器触发补水标志
|
||||
static volatile uint32_t last_auto_water_time = 0; // 上次自动补水时间
|
||||
static const uint32_t AUTO_WATER_INTERVAL = 30000; // 自动补水最小间隔(30秒)
|
||||
|
||||
// 定时喂食控制变量
|
||||
#define MAX_SCHEDULED_FEED_TIMES 5 // 最大定时喂食时间数量
|
||||
static volatile uint8_t scheduled_feed_enabled =
|
||||
1; // 定时喂食使能标志(默认开启)
|
||||
static volatile uint16_t single_feed_weight = 50; // 单次喂食重量(默认50克)
|
||||
static volatile uint8_t scheduled_feed_count = 0; // 定时喂食时间数量
|
||||
static volatile char scheduled_feed_times[MAX_SCHEDULED_FEED_TIMES][6] = {
|
||||
0}; // 定时喂食时间数组(格式:"HH:MM")
|
||||
static volatile uint32_t last_scheduled_feed_time = 0; // 上次定时喂食时间
|
||||
static const uint32_t SCHEDULED_FEED_INTERVAL =
|
||||
60000; // 定时喂食检查间隔(60秒)
|
||||
|
||||
/* USER CODE END Variables */
|
||||
/* Definitions for defaultTask */
|
||||
osThreadId_t defaultTaskHandle;
|
||||
const osThreadAttr_t defaultTask_attributes = {
|
||||
.name = "defaultTask",
|
||||
.stack_size = 256 * 4,
|
||||
.stack_size = 512 * 4,
|
||||
.priority = (osPriority_t) osPriorityHigh1,
|
||||
};
|
||||
/* Definitions for wifi_mqtt */
|
||||
@@ -171,6 +190,13 @@ const osThreadAttr_t water_control_attributes = {
|
||||
.stack_size = 512 * 4,
|
||||
.priority = (osPriority_t) osPriorityNormal,
|
||||
};
|
||||
/* Definitions for hx711 */
|
||||
osThreadId_t hx711Handle;
|
||||
const osThreadAttr_t hx711_attributes = {
|
||||
.name = "hx711",
|
||||
.stack_size = 256 * 4,
|
||||
.priority = (osPriority_t) osPriorityAboveNormal,
|
||||
};
|
||||
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
/* USER CODE BEGIN FunctionPrototypes */
|
||||
@@ -183,16 +209,23 @@ 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);
|
||||
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) ;
|
||||
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);
|
||||
|
||||
// 定时喂食相关函数
|
||||
void Check_Scheduled_Feed(void);
|
||||
void Update_Feed_Config(const char *feed_times_json, uint16_t single_weight);
|
||||
uint16_t Get_Single_Feed_Weight(void);
|
||||
uint8_t Is_Time_Match(volatile const char *scheduled_time, uint8_t current_hour,
|
||||
uint8_t current_minute);
|
||||
|
||||
/* USER CODE END FunctionPrototypes */
|
||||
|
||||
void StartDefaultTask(void *argument);
|
||||
@@ -202,6 +235,7 @@ void button_task(void *argument);
|
||||
void sensorTask(void *argument);
|
||||
void step_motor_task(void *argument);
|
||||
void water_controlTask(void *argument);
|
||||
void hx711Task(void *argument);
|
||||
|
||||
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
|
||||
|
||||
@@ -254,6 +288,9 @@ void MX_FREERTOS_Init(void) {
|
||||
/* creation of water_control */
|
||||
water_controlHandle = osThreadNew(water_controlTask, NULL, &water_control_attributes);
|
||||
|
||||
/* creation of hx711 */
|
||||
hx711Handle = osThreadNew(hx711Task, NULL, &hx711_attributes);
|
||||
|
||||
/* USER CODE BEGIN RTOS_THREADS */
|
||||
/* add threads, ... */
|
||||
/* USER CODE END RTOS_THREADS */
|
||||
@@ -290,8 +327,8 @@ void StartDefaultTask(void *argument)
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置为低音量-测试用
|
||||
MP3_SetVolume(20);
|
||||
// 设置为低音量-测试用
|
||||
// MP3_SetVolume(20);
|
||||
|
||||
elog_i("MP3", "模块初始化完成");
|
||||
MP3_Play(SYS_POWER_ON); // 播放系统启动音
|
||||
@@ -303,6 +340,21 @@ void StartDefaultTask(void *argument)
|
||||
osDelay(1000);
|
||||
Device_Control(DEVICE_LED_RUN, 0); // 关闭运行灯
|
||||
osDelay(1000);
|
||||
|
||||
// ========== 发布传感器数据到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数据失败");
|
||||
}
|
||||
|
||||
osDelay(1000);
|
||||
// ========== 发布结束 ===============
|
||||
}
|
||||
/* USER CODE END StartDefaultTask */
|
||||
}
|
||||
@@ -524,7 +576,7 @@ void LCD_Task(void *argument)
|
||||
}
|
||||
} else {
|
||||
// 其他页面:1秒刷新一次
|
||||
osDelay(2000);
|
||||
osDelay(800);
|
||||
}
|
||||
}
|
||||
/* USER CODE END LCD_Task */
|
||||
@@ -562,18 +614,6 @@ void sensorTask(void *argument)
|
||||
/* USER CODE BEGIN sensorTask */
|
||||
elog_i(TAG, "启动传感器任务");
|
||||
|
||||
// #################### 校准流程(首次使用必须执行!)####################
|
||||
HAL_Delay(1000); // 上电稳定
|
||||
elog_i(TAG, "开始校准零点...");
|
||||
HX711_CalibrateZero(); // 空载时调用(传感器上不放任何东西)
|
||||
elog_i(TAG, "零点校准完成!零点AD值:%d", hx711_zero_offset);
|
||||
HAL_Delay(1000);
|
||||
// elog_i(TAG, "请在传感器上放置已知重量的物体,3秒后开始标定...");
|
||||
// HAL_Delay(3000);
|
||||
// HX711_CalibrateScale(350.0f); // 放置100g的物体(根据实际重量修改)
|
||||
elog_i(TAG, "量程标定完成!标定系数:%.6f", hx711_scale);
|
||||
// #################### 循环读取重量 ####################
|
||||
|
||||
// 扫描I2C设备(调试)
|
||||
elog_i(TAG, "扫描I2C设备");
|
||||
AHT30_ScanI2C(&hi2c1);
|
||||
@@ -615,21 +655,6 @@ void sensorTask(void *argument)
|
||||
elog_w(TAG, "AHT30数据读取失败");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -638,7 +663,6 @@ void sensorTask(void *argument)
|
||||
|
||||
/* USER CODE BEGIN Header_step_motor_task */
|
||||
|
||||
|
||||
/**
|
||||
* @brief Function implementing the step_motor thread.
|
||||
* @param argument: Not used
|
||||
@@ -673,37 +697,39 @@ void step_motor_task(void *argument)
|
||||
|
||||
/* USER CODE BEGIN Header_water_controlTask */
|
||||
/**
|
||||
* @brief Function implementing the water_control thread.
|
||||
* @param argument: Not used
|
||||
* @retval None
|
||||
*/
|
||||
* @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(;;)
|
||||
{
|
||||
// 检查是否有加水命令需要执行
|
||||
for (;;) {
|
||||
// 1. 检查定时喂食
|
||||
Check_Scheduled_Feed();
|
||||
|
||||
// 2. 检查是否有加水命令需要执行
|
||||
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);
|
||||
MP3_Play(WATER_REFILL_START);
|
||||
elog_i("WATER", "执行手动加水");
|
||||
break;
|
||||
case WATER_CMD_AUTO:
|
||||
MP3_Play(WATER_REFILL_DONE);
|
||||
MP3_Play(WATER_REFILL_START);
|
||||
elog_i("WATER", "执行自动加水");
|
||||
break;
|
||||
case WATER_CMD_REMOTE:
|
||||
@@ -713,15 +739,15 @@ void water_controlTask(void *argument)
|
||||
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) {
|
||||
// 检查是否检测到有水
|
||||
@@ -729,33 +755,118 @@ void water_controlTask(void *argument)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// 2. 自动补水逻辑(仅在自动模式下且自动补水功能使能时)
|
||||
if (system_mode && auto_water_enabled && !watering_in_progress) {
|
||||
uint32_t current_time = osKernelGetTickCount();
|
||||
|
||||
// 检查是否满足自动补水条件
|
||||
// 条件1:水位传感器检测到无水(sensor_data.water_level == 0)
|
||||
// 条件2:距离上次自动补水时间超过最小间隔
|
||||
// 条件3:PIR传感器触发补水标志(可选)
|
||||
|
||||
// 首先检查水位状态,如果水位充足,不进行任何补水
|
||||
if (sensor_data.water_level == 0) {
|
||||
// 水位不足,检查是否需要补水
|
||||
|
||||
// 优先级1:PIR传感器触发补水(宠物靠近)
|
||||
if (pir_trigger_water) {
|
||||
elog_i("WATER", "PIR传感器触发补水 - 宠物靠近检测到水位不足");
|
||||
|
||||
// 清除PIR触发标志
|
||||
pir_trigger_water = 0;
|
||||
|
||||
// 设置自动加水命令
|
||||
water_command = WATER_CMD_AUTO;
|
||||
|
||||
// 记录日志
|
||||
elog_i("WATER", "宠物靠近触发补水请求已提交");
|
||||
}
|
||||
// 优先级2:定时自动补水(水位检测)
|
||||
else if (current_time - last_auto_water_time > AUTO_WATER_INTERVAL) {
|
||||
elog_i("WATER", "检测到水位不足,开始自动补水");
|
||||
|
||||
// 设置自动加水命令
|
||||
water_command = WATER_CMD_AUTO;
|
||||
|
||||
// 记录日志
|
||||
elog_i("WATER", "水位检测触发补水请求已提交");
|
||||
}
|
||||
} else {
|
||||
// 水位充足,清除PIR触发标志(如果有)
|
||||
if (pir_trigger_water) {
|
||||
elog_i("WATER", "水位充足,忽略PIR传感器触发补水");
|
||||
pir_trigger_water = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. PIR传感器触发补水处理(宠物靠近时补水)
|
||||
// 注意:HC_SR505人体传感器检测到宠物靠近时,会触发HC_SR055_long_click_handler
|
||||
// 我们可以在该处理函数中设置pir_trigger_water标志
|
||||
// 在water_controlTask中会检查水位状态,避免水位充足时不必要的补水
|
||||
|
||||
osDelay(100); // 短暂延时,保持任务响应性
|
||||
}
|
||||
/* USER CODE END water_controlTask */
|
||||
}
|
||||
|
||||
/* USER CODE BEGIN Header_hx711Task */
|
||||
/**
|
||||
* @brief Function implementing the hx711 thread.
|
||||
* @param argument: Not used
|
||||
* @retval None
|
||||
*/
|
||||
/* USER CODE END Header_hx711Task */
|
||||
void hx711Task(void *argument)
|
||||
{
|
||||
/* USER CODE BEGIN hx711Task */
|
||||
|
||||
HX711_Init(); // 初始化HX711重量传感器
|
||||
// #################### 校准流程(首次使用必须执行!)####################
|
||||
HAL_Delay(1000); // 上电稳定
|
||||
elog_i(TAG, "开始校准零点...");
|
||||
HX711_CalibrateZero(); // 空载时调用(传感器上不放任何东西)
|
||||
elog_i(TAG, "零点校准完成!零点AD值:%d", hx711_zero_offset);
|
||||
HAL_Delay(1000);
|
||||
elog_i(TAG, "请在传感器上放置已知重量的物体,3秒后开始标定...");
|
||||
HAL_Delay(5000);
|
||||
HX711_CalibrateScale(334.0f); // 放置0g的物体(根据实际重量修改)
|
||||
elog_i(TAG, "量程标定完成!标定系数:%.6f", hx711_scale);
|
||||
// #################### 循环读取重量 ####################
|
||||
|
||||
/* Infinite loop */
|
||||
for (;;) {
|
||||
|
||||
sensor_data.food_weight = HX711_GetWeight(10); // 采样10次取平均
|
||||
elog_d(TAG, "当前重量:%.2f g", sensor_data.food_weight);
|
||||
|
||||
osDelay(500);
|
||||
}
|
||||
/* USER CODE END hx711Task */
|
||||
}
|
||||
|
||||
/* Private application code --------------------------------------------------*/
|
||||
/* USER CODE BEGIN Application */
|
||||
|
||||
@@ -873,7 +984,22 @@ void M3_press_up_handler(Button *btn) {
|
||||
elog_i("KEY", "M3水位传感器检测到无水(高电平)");
|
||||
sensor_data.water_level = 0;
|
||||
}
|
||||
void HC_SR055_long_click_handler(Button *btn) { elog_i("KEY", "HC-SR505触发"); }
|
||||
/**
|
||||
* @brief HC-SR505 PIR传感器处理函数:检测到宠物靠近时触发补水
|
||||
* @param btn: 按键句柄
|
||||
*/
|
||||
void HC_SR055_long_click_handler(Button *btn) {
|
||||
elog_i("KEY", "HC-SR505触发 - 检测到宠物靠近");
|
||||
|
||||
// 设置PIR触发补水标志
|
||||
pir_trigger_water = 1;
|
||||
|
||||
// 记录日志
|
||||
elog_i("WATER", "PIR传感器触发补水标志已设置");
|
||||
|
||||
// 播放提示音(可选)- 使用PIR联动补水音效
|
||||
MP3_Play(WATER_PIR_REFILL);
|
||||
}
|
||||
|
||||
void user_button_init(void) {
|
||||
// 初始化按键 (active_level: 0=低电平有效, 1=高电平有效)
|
||||
@@ -923,10 +1049,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 ? "自动" : "手动");
|
||||
}
|
||||
}
|
||||
@@ -1021,9 +1147,6 @@ Sensor_Data_t *LCD_GetSensorData(void) { return &sensor_data; }
|
||||
*/
|
||||
void RTC_TimeUpdateCallback(void) { lcd_force_refresh = 1; }
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 构建传感器数据JSON字符串(符合小程序格式要求)
|
||||
* @param data 传感器数据结构体指针
|
||||
@@ -1031,31 +1154,31 @@ void RTC_TimeUpdateCallback(void) { lcd_force_refresh = 1; }
|
||||
* @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字符串
|
||||
// 注意字段名:foodWeight(camelCase)而不是food_weight(snake_case)
|
||||
// waterLevel(camelCase)而不是water_level(snake_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;
|
||||
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字符串
|
||||
// 注意字段名:foodWeight(camelCase)而不是food_weight(snake_case)
|
||||
// waterLevel(camelCase)而不是water_level(snake_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: 喂食命令类型
|
||||
@@ -1145,27 +1268,283 @@ static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) {
|
||||
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秒
|
||||
// 计算目标减少重量
|
||||
float target_weight_loss;
|
||||
if (cmd == FEED_CMD_MANUAL) {
|
||||
// 手动喂食:每份按10克计算
|
||||
target_weight_loss = amount * 10.0f;
|
||||
} else {
|
||||
// 自动/远程喂食:使用配置的单次喂食重量
|
||||
target_weight_loss = (float)single_feed_weight;
|
||||
}
|
||||
float initial_weight = sensor_data.food_weight;
|
||||
float current_weight_loss = 0.0f;
|
||||
|
||||
// 检查重量传感器数据是否有效(大于0.1克)
|
||||
if (initial_weight < 0.1f) {
|
||||
elog_w("FEED", "重量传感器数据无效(%.2fg),跳过重量监测,按设定份数喂食", initial_weight);
|
||||
// 执行原始的喂食逻辑(无重量监测)
|
||||
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);
|
||||
} else {
|
||||
elog_i("FEED", "喂食开始 - 初始重量: %.2fg, 目标减少重量: %.2fg",
|
||||
initial_weight, target_weight_loss);
|
||||
|
||||
// 设置阈值(目标重量的95%,避免传感器噪声)
|
||||
float weight_threshold = target_weight_loss * 0.95f;
|
||||
|
||||
// 执行实际的喂食动作,每份喂食后检查重量变化
|
||||
for (uint8_t i = 0; i < amount; i++) {
|
||||
// 旋转角度投放一份食物
|
||||
Stepper_Motor_RotateAngle(angle, STEPPER_DIR_CW, 2, STEPPER_MODE_FULL_STEP);
|
||||
|
||||
// 等待食物落下
|
||||
osDelay(3000);
|
||||
|
||||
// 更新当前重量损失
|
||||
current_weight_loss = initial_weight - sensor_data.food_weight;
|
||||
elog_i("FEED", "第%d份后 - 当前重量: %.2fg, 已减少重量: %.2fg",
|
||||
i+1, sensor_data.food_weight, current_weight_loss);
|
||||
|
||||
// 检查是否已达到阈值
|
||||
if (current_weight_loss >= weight_threshold) {
|
||||
elog_i("FEED", "已达到目标减少重量(阈值%.2fg),停止喂食", weight_threshold);
|
||||
break;
|
||||
}
|
||||
|
||||
// 多份之间间隔1秒(最后一份不需要)
|
||||
if (i < amount - 1) {
|
||||
osDelay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待食物落下
|
||||
osDelay(3000);
|
||||
|
||||
// 停止电机
|
||||
Stepper_Motor_Stop();
|
||||
|
||||
// 播放完成音效
|
||||
MP3_Play(FEED_COMPLETE);
|
||||
elog_i("FEED", "喂食完成");
|
||||
elog_i("FEED", "喂食完成,总减少重量: %.2fg", current_weight_loss);
|
||||
|
||||
feeding_in_progress = 0;
|
||||
}
|
||||
|
||||
/* USER CODE BEGIN Scheduled_Feed_Functions */
|
||||
|
||||
/**
|
||||
* @brief 检查时间是否匹配定时喂食时间
|
||||
* @param scheduled_time: 定时时间字符串,格式 "HH:MM"
|
||||
* @param current_hour: 当前小时 (0-23)
|
||||
* @param current_minute: 当前分钟 (0-59)
|
||||
* @retval 1=匹配, 0=不匹配
|
||||
*/
|
||||
uint8_t Is_Time_Match(volatile const char *scheduled_time, uint8_t current_hour,
|
||||
uint8_t current_minute) {
|
||||
if (!scheduled_time) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 创建临时副本以避免volatile问题
|
||||
char time_buf[6];
|
||||
strncpy(time_buf, (const char *)scheduled_time, sizeof(time_buf) - 1);
|
||||
time_buf[sizeof(time_buf) - 1] = '\0';
|
||||
|
||||
if (strlen(time_buf) != 5) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 解析定时时间 "HH:MM"
|
||||
uint8_t scheduled_hour, scheduled_minute;
|
||||
unsigned short temp_hour, temp_minute;
|
||||
if (sscanf(time_buf, "%hu:%hu", &temp_hour, &temp_minute) != 2) {
|
||||
return 0;
|
||||
}
|
||||
scheduled_hour = (uint8_t)temp_hour;
|
||||
scheduled_minute = (uint8_t)temp_minute;
|
||||
|
||||
// 检查时间是否匹配
|
||||
return (scheduled_hour == current_hour && scheduled_minute == current_minute);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查定时喂食
|
||||
* @note 在water_controlTask中调用,每分钟检查一次
|
||||
*/
|
||||
void Check_Scheduled_Feed(void) {
|
||||
static uint32_t last_check_time = 0;
|
||||
uint32_t current_time = osKernelGetTickCount();
|
||||
|
||||
// 每分钟检查一次
|
||||
if (current_time - last_check_time < SCHEDULED_FEED_INTERVAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_check_time = current_time;
|
||||
|
||||
// 添加调试日志
|
||||
elog_d("FEED", "开始检查定时喂食");
|
||||
|
||||
// 检查定时喂食是否使能
|
||||
if (!scheduled_feed_enabled) {
|
||||
elog_d("FEED", "定时喂食未使能");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查系统是否为自动模式
|
||||
if (!system_mode) {
|
||||
elog_d("FEED", "系统为手动模式,不执行定时喂食");
|
||||
return; // 手动模式下不执行定时喂食
|
||||
}
|
||||
|
||||
// 检查是否有定时喂食时间设置
|
||||
if (scheduled_feed_count == 0) {
|
||||
elog_d("FEED", "未设置定时喂食时间");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前时间
|
||||
RTC_Time_t rtc_time;
|
||||
if (BSP_RTC_GetTime(&rtc_time) != 0) {
|
||||
elog_w("FEED", "获取RTC时间失败");
|
||||
return; // 获取时间失败
|
||||
}
|
||||
|
||||
elog_d("FEED", "当前时间: %02d:%02d, 定时时间数量: %d", rtc_time.hour,
|
||||
rtc_time.minute, scheduled_feed_count);
|
||||
|
||||
// 打印所有定时时间用于调试
|
||||
for (uint8_t i = 0; i < scheduled_feed_count; i++) {
|
||||
elog_d("FEED", "定时时间[%d]: %s", i, scheduled_feed_times[i]);
|
||||
}
|
||||
|
||||
// 检查每个定时喂食时间
|
||||
for (uint8_t i = 0; i < scheduled_feed_count; i++) {
|
||||
if (Is_Time_Match(scheduled_feed_times[i], rtc_time.hour,
|
||||
rtc_time.minute)) {
|
||||
// 时间匹配,执行定时喂食
|
||||
elog_i("FEED", "定时喂食时间到达: %s", scheduled_feed_times[i]);
|
||||
|
||||
// 计算喂食份数:单次喂食重量 / 每份重量(假设每份10克)
|
||||
uint8_t feed_amount = single_feed_weight / 10;
|
||||
if (feed_amount < 1) {
|
||||
feed_amount = 1; // 至少喂食1份
|
||||
}
|
||||
|
||||
elog_i("FEED", "单次喂食重量: %dg, 计算份数: %d", single_feed_weight,
|
||||
feed_amount);
|
||||
|
||||
// 提交定时喂食请求
|
||||
if (Request_Feed(FEED_CMD_AUTO, 90, feed_amount)) {
|
||||
elog_i("FEED", "定时喂食请求已提交: 重量=%dg, 份数=%d",
|
||||
single_feed_weight, feed_amount);
|
||||
} else {
|
||||
elog_w("FEED", "定时喂食请求失败(可能正在喂食中)");
|
||||
}
|
||||
|
||||
// 只执行一个定时喂食(避免同一分钟内有多个定时时间)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新喂食配置
|
||||
* @param feed_times_json: 定时喂食时间JSON数组字符串,格式 ["HH:MM", "HH:MM",
|
||||
* ...]
|
||||
* @param single_weight: 单次喂食重量(克)
|
||||
*/
|
||||
void Update_Feed_Config(const char *feed_times_json, uint16_t single_weight) {
|
||||
if (!feed_times_json) {
|
||||
return;
|
||||
}
|
||||
|
||||
elog_i("CONFIG", "更新喂食配置: 单次重量=%dg, 时间JSON=%s", single_weight,
|
||||
feed_times_json);
|
||||
|
||||
// 更新单次喂食重量
|
||||
single_feed_weight = single_weight;
|
||||
|
||||
// 解析定时喂食时间数组
|
||||
const char *ptr = feed_times_json;
|
||||
uint8_t count = 0;
|
||||
|
||||
// 清空现有定时时间
|
||||
for (uint8_t i = 0; i < MAX_SCHEDULED_FEED_TIMES; i++) {
|
||||
scheduled_feed_times[i][0] = '\0';
|
||||
}
|
||||
|
||||
// 简单JSON解析:查找时间字符串 "HH:MM"
|
||||
while (*ptr && count < MAX_SCHEDULED_FEED_TIMES) {
|
||||
// 查找引号
|
||||
if (*ptr == '"') {
|
||||
ptr++; // 跳过引号
|
||||
const char *time_start = ptr;
|
||||
|
||||
// 查找结束引号
|
||||
while (*ptr && *ptr != '"') {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
if (*ptr == '"') {
|
||||
// 提取时间字符串
|
||||
size_t time_len = ptr - time_start;
|
||||
if (time_len == 5) { // "HH:MM" 格式
|
||||
// 使用类型转换避免volatile警告
|
||||
char *dest = (char *)scheduled_feed_times[count];
|
||||
strncpy(dest, time_start, time_len);
|
||||
dest[time_len] = '\0';
|
||||
|
||||
// 验证时间格式
|
||||
uint8_t hour, minute;
|
||||
unsigned short temp_hour, temp_minute;
|
||||
if (sscanf(dest, "%hu:%hu", &temp_hour, &temp_minute) == 2) {
|
||||
hour = (uint8_t)temp_hour;
|
||||
minute = (uint8_t)temp_minute;
|
||||
if (hour < 24 && minute < 60) {
|
||||
elog_i("CONFIG", "添加定时喂食时间: %s",
|
||||
scheduled_feed_times[count]);
|
||||
count++;
|
||||
} else {
|
||||
elog_w("CONFIG", "无效的时间格式: %s",
|
||||
scheduled_feed_times[count]);
|
||||
}
|
||||
} else {
|
||||
elog_w("CONFIG", "时间格式解析失败: %s",
|
||||
scheduled_feed_times[count]);
|
||||
}
|
||||
} else {
|
||||
elog_w("CONFIG", "时间字符串长度不正确: %.*s (长度=%d)",
|
||||
(int)time_len, time_start, (int)time_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (*ptr) {
|
||||
ptr++; // 移动到下一个字符
|
||||
}
|
||||
}
|
||||
|
||||
scheduled_feed_count = count;
|
||||
elog_i("CONFIG", "定时喂食配置更新完成: 共%d个时间", count);
|
||||
|
||||
// 播放配置更新成功音效
|
||||
MP3_Play(PARAM_SAVE_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取单次喂食重量
|
||||
* @return 单次喂食重量(克)
|
||||
*/
|
||||
uint16_t Get_Single_Feed_Weight(void) {
|
||||
return single_feed_weight;
|
||||
}
|
||||
|
||||
/* USER CODE END Scheduled_Feed_Functions */
|
||||
|
||||
/* USER CODE END LCD_Page_Functions */
|
||||
|
||||
|
||||
@@ -72,8 +72,15 @@ void MX_GPIO_Init(void)
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||
HAL_GPIO_Init(HX711_DOUT_GPIO_Port, &GPIO_InitStruct);
|
||||
|
||||
/*Configure GPIO pins : HX711_SCK_Pin LCD_DC_Pin LCD_RESET_Pin STEPPER_IN4_Pin */
|
||||
GPIO_InitStruct.Pin = HX711_SCK_Pin|LCD_DC_Pin|LCD_RESET_Pin|STEPPER_IN4_Pin;
|
||||
/*Configure GPIO pin : HX711_SCK_Pin */
|
||||
GPIO_InitStruct.Pin = HX711_SCK_Pin;
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
||||
HAL_GPIO_Init(HX711_SCK_GPIO_Port, &GPIO_InitStruct);
|
||||
|
||||
/*Configure GPIO pins : LCD_DC_Pin LCD_RESET_Pin STEPPER_IN4_Pin */
|
||||
GPIO_InitStruct.Pin = LCD_DC_Pin|LCD_RESET_Pin|STEPPER_IN4_Pin;
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||
|
||||
Reference in New Issue
Block a user