## 主要修改
### 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:
@@ -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 指令手册。
|
||||
@@ -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; }
|
||||
|
||||
@@ -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字符串
|
||||
// 注意字段名: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: 喂食命令类型
|
||||
* @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 */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user