## 主要修改

### 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

@@ -6,15 +6,22 @@
3. [API 参考](#api-参考)
4. [使用示例](#使用示例)
5. [注意事项](#注意事项)
6. [版本历史](#版本历史)
---
## 概述
本驱动用于 STM32F103 与 DX-WF-24 WiFi 模块的串口通信,基于 HAL 库实现,支持:
- **DMA 发送** - 非阻塞式数据发送
- **DMA + 空闲中断接收** - 自动帧识别接收
- **同步应答检测** - AT 指令交互
- **MQTT 客户端** - 完整的MQTT协议支持包括认证和双主题订阅
- **SNTP 时间同步** - 网络时间同步功能,支持中国时区
- **音频反馈** - 集成MP3模块提供系统状态语音提示
- **断线重连** - 自动检测连接状态并重连
- **消息解析** - 自动解析MQTT订阅消息
---
@@ -57,21 +64,25 @@ HAL_StatusTypeDef WIFI_SEND_DMA(const char *data);
---
### 3. 数据接收
### 3. 数据接收处理
#### `WIFI_Get_Received_Data()`
#### `WIFI_UART_IDLE_Callback()`
```c
int WIFI_Get_Received_Data(uint8_t *buffer, uint16_t len);
void WIFI_UART_IDLE_Callback(UART_HandleTypeDef *huart);
```
- **功能**获取接收到的数据
- **功能**UART空闲中断回调函数需要在USART1_IRQHandler中调用
- **参数**`huart` - UART句柄指针
#### `WIFI_WaitEvent()`
```c
uint8_t WIFI_WaitEvent(const char *ok_str, const char *fail_str, uint32_t timeout_ms);
```
- **功能**:等待特定事件或响应
- **参数**
- `buffer` - 输出缓冲区
- `len` - 缓冲区大小
- **返回值**实际复制的数据长度0 表示无数据
---
### 4. 应答检测(新增)
- `ok_str` - 期望的成功响应字符串
- `fail_str` - 期望的失败响应字符串
- `timeout_ms` - 超时时间(毫秒)
- **返回值**1表示成功0表示失败或超时
#### `WIFI_CheckAck()`
```c
@@ -86,25 +97,35 @@ uint8_t WIFI_CheckAck(const char *cmd, const char *expect, uint32_t timeout_ms);
- `1` - 收到期望应答
- `0` - 超时或收到 ERROR
### 5. MQTT连接支持认证
---
### 4. MQTT连接与发布
#### `Generate_Random_ClientID()`
```c
static void Generate_Random_ClientID(char *out_id, uint16_t max_len);
```
- **功能**生成唯一的MQTT客户端ID基于系统运行时间和STM32 UID
- **参数**
- `out_id` - 输出缓冲区
- `max_len` - 缓冲区最大长度
#### `WIFI_Connect_MQTT()`
```c
uint8_t WIFI_Connect_MQTT(const char *wifi_ssid, const char *wifi_pass,
const char *mqtt_broker, uint16_t mqtt_port,
const char *client_id, const char *mqtt_user,
const char *mqtt_pass, const char *sub_topic);
const char *mqtt_host, uint16_t mqtt_port,
const char *client_id, const char *user,
const char *pass);
```
- **功能**完整MQTT连接流程WiFi+MQTT
- **功能**完整MQTT连接流程WiFi+MQTT+SNTP
- **参数**
- `wifi_ssid` - WiFi名称
- `wifi_pass` - WiFi密码
- `mqtt_broker` - MQTT服务器地址
- `mqtt_host` - MQTT服务器地址
- `mqtt_port` - MQTT端口通常为1883
- `client_id` - MQTT客户端ID需唯一
- `mqtt_user` - MQTT用户名传NULL表示无需认证
- `mqtt_pass` - MQTT密码传NULL表示无需认证
- `sub_topic` - 订阅的主题
- `user` - MQTT用户名
- `pass` - MQTT密码
- **返回值**
- `1` - 连接成功
- `0` - 连接失败
@@ -112,11 +133,115 @@ uint8_t WIFI_Connect_MQTT(const char *wifi_ssid, const char *wifi_pass,
1. 清理MQTT环境
2. AT指令测试
3. 设置STA模式
4. 连接WiFi带重试
5. 配置MQTT参数包括认证信息
6. 连接MQTT服务器(带重试
7. 订阅主题
8. 发布上线消息
4. 连接WiFi带重试和音频反馈
5. 配置SNTP时间同步首次连接时
6. 配置MQTT参数客户端ID、用户名、密码
7. 连接MQTT服务器带重试
8. 订阅双主题:`petfeeder/control``petfeeder/config`
9. 发布设备上线状态
#### `WIFI_MQTT_Publish_RAW()`
```c
uint8_t WIFI_MQTT_Publish_RAW(const char *topic, const uint8_t *payload,
uint16_t length, uint8_t qos, uint8_t retain);
```
- **功能**发布原始数据到MQTT主题
- **参数**
- `topic` - 目标主题
- `payload` - 数据负载
- `length` - 数据长度
- `qos` - 服务质量等级0-2
- `retain` - 保留标志
- **返回值**1表示成功0表示失败
#### `WIFI_MQTT_Publish_Sensor()`
```c
uint8_t WIFI_MQTT_Publish_Sensor(const char *json);
```
- **功能**:发布传感器数据到`petfeeder/sensor`主题
- **参数**`json` - JSON格式的传感器数据
- **返回值**1表示成功0表示失败
#### `WIFI_MQTT_Publish_Status()`
```c
uint8_t WIFI_MQTT_Publish_Status(const char *json);
```
- **功能**:发布设备状态到`petfeeder/status`主题retain=1
- **参数**`json` - JSON格式的设备状态
- **返回值**1表示成功0表示失败
#### `WIFI_Parse_MQTT_Message()`
```c
uint8_t WIFI_Parse_MQTT_Message(char *input, MQTT_Message_t *msg);
```
- **功能**解析MQTT订阅消息+MQTTSUBRECV格式
- **参数**
- `input` - 原始输入字符串
- `msg` - 输出消息结构体
- **返回值**1表示解析成功0表示失败
---
### 5. SNTP时间同步
#### `WIFI_Enable_SNTP()`
```c
uint8_t WIFI_Enable_SNTP(void);
```
- **功能**启用SNTP功能并配置NTP服务器阿里云NTP
- **返回值**1表示成功0表示失败
- **注意**配置后模块会重启需要重新连接WiFi
#### `WIFI_Get_SNTP_Time()`
```c
uint8_t WIFI_Get_SNTP_Time(void);
```
- **功能**获取当前SNTP网络时间
- **返回值**1表示成功获取有效时间0表示失败
- **注意**避免频繁查询默认5秒间隔
#### `WIFI_Get_Current_Time()`
```c
SNTP_Time_t WIFI_Get_Current_Time(void);
```
- **功能**:获取存储的当前时间(返回结构体副本)
- **返回值**SNTP时间结构体
#### `WIFI_Is_Time_Valid()`
```c
uint8_t WIFI_Is_Time_Valid(void);
```
- **功能**检查SNTP时间是否有效
- **返回值**1表示有效0表示无效
---
### 6. 状态检查
#### `WIFI_Is_MQTT_Connected()`
```c
uint8_t WIFI_Is_MQTT_Connected(void);
```
- **功能**检查MQTT是否已连接
- **返回值**1表示已连接0表示未连接
---
### 7. 主任务函数
#### `wifi_task_mqtt()`
```c
void wifi_task_mqtt(void *argument);
```
- **功能**WiFi和MQTT主任务函数包含完整的连接、消息处理和断线重连逻辑
- **参数**`argument` - FreeRTOS任务参数
- **工作流程**
1. 初始化DMA接收
2. 生成唯一ClientID
3. 连接MQTT支持重试
4. 发布设备上线状态
5. 获取SNTP时间
6. 进入主循环处理MQTT消息、后台时间同步、连接状态监测
---
@@ -139,110 +264,81 @@ void System_Init(void)
}
```
### 示例2发送数据
### 示例2在FreeRTOS任务中使用
```c
// 发送 AT 指令测试
void WiFi_Test(void)
// FreeRTOS任务定义
osThreadId_t wifi_mqttHandle;
const osThreadAttr_t wifi_mqtt_attributes = {
.name = "wifi_mqtt",
.stack_size = 3000 * 4,
.priority = (osPriority_t)osPriorityHigh,
};
// 创建任务
wifi_mqttHandle = osThreadNew(wifi_task_mqtt, NULL, &wifi_mqtt_attributes);
```
### 示例3手动发布传感器数据
```c
void Publish_Sensor_Data(float temp, float humi, float weight)
{
HAL_StatusTypeDef status = WIFI_SEND_DMA("AT\r\n");
char json[128];
snprintf(json, sizeof(json),
"{\"temperature\":%.1f,\"humidity\":%.1f,\"weight\":%.1f}",
temp, humi, weight);
if (status == HAL_OK) {
elog_i("WIFI", "Command sent");
} else if (status == HAL_BUSY) {
elog_w("WIFI", "WiFi busy, try later");
if (WIFI_MQTT_Publish_Sensor(json)) {
elog_i("SENSOR", "数据发布成功");
} else {
elog_e("SENSOR", "数据发布失败");
}
}
```
### 示例3检测模块就绪
### 示例4解析MQTT消息
```c
// 初始化时检测 WiFi 模块
uint8_t WiFi_Init_Check(void)
void Process_MQTT_Message(void)
{
// 发送 AT期望收到 OK超时 1 秒
if (WIFI_CheckAck("AT\r\n", "OK", 1000)) {
elog_i("WIFI", "Module ready");
return 1;
} else {
elog_e("WIFI", "Module not responding");
return 0;
}
}
```
### 示例4完整的 MQTT 连接(支持认证)
```c
// 连接到 MQTT 服务器(无需认证)
void Connect_MQTT_Public(void)
{
uint8_t success = WIFI_Connect_MQTT(
"MyWiFi", // WiFi名称
"12345678", // WiFi密码
"broker.emqx.io", // MQTT服务器公共测试服务器
1883, // MQTT端口
"PetFeeder-001", // 客户端ID需唯一
NULL, // MQTT用户名NULL表示无需认证
NULL, // MQTT密码NULL表示无需认证
"pet/control" // 订阅主题
);
if (success) {
elog_i("MQTT", "Connected to public MQTT server");
} else {
elog_e("MQTT", "Connection failed");
}
}
// 连接到 MQTT 服务器(需要用户名密码认证)
void Connect_MQTT_Private(void)
{
uint8_t success = WIFI_Connect_MQTT(
"MyWiFi", // WiFi名称
"12345678", // WiFi密码
"mqtt.myserver.com", // 私有MQTT服务器
1883, // MQTT端口
"PetFeeder-001", // 客户端ID
"myusername", // MQTT用户名
"mypassword", // MQTT密码
"pet/control" // 订阅主题
);
if (success) {
elog_i("MQTT", "Connected to private MQTT server");
} else {
elog_e("MQTT", "Connection failed");
}
}
### 示例5异步接收处理
```c
// 在任务循环中处理接收数据
void WiFi_Task(void)
{
uint8_t buffer[512];
int len;
while (1) {
// 检查是否有新数据
len = WIFI_Get_Received_Data(buffer, sizeof(buffer));
if (wifi.rx_flag) {
wifi.rx_flag = 0;
if (len > 0) {
// 处理接收到的数据
elog_i("WIFI", "Received: %s", buffer);
MQTT_Message_t msg;
if (WIFI_Parse_MQTT_Message((char *)wifi.rx_buffer, &msg)) {
elog_i("MQTT", "收到主题: %s", msg.topic);
elog_i("MQTT", "内容: %s", msg.payload);
// 根据内容做不同处理...
if (strstr((char*)buffer, "+IPD")) {
// 收到网络数据
Process_Network_Data(buffer);
if (strcmp(msg.topic, "petfeeder/control") == 0) {
// 处理控制指令
if (strstr(msg.payload, "feed")) {
elog_i("MQTT", "执行喂食动作");
// 调用喂食函数
}
} else if (strcmp(msg.topic, "petfeeder/config") == 0) {
// 处理配置更新
elog_i("MQTT", "更新配置参数");
}
}
osDelay(10); // 10ms 轮询
}
}
```
### 示例5获取网络时间
```c
void Check_Network_Time(void)
{
if (WIFI_Is_Time_Valid()) {
SNTP_Time_t current_time = WIFI_Get_Current_Time();
elog_i("TIME", "当前网络时间: %04d-%02d-%02d %02d:%02d:%02d",
current_time.year, current_time.month, current_time.day,
current_time.hour, current_time.minute, current_time.second);
} else {
elog_w("TIME", "网络时间未同步");
// 尝试获取时间
WIFI_Get_SNTP_Time();
}
}
```
@@ -280,17 +376,31 @@ void USART1_IRQHandler(void)
#define WIFI_RX_BUF_SIZE 512
```
### 4. MQTT 用户名密码认证
### 4. SNTP时间同步
- 公共MQTT服务器如 broker.emqx.io通常不需要认证`NULL` 即可
- 私有MQTT服务器需要用户名和密码传入对应字符串
- 确保 MQTT 服务器已开启用户名密码认证功能
- SNTP配置后模块会重启需要等待模块恢复约15秒
- 避免频繁查询SNTP时间默认5秒间隔
- 首次连接时会自动配置SNTP后续连接会跳过此步骤
### 5. 超时处理
### 5. 音频反馈
`WIFI_CheckAck()` 是阻塞函数,不适合在中断或高优先级任务中调用。
- WiFi连接成功/失败时会有语音提示
- 需要正确初始化MP3模块并加载音频文件
- 音频文件索引定义在`mp3_play_index.h`
### 6. 多线程安全
### 6. MQTT主题
- 固定订阅两个主题:`petfeeder/control`(控制指令)和`petfeeder/config`(配置更新)
- 固定发布到三个主题:`petfeeder/status`(设备状态)、`petfeeder/sensor`(传感器数据)
- 主题名称在代码中硬编码,如需修改需要修改源代码
### 7. 断线重连
- 自动检测MQTT连接状态
- 断线后自动重连5秒重试间隔
- 长时间无活动5分钟也会触发重连
### 8. 多线程安全
当前实现未加互斥锁,如果在多任务环境中同时调用发送/接收,需要额外保护。
@@ -304,6 +414,9 @@ void USART1_IRQHandler(void)
| 数据截断 | 缓冲区太小 | 增大 `WIFI_RX_BUF_SIZE` |
| 发送失败 | DMA 忙 | 检查返回值,稍后重试 |
| 检测超时 | 波特率不匹配 | 确认模块波特率(默认 115200|
| SNTP配置失败 | 模块重启未完成 | 增加等待时间检查WiFi连接 |
| MQTT连接失败 | 服务器地址错误 | 检查MQTT服务器地址和端口 |
| 音频无输出 | MP3模块未初始化 | 检查MP3模块初始化代码 |
---
@@ -314,9 +427,10 @@ void USART1_IRQHandler(void)
| 1.0 | 2026-02-09 | 初始版本,基础 DMA 收发 |
| 1.1 | 2026-02-09 | 新增 `WIFI_CheckAck()` 同步应答检测 |
| 1.2 | 2026-02-09 | 新增 `WIFI_Connect_MQTT()` 支持用户名密码认证 |
| 2.0 | 2026-02-25 | 重大更新:<br>- 集成SNTP时间同步功能<br>- 增加MP3音频反馈<br>- 改进MQTT连接流程<br>- 增加断线重连机制<br>- 增加消息解析功能<br>- 更新API接口 |
---
## 联系方式
如有问题,请参考 DX-WF-24 模块 AT 指令手册。
如有问题,请参考 DX-WF-24 模块 AT 指令手册。

View File

@@ -1,13 +1,25 @@
#include "dx_wf_24.h"
#include "bsp_rtc.h" // RTC管理模块
#include "cmsis_os.h"
#include "cmsis_os2.h"
#include "elog.h"
#include "mp3_driver.h" // 添加MP3模块头文件
#include "bsp_rtc.h" // RTC管理模块
#include "mp3_driver.h" // 添加MP3模块头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ================= 外部函数声明 ================= */
// 喂食控制函数声明定义在freertos.c中
extern uint8_t Request_Feed(uint8_t cmd, uint16_t angle, uint8_t amount);
#define FEED_CMD_REMOTE 3 // 远程喂食命令与freertos.c中的FEED_CMD_REMOTE对应
// 加水控制函数声明定义在freertos.c中
extern uint8_t Request_Water(uint8_t cmd);
#define WATER_CMD_REMOTE 3 // 远程加水命令与freertos.c中的WATER_CMD_REMOTE对应
// 系统模式设置函数声明定义在freertos.c中
extern void Set_System_Mode(uint8_t mode);
/* ================= 配置 ================= */
#define WIFI_TX_BUF_SIZE 512
@@ -32,9 +44,7 @@ static volatile uint8_t sntp_configured = 0;
static volatile uint32_t sntp_last_query_tick = 0;
static const uint32_t SNTP_QUERY_INTERVAL = 5000; // 5秒查询一次
static void Generate_Random_ClientID(char *out_id, uint16_t max_len) {
static void Generate_Random_ClientID(char *out_id, uint16_t max_len) {
// 获取系统运行时间
uint32_t tick = HAL_GetTick();
@@ -58,6 +68,12 @@ static const uint32_t SNTP_QUERY_INTERVAL = 5000; // 5秒查询一次
sum);
}
/* 清理WiFi接收缓冲区 */
void WIFI_Clear_Rx_Buffer(void) {
wifi.rx_flag = 0;
wifi.rx_len = 0;
memset(wifi.rx_buffer, 0, WIFI_RX_BUF_SIZE);
}
/* ================= DMA RX 初始化 ================= */
@@ -132,6 +148,9 @@ uint8_t WIFI_WaitEvent(const char *ok_str, const char *fail_str,
uint32_t timeout_ms) {
uint32_t start = HAL_GetTick();
// 清理旧数据,避免干扰
WIFI_Clear_Rx_Buffer();
while (HAL_GetTick() - start < timeout_ms) {
if (wifi.rx_flag) {
wifi.rx_flag = 0;
@@ -140,7 +159,6 @@ uint8_t WIFI_WaitEvent(const char *ok_str, const char *fail_str,
elog_i(TAG, "接收到WiFi模块数据: %s", buf);
/* ===== 优先处理断线事件 ===== */
if (strstr(buf, "+MQTTDISCONNECTED")) {
mqtt_connected = 0;
@@ -210,19 +228,19 @@ uint8_t WIFI_Connect_WiFi(const char *ssid, const char *password,
elog_i(TAG, "等待WiFi基础连接响应...");
if (!WIFI_WaitEvent("OK", "ERROR", 3000)) {
elog_e(TAG, "WiFi基础连接失败");
MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败播放提示音
MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败播放提示音
return 0;
}
elog_i(TAG, "等待WiFi详细连接结果...");
if (!WIFI_WaitEvent("+CWJAP:1", "+CWJAP:0", timeout_ms)) {
elog_e(TAG, "WiFi详细连接失败");
MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败播放提示音
MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败播放提示音
return 0;
}
elog_i(TAG, "WiFi连接成功");
MP3_Play(WIFI_CONNECT_OK); // WiFi连接成功播放提示音
MP3_Play(WIFI_CONNECT_OK); // WiFi连接成功播放提示音
return 1;
}
@@ -269,8 +287,6 @@ uint8_t WIFI_Connect_MQTT(const char *wifi_ssid, const char *wifi_pass,
return 0;
}
mqtt_state = MQTT_STATE_WIFI_CONNECTED;
elog_i(TAG, "WiFi连接完成状态: %d", mqtt_state);
@@ -491,140 +507,194 @@ uint8_t WIFI_MQTT_Publish_RAW(const char *topic, const uint8_t *payload,
elog_i(TAG, "MQTT发布成功到主题: %s", topic);
return 1;
}
/* ================= MQTT接收任务 ================= */
void wifi_task_mqtt(void *argument) {
MQTT_Message_t msg;
uint8_t mqtt_connected = 0;
uint32_t retry_count = 0;
MQTT_Message_t msg;
uint8_t mqtt_connected = 0;
uint32_t retry_count = 0;
elog_i(TAG, "启动WiFi MQTT任务");
WIFI_RECV_DMA_Init();
osDelay(3000);
elog_i(TAG, "启动WiFi MQTT任务");
WIFI_RECV_DMA_Init();
osDelay(3000);
char client_id[64] = {0};
char client_id[64] = {0};
// 生成唯一ClientID
Generate_Random_ClientID(client_id, sizeof(client_id));
// 生成唯一ClientID
Generate_Random_ClientID(client_id, sizeof(client_id));
elog_i(TAG, "生成ClientID: %s", client_id);
elog_i(TAG, "生成ClientID: %s", client_id);
// 主循环:一直运行,支持断线重连
for (;;) {
// ========== 尝试MQTT连接 ==========
if (!mqtt_connected) {
elog_i(TAG, "尝试MQTT连接... (重试次数: %lu)", ++retry_count);
// 主循环:一直运行,支持断线重连
for (;;) {
// ========== 尝试MQTT连接 ==========
if (!mqtt_connected) {
elog_i(TAG, "尝试MQTT连接... (重试次数: %lu)", ++retry_count);
if (WIFI_Connect_MQTT("WIFI_TEST", "88888888", "mqtt.beihong.wang", 1883,
client_id, "STM32_MQTT", "123456")) {
elog_i(TAG, "MQTT连接成功");
mqtt_connected = 1;
retry_count = 0;
if (WIFI_Connect_MQTT("WIFI_TEST", "88888888", "mqtt.beihong.wang", 1883,
client_id, "STM32_MQTT", "123456")) {
elog_i(TAG, "MQTT连接成功");
mqtt_connected = 1;
retry_count = 0;
// 发布设备上线状态
elog_i(TAG, "发布设备上线状态");
WIFI_MQTT_Publish_Status("{\"status\":\"online\"}");
// 发布设备上线状态
elog_i(TAG, "发布设备上线状态");
WIFI_MQTT_Publish_Status("{\"status\":\"online\"}");
// 获取SNTP时间需要等待模块同步NTP服务器
elog_i(TAG, "等待NTP服务器同步...");
osDelay(10000); // 等待10秒让模块同步NTP
// 获取SNTP时间需要等待模块同步NTP服务器
elog_i(TAG, "等待NTP服务器同步...");
osDelay(10000); // 等待10秒让模块同步NTP
elog_i(TAG, "获取SNTP时间...");
uint8_t sntp_ok = 0;
// 首次获取失败后等待3秒再重试最多重试5次
for (int retry = 0; retry < 5 && !sntp_ok; retry++) {
if (retry > 0) {
osDelay(3000); // 等待3秒再重试
}
sntp_ok = WIFI_Get_SNTP_Time();
}
if (sntp_time.valid) {
elog_i(TAG, "当前SNTP时间为: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
} else {
elog_w(TAG, "SNTP时间尚未同步将在后台继续尝试");
}
} else {
elog_w(TAG, "MQTT连接失败5秒后重试...");
osDelay(5000);
continue; // 继续尝试连接
}
elog_i(TAG, "获取SNTP时间...");
uint8_t sntp_ok = 0;
// 首次获取失败后等待3秒再重试最多重试5次
for (int retry = 0; retry < 5 && !sntp_ok; retry++) {
if (retry > 0) {
osDelay(3000); // 等待3秒再重试
}
sntp_ok = WIFI_Get_SNTP_Time();
}
// ========== MQTT消息处理循环 ==========
if (wifi.rx_flag) {
wifi.rx_flag = 0;
elog_i(TAG, "收到WiFi数据: %s", wifi.rx_buffer);
if (WIFI_Parse_MQTT_Message((char *)wifi.rx_buffer, &msg)) {
elog_i(TAG, "解析MQTT消息成功");
elog_i(TAG, "收到主题: %s", msg.topic);
elog_i(TAG, "内容: %s", msg.payload);
/* ===== control主题 ===== */
if (strcmp(msg.topic, "petfeeder/control") == 0) {
elog_i(TAG, "处理control主题消息");
if (strstr(msg.payload, "feed")) {
elog_i(TAG, "执行喂食动作");
// 执行喂食函数
}
}
/* ===== config主题 ===== */
else if (strcmp(msg.topic, "petfeeder/config") == 0) {
elog_i(TAG, "处理config主题消息");
elog_i(TAG, "更新配置参数");
// 更新参数逻辑
}
} else {
elog_w(TAG, "MQTT消息解析失败");
}
if (sntp_time.valid) {
elog_i(TAG, "当前SNTP时间为: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day, sntp_time.hour,
sntp_time.minute, sntp_time.second);
} else {
elog_w(TAG, "SNTP时间尚未同步将在后台继续尝试");
}
// ========== 后台SNTP时间同步 ==========
static uint32_t sntp_sync_timer = 0;
static uint8_t sntp_reconfig_count = 0;
const uint32_t SNTP_SYNC_INTERVAL = 30000; // 30秒尝试同步一次
if (!sntp_time.valid && (osKernelGetTickCount() - sntp_sync_timer > SNTP_SYNC_INTERVAL)) {
sntp_sync_timer = osKernelGetTickCount();
// 如果连续10分钟20次都没同步成功尝试重新配置SNTP
if (++sntp_reconfig_count > 20) {
elog_w(TAG, "SNTP长时间未同步尝试重新配置...");
sntp_configured = 0; // 清除配置标志,下次重新配置
sntp_reconfig_count = 0;
}
// 尝试同步时间(静默尝试,不打印日志)
WIFI_Get_SNTP_Time();
if (sntp_time.valid) {
elog_i(TAG, "SNTP时间同步成功: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
}
}
// 检查连接状态:如果长时间没收到数据,认为可能断线
// 这里可以添加心跳检测机制
static uint32_t last_activity = 0;
if (wifi.rx_flag || mqtt_connected) {
last_activity = osKernelGetTickCount();
}
// 5分钟无活动重新连接
if (mqtt_connected && (osKernelGetTickCount() - last_activity > 300000)) {
elog_w(TAG, "检测到长时间无活动重新连接MQTT...");
mqtt_connected = 0;
continue;
}
osDelay(20);
} else {
elog_w(TAG, "MQTT连接失败5秒后重试...");
osDelay(5000);
continue; // 继续尝试连接
}
}
// ========== MQTT消息处理循环 ==========
if (wifi.rx_flag) {
wifi.rx_flag = 0;
elog_i(TAG, "收到WiFi数据: %s", wifi.rx_buffer);
if (WIFI_Parse_MQTT_Message((char *)wifi.rx_buffer, &msg)) {
elog_i(TAG, "解析MQTT消息成功");
elog_i(TAG, "收到主题: %s", msg.topic);
elog_i(TAG, "内容: %s", msg.payload);
/* ===== control主题 ===== */
if (strcmp(msg.topic, "petfeeder/control") == 0) {
elog_i(TAG, "处理control主题消息");
if (strstr(msg.payload, "feed")) {
elog_i(TAG, "执行喂食动作");
// 调用远程喂食函数
if (Request_Feed(FEED_CMD_REMOTE, 90, 1)) {
elog_i(TAG, "远程喂食请求已提交");
} else {
elog_w(TAG, "喂食进行中,无法接受新命令");
}
}
if (strstr(msg.payload, "addWater")) {
elog_i(TAG, "执行添加水动作");
// 调用远程加水函数
if (Request_Water(WATER_CMD_REMOTE)) {
elog_i(TAG, "远程加水请求已提交");
} else {
elog_w(TAG, "加水进行中,无法接受新命令");
}
}
// 解析setMode命令小程序规定在control主题中处理
if (strstr(msg.payload, "setMode")) {
elog_i(TAG, "处理setMode主题消息");
// 简单的JSON解析查找"mode"字段
char *mode_start = strstr(msg.payload, "\"mode\"");
if (mode_start) {
// 查找冒号后的值
char *colon = strstr(mode_start, ":");
if (colon) {
// 查找引号内的值
char *quote1 = strstr(colon, "\"");
if (quote1) {
char *quote2 = strstr(quote1 + 1, "\"");
if (quote2) {
// 提取模式字符串
char mode_str[16] = {0};
int len = quote2 - (quote1 + 1);
if (len > 0 && len < sizeof(mode_str)) {
strncpy(mode_str, quote1 + 1, len);
mode_str[len] = '\0';
elog_i(TAG, "解析到模式: %s", mode_str);
// 根据模式设置系统状态
if (strcmp(mode_str, "auto") == 0) {
Set_System_Mode(1); // 自动模式
elog_i(TAG, "系统模式设置为: 自动模式");
} else if (strcmp(mode_str, "manual") == 0) {
Set_System_Mode(0); // 手动模式
elog_i(TAG, "系统模式设置为: 手动模式");
} else {
elog_w(TAG, "未知模式: %s", mode_str);
}
}
}
}
}
}
}
}
/* ===== config主题 ===== */
else if (strcmp(msg.topic, "petfeeder/config") == 0) {
elog_i(TAG, "处理config主题消息");
elog_i(TAG, "更新配置参数");
// 其他配置参数更新逻辑
}
} else {
elog_w(TAG, "MQTT消息解析失败");
}
}
// ========== 后台SNTP时间同步 ==========
static uint32_t sntp_sync_timer = 0;
static uint8_t sntp_reconfig_count = 0;
const uint32_t SNTP_SYNC_INTERVAL = 30000; // 30秒尝试同步一次
if (!sntp_time.valid &&
(osKernelGetTickCount() - sntp_sync_timer > SNTP_SYNC_INTERVAL)) {
sntp_sync_timer = osKernelGetTickCount();
// 如果连续10分钟20次都没同步成功尝试重新配置SNTP
if (++sntp_reconfig_count > 20) {
elog_w(TAG, "SNTP长时间未同步尝试重新配置...");
sntp_configured = 0; // 清除配置标志,下次重新配置
sntp_reconfig_count = 0;
}
// 尝试同步时间(静默尝试,不打印日志)
WIFI_Get_SNTP_Time();
if (sntp_time.valid) {
elog_i(TAG, "SNTP时间同步成功: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day, sntp_time.hour,
sntp_time.minute, sntp_time.second);
}
}
// 检查连接状态:如果长时间没收到数据,认为可能断线
// 这里可以添加心跳检测机制
static uint32_t last_activity = 0;
if (wifi.rx_flag || mqtt_connected) {
last_activity = osKernelGetTickCount();
}
// 5分钟无活动重新连接
if (mqtt_connected && (osKernelGetTickCount() - last_activity > 300000)) {
elog_w(TAG, "检测到长时间无活动重新连接MQTT...");
mqtt_connected = 0;
continue;
}
osDelay(20);
}
}
uint8_t WIFI_Parse_MQTT_Message(char *input, MQTT_Message_t *msg) {
@@ -695,110 +765,106 @@ uint8_t WIFI_MQTT_Publish_Status(const char *json) {
/* ================= SNTP 时间相关函数 ================= */
uint8_t WIFI_Enable_SNTP(void) {
elog_i(TAG, "使能SNTP服务器设置中国时区");
elog_i(TAG, "使能SNTP服务器设置中国时区");
// 发送AT+CIPSNTPCFG=1,8,ntp.aliyun.com命令阿里云NTP更稳定
if (!WIFI_CheckAck("AT+CIPSNTPCFG=1,8,ntp.aliyun.com\r\n", "OK", 3000)) {
elog_e(TAG, "SNTP配置失败");
return 0;
}
// 发送AT+CIPSNTPCFG=1,8,ntp.aliyun.com命令阿里云NTP更稳定
if (!WIFI_CheckAck("AT+CIPSNTPCFG=1,8,ntp.aliyun.com\r\n", "OK", 3000)) {
elog_e(TAG, "SNTP配置失败");
return 0;
}
elog_i(TAG, "SNTP服务器配置成功模块将自动重启以应用设置");
/* 注意配置SNTP后模块会重启这里只等待基本重启时间
WiFi重连和模块恢复由调用者处理 */
elog_i(TAG, "SNTP服务器配置成功模块将自动重启以应用设置");
/* 注意配置SNTP后模块会重启这里只等待基本重启时间
WiFi重连和模块恢复由调用者处理 */
return 1;
return 1;
}
uint8_t WIFI_Get_SNTP_Time(void) {
char *time_str;
char time_buf[32];
uint32_t current_tick = osKernelGetTickCount();
char *time_str;
char time_buf[32];
uint32_t current_tick = osKernelGetTickCount();
// 避免频繁查询距离上次查询不足5秒则跳过
if (current_tick - sntp_last_query_tick < SNTP_QUERY_INTERVAL) {
return sntp_time.valid; // 返回现有时间状态
// 避免频繁查询距离上次查询不足5秒则跳过
if (current_tick - sntp_last_query_tick < SNTP_QUERY_INTERVAL) {
return sntp_time.valid; // 返回现有时间状态
}
sntp_last_query_tick = current_tick;
// 发送AT+CIPSNTPTIME命令
if (WIFI_SEND_DMA("AT+CIPSNTPTIME\r\n") != HAL_OK) {
return 0;
}
// 等待响应
if (!WIFI_WaitEvent("+CIPSNTPTIME:", "ERROR", 3000)) {
return 0;
}
// 解析时间字符串 "+CIPSNTPTIME:2024-05-08 21:11:38"
time_str = strstr((char *)wifi.rx_buffer, "+CIPSNTPTIME:");
if (!time_str) {
return 0;
}
// 提取时间部分 "2024-05-08 21:11:38"
time_str += strlen("+CIPSNTPTIME:");
// 复制到缓冲区以便处理
strncpy(time_buf, time_str, sizeof(time_buf) - 1);
time_buf[sizeof(time_buf) - 1] = '\0';
// 移除末尾的换行符和回车符
char *newline = strchr(time_buf, '\r');
if (newline)
*newline = '\0';
newline = strchr(time_buf, '\n');
if (newline)
*newline = '\0';
// 解析时间格式 YYYY-MM-DD HH:MM:SS
int year, month, day, hour, minute, second;
if (sscanf(time_buf, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute,
&second) == 6) {
// 检查是否为默认时间2021-01-01如果是则说明未同步成功
if (year == 2021 && month == 1 && day == 1) {
// 静默返回,不打印日志
return 0;
}
sntp_last_query_tick = current_tick;
sntp_time.year = (uint16_t)year;
sntp_time.month = (uint8_t)month;
sntp_time.day = (uint8_t)day;
sntp_time.hour = (uint8_t)hour;
sntp_time.minute = (uint8_t)minute;
sntp_time.second = (uint8_t)second;
sntp_time.valid = 1;
// 发送AT+CIPSNTPTIME命令
if (WIFI_SEND_DMA("AT+CIPSNTPTIME\r\n") != HAL_OK) {
return 0;
// 只在首次获取成功时打印日志
static uint8_t first_time_sync = 0;
if (!first_time_sync) {
elog_i(TAG, "SNTP时间同步成功: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day, sntp_time.hour,
sntp_time.minute, sntp_time.second);
first_time_sync = 1;
}
// 等待响应
if (!WIFI_WaitEvent("+CIPSNTPTIME:", "ERROR", 3000)) {
return 0;
}
// 自动更新RTC时间
BSP_RTC_UpdateFromSNTP(sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
// 解析时间字符串 "+CIPSNTPTIME:2024-05-08 21:11:38"
time_str = strstr((char *)wifi.rx_buffer, "+CIPSNTPTIME:");
if (!time_str) {
return 0;
}
// 提取时间部分 "2024-05-08 21:11:38"
time_str += strlen("+CIPSNTPTIME:");
// 复制到缓冲区以便处理
strncpy(time_buf, time_str, sizeof(time_buf) - 1);
time_buf[sizeof(time_buf) - 1] = '\0';
// 移除末尾的换行符和回车符
char *newline = strchr(time_buf, '\r');
if (newline) *newline = '\0';
newline = strchr(time_buf, '\n');
if (newline) *newline = '\0';
// 解析时间格式 YYYY-MM-DD HH:MM:SS
int year, month, day, hour, minute, second;
if (sscanf(time_buf, "%d-%d-%d %d:%d:%d",
&year, &month, &day, &hour, &minute, &second) == 6) {
// 检查是否为默认时间2021-01-01如果是则说明未同步成功
if (year == 2021 && month == 1 && day == 1) {
// 静默返回,不打印日志
return 0;
}
sntp_time.year = (uint16_t)year;
sntp_time.month = (uint8_t)month;
sntp_time.day = (uint8_t)day;
sntp_time.hour = (uint8_t)hour;
sntp_time.minute = (uint8_t)minute;
sntp_time.second = (uint8_t)second;
sntp_time.valid = 1;
// 只在首次获取成功时打印日志
static uint8_t first_time_sync = 0;
if (!first_time_sync) {
elog_i(TAG, "SNTP时间同步成功: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
first_time_sync = 1;
}
// 自动更新RTC时间
BSP_RTC_UpdateFromSNTP(sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
return 1;
} else {
return 0;
}
return 1;
} else {
return 0;
}
}
// 获取当前SNTP时间返回结构体副本
SNTP_Time_t WIFI_Get_Current_Time(void) {
return sntp_time;
}
SNTP_Time_t WIFI_Get_Current_Time(void) { return sntp_time; }
// 检查SNTP时间是否有效
uint8_t WIFI_Is_Time_Valid(void) {
return sntp_time.valid;
}
uint8_t WIFI_Is_Time_Valid(void) { return sntp_time.valid; }
// 检查MQTT是否已连接
uint8_t WIFI_Is_MQTT_Connected(void) {
return mqtt_connected;
}
uint8_t WIFI_Is_MQTT_Connected(void) { return mqtt_connected; }

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 */