不干了!!!收
This commit is contained in:
@@ -89,12 +89,21 @@ uint8_t AHT30_Init(I2C_HandleTypeDef *hi2c) {
|
||||
elog_d(AHT30_TAG, "开始初始化AHT30传感器");
|
||||
|
||||
// 等待传感器上电稳定
|
||||
osDelay(50);
|
||||
osDelay(100);
|
||||
|
||||
// 读取初始状态
|
||||
// 读取初始状态,最多重试3次
|
||||
uint8_t status;
|
||||
uint8_t retry = 0;
|
||||
for (retry = 0; retry < 3; retry++) {
|
||||
if (AHT30_ReadBytes(&status, 1)) {
|
||||
elog_d(AHT30_TAG, "AHT30初始状态: 0x%02X", status);
|
||||
break;
|
||||
}
|
||||
osDelay(10); // 重试间隔
|
||||
}
|
||||
if (retry >= 3) {
|
||||
elog_e(AHT30_TAG, "读取AHT30初始状态失败");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 检查校准使能位,如果未启用则执行复位校准
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// 全局变量(零点偏移值、标定系数)
|
||||
int32_t hx711_zero_offset = 0; // 零点偏移(空载时的AD值)
|
||||
float hx711_scale = 0.002762; // 标定系数(重量/AD差值)
|
||||
float hx711_scale = 1.0f; // 标定系数(重量/AD差值)
|
||||
// #################### 基础函数 ####################
|
||||
|
||||
void delay_us(uint32_t us) {
|
||||
@@ -13,7 +13,8 @@ void delay_us(uint32_t us) {
|
||||
}
|
||||
// 初始化HX711引脚(SCK输出,DOUT输入)
|
||||
void HX711_Init(void) {
|
||||
|
||||
// 初始化SCK引脚为低电平
|
||||
HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET);
|
||||
}
|
||||
// 读取单次AD原始数据(gain:增益选择)
|
||||
int32_t HX711_ReadData(uint8_t gain) {
|
||||
|
||||
@@ -20,6 +20,10 @@ extern uint8_t Request_Water(uint8_t cmd);
|
||||
// 系统模式设置函数声明(定义在freertos.c中)
|
||||
extern void Set_System_Mode(uint8_t mode);
|
||||
|
||||
// 定时喂食配置函数声明(定义在freertos.c中)
|
||||
extern void Update_Feed_Config(const char *feed_times_json, uint16_t single_weight);
|
||||
extern uint16_t Get_Single_Feed_Weight(void);
|
||||
|
||||
/* ================= 配置 ================= */
|
||||
|
||||
#define WIFI_TX_BUF_SIZE 512
|
||||
@@ -585,8 +589,15 @@ void wifi_task_mqtt(void *argument) {
|
||||
elog_i(TAG, "处理control主题消息");
|
||||
if (strstr(msg.payload, "feed")) {
|
||||
elog_i(TAG, "执行喂食动作");
|
||||
// 根据配置的单次喂食重量计算份数(每份10克)
|
||||
uint16_t single_weight = Get_Single_Feed_Weight();
|
||||
uint8_t feed_amount = single_weight / 10;
|
||||
if (feed_amount < 1) {
|
||||
feed_amount = 1; // 至少喂食1份
|
||||
}
|
||||
elog_i(TAG, "远程喂食重量: %dg, 计算份数: %d", single_weight, feed_amount);
|
||||
// 调用远程喂食函数
|
||||
if (Request_Feed(FEED_CMD_REMOTE, 90, 1)) {
|
||||
if (Request_Feed(FEED_CMD_REMOTE, 90, feed_amount)) {
|
||||
elog_i(TAG, "远程喂食请求已提交");
|
||||
} else {
|
||||
elog_w(TAG, "喂食进行中,无法接受新命令");
|
||||
@@ -644,10 +655,71 @@ void wifi_task_mqtt(void *argument) {
|
||||
}
|
||||
|
||||
/* ===== config主题 ===== */
|
||||
/**
|
||||
{
|
||||
"feedTimes": [
|
||||
"02:00",
|
||||
"02:04"
|
||||
],
|
||||
"singleFeedWeight": 50
|
||||
}
|
||||
*/
|
||||
else if (strcmp(msg.topic, "petfeeder/config") == 0) {
|
||||
elog_i(TAG, "处理config主题消息");
|
||||
elog_i(TAG, "更新配置参数");
|
||||
// 其他配置参数更新逻辑
|
||||
|
||||
// 解析JSON配置
|
||||
char *feed_times_start = strstr(msg.payload, "\"feedTimes\"");
|
||||
char *single_weight_start = strstr(msg.payload, "\"singleFeedWeight\"");
|
||||
|
||||
if (feed_times_start && single_weight_start) {
|
||||
// 提取feedTimes数组字符串
|
||||
char *bracket_start = strstr(feed_times_start, "[");
|
||||
char *bracket_end = strstr(feed_times_start, "]");
|
||||
|
||||
if (bracket_start && bracket_end && bracket_end > bracket_start) {
|
||||
// 计算数组字符串长度
|
||||
size_t array_len = bracket_end - bracket_start + 1;
|
||||
char feed_times_array[256] = {0};
|
||||
|
||||
if (array_len < sizeof(feed_times_array)) {
|
||||
strncpy(feed_times_array, bracket_start, array_len);
|
||||
feed_times_array[array_len] = '\0';
|
||||
|
||||
// 提取singleFeedWeight值
|
||||
char *colon = strstr(single_weight_start, ":");
|
||||
if (colon) {
|
||||
// 跳过冒号和可能的空格
|
||||
char *value_start = colon + 1;
|
||||
while (*value_start == ' ' || *value_start == '\t') {
|
||||
value_start++;
|
||||
}
|
||||
|
||||
// 解析数字
|
||||
uint16_t single_weight = 0;
|
||||
if (sscanf(value_start, "%hu", &single_weight) == 1) {
|
||||
elog_i(TAG, "解析到配置: feedTimes=%s, singleFeedWeight=%d",
|
||||
feed_times_array, single_weight);
|
||||
|
||||
// 调用更新函数
|
||||
Update_Feed_Config(feed_times_array, single_weight);
|
||||
|
||||
elog_i(TAG, "喂食配置更新完成");
|
||||
} else {
|
||||
elog_w(TAG, "解析singleFeedWeight失败");
|
||||
}
|
||||
} else {
|
||||
elog_w(TAG, "找不到singleFeedWeight的冒号");
|
||||
}
|
||||
} else {
|
||||
elog_w(TAG, "feedTimes数组太长,超出缓冲区");
|
||||
}
|
||||
} else {
|
||||
elog_w(TAG, "找不到feedTimes数组的括号");
|
||||
}
|
||||
} else {
|
||||
elog_w(TAG, "JSON格式错误,缺少feedTimes或singleFeedWeight字段");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
elog_w(TAG, "MQTT消息解析失败");
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
#define configTICK_RATE_HZ ((TickType_t)1000)
|
||||
#define configMAX_PRIORITIES ( 56 )
|
||||
#define configMINIMAL_STACK_SIZE ((uint16_t)128)
|
||||
#define configTOTAL_HEAP_SIZE ((size_t)30000)
|
||||
#define configTOTAL_HEAP_SIZE ((size_t)31000)
|
||||
#define configMAX_TASK_NAME_LEN ( 16 )
|
||||
#define configUSE_TRACE_FACILITY 1
|
||||
#define configUSE_16_BIT_TICKS 0
|
||||
|
||||
@@ -121,12 +121,31 @@ typedef enum {
|
||||
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 */
|
||||
@@ -291,7 +328,7 @@ void StartDefaultTask(void *argument)
|
||||
}
|
||||
|
||||
// 设置为低音量-测试用
|
||||
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,10 +697,10 @@ 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)
|
||||
{
|
||||
@@ -684,9 +708,11 @@ void water_controlTask(void *argument)
|
||||
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;
|
||||
|
||||
@@ -699,11 +725,11 @@ void water_controlTask(void *argument)
|
||||
// 根据命令类型播放不同音频
|
||||
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:
|
||||
@@ -751,11 +777,96 @@ void water_controlTask(void *argument)
|
||||
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=高电平有效)
|
||||
@@ -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,7 +1154,8 @@ 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) {
|
||||
uint8_t Build_Sensor_JSON(const Sensor_Data_t *data, char *buffer,
|
||||
uint16_t buffer_size) {
|
||||
// 参数检查
|
||||
if (!data || !buffer || buffer_size < 128) {
|
||||
return 0;
|
||||
@@ -1045,7 +1169,8 @@ uint8_t Build_Sensor_JSON(const Sensor_Data_t *data, char *buffer, uint16_t buff
|
||||
// 注意字段名:foodWeight(camelCase)而不是food_weight(snake_case)
|
||||
// waterLevel(camelCase)而不是water_level(snake_case)
|
||||
snprintf(buffer, buffer_size,
|
||||
"{\"temperature\":%.1f,\"humidity\":%d,\"foodWeight\":%d,\"waterLevel\":%d}",
|
||||
"{\"temperature\":%.1f,\"humidity\":%d,\"foodWeight\":%d,"
|
||||
"\"waterLevel\":%d}",
|
||||
data->temperature, // 温度(保留1位小数)
|
||||
humidity_int, // 湿度(整数)
|
||||
food_weight_int, // 食物重量(整数)
|
||||
@@ -1054,8 +1179,6 @@ uint8_t Build_Sensor_JSON(const Sensor_Data_t *data, char *buffer, uint16_t buff
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 请求喂食操作
|
||||
* @param cmd: 喂食命令类型
|
||||
@@ -1145,27 +1268,283 @@ static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 执行实际的喂食动作
|
||||
// 计算目标减少重量
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 停止电机
|
||||
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;
|
||||
|
||||
@@ -33,10 +33,10 @@ Dma.USART1_TX.0.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
|
||||
Dma.USART1_TX.0.PeriphInc=DMA_PINC_DISABLE
|
||||
Dma.USART1_TX.0.Priority=DMA_PRIORITY_VERY_HIGH
|
||||
Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority
|
||||
FREERTOS.FootprintOK=true
|
||||
FREERTOS.FootprintOK=false
|
||||
FREERTOS.IPParameters=Tasks01,FootprintOK,configTOTAL_HEAP_SIZE
|
||||
FREERTOS.Tasks01=defaultTask,41,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL;button,50,512,button_task,Default,NULL,Dynamic,NULL,NULL;sensor,24,1024,sensorTask,Default,NULL,Dynamic,NULL,NULL;step_motor,24,512,step_motor_task,Default,NULL,Dynamic,NULL,NULL;water_control,24,512,water_controlTask,Default,NULL,Dynamic,NULL,NULL
|
||||
FREERTOS.configTOTAL_HEAP_SIZE=30000
|
||||
FREERTOS.Tasks01=defaultTask,41,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL;button,50,512,button_task,Default,NULL,Dynamic,NULL,NULL;sensor,24,1024,sensorTask,Default,NULL,Dynamic,NULL,NULL;step_motor,24,512,step_motor_task,Default,NULL,Dynamic,NULL,NULL;water_control,24,512,water_controlTask,Default,NULL,Dynamic,NULL,NULL;hx711,32,256,hx711Task,Default,NULL,Dynamic,NULL,NULL
|
||||
FREERTOS.configTOTAL_HEAP_SIZE=31000
|
||||
File.Version=6
|
||||
GPIO.groupedBy=Group By Peripherals
|
||||
KeepUserPlacement=false
|
||||
@@ -132,8 +132,9 @@ PA2.GPIOParameters=GPIO_Label
|
||||
PA2.GPIO_Label=HX711_DOUT
|
||||
PA2.Locked=true
|
||||
PA2.Signal=GPIO_Input
|
||||
PA3.GPIOParameters=GPIO_Label
|
||||
PA3.GPIOParameters=GPIO_Speed,GPIO_Label
|
||||
PA3.GPIO_Label=HX711_SCK
|
||||
PA3.GPIO_Speed=GPIO_SPEED_FREQ_HIGH
|
||||
PA3.Locked=true
|
||||
PA3.Signal=GPIO_Output
|
||||
PA4.GPIOParameters=GPIO_Label
|
||||
|
||||
Reference in New Issue
Block a user