Compare commits

..

10 Commits

Author SHA1 Message Date
Wang Beihong
80de6eb366 删除不必要的文档文件 2026-04-22 22:26:12 +08:00
Wang Beihong
d3f671f3cf 禁用门未关告警状态更新 2026-04-22 20:31:00 +08:00
Wang Beihong
93bc94a0ee 添加门未关超时告警功能,更新状态结构以支持门磁告警和开门时长 2026-04-22 19:54:39 +08:00
Wang Beihong
e446b7515c 添加 MQTT 命令处理功能,支持远程控制模式与阈值配置 2026-04-22 16:49:00 +08:00
Wang Beihong
0d117d9d47 添加 MQTT 定时发布传感器数据功能,支持将数据打包成 JSON 格式并发布 2026-04-22 16:30:02 +08:00
Wang Beihong
811b47d274 添加农业环境模块,集成 MQTT 客户端功能,支持配置参数和数据发布 2026-04-22 01:21:26 +08:00
Wang Beihong
ffdb7065e3 功能:集成SU-03T语音模块,完善UI代码文档
- 在CMakeLists.txt中添加SU-03T语音模块依赖。
- 在main.cpp中实现SU-03T接收回调函数,处理接收消息。
- 完善各UI源文件文档,包括动作、屏幕和字体,明确模块作用与数据流向。
- 更新主应用逻辑,初始化并启动SU-03T接收器。
- 修改过程中确保兼容性,保留原有接口。
2026-04-22 01:06:10 +08:00
Wang Beihong
65de57a49c 添加继电器状态同步功能,更新环境数据结构以支持风扇、灯光、冷却和加热状态 2026-04-21 23:57:27 +08:00
Wang Beihong
0eb428aaa5 添加 HX711 电子秤支持,更新依赖项并集成到主程序中 2026-04-21 22:46:53 +08:00
Wang Beihong
4e829bc56f 添加独立的 SNTP 对时任务,确保在连接 Wi-Fi 后同步时间 2026-04-21 18:59:00 +08:00
53 changed files with 2388 additions and 17 deletions

182
README.md
View File

@@ -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 次
- QoS0
- Retain0
### 3. 上报数据 JSONagri/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 | 传感器原始单位 | TVOCJW01 |
| 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. 下发命令 JSONagri/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 时突出提示用户

View File

@@ -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");

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/JW01/include/JW01.h
* 角色: JW01 气体传感器串口解析与数据提取
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#pragma once
#include <stdbool.h>

View File

@@ -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;

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/MQ-2/include/MQ-2.h
* 角色: MQ-2 模拟气体传感器采样与百分比映射
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#pragma once
#include <stdbool.h>

View 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
)

View 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

View 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);
}

View 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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/bh1750/include/bh1750.h
* 角色: BH1750 光照传感器驱动封装
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/bh1750/include/bh1750_use.h
* 角色: BH1750 光照传感器驱动封装
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef BH1750_USE_H
#define BH1750_USE_H

View File

@@ -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;

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/fire_sensor/include/fire_sensor.h
* 角色: 火焰传感器 ADC 采样与危险判定
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#pragma once
#include <stdbool.h>

View File

@@ -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");

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/human_door/include/human_door.h
* 角色: 人体/门磁输入采集
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#pragma once
#include <stdbool.h>

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/lvgl_st7789_use/include/lvgl_st7789_use.h
* 角色: ST7789 屏幕与 LVGL 适配
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
// SPDX-License-Identifier: MIT
#pragma once

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/relay_ctrl/include/relay_ctrl.h
* 角色: 四路继电器状态控制
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#pragma once
#include "esp_err.h"

View File

@@ -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");

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/sntp_time/include/sntp_time.h
* 角色: SNTP 对时与时间刷新
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#pragma once
#include <stdint.h>

View File

@@ -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) {

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "su-03t.c"
INCLUDE_DIRS "include"
REQUIRES driver)

View 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
View 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;
}

View File

@@ -1,3 +1,12 @@
/*
* 文件: components/wifi-connect/include/wifi-connect.h
* 角色: Wi-Fi 连接、配网与网络状态管理
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#pragma once
#include <stdbool.h>

View File

@@ -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)

View File

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

View File

@@ -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)

View File

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

View File

@@ -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);
}

Binary file not shown.

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/actions.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_EVENTS_H
#define EEZ_LVGL_UI_EVENTS_H

View File

@@ -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;
}
}
}
}

View File

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

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/fonts.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_FONTS_H
#define EEZ_LVGL_UI_FONTS_H

View File

@@ -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 },
};
};

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/images.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_IMAGES_H
#define EEZ_LVGL_UI_IMAGES_H

View File

@@ -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();
}
}

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/screens.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_SCREENS_H
#define EEZ_LVGL_UI_SCREENS_H

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/structs.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_STRUCTS_H
#define EEZ_LVGL_UI_STRUCTS_H

View File

@@ -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"

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/styles.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_STYLES_H
#define EEZ_LVGL_UI_STYLES_H

View File

@@ -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);
}
}

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/ui.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_GUI_H
#define EEZ_LVGL_UI_GUI_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
};
};

View File

@@ -1,3 +1,12 @@
/*
* 文件: 相关资料/smart-granary/src/ui/vars.h
* 角色: 参考 UI 代码(资料目录)
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#ifndef EEZ_LVGL_UI_VARS_H
#define EEZ_LVGL_UI_VARS_H