Compare commits
10 Commits
f6f33a6f84
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80de6eb366 | ||
|
|
d3f671f3cf | ||
|
|
93bc94a0ee | ||
|
|
e446b7515c | ||
|
|
0d117d9d47 | ||
|
|
811b47d274 | ||
|
|
ffdb7065e3 | ||
|
|
65de57a49c | ||
|
|
0eb428aaa5 | ||
|
|
4e829bc56f |
182
README.md
182
README.md
@@ -61,3 +61,185 @@
|
||||
set_var_fan_status("开");
|
||||
set_var_light_status("开");
|
||||
```
|
||||
|
||||
## MQTT 协议说明(上位机对接)
|
||||
|
||||
本项目通过 MQTT 实现数据上报与远程控制。上位机可直接按以下协议开发。
|
||||
|
||||
### 1. 连接参数与默认配置
|
||||
|
||||
参数来自 menuconfig:
|
||||
|
||||
| 配置项 | 说明 | 默认值 |
|
||||
| ------ | ---- | ------ |
|
||||
| AGRI_ENV_MQTT_BROKER_URI | MQTT 服务器地址 | 空 |
|
||||
| AGRI_ENV_MQTT_USERNAME | MQTT 用户名 | 空 |
|
||||
| AGRI_ENV_MQTT_PASSWORD | MQTT 密码 | 空 |
|
||||
| AGRI_ENV_MQTT_CLIENT_ID | 客户端 ID | agri-env-monitor |
|
||||
| AGRI_ENV_MQTT_PUBLISH_TOPIC | 设备上报主题 | agri/env/data |
|
||||
| AGRI_ENV_MQTT_SUBSCRIBE_TOPIC | 设备订阅主题 | agri/env/cmd |
|
||||
|
||||
协议与连接行为:
|
||||
|
||||
- MQTT 协议版本:MQTT v3.1
|
||||
- 如果 Broker URI 未写协议头,会自动补成 mqtt:// 并默认端口 1883
|
||||
- 连接成功后设备自动订阅 AGRI_ENV_MQTT_SUBSCRIBE_TOPIC
|
||||
|
||||
### 2. 主题定义
|
||||
|
||||
| 方向 | 主题 | 用途 |
|
||||
| ---- | ---- | ---- |
|
||||
| 设备 -> 上位机 | agri/env/data | 周期上报环境数据、设备状态、阈值配置 |
|
||||
| 上位机 -> 设备 | agri/env/cmd | 下发模式切换、阈值修改、手动控制命令 |
|
||||
|
||||
发布参数(当前固件实现):
|
||||
|
||||
- 上报周期:1 秒 1 次
|
||||
- QoS:0
|
||||
- Retain:0
|
||||
|
||||
### 3. 上报数据 JSON(agri/env/data)
|
||||
|
||||
#### 3.1 完整字段
|
||||
|
||||
| 字段名 | 类型 | 单位/取值 | 说明 |
|
||||
| ------ | ---- | --------- | ---- |
|
||||
| time | string | YYYY-MM-DD HH:MM:SS | 设备本地时间 |
|
||||
| lux | number | lux | 光照强度 |
|
||||
| temp | number | 摄氏度 | 温度 |
|
||||
| humidity | number | %RH | 湿度 |
|
||||
| gas_percent | number | % | MQ-2 气体浓度百分比 |
|
||||
| tvoc | number | 传感器原始单位 | TVOC(JW01) |
|
||||
| hcho | number | 传感器原始单位 | 甲醛(JW01) |
|
||||
| co2 | number | ppm | 二氧化碳(JW01) |
|
||||
| ice_weight | number | g | HX711 重量值 |
|
||||
| fire_percent | number | % | 火焰传感器百分比 |
|
||||
| fire_danger | boolean | true/false | 火焰危险判定 |
|
||||
| human_present | boolean | true/false | 人体存在状态 |
|
||||
| door_closed | boolean | true/false | 门磁状态(true=关门) |
|
||||
| door_alarm | boolean | true/false | 门未关超时告警状态 |
|
||||
| door_open_seconds | number | s | 当前连续开门时长 |
|
||||
| fan_on | boolean | true/false | 风扇继电器状态 |
|
||||
| light_on | boolean | true/false | 照明继电器状态 |
|
||||
| cool_on | boolean | true/false | 制冷继电器状态 |
|
||||
| hot_on | boolean | true/false | 制热继电器状态 |
|
||||
| su03t_last_msgno | number | 0-255 | SU-03T 最近消息号 |
|
||||
| su03t_rx_count | number | >=0 | SU-03T 累计收包次数 |
|
||||
| mode | string | auto/manual | 当前控制模式 |
|
||||
| th_temp_h | number | 摄氏度 | 制冷阈值(temp >= th_temp_h 触发制冷) |
|
||||
| th_temp_l | number | 摄氏度 | 制热阈值(temp <= th_temp_l 触发制热) |
|
||||
| th_hum_h | number | %RH | 湿度阈值(humidity >= th_hum_h 触发风扇) |
|
||||
| th_gas_h | number | % | 气体阈值(gas_percent >= th_gas_h 触发风扇) |
|
||||
| ip_address | string | IPv4 字符串 | 当前联网 IP |
|
||||
| food_status | string | good/spoilage | 粮食状态(依据 co2 判定) |
|
||||
| uptime_s | number | s | 设备运行时长 |
|
||||
| free_heap_kb | number | KB | 剩余堆内存 |
|
||||
|
||||
#### 3.2 上报示例
|
||||
|
||||
```json
|
||||
{
|
||||
"time": "2026-04-22 16:24:39",
|
||||
"lux": 5,
|
||||
"temp": 30.8,
|
||||
"humidity": 65.4,
|
||||
"gas_percent": 9.7,
|
||||
"tvoc": 0,
|
||||
"hcho": 0,
|
||||
"co2": 350,
|
||||
"ice_weight": 0,
|
||||
"fire_percent": 0,
|
||||
"fire_danger": false,
|
||||
"human_present": false,
|
||||
"door_closed": false,
|
||||
"fan_on": false,
|
||||
"light_on": false,
|
||||
"cool_on": false,
|
||||
"hot_on": false,
|
||||
"su03t_last_msgno": 0,
|
||||
"su03t_rx_count": 0,
|
||||
"mode": "manual",
|
||||
"th_temp_h": 35,
|
||||
"th_temp_l": 15,
|
||||
"th_hum_h": 70,
|
||||
"th_gas_h": 20,
|
||||
"ip_address": "192.168.1.12",
|
||||
"food_status": "good",
|
||||
"uptime_s": 1234,
|
||||
"free_heap_kb": 182
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 下发命令 JSON(agri/env/cmd)
|
||||
|
||||
下发命令为 JSON。字段可按需携带,未携带字段保持原值。
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------ | ---- | ---- |
|
||||
| mode | string | auto 或 manual |
|
||||
| th_temp_h | number | 更新制冷阈值 |
|
||||
| th_temp_l | number | 更新制热阈值 |
|
||||
| th_hum_h | number | 更新湿度阈值 |
|
||||
| th_gas_h | number | 更新气体阈值 |
|
||||
| fan | boolean | 手动控制风扇继电器(1号) |
|
||||
| light | boolean | 手动控制照明继电器(2号) |
|
||||
| cool | boolean | 手动控制制冷继电器(3号) |
|
||||
| hot | boolean | 手动控制制热继电器(4号) |
|
||||
|
||||
### 5. 控制逻辑规则
|
||||
|
||||
#### 5.1 手动模式(mode=manual)
|
||||
|
||||
- 支持 fan/light/cool/hot 四个布尔量直接控制继电器
|
||||
- 自动联动逻辑不接管继电器
|
||||
|
||||
#### 5.2 自动模式(mode=auto)
|
||||
|
||||
- 制冷:temp >= th_temp_h 时开启,否则关闭
|
||||
- 制热:temp <= th_temp_l 时开启,否则关闭
|
||||
- 风扇:humidity >= th_hum_h 或 gas_percent >= th_gas_h 时开启,否则关闭
|
||||
- 照明:当前自动逻辑不控制照明继电器
|
||||
|
||||
### 6. 上电默认值
|
||||
|
||||
设备上电后的默认模式与阈值:
|
||||
|
||||
- mode = manual
|
||||
- th_temp_h = 35.0
|
||||
- th_temp_l = 15.0
|
||||
- th_hum_h = 70.0
|
||||
- th_gas_h = 20.0
|
||||
- door_open_alarm_seconds = 60
|
||||
|
||||
### 7. 下发示例
|
||||
|
||||
#### 7.1 切到自动并设置阈值
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "auto",
|
||||
"th_temp_h": 28.5,
|
||||
"th_temp_l": 16.0,
|
||||
"th_hum_h": 65.0,
|
||||
"th_gas_h": 18.0
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.2 切到手动并开风扇、开制冷
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "manual",
|
||||
"fan": true,
|
||||
"cool": true,
|
||||
"hot": false,
|
||||
"light": false
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 上位机实现建议
|
||||
|
||||
- 命令下发后,以上报主题 agri/env/data 的最新数据作为状态确认
|
||||
- 建议上位机做字段容错:新增字段应忽略,缺失字段使用默认显示
|
||||
- 当前固件未提供独立 ACK 主题,建议在上位机侧做超时重发策略
|
||||
- 门磁告警建议以 door_alarm 与 door_open_seconds 组合显示,door_alarm=true 时突出提示用户
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/JW01/JW01.c
|
||||
* 角色: JW01 气体传感器串口解析与数据提取
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "JW01.h"
|
||||
|
||||
#include <ctype.h>
|
||||
@@ -13,6 +22,10 @@ static const char *TAG = "JW01";
|
||||
static bool s_inited = false;
|
||||
static uint32_t s_parse_fail_count = 0;
|
||||
|
||||
/* 函数: jw01_parse_binary_frames
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t jw01_parse_binary_frames(const uint8_t *buf, int len, jw01_data_t *out)
|
||||
{
|
||||
bool found = false;
|
||||
@@ -43,6 +56,10 @@ static esp_err_t jw01_parse_binary_frames(const uint8_t *buf, int len, jw01_data
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: str_to_upper_inplace
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void str_to_upper_inplace(char *s)
|
||||
{
|
||||
while (*s != '\0') {
|
||||
@@ -51,6 +68,10 @@ static void str_to_upper_inplace(char *s)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: extract_float_by_key
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static bool extract_float_by_key(const char *line, const char *key, float *out)
|
||||
{
|
||||
char up_line[160];
|
||||
@@ -91,6 +112,10 @@ static bool extract_float_by_key(const char *line, const char *key, float *out)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 函数: jw01_parse_line
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t jw01_parse_line(const char *line, jw01_data_t *out)
|
||||
{
|
||||
jw01_data_t data = {0};
|
||||
@@ -115,6 +140,10 @@ static esp_err_t jw01_parse_line(const char *line, jw01_data_t *out)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: jw01_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t jw01_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
@@ -148,6 +177,10 @@ esp_err_t jw01_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: jw01_read
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t jw01_read(jw01_data_t *out_data, int timeout_ms)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(out_data != NULL, ESP_ERR_INVALID_ARG, TAG, "out_data is null");
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/JW01/include/JW01.h
|
||||
* 角色: JW01 气体传感器串口解析与数据提取
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/MQ-2/MQ-2.c
|
||||
* 角色: MQ-2 模拟气体传感器采样与百分比映射
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "MQ-2.h"
|
||||
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
@@ -11,6 +20,10 @@ static const char *TAG = "MQ2";
|
||||
static adc_oneshot_unit_handle_t s_adc_handle = NULL;
|
||||
static bool s_inited = false;
|
||||
|
||||
/* 函数: mq2_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t mq2_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
@@ -34,6 +47,10 @@ esp_err_t mq2_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: mq2_read_raw
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t mq2_read_raw(int *raw_out)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(raw_out != NULL, ESP_ERR_INVALID_ARG, TAG, "raw_out is null");
|
||||
@@ -51,6 +68,10 @@ esp_err_t mq2_read_raw(int *raw_out)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: mq2_read_percent
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t mq2_read_percent(float *percent_out)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(percent_out != NULL, ESP_ERR_INVALID_ARG, TAG, "percent_out is null");
|
||||
@@ -61,11 +82,19 @@ esp_err_t mq2_read_percent(float *percent_out)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: mq2_is_alarm
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
bool mq2_is_alarm(float percent, float threshold_percent)
|
||||
{
|
||||
return percent >= threshold_percent;
|
||||
}
|
||||
|
||||
/* 函数: mq2_get_adc_handle
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
adc_oneshot_unit_handle_t mq2_get_adc_handle(void)
|
||||
{
|
||||
return s_adc_handle;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/MQ-2/include/MQ-2.h
|
||||
* 角色: MQ-2 模拟气体传感器采样与百分比映射
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
5
components/agri_env/CMakeLists.txt
Normal file
5
components/agri_env/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "agri_env.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES driver mqtt cjson esp_timer esp_event relay_ctrl
|
||||
)
|
||||
26
components/agri_env/Kconfig.projbuild
Normal file
26
components/agri_env/Kconfig.projbuild
Normal file
@@ -0,0 +1,26 @@
|
||||
menu "MQTT 配置参数"
|
||||
config AGRI_ENV_MQTT_BROKER_URI
|
||||
string "MQTT 服务器地址"
|
||||
default ""
|
||||
|
||||
config AGRI_ENV_MQTT_USERNAME
|
||||
string "MQTT 用户名"
|
||||
default ""
|
||||
|
||||
config AGRI_ENV_MQTT_PASSWORD
|
||||
string "MQTT 密码"
|
||||
default ""
|
||||
|
||||
config AGRI_ENV_MQTT_CLIENT_ID
|
||||
string "MQTT Client ID"
|
||||
default "agri-env-monitor"
|
||||
|
||||
config AGRI_ENV_MQTT_PUBLISH_TOPIC
|
||||
string "MQTT 发布主题"
|
||||
default "agri/env/data"
|
||||
|
||||
config AGRI_ENV_MQTT_SUBSCRIBE_TOPIC
|
||||
string "MQTT 订阅主题"
|
||||
default "agri/env/cmd"
|
||||
|
||||
endmenu
|
||||
270
components/agri_env/agri_env.c
Normal file
270
components/agri_env/agri_env.c
Normal file
@@ -0,0 +1,270 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "cJSON.h"
|
||||
#include "relay_ctrl.h"
|
||||
|
||||
#include "agri_env.h"
|
||||
|
||||
static const char *TAG = "agri_env";
|
||||
|
||||
/**
|
||||
* @brief 农业环境模块上下文结构体
|
||||
* 包含 MQTT 客户端句柄、连接状态以及用于线程安全的互斥锁
|
||||
*/
|
||||
typedef struct {
|
||||
SemaphoreHandle_t lock; /*!< 互斥锁,保护共享资源 */
|
||||
|
||||
esp_mqtt_client_handle_t mqtt_client; /*!< ESP MQTT 客户端句柄 */
|
||||
bool mqtt_connected; /*!< MQTT 连接状态标识 */
|
||||
} agri_env_ctx_t;
|
||||
|
||||
static agri_env_ctx_t s_ctx;
|
||||
static agri_env_mqtt_cmd_cb_t s_mqtt_cmd_cb = NULL;
|
||||
|
||||
void agri_env_set_mqtt_cmd_cb(agri_env_mqtt_cmd_cb_t cb)
|
||||
{
|
||||
s_mqtt_cmd_cb = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 规范化 MQTT 代理 URI
|
||||
*
|
||||
* 将 menuconfig 中的原始字符串处理为标准形式(例如加上 mqtt:// 前缀或默认端口 1883)
|
||||
*
|
||||
* @param buffer 存储结果的缓冲区
|
||||
* @param buffer_size 缓冲区大小
|
||||
* @param was_prefixed [out] 如果进行了修整或添加前缀,则设为 true
|
||||
* @return const char* 返回规范化后的字符串指针,失败返回 NULL
|
||||
*/
|
||||
static const char *agri_env_get_normalized_mqtt_uri(char *buffer, size_t buffer_size, bool *was_prefixed)
|
||||
{
|
||||
const char *uri = CONFIG_AGRI_ENV_MQTT_BROKER_URI;
|
||||
if (was_prefixed != NULL) {
|
||||
*was_prefixed = false;
|
||||
}
|
||||
|
||||
if (uri == NULL || uri[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 去除前导空白字符
|
||||
while (*uri == ' ' || *uri == '\t' || *uri == '\r' || *uri == '\n') {
|
||||
++uri;
|
||||
}
|
||||
|
||||
size_t uri_len = strlen(uri);
|
||||
// 去除尾部空白字符
|
||||
while (uri_len > 0 && (uri[uri_len - 1] == ' ' || uri[uri_len - 1] == '\t' || uri[uri_len - 1] == '\r' || uri[uri_len - 1] == '\n')) {
|
||||
--uri_len;
|
||||
}
|
||||
|
||||
if (uri_len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 如果已经包含协议前缀 (如 mqtt://), 直接复制并返回
|
||||
if (strstr(uri, "://") != NULL) {
|
||||
if (uri_len + 1 >= buffer_size) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(buffer, uri, uri_len);
|
||||
buffer[uri_len] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// 自动补全协议前缀和默认端口
|
||||
const bool has_port = memchr(uri, ':', uri_len) != NULL;
|
||||
int written;
|
||||
if (has_port) {
|
||||
written = snprintf(buffer, buffer_size, "mqtt://%.*s", (int)uri_len, uri);
|
||||
} else {
|
||||
written = snprintf(buffer, buffer_size, "mqtt://%.*s:1883", (int)uri_len, uri);
|
||||
}
|
||||
|
||||
if (written < 0 || written >= (int)buffer_size) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (was_prefixed != NULL) {
|
||||
*was_prefixed = true;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MQTT 事件处理回调函数
|
||||
*
|
||||
* 处理连接、断开、接收数据等事件
|
||||
*/
|
||||
static void agri_env_mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
(void)handler_args;
|
||||
(void)base;
|
||||
|
||||
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
|
||||
if (event == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_id == MQTT_EVENT_CONNECTED) {
|
||||
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
||||
s_ctx.mqtt_connected = true;
|
||||
xSemaphoreGive(s_ctx.lock);
|
||||
ESP_LOGI(TAG, "MQTT 已连接");
|
||||
|
||||
// 连接成功后订阅配置的主题
|
||||
if (strlen(CONFIG_AGRI_ENV_MQTT_SUBSCRIBE_TOPIC) > 0) {
|
||||
esp_mqtt_client_subscribe(s_ctx.mqtt_client, CONFIG_AGRI_ENV_MQTT_SUBSCRIBE_TOPIC, 0);
|
||||
ESP_LOGI(TAG, "已订阅主题: %s", CONFIG_AGRI_ENV_MQTT_SUBSCRIBE_TOPIC);
|
||||
}
|
||||
} else if (event_id == MQTT_EVENT_DISCONNECTED) {
|
||||
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
||||
s_ctx.mqtt_connected = false;
|
||||
xSemaphoreGive(s_ctx.lock);
|
||||
ESP_LOGW(TAG, "MQTT 已断开连接");
|
||||
} else if (event_id == MQTT_EVENT_DATA) {
|
||||
ESP_LOGI(TAG, "收到 MQTT 消息: topic=%.*s payload=%.*s",
|
||||
event->topic_len,
|
||||
event->topic,
|
||||
event->data_len,
|
||||
event->data);
|
||||
if (s_mqtt_cmd_cb != NULL) {
|
||||
s_mqtt_cmd_cb(event->topic, event->data, event->data_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启动 MQTT 客户端
|
||||
*/
|
||||
esp_err_t agri_env_mqtt_start(void)
|
||||
{
|
||||
if (s_ctx.lock == NULL) {
|
||||
s_ctx.lock = xSemaphoreCreateMutex();
|
||||
ESP_RETURN_ON_FALSE(s_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "互斥锁创建失败");
|
||||
}
|
||||
|
||||
if (s_ctx.mqtt_client != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
char mqtt_uri[256];
|
||||
bool mqtt_uri_prefixed = false;
|
||||
const char *normalized_uri = agri_env_get_normalized_mqtt_uri(mqtt_uri, sizeof(mqtt_uri), &mqtt_uri_prefixed);
|
||||
|
||||
if (normalized_uri == NULL) {
|
||||
ESP_LOGW(TAG, "MQTT Broker URI 为空,请在 menuconfig 中填写");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (mqtt_uri_prefixed) {
|
||||
ESP_LOGW(TAG, "MQTT Broker URI 已规范化为: %s", normalized_uri);
|
||||
}
|
||||
|
||||
// MQTT 客户端配置
|
||||
const esp_mqtt_client_config_t mqtt_cfg = {
|
||||
.broker.address.uri = normalized_uri,
|
||||
.credentials.client_id = CONFIG_AGRI_ENV_MQTT_CLIENT_ID,
|
||||
.credentials.username = CONFIG_AGRI_ENV_MQTT_USERNAME,
|
||||
.credentials.authentication.password = CONFIG_AGRI_ENV_MQTT_PASSWORD,
|
||||
.session.protocol_ver = MQTT_PROTOCOL_V_3_1, // 使用 MQTT v3.1 协议
|
||||
};
|
||||
|
||||
s_ctx.mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
|
||||
ESP_RETURN_ON_FALSE(s_ctx.mqtt_client != NULL, ESP_FAIL, TAG, "MQTT 客户端初始化失败");
|
||||
|
||||
// 注册所有 MQTT 事件的回调
|
||||
ESP_RETURN_ON_ERROR(
|
||||
esp_mqtt_client_register_event(s_ctx.mqtt_client, MQTT_EVENT_ANY, agri_env_mqtt_event_handler, NULL),
|
||||
TAG,
|
||||
"MQTT 注册事件失败");
|
||||
|
||||
// 启动 MQTT 客户端任务
|
||||
ESP_RETURN_ON_ERROR(esp_mqtt_client_start(s_ctx.mqtt_client), TAG, "MQTT 启动失败");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 停止并销毁 MQTT 客户端
|
||||
*/
|
||||
esp_err_t agri_env_mqtt_stop(void)
|
||||
{
|
||||
if (s_ctx.mqtt_client == NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_mqtt_client_stop(s_ctx.mqtt_client);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = esp_mqtt_client_destroy(s_ctx.mqtt_client);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
||||
s_ctx.mqtt_client = NULL;
|
||||
s_ctx.mqtt_connected = false;
|
||||
xSemaphoreGive(s_ctx.lock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查 MQTT 是否已成功连接
|
||||
*
|
||||
* @return true 已连接, false 未连接
|
||||
*/
|
||||
bool agri_env_mqtt_is_connected(void)
|
||||
{
|
||||
bool connected = false;
|
||||
if (s_ctx.lock == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
||||
connected = s_ctx.mqtt_connected;
|
||||
xSemaphoreGive(s_ctx.lock);
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发布数据到指定主题
|
||||
*
|
||||
* 当前处于“仅 MQTT”模式,发布内容为固定的 JSON 数据:{"mode":"mqtt_only"}
|
||||
*
|
||||
* @param topic 目标主题
|
||||
* @param qos 服务质量等级
|
||||
* @param retain 保留消息标识
|
||||
* @return esp_err_t 成功返回 ESP_OK,失败返回相应错误码
|
||||
*/
|
||||
esp_err_t agri_env_mqtt_publish(const char *topic, const char *payload, int qos, int retain)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(topic != NULL && topic[0] != '\0', ESP_ERR_INVALID_ARG, TAG, "主题为空");
|
||||
ESP_RETURN_ON_FALSE(payload != NULL, ESP_ERR_INVALID_ARG, TAG, "内容为空");
|
||||
ESP_RETURN_ON_FALSE(s_ctx.mqtt_client != NULL, ESP_ERR_INVALID_STATE, TAG, "MQTT 客户端未启动");
|
||||
ESP_RETURN_ON_FALSE(agri_env_mqtt_is_connected(), ESP_ERR_INVALID_STATE, TAG, "MQTT 未连接");
|
||||
|
||||
int msg_id = esp_mqtt_client_publish(s_ctx.mqtt_client, topic, payload, 0, qos, retain);
|
||||
|
||||
ESP_RETURN_ON_FALSE(msg_id >= 0, ESP_FAIL, TAG, "MQTT 发布失败");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain)
|
||||
{
|
||||
static const char *payload = "{\"mode\":\"mqtt_only\"}";
|
||||
return agri_env_mqtt_publish(topic, payload, qos, retain);
|
||||
}
|
||||
28
components/agri_env/include/agri_env.h
Normal file
28
components/agri_env/include/agri_env.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 启动 MQTT 客户端连接。 */
|
||||
esp_err_t agri_env_mqtt_start(void);
|
||||
/* 停止 MQTT 客户端连接并释放资源。 */
|
||||
esp_err_t agri_env_mqtt_stop(void);
|
||||
/* 查询 MQTT 当前是否已连接。 */
|
||||
bool agri_env_mqtt_is_connected(void);
|
||||
/* 发布指定的 JSON 载荷到指定主题。 */
|
||||
esp_err_t agri_env_mqtt_publish(const char *topic, const char *payload, int qos, int retain);
|
||||
/* 发布固定 MQTT-only 心跳载荷到指定主题。 */
|
||||
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain);
|
||||
|
||||
typedef void (*agri_env_mqtt_cmd_cb_t)(const char *topic, const char *payload, int len);
|
||||
void agri_env_set_mqtt_cmd_cb(agri_env_mqtt_cmd_cb_t cb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/bh1750/bh1750.c
|
||||
* 角色: BH1750 光照传感器驱动封装
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
@@ -25,6 +34,10 @@ typedef struct {
|
||||
/**
|
||||
* @brief 向 BH1750 写入一个字节的辅助函数
|
||||
*/
|
||||
/* 函数: bh1750_write_byte
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t bh1750_write_byte(const bh1750_dev_t *const sens, const uint8_t byte)
|
||||
{
|
||||
return i2c_master_transmit(sens->i2c_handle, &byte, 1, pdMS_TO_TICKS(1000));
|
||||
@@ -38,6 +51,10 @@ static esp_err_t bh1750_write_byte(const bh1750_dev_t *const sens, const uint8_t
|
||||
* @param handle_ret [out] 返回创建好的设备句柄
|
||||
* @return esp_err_t 成功返回 ESP_OK
|
||||
*/
|
||||
/* 函数: bh1750_create
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_create(i2c_master_bus_handle_t i2c_bus, const uint8_t dev_addr, bh1750_handle_t *handle_ret)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
@@ -65,6 +82,10 @@ esp_err_t bh1750_create(i2c_master_bus_handle_t i2c_bus, const uint8_t dev_addr,
|
||||
/**
|
||||
* @brief 删除 BH1750 设备并释放资源
|
||||
*/
|
||||
/* 函数: bh1750_delete
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_delete(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
@@ -78,6 +99,10 @@ esp_err_t bh1750_delete(bh1750_handle_t sensor)
|
||||
/**
|
||||
* @brief 进入掉电模式(低功耗)
|
||||
*/
|
||||
/* 函数: bh1750_power_down
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_power_down(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
@@ -87,6 +112,10 @@ esp_err_t bh1750_power_down(bh1750_handle_t sensor)
|
||||
/**
|
||||
* @brief 唤醒并进入上电模式
|
||||
*/
|
||||
/* 函数: bh1750_power_on
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_power_on(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
@@ -97,6 +126,10 @@ esp_err_t bh1750_power_on(bh1750_handle_t sensor)
|
||||
* @brief 设置测量时间倍率 (MTreg)
|
||||
* 用于改变传感器的测量灵敏度
|
||||
*/
|
||||
/* 函数: bh1750_set_measure_time
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_set_measure_time(bh1750_handle_t sensor, const uint8_t measure_time)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
@@ -116,6 +149,10 @@ esp_err_t bh1750_set_measure_time(bh1750_handle_t sensor, const uint8_t measure_
|
||||
/**
|
||||
* @brief 设置测量模式(连续测量或单词测量,以及分辨率选择)
|
||||
*/
|
||||
/* 函数: bh1750_set_measure_mode
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_set_measure_mode(bh1750_handle_t sensor, const bh1750_measure_mode_t cmd_measure)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
@@ -129,6 +166,10 @@ esp_err_t bh1750_set_measure_mode(bh1750_handle_t sensor, const bh1750_measure_m
|
||||
* @param data [out] 返回转换后的光照强度值 (单位: Lux)
|
||||
* @return esp_err_t
|
||||
*/
|
||||
/* 函数: bh1750_get_data
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_get_data(bh1750_handle_t sensor, float *const data)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/bh1750/bh1750_use.c
|
||||
* 角色: BH1750 光照传感器驱动封装
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "driver/i2c_master.h"
|
||||
@@ -11,11 +20,19 @@ static const char *TAG = "BH1750_USE";
|
||||
static i2c_master_bus_handle_t s_i2c_bus_handle = NULL;
|
||||
static bh1750_handle_t s_bh1750_handle = NULL;
|
||||
|
||||
/* 函数: bh1750_get_i2c_bus_handle
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
i2c_master_bus_handle_t bh1750_get_i2c_bus_handle(void)
|
||||
{
|
||||
return s_i2c_bus_handle;
|
||||
}
|
||||
|
||||
/* 函数: bh1750_user_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_user_init(void)
|
||||
{
|
||||
if (s_i2c_bus_handle == NULL) {
|
||||
@@ -38,6 +55,10 @@ esp_err_t bh1750_user_init(void)
|
||||
return bh1750_power_on(s_bh1750_handle);
|
||||
}
|
||||
|
||||
/* 函数: bh1750_user_read
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t bh1750_user_read(float *lux)
|
||||
{
|
||||
if (s_bh1750_handle == NULL) return ESP_ERR_INVALID_STATE;
|
||||
@@ -48,6 +69,10 @@ esp_err_t bh1750_user_read(float *lux)
|
||||
return bh1750_get_data(s_bh1750_handle, lux);
|
||||
}
|
||||
|
||||
/* 函数: bh1750_user_deinit
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
void bh1750_user_deinit(void)
|
||||
{
|
||||
if (s_bh1750_handle) {
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/bh1750/include/bh1750.h
|
||||
* 角色: BH1750 光照传感器驱动封装
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/bh1750/include/bh1750_use.h
|
||||
* 角色: BH1750 光照传感器驱动封装
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef BH1750_USE_H
|
||||
#define BH1750_USE_H
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/fire_sensor/fire_sensor.c
|
||||
* 角色: 火焰传感器 ADC 采样与危险判定
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "fire_sensor.h"
|
||||
|
||||
#include "MQ-2.h"
|
||||
@@ -13,6 +22,10 @@ static const char *TAG = "fire_sensor";
|
||||
static adc_oneshot_unit_handle_t s_adc_handle = NULL;
|
||||
static bool s_inited = false;
|
||||
|
||||
/* 函数: fire_sensor_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t fire_sensor_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
@@ -35,6 +48,10 @@ esp_err_t fire_sensor_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: fire_sensor_read_raw
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t fire_sensor_read_raw(int *raw_out)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(raw_out != NULL, ESP_ERR_INVALID_ARG, TAG, "raw_out is null");
|
||||
@@ -52,6 +69,10 @@ esp_err_t fire_sensor_read_raw(int *raw_out)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: fire_sensor_read_percent
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t fire_sensor_read_percent(float *percent_out)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(percent_out != NULL, ESP_ERR_INVALID_ARG, TAG, "percent_out is null");
|
||||
@@ -71,6 +92,10 @@ esp_err_t fire_sensor_read_percent(float *percent_out)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: fire_sensor_is_danger
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
bool fire_sensor_is_danger(float percent, float threshold_percent)
|
||||
{
|
||||
return percent >= threshold_percent;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/fire_sensor/include/fire_sensor.h
|
||||
* 角色: 火焰传感器 ADC 采样与危险判定
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/human_door/human_door.c
|
||||
* 角色: 人体/门磁输入采集
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "human_door.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
@@ -6,6 +15,10 @@
|
||||
static const char *TAG = "human_door";
|
||||
static bool s_inited = false;
|
||||
|
||||
/* 函数: human_door_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t human_door_init(void)
|
||||
{
|
||||
if (s_inited) {
|
||||
@@ -25,6 +38,10 @@ esp_err_t human_door_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: human_door_read
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t human_door_read(human_door_state_t *out_state)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(out_state != NULL, ESP_ERR_INVALID_ARG, TAG, "out_state is null");
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/human_door/include/human_door.h
|
||||
* 角色: 人体/门磁输入采集
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/lvgl_st7789_use/include/lvgl_st7789_use.h
|
||||
* 角色: ST7789 屏幕与 LVGL 适配
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/relay_ctrl/include/relay_ctrl.h
|
||||
* 角色: 四路继电器状态控制
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/relay_ctrl/relay_ctrl.c
|
||||
* 角色: 四路继电器状态控制
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "relay_ctrl.h"
|
||||
#include <stdint.h>
|
||||
#include "esp_check.h"
|
||||
@@ -13,11 +22,19 @@ typedef struct {
|
||||
|
||||
static relay_ctx_t s_ctx;
|
||||
|
||||
/* 函数: relay_level_from_state
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static inline int relay_level_from_state(int id, bool on)
|
||||
{
|
||||
return (on == s_ctx.config[id].active_high) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* 函数: relay_validate_id
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t relay_validate_id(relay_ctrl_id_t relay_id)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(relay_id >= RELAY_CTRL_ID_1 && relay_id < RELAY_CTRL_ID_MAX,
|
||||
@@ -25,6 +42,10 @@ static esp_err_t relay_validate_id(relay_ctrl_id_t relay_id)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: relay_ctrl_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t relay_ctrl_init(const relay_config_t config[RELAY_CTRL_ID_MAX])
|
||||
{
|
||||
uint64_t pin_bit_mask = 0;
|
||||
@@ -58,6 +79,10 @@ esp_err_t relay_ctrl_init(const relay_config_t config[RELAY_CTRL_ID_MAX])
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: relay_ctrl_set
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t relay_ctrl_set(relay_ctrl_id_t relay_id, bool on)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ctx.inited, ESP_ERR_INVALID_STATE, TAG, "relay not initialized");
|
||||
@@ -74,6 +99,10 @@ esp_err_t relay_ctrl_set(relay_ctrl_id_t relay_id, bool on)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: relay_ctrl_toggle
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t relay_ctrl_toggle(relay_ctrl_id_t relay_id)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ctx.inited, ESP_ERR_INVALID_STATE, TAG, "relay not initialized");
|
||||
@@ -81,6 +110,10 @@ esp_err_t relay_ctrl_toggle(relay_ctrl_id_t relay_id)
|
||||
return relay_ctrl_set(relay_id, !s_ctx.states[relay_id]);
|
||||
}
|
||||
|
||||
/* 函数: relay_ctrl_get
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t relay_ctrl_get(relay_ctrl_id_t relay_id, bool *on_out)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ctx.inited, ESP_ERR_INVALID_STATE, TAG, "relay not initialized");
|
||||
@@ -90,6 +123,10 @@ esp_err_t relay_ctrl_get(relay_ctrl_id_t relay_id, bool *on_out)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: relay_ctrl_set_all
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t relay_ctrl_set_all(bool relay1_on, bool relay2_on, bool relay3_on, bool relay4_on)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(relay_ctrl_set(RELAY_CTRL_ID_1, relay1_on), TAG, "set relay1 failed");
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/sntp_time/include/sntp_time.h
|
||||
* 角色: SNTP 对时与时间刷新
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/sntp_time/sntp_time.c
|
||||
* 角色: SNTP 对时与时间刷新
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
@@ -23,6 +32,10 @@ static TaskHandle_t s_time_refresh_task = NULL;
|
||||
|
||||
static time_t get_current_time(void);
|
||||
|
||||
/* 函数: publish_sntp_time_var
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void publish_sntp_time_var(const char *value)
|
||||
{
|
||||
if (set_var_sntp_time != NULL) {
|
||||
@@ -30,6 +43,10 @@ static void publish_sntp_time_var(const char *value)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: format_current_time
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void format_current_time(char *buffer, size_t buffer_size)
|
||||
{
|
||||
time_t now = get_current_time();
|
||||
@@ -39,6 +56,10 @@ static void format_current_time(char *buffer, size_t buffer_size)
|
||||
strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S", &timeinfo);
|
||||
}
|
||||
|
||||
/* 函数: sntp_time_refresh_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void sntp_time_refresh_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
@@ -52,6 +73,10 @@ static void sntp_time_refresh_task(void *arg)
|
||||
}
|
||||
|
||||
// =========================== 时间相关函数 ===========================
|
||||
/* 函数: set_timezone
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void set_timezone(void)
|
||||
{
|
||||
// 设置中国标准时间(北京时间)
|
||||
@@ -60,12 +85,20 @@ static void set_timezone(void)
|
||||
ESP_LOGI(TAG, "时区设置为北京时间 (CST-8)");
|
||||
}
|
||||
|
||||
/* 函数: get_current_time
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static time_t get_current_time(void)
|
||||
{
|
||||
// 使用POSIX函数获取时间
|
||||
return time(NULL);
|
||||
}
|
||||
|
||||
/* 函数: print_current_time
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void print_current_time(void)
|
||||
{
|
||||
char buffer[64];
|
||||
@@ -74,6 +107,10 @@ static void print_current_time(void)
|
||||
ESP_LOGI(TAG, "当前时间: %s", buffer);
|
||||
}
|
||||
|
||||
/* 函数: start_time_refresh_task_if_needed
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t start_time_refresh_task_if_needed(void)
|
||||
{
|
||||
if (s_time_refresh_task != NULL) {
|
||||
@@ -89,6 +126,10 @@ static esp_err_t start_time_refresh_task_if_needed(void)
|
||||
return (ok == pdPASS) ? ESP_OK : ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
/* 函数: configure_sntp_servers
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void configure_sntp_servers(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "初始化SNTP服务");
|
||||
@@ -105,6 +146,10 @@ static void configure_sntp_servers(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 函数: wait_for_time_sync
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wait_for_time_sync(uint32_t timeout_ms)
|
||||
{
|
||||
int64_t start_ms = esp_timer_get_time() / 1000;
|
||||
@@ -123,6 +168,10 @@ static esp_err_t wait_for_time_sync(uint32_t timeout_ms)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: sntp_timp_sync_time
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t sntp_timp_sync_time(uint32_t timeout_ms)
|
||||
{
|
||||
if (timeout_ms == 0) {
|
||||
|
||||
3
components/su-03t/CMakeLists.txt
Normal file
3
components/su-03t/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "su-03t.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES driver)
|
||||
113
components/su-03t/include/su-03t.h
Normal file
113
components/su-03t/include/su-03t.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 文件: components/su-03t/include/su-03t.h
|
||||
* 角色: SU-03T 语音模块串口协议适配
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*
|
||||
* SU-03T 语音模块接口声明
|
||||
*
|
||||
* 说明:
|
||||
* 1) 固定使用 UART2,默认引脚 RX=IO41 / TX=IO42,波特率 115200。
|
||||
* 2) 提供固定帧协议封装与原始十六进制发送两种方式。
|
||||
* 3) 提供同步收帧与后台异步回调,便于语音事件实时上报。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SU03T_UART_PORT UART_NUM_2
|
||||
#define SU03T_UART_BAUD 115200
|
||||
#define SU03T_UART_TX_IO GPIO_NUM_42
|
||||
#define SU03T_UART_RX_IO GPIO_NUM_41
|
||||
|
||||
#define SU03T_DEFAULT_HEAD_H 0xAA
|
||||
#define SU03T_DEFAULT_HEAD_L 0x55
|
||||
#define SU03T_DEFAULT_TAIL_H 0x55
|
||||
#define SU03T_DEFAULT_TAIL_L 0xAA
|
||||
|
||||
#define SU03T_MAX_PARAM_LEN 256
|
||||
|
||||
typedef struct {
|
||||
uint8_t head_h;
|
||||
uint8_t head_l;
|
||||
uint8_t tail_h;
|
||||
uint8_t tail_l;
|
||||
} su03t_frame_format_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t msgno;
|
||||
uint8_t params[SU03T_MAX_PARAM_LEN];
|
||||
size_t params_len;
|
||||
} su03t_frame_t;
|
||||
|
||||
/* 异步接收回调: 每收到一帧完整数据触发一次。 */
|
||||
typedef void (*su03t_rx_callback_t)(const su03t_frame_t *frame, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief 初始化 SU-03T 串口驱动
|
||||
* @return ESP_OK 成功;其它为驱动安装或串口配置错误
|
||||
*/
|
||||
esp_err_t su03t_init(void);
|
||||
|
||||
/**
|
||||
* @brief 配置帧头帧尾(默认 AA55 ... 55AA)
|
||||
*/
|
||||
esp_err_t su03t_set_frame_format(const su03t_frame_format_t *fmt);
|
||||
|
||||
/**
|
||||
* @brief 获取当前帧格式配置
|
||||
*/
|
||||
esp_err_t su03t_get_frame_format(su03t_frame_format_t *fmt_out);
|
||||
|
||||
/**
|
||||
* @brief 发送固定格式消息
|
||||
* @param msgno 消息编号(1字节)
|
||||
* @param params 参数区首地址,可为 NULL(当 params_len 为 0)
|
||||
* @param params_len 参数区长度
|
||||
*/
|
||||
esp_err_t su03t_send_frame(uint8_t msgno, const uint8_t *params, size_t params_len);
|
||||
|
||||
/**
|
||||
* @brief 发送十六进制字符串(支持空格分隔)
|
||||
* @param hex_string 十六进制文本,例如 "AA 55 01 11 22 55 AA"
|
||||
*/
|
||||
esp_err_t su03t_send_hex_string(const char *hex_string);
|
||||
|
||||
/**
|
||||
* @brief 同步接收并解析一帧消息
|
||||
* @param out_frame 输出帧
|
||||
* @param timeout_ms 超时时间(毫秒)
|
||||
*/
|
||||
esp_err_t su03t_recv_frame(su03t_frame_t *out_frame, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief 启动后台接收任务,持续解析并回调
|
||||
* @param callback 收到完整帧时的回调函数
|
||||
* @param user_ctx 回调透传上下文指针
|
||||
* @param task_stack_size 接收任务栈大小,传 0 使用默认值
|
||||
* @param task_priority 接收任务优先级,传 0 使用默认值
|
||||
*/
|
||||
esp_err_t su03t_start_receiver(su03t_rx_callback_t callback, void *user_ctx, uint32_t task_stack_size, UBaseType_t task_priority);
|
||||
|
||||
/**
|
||||
* @brief 停止后台接收任务
|
||||
*/
|
||||
esp_err_t su03t_stop_receiver(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
397
components/su-03t/su-03t.c
Normal file
397
components/su-03t/su-03t.c
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* 文件: components/su-03t/su-03t.c
|
||||
* 角色: SU-03T 语音模块串口协议适配
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*
|
||||
* SU-03T 语音模块串口适配层
|
||||
*
|
||||
* 设计目标:
|
||||
* 1) 提供统一的 UART2 初始化接口(RX=IO41, TX=IO42, 115200)。
|
||||
* 2) 支持两类发送方式:
|
||||
* - 结构化帧发送: 头(2) + msgno(1) + params(N) + 尾(2)
|
||||
* - 原始十六进制串发送: 方便调试工具或协议快速验证。
|
||||
* 3) 支持同步收帧与后台异步收帧回调,满足“主控主动发、模块随时上报”的场景。
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "su-03t.h"
|
||||
|
||||
static const char *TAG = "su03t";
|
||||
|
||||
static bool s_inited = false;
|
||||
static TaskHandle_t s_rx_task = NULL;
|
||||
static volatile bool s_rx_running = false;
|
||||
static su03t_rx_callback_t s_rx_callback = NULL;
|
||||
static void *s_rx_user_ctx = NULL;
|
||||
static su03t_frame_format_t s_fmt = {
|
||||
.head_h = SU03T_DEFAULT_HEAD_H,
|
||||
.head_l = SU03T_DEFAULT_HEAD_L,
|
||||
.tail_h = SU03T_DEFAULT_TAIL_H,
|
||||
.tail_l = SU03T_DEFAULT_TAIL_L,
|
||||
};
|
||||
|
||||
/*
|
||||
* 将单个十六进制字符转换为 4bit 数值。
|
||||
* 返回值:
|
||||
* - 0~15: 有效十六进制字符
|
||||
* - -1 : 非法字符
|
||||
*/
|
||||
/* 函数: hex_to_nibble
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static int hex_to_nibble(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* SU-03T 后台接收任务。
|
||||
* 逻辑:
|
||||
* 1) 循环调用 su03t_recv_frame() 解析帧。
|
||||
* 2) 解析成功后,回调上层业务。
|
||||
* 3) 超时属于正常空闲,不打印告警;仅对非超时错误告警。
|
||||
*/
|
||||
/* 函数: su03t_rx_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void su03t_rx_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
su03t_frame_t frame;
|
||||
while (s_rx_running) {
|
||||
esp_err_t ret = su03t_recv_frame(&frame, 200);
|
||||
if (ret == ESP_OK) {
|
||||
if (s_rx_callback != NULL) {
|
||||
s_rx_callback(&frame, s_rx_user_ctx);
|
||||
}
|
||||
} else if (ret != ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW(TAG, "recv frame failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
s_rx_task = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* 初始化 UART2 驱动并配置 SU-03T 引脚。
|
||||
* 注意: 若驱动已安装,uart_driver_install 可能返回 ESP_ERR_INVALID_STATE,
|
||||
* 这里将其视为“可继续配置”的情况。
|
||||
*/
|
||||
/* 函数: su03t_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_init(void)
|
||||
{
|
||||
const uart_config_t uart_cfg = {
|
||||
.baud_rate = SU03T_UART_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
esp_err_t ret = uart_driver_install(SU03T_UART_PORT, 1024, 1024, 0, NULL, 0);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uart_param_config(SU03T_UART_PORT, &uart_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uart_set_pin(SU03T_UART_PORT, SU03T_UART_TX_IO, SU03T_UART_RX_IO,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
s_inited = true;
|
||||
ESP_LOGI(TAG, "init UART2 done, TX=%d RX=%d baud=%d",
|
||||
SU03T_UART_TX_IO, SU03T_UART_RX_IO, SU03T_UART_BAUD);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 设置当前帧头帧尾格式,后续发送与解析都按该格式执行。 */
|
||||
/* 函数: su03t_set_frame_format
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_set_frame_format(const su03t_frame_format_t *fmt)
|
||||
{
|
||||
if (fmt == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
s_fmt = *fmt;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 获取当前帧格式,便于业务层调试或回显配置。 */
|
||||
/* 函数: su03t_get_frame_format
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_get_frame_format(su03t_frame_format_t *fmt_out)
|
||||
{
|
||||
if (fmt_out == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
*fmt_out = s_fmt;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 按协议帧格式发送:
|
||||
* [head_h head_l msgno params... tail_h tail_l]
|
||||
*/
|
||||
/* 函数: su03t_send_frame
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_send_frame(uint8_t msgno, const uint8_t *params, size_t params_len)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (params_len > SU03T_MAX_PARAM_LEN) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
if (params_len > 0 && params == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
uint8_t tx_buf[2 + 1 + SU03T_MAX_PARAM_LEN + 2];
|
||||
size_t idx = 0;
|
||||
tx_buf[idx++] = s_fmt.head_h;
|
||||
tx_buf[idx++] = s_fmt.head_l;
|
||||
tx_buf[idx++] = msgno;
|
||||
if (params_len > 0) {
|
||||
memcpy(&tx_buf[idx], params, params_len);
|
||||
idx += params_len;
|
||||
}
|
||||
tx_buf[idx++] = s_fmt.tail_h;
|
||||
tx_buf[idx++] = s_fmt.tail_l;
|
||||
|
||||
int written = uart_write_bytes(SU03T_UART_PORT, (const char *)tx_buf, idx);
|
||||
if (written < 0 || (size_t)written != idx) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return uart_wait_tx_done(SU03T_UART_PORT, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
/*
|
||||
* 发送十六进制字符串。
|
||||
* 输入示例: "AA 55 01 11 22 55 AA"
|
||||
* 规则:
|
||||
* 1) 允许空白字符。
|
||||
* 2) 必须是偶数个十六进制字符。
|
||||
* 3) 遇到非法字符直接返回参数错误。
|
||||
*/
|
||||
/* 函数: su03t_send_hex_string
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_send_hex_string(const char *hex_string)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (hex_string == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
uint8_t tx_buf[2 + 1 + SU03T_MAX_PARAM_LEN + 2];
|
||||
size_t out_len = 0;
|
||||
int high = -1;
|
||||
|
||||
for (const char *p = hex_string; *p != '\0'; ++p) {
|
||||
if (isspace((unsigned char)*p)) {
|
||||
continue;
|
||||
}
|
||||
int n = hex_to_nibble(*p);
|
||||
if (n < 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (high < 0) {
|
||||
high = n;
|
||||
} else {
|
||||
if (out_len >= sizeof(tx_buf)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
tx_buf[out_len++] = (uint8_t)((high << 4) | n);
|
||||
high = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (high >= 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (out_len == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int written = uart_write_bytes(SU03T_UART_PORT, (const char *)tx_buf, out_len);
|
||||
if (written < 0 || (size_t)written != out_len) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return uart_wait_tx_done(SU03T_UART_PORT, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
/*
|
||||
* 在超时时间内解析一帧。
|
||||
* 协议格式:
|
||||
* head_h head_l msgno param... tail_h tail_l
|
||||
* 返回:
|
||||
* - ESP_OK: 成功拿到完整帧
|
||||
* - ESP_ERR_TIMEOUT: 超时未收到完整帧
|
||||
* - 其它: 参数或协议错误
|
||||
*/
|
||||
/* 函数: su03t_recv_frame
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_recv_frame(su03t_frame_t *out_frame, uint32_t timeout_ms)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (out_frame == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
memset(out_frame, 0, sizeof(*out_frame));
|
||||
|
||||
const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
|
||||
const TickType_t start_tick = xTaskGetTickCount();
|
||||
uint8_t byte = 0;
|
||||
uint8_t payload[1 + SU03T_MAX_PARAM_LEN];
|
||||
size_t payload_len = 0;
|
||||
bool head_found = false;
|
||||
|
||||
while ((xTaskGetTickCount() - start_tick) < timeout_ticks) {
|
||||
int n = uart_read_bytes(SU03T_UART_PORT, &byte, 1, pdMS_TO_TICKS(20));
|
||||
if (n <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!head_found) {
|
||||
if (byte == s_fmt.head_h) {
|
||||
int n2 = uart_read_bytes(SU03T_UART_PORT, &byte, 1, pdMS_TO_TICKS(20));
|
||||
if (n2 > 0 && byte == s_fmt.head_l) {
|
||||
head_found = true;
|
||||
payload_len = 0;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (payload_len >= sizeof(payload)) {
|
||||
head_found = false;
|
||||
payload_len = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
payload[payload_len++] = byte;
|
||||
|
||||
if (payload_len >= 3 &&
|
||||
payload[payload_len - 2] == s_fmt.tail_h &&
|
||||
payload[payload_len - 1] == s_fmt.tail_l) {
|
||||
|
||||
size_t body_len = payload_len - 2;
|
||||
if (body_len < 1) {
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
out_frame->msgno = payload[0];
|
||||
out_frame->params_len = body_len - 1;
|
||||
if (out_frame->params_len > 0) {
|
||||
memcpy(out_frame->params, &payload[1], out_frame->params_len);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
/*
|
||||
* 启动异步接收:
|
||||
* - callback: 每收到一帧就回调一次
|
||||
* - task_stack_size/task_priority: 允许上层按系统负载调优
|
||||
*/
|
||||
/* 函数: su03t_start_receiver
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_start_receiver(su03t_rx_callback_t callback, void *user_ctx, uint32_t task_stack_size, UBaseType_t task_priority)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (callback == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (s_rx_task != NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (task_stack_size == 0) {
|
||||
task_stack_size = 4096;
|
||||
}
|
||||
if (task_priority == 0) {
|
||||
task_priority = 5;
|
||||
}
|
||||
|
||||
s_rx_callback = callback;
|
||||
s_rx_user_ctx = user_ctx;
|
||||
s_rx_running = true;
|
||||
|
||||
BaseType_t ok = xTaskCreate(su03t_rx_task, "su03t_rx", task_stack_size, NULL, task_priority, &s_rx_task);
|
||||
if (ok != pdPASS) {
|
||||
s_rx_running = false;
|
||||
s_rx_callback = NULL;
|
||||
s_rx_user_ctx = NULL;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 停止异步接收。
|
||||
* 该实现通过标志位让接收任务自行退出,避免跨任务强制删除导致资源状态不一致。
|
||||
*/
|
||||
/* 函数: su03t_stop_receiver
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t su03t_stop_receiver(void)
|
||||
{
|
||||
if (s_rx_task == NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_rx_running = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/wifi-connect/include/wifi-connect.h
|
||||
* 角色: Wi-Fi 连接、配网与网络状态管理
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: components/wifi-connect/wifi-connect.c
|
||||
* 角色: Wi-Fi 连接、配网与网络状态管理
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -36,6 +45,10 @@
|
||||
|
||||
static const char *TAG = "wifi_connect";
|
||||
|
||||
/* 函数: wifi_connect_log_state_i
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_log_state_i(const char *state, const char *detail)
|
||||
{
|
||||
if (detail != NULL && detail[0] != '\0')
|
||||
@@ -44,6 +57,10 @@ static void wifi_connect_log_state_i(const char *state, const char *detail)
|
||||
ESP_LOGI(TAG, "【状态】%s", state);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_log_state_w
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_log_state_w(const char *state, const char *detail)
|
||||
{
|
||||
if (detail != NULL && detail[0] != '\0')
|
||||
@@ -52,6 +69,10 @@ static void wifi_connect_log_state_w(const char *state, const char *detail)
|
||||
ESP_LOGW(TAG, "【状态】%s", state);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_log_state_e
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_log_state_e(const char *state, const char *detail)
|
||||
{
|
||||
if (detail != NULL && detail[0] != '\0')
|
||||
@@ -192,11 +213,19 @@ static const char *s_html_page =
|
||||
"setInterval(()=>{if(isConnected)fetchInfo();},3000);"
|
||||
"</script></body></html>";
|
||||
|
||||
/* 函数: wifi_connect_set_status_locked
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_set_status_locked(wifi_connect_status_t status)
|
||||
{
|
||||
s_ctx.status = status;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_set_error_locked
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_set_error_locked(const char *message)
|
||||
{
|
||||
if (message == NULL)
|
||||
@@ -207,6 +236,10 @@ static void wifi_connect_set_error_locked(const char *message)
|
||||
snprintf(s_ctx.last_error, sizeof(s_ctx.last_error), "%s", message);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_save_credentials
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_save_credentials(const char *ssid, const char *password)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
@@ -220,6 +253,10 @@ static esp_err_t wifi_connect_save_credentials(const char *ssid, const char *pas
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_clear_config
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t wifi_connect_clear_config(void)
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
@@ -270,6 +307,10 @@ esp_err_t wifi_connect_clear_config(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_get_config
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t wifi_connect_get_config(wifi_connect_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is null");
|
||||
@@ -290,6 +331,10 @@ esp_err_t wifi_connect_get_config(wifi_connect_config_t *config)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_status_to_string
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static const char *wifi_connect_status_to_string(wifi_connect_status_t status)
|
||||
{
|
||||
switch (status)
|
||||
@@ -311,6 +356,10 @@ static const char *wifi_connect_status_to_string(wifi_connect_status_t status)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_get_status
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
wifi_connect_status_t wifi_connect_get_status(void)
|
||||
{
|
||||
if (!s_ctx.initialized || s_ctx.lock == NULL)
|
||||
@@ -323,6 +372,10 @@ wifi_connect_status_t wifi_connect_get_status(void)
|
||||
return status;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_get_ip
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
const char *wifi_connect_get_ip(void)
|
||||
{
|
||||
if (!s_ctx.initialized || s_ctx.lock == NULL)
|
||||
@@ -335,12 +388,20 @@ const char *wifi_connect_get_ip(void)
|
||||
return ip_buf;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_send_json
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_send_json(httpd_req_t *req, const char *json)
|
||||
{
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
return httpd_resp_sendstr(req, json);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_json_escape
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_json_escape(const char *src, char *dst, size_t dst_size)
|
||||
{
|
||||
size_t j = 0;
|
||||
@@ -358,6 +419,10 @@ static void wifi_connect_json_escape(const char *src, char *dst, size_t dst_size
|
||||
dst[j] = '\0';
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_auth_to_string
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static const char *wifi_connect_auth_to_string(wifi_auth_mode_t auth)
|
||||
{
|
||||
switch (auth)
|
||||
@@ -383,6 +448,10 @@ static const char *wifi_connect_auth_to_string(wifi_auth_mode_t auth)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_scan_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_scan_handler(httpd_req_t *req)
|
||||
{
|
||||
wifi_scan_config_t scan_cfg = {.show_hidden = false};
|
||||
@@ -430,6 +499,10 @@ static esp_err_t wifi_connect_http_scan_handler(httpd_req_t *req)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_extract_json_string
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static bool wifi_connect_extract_json_string(const char *json, const char *key, char *out, size_t out_len)
|
||||
{
|
||||
char pattern[32];
|
||||
@@ -449,6 +522,10 @@ static bool wifi_connect_extract_json_string(const char *json, const char *key,
|
||||
return idx > 0 || strcmp(key, "password") == 0;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_apply_sta_credentials
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_apply_sta_credentials(const char *ssid, const char *password)
|
||||
{
|
||||
wifi_config_t sta_cfg = {0};
|
||||
@@ -468,6 +545,10 @@ static esp_err_t wifi_connect_apply_sta_credentials(const char *ssid, const char
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_connect_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_connect_handler(httpd_req_t *req)
|
||||
{
|
||||
if (req->content_len <= 0 || req->content_len >= WIFI_CONNECT_HTTP_BUF_SIZE)
|
||||
@@ -513,6 +594,10 @@ static esp_err_t wifi_connect_http_connect_handler(httpd_req_t *req)
|
||||
return wifi_connect_send_json(req, "{\"ok\":true}");
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_status_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_status_handler(httpd_req_t *req)
|
||||
{
|
||||
wifi_connect_status_t status;
|
||||
@@ -531,6 +616,10 @@ static esp_err_t wifi_connect_http_status_handler(httpd_req_t *req)
|
||||
return wifi_connect_send_json(req, payload);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_clear_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_clear_handler(httpd_req_t *req)
|
||||
{
|
||||
esp_err_t err = wifi_connect_clear_config();
|
||||
@@ -539,6 +628,10 @@ static esp_err_t wifi_connect_http_clear_handler(httpd_req_t *req)
|
||||
return wifi_connect_send_json(req, "{\"ok\":true}");
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_sysinfo_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_sysinfo_handler(httpd_req_t *req)
|
||||
{
|
||||
char payload[128];
|
||||
@@ -546,6 +639,10 @@ static esp_err_t wifi_connect_http_sysinfo_handler(httpd_req_t *req)
|
||||
return wifi_connect_send_json(req, payload);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_index_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_index_handler(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_hdr(req, "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
|
||||
@@ -554,6 +651,10 @@ static esp_err_t wifi_connect_http_index_handler(httpd_req_t *req)
|
||||
return httpd_resp_send(req, s_html_page, HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_get_ap_http_url
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_get_ap_http_url(char *out, size_t out_len)
|
||||
{
|
||||
esp_netif_ip_info_t ip_info = {0};
|
||||
@@ -567,6 +668,10 @@ static void wifi_connect_get_ap_http_url(char *out, size_t out_len)
|
||||
snprintf(out, out_len, "http://192.168.4.1/");
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_probe_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_probe_handler(httpd_req_t *req)
|
||||
{
|
||||
char location[48] = {0};
|
||||
@@ -580,6 +685,10 @@ static esp_err_t wifi_connect_http_probe_handler(httpd_req_t *req)
|
||||
static esp_err_t wifi_connect_http_start(void);
|
||||
static void wifi_connect_http_stop(void);
|
||||
|
||||
/* 函数: wifi_connect_http_start
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_http_start(void)
|
||||
{
|
||||
if (s_ctx.http_server != NULL)
|
||||
@@ -622,6 +731,10 @@ static esp_err_t wifi_connect_http_start(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_http_stop
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_http_stop(void)
|
||||
{
|
||||
if (s_ctx.http_server != NULL)
|
||||
@@ -631,6 +744,10 @@ static void wifi_connect_http_stop(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_build_dns_response
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static size_t wifi_connect_build_dns_response(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_max, uint32_t ip_addr)
|
||||
{
|
||||
if (req_len < 12 || resp_max < 64)
|
||||
@@ -683,6 +800,10 @@ static size_t wifi_connect_build_dns_response(const uint8_t *req, size_t req_len
|
||||
return pos;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_dns_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_dns_task(void *arg)
|
||||
{
|
||||
uint8_t rx_buf[256], tx_buf[512];
|
||||
@@ -728,6 +849,10 @@ static void wifi_connect_dns_task(void *arg)
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_dns_start
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_dns_start(void)
|
||||
{
|
||||
if (s_ctx.dns_running)
|
||||
@@ -741,6 +866,10 @@ static esp_err_t wifi_connect_dns_start(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_dns_stop
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_dns_stop(void)
|
||||
{
|
||||
if (!s_ctx.dns_running)
|
||||
@@ -751,6 +880,10 @@ static void wifi_connect_dns_stop(void)
|
||||
s_ctx.dns_task = NULL;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_connect_timeout_cb
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_connect_timeout_cb(void *arg)
|
||||
{
|
||||
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
||||
@@ -775,6 +908,10 @@ static void wifi_connect_connect_timeout_cb(void *arg)
|
||||
xSemaphoreGive(s_ctx.lock);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_ap_stop_timer_cb
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_ap_stop_timer_cb(void *arg)
|
||||
{
|
||||
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
||||
@@ -788,6 +925,10 @@ static void wifi_connect_ap_stop_timer_cb(void *arg)
|
||||
xSemaphoreGive(s_ctx.lock);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_event_handler
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
|
||||
@@ -849,6 +990,10 @@ static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, i
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_generate_ap_ssid
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void wifi_connect_generate_ap_ssid(char *out, size_t out_len)
|
||||
{
|
||||
uint8_t mac[6] = {0};
|
||||
@@ -857,6 +1002,10 @@ static void wifi_connect_generate_ap_ssid(char *out, size_t out_len)
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_start_apsta_locked
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_start_apsta_locked(void)
|
||||
{
|
||||
wifi_config_t ap_cfg = {0};
|
||||
@@ -879,6 +1028,10 @@ static esp_err_t wifi_connect_start_apsta_locked(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_start
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t wifi_connect_start(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized");
|
||||
@@ -909,6 +1062,10 @@ esp_err_t wifi_connect_start(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_stop
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t wifi_connect_stop(void)
|
||||
{
|
||||
if (!s_ctx.initialized)
|
||||
@@ -941,6 +1098,10 @@ esp_err_t wifi_connect_stop(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_try_auto_connect
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static esp_err_t wifi_connect_try_auto_connect(void)
|
||||
{
|
||||
wifi_connect_config_t config = {0};
|
||||
@@ -980,6 +1141,10 @@ static esp_err_t wifi_connect_try_auto_connect(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 函数: wifi_connect_init
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
esp_err_t wifi_connect_init(void)
|
||||
{
|
||||
if (s_ctx.initialized)
|
||||
|
||||
@@ -13,6 +13,16 @@ dependencies:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.0
|
||||
espressif/cjson:
|
||||
component_hash: e788323270d90738662d66fffa910bfe1fba019bba087f01557e70c40485b469
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.7.19~2
|
||||
espressif/cmake_utilities:
|
||||
component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
|
||||
dependencies:
|
||||
@@ -51,6 +61,16 @@ dependencies:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.5.1
|
||||
espressif/mqtt:
|
||||
component_hash: ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.0
|
||||
espressif/sensor_hub:
|
||||
component_hash: b3e72f3e2d10e165b972b17fff889f294b1a685769efacf73ec8f1e92152d1d5
|
||||
dependencies:
|
||||
@@ -80,10 +100,23 @@ dependencies:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 9.5.0
|
||||
supcik/hx711:
|
||||
component_hash: 38de09d1a896b3eda3cbb8c259979deed77fa0c517516c2b344dc55ae04f4fb6
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.1.3
|
||||
direct_dependencies:
|
||||
- espressif/aht30
|
||||
- espressif/cjson
|
||||
- espressif/esp_lvgl_port
|
||||
- espressif/mqtt
|
||||
- idf
|
||||
manifest_hash: 15dd4fd3d660b11406a1dde8c0ec0b63f12e3cd4064ff9026e70648e3f7bc8a3
|
||||
- supcik/hx711
|
||||
manifest_hash: f379725444325394085b05956aaab66b9a797466dc72d898cd7645dbe6cb359c
|
||||
target: esp32s3
|
||||
version: 2.0.0
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
idf_component_register(SRCS "main.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES nvs_flash esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2 JW01 human_door fire_sensor)
|
||||
REQUIRES nvs_flash agri_env esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2 JW01 human_door fire_sensor hx711 su-03t)
|
||||
|
||||
@@ -16,3 +16,6 @@ dependencies:
|
||||
# public: true
|
||||
espressif/esp_lvgl_port: ^2.7.2
|
||||
espressif/aht30: ^1.0.0
|
||||
supcik/hx711: ^1.1.3
|
||||
espressif/mqtt: ^1.0.0
|
||||
espressif/cjson: ^1.7.19~2
|
||||
|
||||
600
main/main.cpp
600
main/main.cpp
@@ -1,6 +1,16 @@
|
||||
/*
|
||||
* 文件: main/main.cpp
|
||||
* 角色: 系统主流程与任务调度入口
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "wifi-connect.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
#include "lvgl_st7789_use.h"
|
||||
@@ -12,21 +22,44 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "sntp_time.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_efuse.h"
|
||||
#include "esp_efuse_table.h"
|
||||
#include "esp_timer.h"
|
||||
#include "sntp_time.h"
|
||||
#include "bh1750_use.h"
|
||||
#include "aht30.h"
|
||||
#include "MQ-2.h"
|
||||
#include "JW01.h"
|
||||
#include "human_door.h"
|
||||
#include "fire_sensor.h"
|
||||
#include "hx711.hpp"
|
||||
#include "su-03t.h"
|
||||
#include "agri_env.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
#define TAG "MAIN"
|
||||
#define CO2_SPOILAGE_THRESHOLD_PPM 1500.0f
|
||||
#define FIRE_DANGER_THRESHOLD_PERCENT 35.0f
|
||||
static const gpio_num_t kClockPin = GPIO_NUM_13;
|
||||
static const gpio_num_t kDataPin = GPIO_NUM_14;
|
||||
|
||||
#define HX711_TARE_SAMPLES 24
|
||||
#define HX711_READ_TIMEOUT_MS 350
|
||||
#define HX711_REFRESH_MS 120
|
||||
#define HX711_COUNTS_PER_GRAM 430.0f
|
||||
#define HX711_ZERO_DEADBAND_G 2.0f
|
||||
#define HX711_FILTER_ALPHA 0.15f
|
||||
#define HX711_STABLE_BAND_G 0.30f
|
||||
#define HX711_UNLOCK_DELTA_G 2.00f
|
||||
#define HX711_UPDATE_MIN_STEP_G 0.05f
|
||||
#define HX711_STABLE_SAMPLES 15
|
||||
#define TWDT_NORMAL_TIMEOUT_MS 5000
|
||||
#define TWDT_UI_INIT_TIMEOUT_MS 120000
|
||||
#define DOOR_OPEN_ALARM_SECONDS 60
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -38,16 +71,121 @@ typedef struct
|
||||
float tvoc;
|
||||
float hcho;
|
||||
float co2;
|
||||
float ice_weight;
|
||||
float fire_percent;
|
||||
bool fire_danger;
|
||||
bool human_present;
|
||||
bool door_closed;
|
||||
bool door_alarm;
|
||||
uint32_t door_open_seconds;
|
||||
bool fan_on;
|
||||
bool light_on;
|
||||
bool cool_on;
|
||||
bool hot_on;
|
||||
uint8_t su03t_last_msgno;
|
||||
uint32_t su03t_rx_count;
|
||||
|
||||
// ======== 新增:阈值与模式配置 ========
|
||||
bool auto_mode; // 自动/手动模式:true=自动,false=手动
|
||||
float th_temp_h; // 制冷阈值(温度高于此值开启制冷)
|
||||
float th_temp_l; // 制热阈值(温度低于此值开启制热)
|
||||
float th_hum_h; // 湿度排风阈值
|
||||
float th_gas_h; // 烟雾/有害气体排风阈值
|
||||
} env_data_t;
|
||||
|
||||
static env_data_t s_env_data;
|
||||
static SemaphoreHandle_t s_env_data_lock = NULL;
|
||||
static volatile bool s_ui_ready = false;
|
||||
|
||||
/* 函数: app_mqtt_cmd_handler
|
||||
* 作用: 解析并处理远端下发的 MQTT 配置项和手动控制指令
|
||||
*/
|
||||
static void app_mqtt_cmd_handler(const char *topic, const char *payload, int len)
|
||||
{
|
||||
// 如果不是下发的命令主题则忽略(可根据需要支持通配符判断,这里简单处理)
|
||||
// 将 payload 拷贝并追加 \0 变为合法字符串
|
||||
char *json_str = (char*)malloc(len + 1);
|
||||
if (!json_str) return;
|
||||
memcpy(json_str, payload, len);
|
||||
json_str[len] = '\0';
|
||||
|
||||
cJSON *root = cJSON_Parse(json_str);
|
||||
if (root)
|
||||
{
|
||||
bool auto_mode = false; // 默认手动
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
|
||||
// 1. 解析模式
|
||||
cJSON *item = cJSON_GetObjectItem(root, "mode");
|
||||
if (item && cJSON_IsString(item)) {
|
||||
if (strcmp(item->valuestring, "auto") == 0) s_env_data.auto_mode = true;
|
||||
else if (strcmp(item->valuestring, "manual") == 0) s_env_data.auto_mode = false;
|
||||
}
|
||||
|
||||
// 2. 解析阈值配置
|
||||
item = cJSON_GetObjectItem(root, "th_temp_h");
|
||||
if (item && cJSON_IsNumber(item)) s_env_data.th_temp_h = item->valuedouble;
|
||||
|
||||
item = cJSON_GetObjectItem(root, "th_temp_l");
|
||||
if (item && cJSON_IsNumber(item)) s_env_data.th_temp_l = item->valuedouble;
|
||||
|
||||
item = cJSON_GetObjectItem(root, "th_hum_h");
|
||||
if (item && cJSON_IsNumber(item)) s_env_data.th_hum_h = item->valuedouble;
|
||||
|
||||
item = cJSON_GetObjectItem(root, "th_gas_h");
|
||||
if (item && cJSON_IsNumber(item)) s_env_data.th_gas_h = item->valuedouble;
|
||||
|
||||
auto_mode = s_env_data.auto_mode;
|
||||
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
|
||||
// 3. 在手动模式下响应远程控制
|
||||
if (!auto_mode)
|
||||
{
|
||||
cJSON *item = cJSON_GetObjectItem(root, "fan");
|
||||
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_1, cJSON_IsTrue(item));
|
||||
|
||||
item = cJSON_GetObjectItem(root, "light");
|
||||
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_2, cJSON_IsTrue(item));
|
||||
|
||||
item = cJSON_GetObjectItem(root, "cool");
|
||||
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_3, cJSON_IsTrue(item));
|
||||
|
||||
item = cJSON_GetObjectItem(root, "hot");
|
||||
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_4, cJSON_IsTrue(item));
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
free(json_str);
|
||||
}
|
||||
|
||||
/* 函数: reconfigure_twdt
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void reconfigure_twdt(uint32_t timeout_ms, uint32_t idle_core_mask)
|
||||
{
|
||||
const esp_task_wdt_config_t twdt_cfg = {
|
||||
.timeout_ms = timeout_ms,
|
||||
.idle_core_mask = idle_core_mask,
|
||||
.trigger_panic = true,
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_task_wdt_reconfigure(&twdt_cfg);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "TWDT reconfigure failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: wait_for_wifi_connected
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static bool wait_for_wifi_connected(TickType_t timeout_ticks)
|
||||
{
|
||||
const TickType_t start_ticks = xTaskGetTickCount();
|
||||
@@ -60,6 +198,10 @@ static bool wait_for_wifi_connected(TickType_t timeout_ticks)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 函数: env_data_update_system_info
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void env_data_update_system_info(void)
|
||||
{
|
||||
if (s_env_data_lock == NULL)
|
||||
@@ -77,6 +219,10 @@ static void env_data_update_system_info(void)
|
||||
set_var_local_time(s_env_data.time_str);
|
||||
}
|
||||
|
||||
/* 函数: ui_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void ui_task(void *arg)
|
||||
{
|
||||
for (;;)
|
||||
@@ -94,34 +240,79 @@ static void ui_task(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: ui_init_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void ui_init_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
ESP_LOGI(TAG, "UI init start");
|
||||
lvgl_port_lock(100 / portTICK_PERIOD_MS);
|
||||
ui_init();
|
||||
lvgl_port_unlock();
|
||||
ESP_LOGI(TAG, "UI init done");
|
||||
|
||||
s_ui_ready = true;
|
||||
reconfigure_twdt(TWDT_NORMAL_TIMEOUT_MS, (1U << portNUM_PROCESSORS) - 1U);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* 函数: status_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void status_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
TickType_t door_open_start_ticks = 0;
|
||||
bool door_alarm_active = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
human_door_state_t io_state{};
|
||||
if (human_door_read(&io_state) == ESP_OK)
|
||||
{
|
||||
set_var_hum_status(io_state.human_present ? "有人" : "无人");
|
||||
set_var_door_status(io_state.door_closed ? "关闭" : "开启");
|
||||
|
||||
uint32_t door_open_seconds = 0;
|
||||
if (io_state.door_closed)
|
||||
{
|
||||
door_open_start_ticks = 0;
|
||||
door_alarm_active = false;
|
||||
set_var_door_status("关闭");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (door_open_start_ticks == 0)
|
||||
{
|
||||
door_open_start_ticks = xTaskGetTickCount();
|
||||
}
|
||||
|
||||
door_open_seconds = (uint32_t)((xTaskGetTickCount() - door_open_start_ticks) * portTICK_PERIOD_MS / 1000U);
|
||||
if (door_open_seconds >= DOOR_OPEN_ALARM_SECONDS)
|
||||
{
|
||||
if (!door_alarm_active)
|
||||
{
|
||||
ESP_LOGW(TAG, "Door left open too long: %us", (unsigned)door_open_seconds);
|
||||
}
|
||||
door_alarm_active = true;
|
||||
// set_var_door_status("未关告警");
|
||||
}
|
||||
else
|
||||
{
|
||||
set_var_door_status("开启");
|
||||
}
|
||||
}
|
||||
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
s_env_data.human_present = io_state.human_present;
|
||||
s_env_data.door_closed = io_state.door_closed;
|
||||
s_env_data.door_alarm = door_alarm_active;
|
||||
s_env_data.door_open_seconds = door_open_seconds;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
}
|
||||
@@ -129,11 +320,346 @@ static void status_task(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: relay_status_text
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static const char *relay_status_text(bool on)
|
||||
{
|
||||
return on ? "开" : "关";
|
||||
}
|
||||
|
||||
/* 函数: relay_status_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void relay_status_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
bool relay_on = false;
|
||||
|
||||
if (relay_ctrl_get(RELAY_CTRL_ID_1, &relay_on) == ESP_OK)
|
||||
{
|
||||
set_var_fan_status(relay_status_text(relay_on));
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
s_env_data.fan_on = relay_on;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
}
|
||||
if (relay_ctrl_get(RELAY_CTRL_ID_2, &relay_on) == ESP_OK)
|
||||
{
|
||||
set_var_light_status(relay_status_text(relay_on));
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
s_env_data.light_on = relay_on;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
}
|
||||
if (relay_ctrl_get(RELAY_CTRL_ID_3, &relay_on) == ESP_OK)
|
||||
{
|
||||
set_var_cool_status(relay_status_text(relay_on));
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
s_env_data.cool_on = relay_on;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
}
|
||||
if (relay_ctrl_get(RELAY_CTRL_ID_4, &relay_on) == ESP_OK)
|
||||
{
|
||||
set_var_hot_status(relay_status_text(relay_on));
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
s_env_data.hot_on = relay_on;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: sntp_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void sntp_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000)))
|
||||
{
|
||||
esp_err_t sntp_ret = sntp_timp_sync_time(10000);
|
||||
if (sntp_ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "SNTP sync failed: %s", esp_err_to_name(sntp_ret));
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* 函数: su03t_rx_callback
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx)
|
||||
{
|
||||
(void)user_ctx;
|
||||
if (frame == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char hex_buf[256];
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i < frame->params_len && pos + 4 < sizeof(hex_buf); ++i)
|
||||
{
|
||||
int n = snprintf(&hex_buf[pos], sizeof(hex_buf) - pos, "%02X ", frame->params[i]);
|
||||
if (n <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
pos += (size_t)n;
|
||||
}
|
||||
if (pos == 0)
|
||||
{
|
||||
snprintf(hex_buf, sizeof(hex_buf), "(no params)");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SU03T RX msgno=0x%02X len=%u params=%s",
|
||||
frame->msgno,
|
||||
(unsigned)frame->params_len,
|
||||
hex_buf);
|
||||
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
s_env_data.su03t_last_msgno = frame->msgno;
|
||||
s_env_data.su03t_rx_count++;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: hx711_task
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
static void hx711_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
HX711 hx711(kClockPin, kDataPin, HX711::Mode::kChannelA128);
|
||||
int64_t tare_sum = 0;
|
||||
int tare_ok_count = 0;
|
||||
|
||||
// 上电空载自动去皮:当前重量作为 0g 基准
|
||||
for (int i = 0; i < HX711_TARE_SAMPLES; ++i)
|
||||
{
|
||||
int32_t raw = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS));
|
||||
if (raw != HX711::kUndefined)
|
||||
{
|
||||
tare_sum += raw;
|
||||
tare_ok_count++;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
}
|
||||
|
||||
int32_t tare_offset = 0;
|
||||
if (tare_ok_count > 0)
|
||||
{
|
||||
tare_offset = (int32_t)(tare_sum / tare_ok_count);
|
||||
ESP_LOGI(TAG, "HX711 tare done: raw0=%ld, samples=%d", (long)tare_offset, tare_ok_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "HX711 tare failed, use 0 as offset");
|
||||
}
|
||||
|
||||
float filtered_weight_g = 0.0f;
|
||||
float display_weight_g = 0.0f;
|
||||
bool display_initialized = false;
|
||||
bool display_locked = false;
|
||||
uint32_t stable_count = 0;
|
||||
uint32_t err_cnt = 0;
|
||||
for (;;)
|
||||
{
|
||||
int32_t value = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS));
|
||||
if (value != HX711::kUndefined)
|
||||
{
|
||||
float weight_g = ((float)(value - tare_offset)) / HX711_COUNTS_PER_GRAM;
|
||||
if (fabsf(weight_g) < HX711_ZERO_DEADBAND_G)
|
||||
{
|
||||
weight_g = 0.0f;
|
||||
}
|
||||
if (weight_g < 0.0f)
|
||||
{
|
||||
weight_g = 0.0f;
|
||||
}
|
||||
|
||||
// 一阶低通,减小抖动
|
||||
filtered_weight_g = filtered_weight_g * (1.0f - HX711_FILTER_ALPHA) + weight_g * HX711_FILTER_ALPHA;
|
||||
float rounded_weight_g = roundf(filtered_weight_g * 100.0f) / 100.0f;
|
||||
|
||||
if (!display_initialized)
|
||||
{
|
||||
display_weight_g = rounded_weight_g;
|
||||
display_initialized = true;
|
||||
}
|
||||
|
||||
float diff_from_display = fabsf(rounded_weight_g - display_weight_g);
|
||||
|
||||
// 稳定后锁定显示,重量明显变化时再解锁并继续更新
|
||||
if (display_locked)
|
||||
{
|
||||
if (diff_from_display >= HX711_UNLOCK_DELTA_G)
|
||||
{
|
||||
display_locked = false;
|
||||
stable_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!display_locked)
|
||||
{
|
||||
if (diff_from_display <= HX711_STABLE_BAND_G)
|
||||
{
|
||||
if (stable_count < HX711_STABLE_SAMPLES)
|
||||
{
|
||||
stable_count++;
|
||||
}
|
||||
if (stable_count >= HX711_STABLE_SAMPLES)
|
||||
{
|
||||
display_locked = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stable_count = 0;
|
||||
}
|
||||
|
||||
if (diff_from_display >= HX711_UPDATE_MIN_STEP_G)
|
||||
{
|
||||
display_weight_g = rounded_weight_g;
|
||||
}
|
||||
}
|
||||
|
||||
set_var_ice_weight(display_weight_g);
|
||||
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
s_env_data.ice_weight = display_weight_g;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
err_cnt = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((++err_cnt % 20) == 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "HX711 read timeout, check DOUT/SCK wiring and power");
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(HX711_REFRESH_MS));
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: mqtt_publish_task
|
||||
* 作用: 定时将传感器数据打包成JSON并发布到MQTT
|
||||
*/
|
||||
static void mqtt_publish_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;)
|
||||
{
|
||||
if (agri_env_mqtt_is_connected())
|
||||
{
|
||||
env_data_t local_data;
|
||||
if (s_env_data_lock)
|
||||
{
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
local_data = s_env_data;
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
if (root)
|
||||
{
|
||||
cJSON_AddStringToObject(root, "time", local_data.time_str);
|
||||
cJSON_AddNumberToObject(root, "lux", local_data.lux);
|
||||
cJSON_AddNumberToObject(root, "temp", local_data.temp);
|
||||
cJSON_AddNumberToObject(root, "humidity", local_data.humidity);
|
||||
cJSON_AddNumberToObject(root, "gas_percent", local_data.gas_percent);
|
||||
cJSON_AddNumberToObject(root, "tvoc", local_data.tvoc);
|
||||
cJSON_AddNumberToObject(root, "hcho", local_data.hcho);
|
||||
cJSON_AddNumberToObject(root, "co2", local_data.co2);
|
||||
cJSON_AddNumberToObject(root, "ice_weight", local_data.ice_weight);
|
||||
cJSON_AddNumberToObject(root, "fire_percent", local_data.fire_percent);
|
||||
|
||||
cJSON_AddBoolToObject(root, "fire_danger", local_data.fire_danger);
|
||||
cJSON_AddBoolToObject(root, "human_present", local_data.human_present);
|
||||
cJSON_AddBoolToObject(root, "door_closed", local_data.door_closed);
|
||||
cJSON_AddBoolToObject(root, "door_alarm", local_data.door_alarm);
|
||||
cJSON_AddNumberToObject(root, "door_open_seconds", local_data.door_open_seconds);
|
||||
cJSON_AddBoolToObject(root, "fan_on", local_data.fan_on);
|
||||
cJSON_AddBoolToObject(root, "light_on", local_data.light_on);
|
||||
cJSON_AddBoolToObject(root, "cool_on", local_data.cool_on);
|
||||
cJSON_AddBoolToObject(root, "hot_on", local_data.hot_on);
|
||||
|
||||
cJSON_AddNumberToObject(root, "su03t_last_msgno", local_data.su03t_last_msgno);
|
||||
cJSON_AddNumberToObject(root, "su03t_rx_count", local_data.su03t_rx_count);
|
||||
|
||||
// ======== 新增:系统模式与阈值 ========
|
||||
cJSON_AddStringToObject(root, "mode", local_data.auto_mode ? "auto" : "manual");
|
||||
cJSON_AddNumberToObject(root, "th_temp_h", local_data.th_temp_h);
|
||||
cJSON_AddNumberToObject(root, "th_temp_l", local_data.th_temp_l);
|
||||
cJSON_AddNumberToObject(root, "th_hum_h", local_data.th_hum_h);
|
||||
cJSON_AddNumberToObject(root, "th_gas_h", local_data.th_gas_h);
|
||||
|
||||
// 补充系统及其他分析状态数据
|
||||
cJSON_AddStringToObject(root, "ip_address", wifi_connect_get_ip());
|
||||
cJSON_AddStringToObject(root, "food_status", local_data.co2 >= CO2_SPOILAGE_THRESHOLD_PPM ? "spoilage" : "good");
|
||||
cJSON_AddNumberToObject(root, "uptime_s", esp_timer_get_time() / 1000000ULL);
|
||||
cJSON_AddNumberToObject(root, "free_heap_kb", esp_get_free_heap_size() / 1024);
|
||||
|
||||
char *json_str = cJSON_PrintUnformatted(root);
|
||||
if (json_str)
|
||||
{
|
||||
agri_env_mqtt_publish(CONFIG_AGRI_ENV_MQTT_PUBLISH_TOPIC, json_str, 0, 0);
|
||||
free(json_str); // 注意 cJSON_PrintUnformatted 使用 malloc 分配内存
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000)); // 每1秒发布一次
|
||||
}
|
||||
}
|
||||
|
||||
/* 函数: app_main
|
||||
* 作用: 执行模块内与函数名对应的业务逻辑。
|
||||
* 重点: 关注输入合法性、返回码与并发安全。
|
||||
*/
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
ESP_LOGI(TAG, "--- APP STARTING ---");
|
||||
s_env_data_lock = xSemaphoreCreateMutex();
|
||||
|
||||
// 初始化默认配置与阈值
|
||||
s_env_data.auto_mode = false; // 默认手动模式
|
||||
s_env_data.th_temp_h = 35.0f; // 超过 35度 制冷
|
||||
s_env_data.th_temp_l = 15.0f; // 低于 15度 制热
|
||||
s_env_data.th_hum_h = 70.0f; // 湿度高于 70% 通风
|
||||
s_env_data.th_gas_h = 20.0f; // 气体浓度高于 20% 通风
|
||||
s_env_data.door_alarm = false;
|
||||
s_env_data.door_open_seconds = 0;
|
||||
|
||||
// 1. 初始化 Wi-Fi
|
||||
ESP_ERROR_CHECK(wifi_connect_init());
|
||||
@@ -141,6 +667,9 @@ extern "C" void app_main(void)
|
||||
// 2. 初始化显示屏和 LVGL
|
||||
start_lvgl_demo();
|
||||
|
||||
// UI 初始化期间,临时放宽 TWDT(仅监控 CPU0 空闲任务)
|
||||
reconfigure_twdt(TWDT_UI_INIT_TIMEOUT_MS, 1U << 0);
|
||||
|
||||
// 3. 在 CPU1 上执行 UI 初始化,避免 app_main 长时间占用 CPU0
|
||||
xTaskCreatePinnedToCore(ui_init_task, "ui_init_task", 8192, NULL, 8, NULL, 1);
|
||||
set_var_food_status("良好");
|
||||
@@ -152,16 +681,38 @@ extern "C" void app_main(void)
|
||||
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000)))
|
||||
{
|
||||
set_var_system_ip(wifi_connect_get_ip());
|
||||
|
||||
// 注册 MQTT 数据接收回调
|
||||
agri_env_set_mqtt_cmd_cb(app_mqtt_cmd_handler);
|
||||
esp_err_t err = agri_env_mqtt_start();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "MQTT 启动失败: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 独立 SNTP 对时任务
|
||||
xTaskCreate(sntp_task, "sntp_task", 4096, NULL, 4, NULL);
|
||||
|
||||
// HX711 电子秤:GPIO13(SCK) / GPIO14(DOUT)
|
||||
xTaskCreate(hx711_task, "hx711_task", 4096, NULL, 6, NULL);
|
||||
|
||||
// 初始化继电器 (独立配置每个通道)
|
||||
const relay_config_t relay_cfg[RELAY_CTRL_ID_MAX] = {
|
||||
{.pin = GPIO_NUM_9, .active_high = false},
|
||||
{.pin = GPIO_NUM_10, .active_high = false},
|
||||
{.pin = GPIO_NUM_11, .active_high = true},
|
||||
{.pin = GPIO_NUM_12, .active_high = true},
|
||||
{.pin = GPIO_NUM_11, .active_high = true},
|
||||
{.pin = GPIO_NUM_10, .active_high = true},
|
||||
{.pin = GPIO_NUM_9, .active_high = true},
|
||||
};
|
||||
ESP_ERROR_CHECK(relay_ctrl_init(relay_cfg));
|
||||
set_var_fan_status("关");
|
||||
set_var_light_status("关");
|
||||
set_var_cool_status("关");
|
||||
set_var_hot_status("关");
|
||||
s_env_data.fan_on = false;
|
||||
s_env_data.light_on = false;
|
||||
s_env_data.cool_on = false;
|
||||
s_env_data.hot_on = false;
|
||||
|
||||
// 5. 初始化 I2C 总线并注册传感器 (共享总线)
|
||||
ESP_ERROR_CHECK(bh1750_user_init());
|
||||
@@ -180,6 +731,10 @@ extern "C" void app_main(void)
|
||||
// 火焰传感器使用 ADC(GPIO3)
|
||||
ESP_ERROR_CHECK(fire_sensor_init());
|
||||
|
||||
// SU-03T 语音模块(UART2: RX=IO41 TX=IO42)
|
||||
ESP_ERROR_CHECK(su03t_init());
|
||||
ESP_ERROR_CHECK(su03t_start_receiver(su03t_rx_callback, NULL, 4096, 5));
|
||||
|
||||
// GPIO16: HC-SR312, GPIO17: Door switch(低电平=关门)
|
||||
ESP_ERROR_CHECK(human_door_init());
|
||||
set_var_hum_status("无人");
|
||||
@@ -247,6 +802,13 @@ extern "C" void app_main(void)
|
||||
}
|
||||
|
||||
|
||||
// 提取自动化逻辑所需参数
|
||||
bool auto_mode = false; // 默认手动
|
||||
float th_temp_h = 35.0f;
|
||||
float th_temp_l = 15.0f;
|
||||
float th_hum_h = 70.0f;
|
||||
float th_gas_h = 20.0f;
|
||||
|
||||
// 数据存入共享结构体
|
||||
if (s_env_data_lock) {
|
||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
||||
@@ -259,11 +821,41 @@ extern "C" void app_main(void)
|
||||
if (jw01.tvoc_valid) s_env_data.tvoc = jw01.tvoc;
|
||||
if (jw01.hcho_valid) s_env_data.hcho = jw01.hcho;
|
||||
if (jw01.co2_valid) s_env_data.co2 = jw01.co2;
|
||||
|
||||
auto_mode = s_env_data.auto_mode;
|
||||
th_temp_h = s_env_data.th_temp_h;
|
||||
th_temp_l = s_env_data.th_temp_l;
|
||||
th_hum_h = s_env_data.th_hum_h;
|
||||
th_gas_h = s_env_data.th_gas_h;
|
||||
|
||||
xSemaphoreGive(s_env_data_lock);
|
||||
}
|
||||
|
||||
// ======== 新增:自动联动逻辑 ========
|
||||
if (auto_mode)
|
||||
{
|
||||
// 制冷控制(温度高于上限)
|
||||
if (temp >= th_temp_h) relay_ctrl_set(RELAY_CTRL_ID_3, true);
|
||||
else relay_ctrl_set(RELAY_CTRL_ID_3, false);
|
||||
|
||||
// 制热控制(温度低于下限)
|
||||
if (temp <= th_temp_l) relay_ctrl_set(RELAY_CTRL_ID_4, true);
|
||||
else relay_ctrl_set(RELAY_CTRL_ID_4, false);
|
||||
|
||||
// 风扇控制(湿度或有害气体超标)
|
||||
if (hum >= th_hum_h || gas_percent >= th_gas_h) relay_ctrl_set(RELAY_CTRL_ID_1, true);
|
||||
else relay_ctrl_set(RELAY_CTRL_ID_1, false);
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
} }, "sensor_task", 4096 * 3, (void *)aht30_dev, 6, NULL);
|
||||
|
||||
// 独立任务:人体与门状态检测
|
||||
xTaskCreate(status_task, "status_task", 4096, NULL, 6, NULL);
|
||||
|
||||
// 独立任务:继电器状态同步到 UI
|
||||
xTaskCreate(relay_status_task, "relay_status_task", 4096, NULL, 5, NULL);
|
||||
|
||||
// 独立任务:MQTT 定时发布传感器数据
|
||||
xTaskCreate(mqtt_publish_task, "mqtt_publish_task", 4096 * 2, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
BIN
相关资料/SU-03T技术手册v1.2.pdf
Normal file
BIN
相关资料/SU-03T技术手册v1.2.pdf
Normal file
Binary file not shown.
BIN
相关资料/SU-03T技术手册v1.2.pdf:Zone.Identifier
Normal file
BIN
相关资料/SU-03T技术手册v1.2.pdf:Zone.Identifier
Normal file
Binary file not shown.
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/actions.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_EVENTS_H
|
||||
#define EEZ_LVGL_UI_EVENTS_H
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/eez-flow.cpp
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/* Autogenerated on February 9, 2026 10:19:04 AM from eez-framework commit 123e5b8ddf542fe522cd07c6f6d09fcb044ea1db */
|
||||
/*
|
||||
* eez-framework
|
||||
@@ -9898,4 +9907,4 @@ unsigned getWatchListSize() {
|
||||
return g_watchList.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/eez-flow.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/* Autogenerated on February 9, 2026 10:19:04 AM from eez-framework commit 123e5b8ddf542fe522cd07c6f6d09fcb044ea1db */
|
||||
/*
|
||||
* eez-framework
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/fonts.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_FONTS_H
|
||||
#define EEZ_LVGL_UI_FONTS_H
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/images.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "images.h"
|
||||
|
||||
const ext_img_desc_t images[1] = {
|
||||
{ "main_page", &img_main_page },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/images.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_IMAGES_H
|
||||
#define EEZ_LVGL_UI_IMAGES_H
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/screens.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "screens.h"
|
||||
@@ -25,6 +34,10 @@ lv_obj_t *tick_value_change_obj;
|
||||
// Screens
|
||||
//
|
||||
|
||||
/* 函数: \1
|
||||
* 作用: 执行该模块的具体逻辑实现。
|
||||
* 关注点: 参数有效性、错误返回与并发访问安全。
|
||||
*/
|
||||
void create_screen_main() {
|
||||
void *flowState = getFlowState(0, 0);
|
||||
(void)flowState;
|
||||
@@ -281,6 +294,10 @@ void create_screen_main() {
|
||||
tick_screen_main();
|
||||
}
|
||||
|
||||
/* 函数: \1
|
||||
* 作用: 执行该模块的具体逻辑实现。
|
||||
* 关注点: 参数有效性、错误返回与并发访问安全。
|
||||
*/
|
||||
void tick_screen_main() {
|
||||
void *flowState = getFlowState(0, 0);
|
||||
(void)flowState;
|
||||
@@ -425,9 +442,17 @@ typedef void (*tick_screen_func_t)();
|
||||
tick_screen_func_t tick_screen_funcs[] = {
|
||||
tick_screen_main,
|
||||
};
|
||||
/* 函数: \1
|
||||
* 作用: 执行该模块的具体逻辑实现。
|
||||
* 关注点: 参数有效性、错误返回与并发访问安全。
|
||||
*/
|
||||
void tick_screen(int screen_index) {
|
||||
tick_screen_funcs[screen_index]();
|
||||
}
|
||||
/* 函数: \1
|
||||
* 作用: 执行该模块的具体逻辑实现。
|
||||
* 关注点: 参数有效性、错误返回与并发访问安全。
|
||||
*/
|
||||
void tick_screen_by_id(enum ScreensEnum screenId) {
|
||||
tick_screen_funcs[screenId - 1]();
|
||||
}
|
||||
@@ -511,6 +536,10 @@ ext_font_desc_t fonts[] = {
|
||||
//
|
||||
//
|
||||
|
||||
/* 函数: \1
|
||||
* 作用: 执行该模块的具体逻辑实现。
|
||||
* 关注点: 参数有效性、错误返回与并发访问安全。
|
||||
*/
|
||||
void create_screens() {
|
||||
|
||||
eez_flow_init_fonts(fonts, sizeof(fonts) / sizeof(ext_font_desc_t));
|
||||
@@ -526,4 +555,4 @@ void create_screens() {
|
||||
|
||||
// Create screens
|
||||
create_screen_main();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/screens.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_SCREENS_H
|
||||
#define EEZ_LVGL_UI_SCREENS_H
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/structs.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_STRUCTS_H
|
||||
#define EEZ_LVGL_UI_STRUCTS_H
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/styles.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "styles.h"
|
||||
#include "images.h"
|
||||
#include "fonts.h"
|
||||
|
||||
#include "ui.h"
|
||||
#include "screens.h"
|
||||
#include "screens.h"
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/styles.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_STYLES_H
|
||||
#define EEZ_LVGL_UI_STYLES_H
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#include "ui.h"
|
||||
#include "screens.h"
|
||||
#include "images.h"
|
||||
@@ -169,11 +178,19 @@ ActionExecFunc actions[] = {
|
||||
0
|
||||
};
|
||||
|
||||
/* 函数: \1
|
||||
* 作用: 执行该模块的具体逻辑实现。
|
||||
* 关注点: 参数有效性、错误返回与并发访问安全。
|
||||
*/
|
||||
void ui_init() {
|
||||
eez_flow_init(assets, sizeof(assets), (lv_obj_t **)&objects, sizeof(objects), images, sizeof(images), actions);
|
||||
}
|
||||
|
||||
/* 函数: \1
|
||||
* 作用: 执行该模块的具体逻辑实现。
|
||||
* 关注点: 参数有效性、错误返回与并发访问安全。
|
||||
*/
|
||||
void ui_tick() {
|
||||
eez_flow_tick();
|
||||
tick_screen(g_currentScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_GUI_H
|
||||
#define EEZ_LVGL_UI_GUI_H
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui_font_chinese_16.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 8
|
||||
@@ -3026,4 +3035,4 @@ lv_font_t ui_font_chinese_16 = {
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
#endif /*#if UI_FONT_CHINESE_16*/
|
||||
#endif /*#if UI_FONT_CHINESE_16*/
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui_font_chinese_24.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 24 px
|
||||
* Bpp: 8
|
||||
@@ -3271,4 +3280,4 @@ lv_font_t ui_font_chinese_24 = {
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
#endif /*#if UI_FONT_CHINESE_24*/
|
||||
#endif /*#if UI_FONT_CHINESE_24*/
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui_font_chinese_36.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 36 px
|
||||
* Bpp: 8
|
||||
@@ -6461,4 +6470,4 @@ lv_font_t ui_font_chinese_36 = {
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
#endif /*#if UI_FONT_CHINESE_36*/
|
||||
#endif /*#if UI_FONT_CHINESE_36*/
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui_font_number_14.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 14 px
|
||||
* Bpp: 8
|
||||
@@ -1101,4 +1110,4 @@ lv_font_t ui_font_number_14 = {
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
#endif /*#if UI_FONT_NUMBER_14*/
|
||||
#endif /*#if UI_FONT_NUMBER_14*/
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui_font_number_16.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 8
|
||||
@@ -1289,4 +1298,4 @@ lv_font_t ui_font_number_16 = {
|
||||
.user_data = NULL,
|
||||
};
|
||||
|
||||
#endif /*#if UI_FONT_NUMBER_16*/
|
||||
#endif /*#if UI_FONT_NUMBER_16*/
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/ui_image_main_page.c
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifdef __has_include
|
||||
#if __has_include("lvgl.h")
|
||||
#ifndef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@@ -404,4 +413,4 @@ const lv_image_dsc_t img_main_page = {
|
||||
.data_size = sizeof(img_main_page_map),
|
||||
.data = img_main_page_map,
|
||||
.reserved = NULL,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* 文件: 相关资料/smart-granary/src/ui/vars.h
|
||||
* 角色: 参考 UI 代码(资料目录)
|
||||
* 说明:
|
||||
* - 本文件用于实现当前模块的核心功能或接口定义。
|
||||
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
|
||||
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
|
||||
*/
|
||||
|
||||
#ifndef EEZ_LVGL_UI_VARS_H
|
||||
#define EEZ_LVGL_UI_VARS_H
|
||||
|
||||
|
||||
Reference in New Issue
Block a user