完成了WIFI连接,MQTT订阅发布,光照监测,温湿度数据,继电器控制
This commit is contained in:
13
.devcontainer/Dockerfile
Normal file
13
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
ARG DOCKER_TAG=latest
|
||||
FROM espressif/idf:${DOCKER_TAG}
|
||||
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
|
||||
RUN apt-get update -y && apt-get install udev -y
|
||||
|
||||
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
|
||||
|
||||
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
|
||||
|
||||
CMD ["/bin/bash", "-c"]
|
||||
19
.devcontainer/devcontainer.json
Normal file
19
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "ESP-IDF QEMU",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"idf.gitPath": "/usr/bin/git"
|
||||
},
|
||||
"extensions": [
|
||||
"espressif.esp-idf-extension",
|
||||
"espressif.esp-idf-web"
|
||||
]
|
||||
}
|
||||
},
|
||||
"runArgs": ["--privileged"]
|
||||
}
|
||||
78
.gitignore
vendored
Normal file
78
.gitignore
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Directory metadata
|
||||
.directory
|
||||
|
||||
# Temporary files
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
*.bak
|
||||
*.tmp
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Build artifacts and directories
|
||||
**/build/
|
||||
build/
|
||||
*.o
|
||||
*.a
|
||||
*.out
|
||||
*.exe # For any host-side utilities compiled on Windows
|
||||
|
||||
# ESP-IDF specific build outputs
|
||||
*.bin
|
||||
*.elf
|
||||
*.map
|
||||
flasher_args.json # Generated in build directory
|
||||
sdkconfig.old
|
||||
sdkconfig
|
||||
|
||||
# ESP-IDF dependencies
|
||||
# For older versions or manual component management
|
||||
/components/.idf/
|
||||
**/components/.idf/
|
||||
# For modern ESP-IDF component manager
|
||||
managed_components/
|
||||
# If ESP-IDF tools are installed/referenced locally to the project
|
||||
.espressif/
|
||||
|
||||
# CMake generated files
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
CTestTestfile.cmake
|
||||
|
||||
# Python environment files
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
__pycache__/
|
||||
*.egg-info/
|
||||
dist/
|
||||
|
||||
# Virtual environment folders
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
|
||||
# Language Servers
|
||||
.clangd/
|
||||
.ccls-cache/
|
||||
compile_commands.json
|
||||
|
||||
# Windows specific
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# User-specific configuration files
|
||||
*.user
|
||||
*.workspace # General workspace files, can be from various tools
|
||||
*.suo # Visual Studio Solution User Options
|
||||
*.sln.docstates # Visual Studio
|
||||
19
.vscode/c_cpp_properties.json
vendored
Normal file
19
.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ESP-IDF",
|
||||
"compilerPath": "/home/beihong/.espressif/tools/riscv32-esp-elf/esp-14.2.0_20251107/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
|
||||
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
10
.vscode/launch.json
vendored
Normal file
10
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "gdbtarget",
|
||||
"request": "attach",
|
||||
"name": "Eclipse CDT GDB Adapter"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.vscode/settings.json
vendored
Normal file
18
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"C_Cpp.intelliSenseEngine": "default",
|
||||
"idf.openOcdConfigs": [
|
||||
"board/esp32c3-builtin.cfg"
|
||||
],
|
||||
"idf.port": "/dev/ttyACM0",
|
||||
"idf.currentSetup": "/home/beihong/esp/v5.5.2/esp-idf",
|
||||
"idf.customExtraVars": {
|
||||
"IDF_TARGET": "esp32c3"
|
||||
},
|
||||
"clangd.path": "/home/beihong/.espressif/tools/esp-clang/esp-19.1.2_20250312/esp-clang/bin/clangd",
|
||||
"clangd.arguments": [
|
||||
"--background-index",
|
||||
"--query-driver=**",
|
||||
"--compile-commands-dir=/home/beihong/esp_projects/AgriEnv_Monitor/build"
|
||||
],
|
||||
"idf.flashType": "UART"
|
||||
}
|
||||
6
CMakeLists.txt
Executable file
6
CMakeLists.txt
Executable file
@@ -0,0 +1,6 @@
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(AgriEnv_Monitor)
|
||||
211
README.md
Normal file
211
README.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# AgriEnv_Monitor
|
||||
|
||||
本项目基于 ESP-IDF,新增了一个统一环境采集与控制组件 `agri_env`,实现以下功能:
|
||||
|
||||
1. DHT11 温湿度采集(DATA -> GPIO10)
|
||||
2. BH1750 光照采集(I2C 地址 0x23,SDA -> GPIO4,SCL -> GPIO5)
|
||||
3. MQ135 数据采集(AO -> GPIO1)
|
||||
4. 双路继电器控制
|
||||
5. 统一数据结构与获取接口
|
||||
6. MQTT 连接(MQTT 3.1 + 用户名密码)
|
||||
7. MQTT 订阅主题与发布主题
|
||||
8. 定时任务采样、打印和上报
|
||||
9. cJSON 数据包构建用于上报
|
||||
|
||||
## 目录说明
|
||||
|
||||
新增组件位于:
|
||||
|
||||
- `components/agri_env/include/agri_env.h`
|
||||
- `components/agri_env/agri_env.c`
|
||||
- `components/agri_env/CMakeLists.txt`
|
||||
|
||||
`main/main.c` 已接入初始化与示例读取逻辑。
|
||||
|
||||
## 硬件接线
|
||||
|
||||
### 1. DHT11
|
||||
|
||||
- VCC -> 3.3V
|
||||
- GND -> GND
|
||||
- DATA -> GPIO10
|
||||
- 推荐 DATA 加 4.7k~10k 上拉电阻到 3.3V
|
||||
|
||||
### 2. BH1750
|
||||
|
||||
- VCC -> 3.3V
|
||||
- GND -> GND
|
||||
- SDA -> GPIO4
|
||||
- SCL -> GPIO5
|
||||
- 地址:0x23(ADDR 引脚悬空或接地)
|
||||
|
||||
### 3. MQ135
|
||||
|
||||
- DO(数字输出)-> GPIO10
|
||||
|
||||
注意:当前工程使用 MQ135 的 DO 数字输出,不做 ADC 采样。
|
||||
|
||||
- 组件直接读取 `mq135_digital_level`(0/1)
|
||||
|
||||
如需模拟量采集,需要单独引入 AO 接线和 ADC 逻辑(当前版本未启用)。
|
||||
|
||||
### 4. 两路继电器
|
||||
|
||||
- 风扇继电器 IN -> GPIO6(高电平有效)
|
||||
- 灯光继电器 IN -> GPIO7(高电平有效)
|
||||
|
||||
## 软件实现概览
|
||||
|
||||
### 1. 统一数据结构
|
||||
|
||||
定义在 `agri_env.h`:
|
||||
|
||||
- `temperature_c`
|
||||
- `humidity_percent`
|
||||
- `illuminance_lux`
|
||||
- `mq135_digital_level`
|
||||
- `timestamp_ms`
|
||||
|
||||
### 2. 传感器读取接口
|
||||
|
||||
- `agri_env_init()`:初始化 I2C、BH1750、MQ135、继电器
|
||||
- `agri_env_read_sensors(agri_env_data_t *out_data)`:一次读取并刷新缓存
|
||||
- `agri_env_get_latest_data(agri_env_data_t *out_data)`:读取最近缓存
|
||||
|
||||
### 3. 继电器控制接口
|
||||
|
||||
- `agri_env_set_fan_relay(bool on)`
|
||||
- `agri_env_set_light_relay(bool on)`
|
||||
|
||||
### 4. MQTT 接口(MQTT 3.1 + 用户名密码)
|
||||
|
||||
- `agri_env_mqtt_start()`
|
||||
- `agri_env_mqtt_stop()`
|
||||
- `agri_env_mqtt_is_connected()`
|
||||
- `agri_env_mqtt_publish_latest(const char *topic, int qos, int retain)`
|
||||
|
||||
组件会在 MQTT 连接成功后自动订阅 `CONFIG_AGRI_ENV_MQTT_SUBSCRIBE_TOPIC`。
|
||||
|
||||
默认订阅主题与发布主题来自 menuconfig:
|
||||
|
||||
- `AGRI_ENV_MQTT_SUBSCRIBE_TOPIC`
|
||||
- `AGRI_ENV_MQTT_PUBLISH_TOPIC`
|
||||
|
||||
订阅到的 JSON 命令支持如下字段:
|
||||
|
||||
- `fan_relay`: `true` / `false`
|
||||
- `light_relay`: `true` / `false`
|
||||
|
||||
### 5. cJSON 数据打包
|
||||
|
||||
- `char *agri_env_build_data_json(const agri_env_data_t *data)`
|
||||
|
||||
说明:返回值是动态分配字符串,使用完后需要 `free()`。
|
||||
|
||||
## menuconfig 配置方法
|
||||
|
||||
在 `idf.py menuconfig` 中打开 `AgriEnv Monitor` 菜单,配置以下项:
|
||||
|
||||
- `AGRI_ENV_SAMPLE_PERIOD_MS`:采样周期
|
||||
- `AGRI_ENV_MQTT_BROKER_URI`:MQTT 服务器地址
|
||||
- `AGRI_ENV_MQTT_USERNAME`:MQTT 用户名
|
||||
- `AGRI_ENV_MQTT_PASSWORD`:MQTT 密码
|
||||
- `AGRI_ENV_MQTT_CLIENT_ID`:客户端 ID
|
||||
- `AGRI_ENV_MQTT_PUBLISH_TOPIC`:发布主题
|
||||
- `AGRI_ENV_MQTT_SUBSCRIBE_TOPIC`:订阅主题
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
AGRI_ENV_SAMPLE_PERIOD_MS=5000
|
||||
AGRI_ENV_MQTT_BROKER_URI=mqtt://192.168.1.100:1883
|
||||
AGRI_ENV_MQTT_USERNAME=your_user
|
||||
AGRI_ENV_MQTT_PASSWORD=your_pass
|
||||
AGRI_ENV_MQTT_CLIENT_ID=agri-env-monitor
|
||||
AGRI_ENV_MQTT_PUBLISH_TOPIC=agri/env/data
|
||||
AGRI_ENV_MQTT_SUBSCRIBE_TOPIC=agri/env/cmd
|
||||
```
|
||||
|
||||
协议版本固定为 `MQTT_PROTOCOL_V_3_1`。
|
||||
|
||||
## 在 main 中的使用示例
|
||||
|
||||
当前 `main/main.c` 已包含如下流程:
|
||||
|
||||
1. 初始化 Wi-Fi 配网:`wifi_connect_init()`
|
||||
2. 初始化农业环境组件:`agri_env_init()`
|
||||
3. 组件内部周期任务自动读取传感器数据并打印
|
||||
4. 若 MQTT 参数已填写,则自动连接并按主题上报
|
||||
|
||||
你也可以继续在业务层调用 `agri_env_read_sensors()` 和 `agri_env_mqtt_publish_latest()` 做自定义上报。
|
||||
|
||||
## 构建与烧录步骤(详细)
|
||||
|
||||
### 1. 进入工程
|
||||
|
||||
```bash
|
||||
cd /home/beihong/esp_projects/AgriEnv_Monitor
|
||||
```
|
||||
|
||||
### 2. 配置目标芯片(如果尚未设置)
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c3
|
||||
```
|
||||
|
||||
### 3. menuconfig(可选)
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
按需配置串口、日志等级、Wi-Fi 配网组件参数。
|
||||
|
||||
### 4. 编译
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
### 5. 烧录
|
||||
|
||||
```bash
|
||||
idf.py -p /dev/ttyUSB0 flash
|
||||
```
|
||||
|
||||
### 6. 查看日志
|
||||
|
||||
```bash
|
||||
idf.py -p /dev/ttyUSB0 monitor
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. DHT11 读取失败
|
||||
|
||||
- 检查 DATA 是否正确接 GPIO10
|
||||
- 确保上拉电阻存在
|
||||
- 线长过长时需降低干扰
|
||||
|
||||
### 2. BH1750 无数据
|
||||
|
||||
- 确认地址是 0x23
|
||||
- 检查 SDA/SCL 是否与 GPIO4/GPIO5 对应
|
||||
- 检查模块供电是否 3.3V
|
||||
|
||||
### 3. MQ135 只有数字量
|
||||
|
||||
- 当前版本设计即为数字量模式,只输出 `mq135_digital_level`
|
||||
- 若 DO 一直不变,检查 MQ135 模块阈值电位器和 DO 接线
|
||||
|
||||
### 4. MQTT 无法连接
|
||||
|
||||
- 确认 `MQTT_BROKER_URI`/用户名/密码已填写
|
||||
- 确认设备已联网
|
||||
- 检查 broker 是否允许 MQTT 3.1
|
||||
|
||||
## 后续建议
|
||||
|
||||
1. 增加 FreeRTOS 周期任务,定时调用 `agri_env_read_sensors()` 与 `agri_env_mqtt_publish_latest()`。
|
||||
2. 将 MQTT 参数改为 Kconfig 配置,避免每次改源码。
|
||||
3. 对 MQ135 增加校准模型(洁净空气基线和 ppm 拟合)。
|
||||
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
|
||||
)
|
||||
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
|
||||
253
components/agri_env/agri_env.c
Normal file
253
components/agri_env/agri_env.c
Normal file
@@ -0,0 +1,253 @@
|
||||
#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 "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;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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_latest(const char *topic, int qos, int retain)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(topic != NULL && topic[0] != '\0', 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 未连接");
|
||||
|
||||
static const char *payload = "{\"mode\":\"mqtt_only\"}";
|
||||
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;
|
||||
}
|
||||
23
components/agri_env/include/agri_env.h
Normal file
23
components/agri_env/include/agri_env.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#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);
|
||||
/* 发布固定 MQTT-only 心跳载荷到指定主题。 */
|
||||
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
3
components/bh1750/CMakeLists.txt
Normal file
3
components/bh1750/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "bh1750.c" "bh1750_use.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "esp_driver_i2c")
|
||||
13
components/bh1750/README.md
Normal file
13
components/bh1750/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 组件:BH1750
|
||||
|
||||
[](https://components.espressif.com/components/espressif/bh1750)
|
||||

|
||||
|
||||
:warning: **BH1750 组件按“现状”提供,不再进行后续开发及兼容性维护**
|
||||
|
||||
* 本组件将向您展示如何使用 I2C 模块读取外部 I2C 传感器数据,此处以 BH1750 光传感器(GY-30 模块)为例。
|
||||
* BH1750 测量模式:
|
||||
* 单次模式:BH1750 仅在接收到单次测量命令时测量一次,因此每次需要获取光照强度值时,都需要发送该命令。
|
||||
* 连续模式:BH1750 在接收到连续测量命令后将持续进行测量,只需发送一次该命令,之后反复调用 `bh1750_get_data()` 即可获取光照强度值。
|
||||
## 注意:
|
||||
* BH1750 在不同测量模式下的测量时间不同。可通过调用 `bh1750_change_measure_time()` 更改测量时间。
|
||||
144
components/bh1750/bh1750.c
Normal file
144
components/bh1750/bh1750.c
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "bh1750.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/projdefs.h" // for pdMS_TO_TICKS
|
||||
|
||||
#define BH_1750_MEASUREMENT_ACCURACY 1.2 /*!< BH1750 传感器的典型测量精度因子 */
|
||||
|
||||
#define BH1750_POWER_DOWN 0x00 /*!< 设置为掉电模式的命令 */
|
||||
#define BH1750_POWER_ON 0x01 /*!< 设置为上电模式的命令 */
|
||||
#define I2C_CLK_SPEED 400000 /*!< I2C 通信时钟频率 (400kHz) */
|
||||
|
||||
/**
|
||||
* @brief BH1750 设备私有结构体
|
||||
*/
|
||||
typedef struct {
|
||||
i2c_master_dev_handle_t i2c_handle; /*!< I2C 主设备句柄 */
|
||||
} bh1750_dev_t;
|
||||
|
||||
/**
|
||||
* @brief 向 BH1750 写入一个字节的辅助函数
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建并注册 BH1750 设备
|
||||
*
|
||||
* @param i2c_bus I2C 总线句柄
|
||||
* @param dev_addr 传感器 I2C 地址 (一般为 0x23 或 0x5C)
|
||||
* @param handle_ret [out] 返回创建好的设备句柄
|
||||
* @return esp_err_t 成功返回 ESP_OK
|
||||
*/
|
||||
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;
|
||||
bh1750_dev_t *sensor = (bh1750_dev_t *) calloc(1, sizeof(bh1750_dev_t));
|
||||
if (!sensor) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// 配置并添加新的 I2C 设备到总线
|
||||
const i2c_device_config_t i2c_dev_cfg = {
|
||||
.device_address = dev_addr,
|
||||
.scl_speed_hz = I2C_CLK_SPEED,
|
||||
};
|
||||
ret = i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg, &sensor->i2c_handle);
|
||||
if (ret != ESP_OK) {
|
||||
free(sensor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
assert(sensor->i2c_handle);
|
||||
*handle_ret = sensor;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 删除 BH1750 设备并释放资源
|
||||
*/
|
||||
esp_err_t bh1750_delete(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
if (sens->i2c_handle) {
|
||||
i2c_master_bus_rm_device(sens->i2c_handle);
|
||||
}
|
||||
free(sens);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 进入掉电模式(低功耗)
|
||||
*/
|
||||
esp_err_t bh1750_power_down(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
return bh1750_write_byte(sens, BH1750_POWER_DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 唤醒并进入上电模式
|
||||
*/
|
||||
esp_err_t bh1750_power_on(bh1750_handle_t sensor)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
return bh1750_write_byte(sens, BH1750_POWER_ON);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置测量时间倍率 (MTreg)
|
||||
* 用于改变传感器的测量灵敏度
|
||||
*/
|
||||
esp_err_t bh1750_set_measure_time(bh1750_handle_t sensor, const uint8_t measure_time)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
uint32_t i = 0;
|
||||
uint8_t buf[2] = {0x40, 0x60}; // MTreg 常量部分
|
||||
buf[0] |= measure_time >> 5;
|
||||
buf[1] |= measure_time & 0x1F;
|
||||
for (i = 0; i < 2; i++) {
|
||||
esp_err_t ret = bh1750_write_byte(sens, buf[i]);
|
||||
if (ESP_OK != ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置测量模式(连续测量或单词测量,以及分辨率选择)
|
||||
*/
|
||||
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;
|
||||
return bh1750_write_byte(sens, (uint8_t)cmd_measure);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取测量结果
|
||||
*
|
||||
* @param sensor 设备句柄
|
||||
* @param data [out] 返回转换后的光照强度值 (单位: Lux)
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t bh1750_get_data(bh1750_handle_t sensor, float *const data)
|
||||
{
|
||||
bh1750_dev_t *sens = (bh1750_dev_t *) sensor;
|
||||
uint8_t read_buffer[2];
|
||||
// 从 I2C 读取 2 字节原始数据
|
||||
esp_err_t ret = i2c_master_receive(sens->i2c_handle, read_buffer, sizeof(read_buffer), pdMS_TO_TICKS(1000));
|
||||
if (ESP_OK != ret) {
|
||||
return ret;
|
||||
}
|
||||
// 将原始数据转换为 Lux (公式: (高8位 << 8 | 低8位) / 1.2)
|
||||
*data = (( read_buffer[0] << 8 | read_buffer[1] ) / BH_1750_MEASUREMENT_ACCURACY);
|
||||
return ESP_OK;
|
||||
}
|
||||
131
components/bh1750/bh1750_use.c
Normal file
131
components/bh1750/bh1750_use.c
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "bh1750.h"
|
||||
#include "bh1750_use.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "BH1750_USE";
|
||||
|
||||
#define BH1750_READ_RETRY_COUNT 3
|
||||
#define BH1750_MEASURE_DELAY_MS 200
|
||||
#define BH1750_RETRY_INTERVAL_MS 30
|
||||
|
||||
static i2c_master_bus_handle_t s_i2c_bus_handle = NULL;
|
||||
static bh1750_handle_t s_bh1750_handle = NULL;
|
||||
|
||||
/**
|
||||
* @brief 初始化 BH1750 传感器及其所需的 I2C 总线
|
||||
*/
|
||||
esp_err_t bh1750_user_init(void)
|
||||
{
|
||||
// 1. 配置并初始化 I2C 总线 (Master Bus)
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.scl_io_num = BH1750_I2C_SCL_IO,
|
||||
.sda_io_num = BH1750_I2C_SDA_IO,
|
||||
.glitch_ignore_cnt = 7,
|
||||
};
|
||||
|
||||
esp_err_t ret = i2c_new_master_bus(&bus_config, &s_i2c_bus_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "I2C 总线初始化失败: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 2. 创建 BH1750 设备句柄 (使用驱动默认地址 0x23)
|
||||
ret = bh1750_create(s_i2c_bus_handle, BH1750_I2C_ADDRESS_DEFAULT, &s_bh1750_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "BH1750 设备创建失败: %s", esp_err_to_name(ret));
|
||||
if (s_i2c_bus_handle) {
|
||||
i2c_del_master_bus(s_i2c_bus_handle);
|
||||
s_i2c_bus_handle = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 3. 初始上电
|
||||
ret = bh1750_power_on(s_bh1750_handle);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "BH1750 初始化成功");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取一次光照强度数据 (Lux)
|
||||
*/
|
||||
esp_err_t bh1750_user_read(float *lux)
|
||||
{
|
||||
if (lux == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (s_bh1750_handle == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
for (int attempt = 1; attempt <= BH1750_READ_RETRY_COUNT; ++attempt) {
|
||||
// 单次模式每次读取前都先上电,避免传感器处于掉电状态导致返回 0
|
||||
ret = bh1750_power_on(s_bh1750_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "上电失败(第%d次): %s", attempt, esp_err_to_name(ret));
|
||||
vTaskDelay(pdMS_TO_TICKS(BH1750_RETRY_INTERVAL_MS));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 设置测量模式:单次高分辨率模式 (1lx)
|
||||
ret = bh1750_set_measure_mode(s_bh1750_handle, BH1750_ONETIME_1LX_RES);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "设置测量模式失败(第%d次): %s", attempt, esp_err_to_name(ret));
|
||||
vTaskDelay(pdMS_TO_TICKS(BH1750_RETRY_INTERVAL_MS));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据数据手册,单次高分辨率模式需要约 120ms-180ms 测量时间
|
||||
vTaskDelay(pdMS_TO_TICKS(BH1750_MEASURE_DELAY_MS));
|
||||
|
||||
ret = bh1750_get_data(s_bh1750_handle, lux);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "数据读取失败(第%d次): %s", attempt, esp_err_to_name(ret));
|
||||
vTaskDelay(pdMS_TO_TICKS(BH1750_RETRY_INTERVAL_MS));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 在强光/室内环境下长期 0 Lux 通常不合理,重试一次可规避偶发总线抖动
|
||||
if (*lux <= 0.0f && attempt < BH1750_READ_RETRY_COUNT) {
|
||||
ESP_LOGW(TAG, "读取到 0 Lux,准备重试(第%d次)", attempt);
|
||||
vTaskDelay(pdMS_TO_TICKS(BH1750_RETRY_INTERVAL_MS));
|
||||
continue;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (ret == ESP_OK && *lux <= 0.0f) {
|
||||
ESP_LOGW(TAG, "连续读取均为 0 Lux,请优先检查 I2C 上拉电阻和传感器供电");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放 BH1750 相关资源
|
||||
*/
|
||||
void bh1750_user_deinit(void)
|
||||
{
|
||||
if (s_bh1750_handle) {
|
||||
bh1750_delete(s_bh1750_handle);
|
||||
s_bh1750_handle = NULL;
|
||||
}
|
||||
if (s_i2c_bus_handle) {
|
||||
i2c_del_master_bus(s_i2c_bus_handle);
|
||||
s_i2c_bus_handle = NULL;
|
||||
}
|
||||
ESP_LOGI(TAG, "资源已释放");
|
||||
}
|
||||
136
components/bh1750/include/bh1750.h
Normal file
136
components/bh1750/include/bh1750.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief BH1750 driver
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "driver/i2c_types.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
typedef enum {
|
||||
BH1750_CONTINUE_1LX_RES = 0x10, /*!< Command to set measure mode as Continuously H-Resolution mode*/
|
||||
BH1750_CONTINUE_HALFLX_RES = 0x11, /*!< Command to set measure mode as Continuously H-Resolution mode2*/
|
||||
BH1750_CONTINUE_4LX_RES = 0x13, /*!< Command to set measure mode as Continuously L-Resolution mode*/
|
||||
BH1750_ONETIME_1LX_RES = 0x20, /*!< Command to set measure mode as One Time H-Resolution mode*/
|
||||
BH1750_ONETIME_HALFLX_RES = 0x21, /*!< Command to set measure mode as One Time H-Resolution mode2*/
|
||||
BH1750_ONETIME_4LX_RES = 0x23, /*!< Command to set measure mode as One Time L-Resolution mode*/
|
||||
} bh1750_measure_mode_t;
|
||||
|
||||
#define BH1750_I2C_ADDRESS_DEFAULT (0x23)
|
||||
typedef void *bh1750_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Set bh1750 as power down mode (low current)
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_power_down(bh1750_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Set bh1750 as power on mode
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_power_on(bh1750_handle_t sensor);
|
||||
|
||||
/**
|
||||
* @brief Get light intensity from bh1750
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
* @param[in] cmd_measure the instruction to set measurement mode
|
||||
*
|
||||
* @note
|
||||
* You should call this funtion to set measurement mode before call bh1750_get_data() to acquire data.
|
||||
* If you set onetime mode, you just can get one measurement result.
|
||||
* If you set continuous mode, you can call bh1750_get_data() to acquire data repeatedly.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_set_measure_mode(bh1750_handle_t sensor, const bh1750_measure_mode_t cmd_measure);
|
||||
|
||||
/**
|
||||
* @brief Get light intensity from BH1750
|
||||
*
|
||||
* Returns light intensity in [lx] corrected by typical BH1750 Measurement Accuracy (= 1.2).
|
||||
*
|
||||
* @see BH1750 datasheet Rev. D page 2
|
||||
*
|
||||
* @note
|
||||
* You should acquire data from the sensor after the measurement time is over,
|
||||
* so take care of measurement time in different modes.
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
* @param[out] data light intensity value got from bh1750 in [lx]
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_get_data(bh1750_handle_t sensor, float *const data);
|
||||
|
||||
/**
|
||||
* @brief Set measurement time
|
||||
*
|
||||
* This function is used to adjust BH1750 sensitivity, i.e. compensating influence from optical window.
|
||||
*
|
||||
* @see BH1750 datasheet Rev. D page 11
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
* @param[in] measure_time measurement time
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_set_measure_time(bh1750_handle_t sensor, const uint8_t measure_time);
|
||||
|
||||
/**
|
||||
* @brief Create and init sensor object and return a sensor handle
|
||||
*
|
||||
* @param[in] i2c_bus I2C bus handle. Obtained from i2c_new_master_bus().s
|
||||
* @param[in] dev_addr I2C device address of sensor. Use BH1750_I2C_ADDRESS_DEFAULT for default address.
|
||||
* @param[out] handle_ret Handle to created BH1750 driver object.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Not enough memory for the driver
|
||||
* - ESP_ERR_NOT_FOUND Sensor not found on the I2C bus
|
||||
* - Others Error from underlying I2C driver
|
||||
*/
|
||||
esp_err_t bh1750_create(i2c_master_bus_handle_t i2c_bus, const uint8_t dev_addr, bh1750_handle_t *handle_ret);
|
||||
|
||||
/**
|
||||
* @brief Delete and release a sensor object
|
||||
*
|
||||
* @param sensor object handle of bh1750
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_FAIL Fail
|
||||
*/
|
||||
esp_err_t bh1750_delete(bh1750_handle_t sensor);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
38
components/bh1750/include/bh1750_use.h
Normal file
38
components/bh1750/include/bh1750_use.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef BH1750_USE_H
|
||||
#define BH1750_USE_H
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 定义使用的 I2C 引脚(根据你的硬件实际连接修改)
|
||||
#define BH1750_I2C_SCL_IO 5 // ESP32-C3 建议引脚
|
||||
#define BH1750_I2C_SDA_IO 4 // ESP32-C3 建议引脚
|
||||
|
||||
/**
|
||||
* @brief 初始化 BH1750 传感器及其所需的 I2C 总线
|
||||
*
|
||||
* @return esp_err_t 成功返回 ESP_OK
|
||||
*/
|
||||
esp_err_t bh1750_user_init(void);
|
||||
|
||||
/**
|
||||
* @brief 读取一次光照强度数据 (Lux)
|
||||
*
|
||||
* @param lux [out] 存储结果的指针
|
||||
* @return esp_err_t 成功返回 ESP_OK
|
||||
*/
|
||||
esp_err_t bh1750_user_read(float *lux);
|
||||
|
||||
/**
|
||||
* @brief 释放 BH1750 相关资源
|
||||
*/
|
||||
void bh1750_user_deinit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // BH1750_USE_H
|
||||
3
components/relay_ctrl/CMakeLists.txt
Normal file
3
components/relay_ctrl/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "relay_ctrl.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_driver_gpio)
|
||||
55
components/relay_ctrl/include/relay_ctrl.h
Normal file
55
components/relay_ctrl/include/relay_ctrl.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
RELAY_CTRL_ID_1 = 0,
|
||||
RELAY_CTRL_ID_2 = 1,
|
||||
RELAY_CTRL_ID_MAX,
|
||||
} relay_ctrl_id_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化双继电器控制模块。
|
||||
*
|
||||
* @param relay1_gpio 继电器1控制引脚
|
||||
* @param relay2_gpio 继电器2控制引脚
|
||||
* @param active_high 继电器有效电平,true=高电平吸合,false=低电平吸合
|
||||
*/
|
||||
esp_err_t relay_ctrl_init(gpio_num_t relay1_gpio, gpio_num_t relay2_gpio, bool active_high);
|
||||
|
||||
/**
|
||||
* @brief 设置指定继电器状态。
|
||||
*
|
||||
* @param relay_id 继电器编号
|
||||
* @param on true=吸合,false=断开
|
||||
*/
|
||||
esp_err_t relay_ctrl_set(relay_ctrl_id_t relay_id, bool on);
|
||||
|
||||
/**
|
||||
* @brief 翻转指定继电器状态。
|
||||
*/
|
||||
esp_err_t relay_ctrl_toggle(relay_ctrl_id_t relay_id);
|
||||
|
||||
/**
|
||||
* @brief 获取指定继电器状态。
|
||||
*
|
||||
* @param relay_id 继电器编号
|
||||
* @param on_out [out] 当前逻辑状态,true=吸合,false=断开
|
||||
*/
|
||||
esp_err_t relay_ctrl_get(relay_ctrl_id_t relay_id, bool *on_out);
|
||||
|
||||
/**
|
||||
* @brief 一次性设置两个继电器状态。
|
||||
*/
|
||||
esp_err_t relay_ctrl_set_all(bool relay1_on, bool relay2_on);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
102
components/relay_ctrl/relay_ctrl.c
Normal file
102
components/relay_ctrl/relay_ctrl.c
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "relay_ctrl.h"
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "relay_ctrl";
|
||||
|
||||
typedef struct {
|
||||
bool inited;
|
||||
bool active_high;
|
||||
gpio_num_t pins[RELAY_CTRL_ID_MAX];
|
||||
bool states[RELAY_CTRL_ID_MAX];
|
||||
} relay_ctx_t;
|
||||
|
||||
static relay_ctx_t s_ctx;
|
||||
|
||||
static inline int relay_level_from_state(bool on)
|
||||
{
|
||||
return (on == s_ctx.active_high) ? 1 : 0;
|
||||
}
|
||||
|
||||
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,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid relay id");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_ctrl_init(gpio_num_t relay1_gpio, gpio_num_t relay2_gpio, bool active_high)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(relay1_gpio), ESP_ERR_INVALID_ARG, TAG, "relay1 gpio invalid");
|
||||
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(relay2_gpio), ESP_ERR_INVALID_ARG, TAG, "relay2 gpio invalid");
|
||||
|
||||
s_ctx.active_high = active_high;
|
||||
s_ctx.pins[RELAY_CTRL_ID_1] = relay1_gpio;
|
||||
s_ctx.pins[RELAY_CTRL_ID_2] = relay2_gpio;
|
||||
|
||||
const gpio_config_t io_cfg = {
|
||||
.pin_bit_mask = (1ULL << relay1_gpio) | (1ULL << relay2_gpio),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(gpio_config(&io_cfg), TAG, "relay gpio config failed");
|
||||
|
||||
// 默认上电全部断开
|
||||
s_ctx.states[RELAY_CTRL_ID_1] = false;
|
||||
s_ctx.states[RELAY_CTRL_ID_2] = false;
|
||||
ESP_RETURN_ON_ERROR(gpio_set_level(relay1_gpio, relay_level_from_state(false)), TAG, "relay1 set init level failed");
|
||||
ESP_RETURN_ON_ERROR(gpio_set_level(relay2_gpio, relay_level_from_state(false)), TAG, "relay2 set init level failed");
|
||||
|
||||
s_ctx.inited = true;
|
||||
ESP_LOGI(TAG, "继电器初始化完成: relay1=GPIO%d relay2=GPIO%d active_high=%d",
|
||||
relay1_gpio,
|
||||
relay2_gpio,
|
||||
active_high);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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");
|
||||
ESP_RETURN_ON_ERROR(relay_validate_id(relay_id), TAG, "invalid relay id");
|
||||
|
||||
ESP_RETURN_ON_ERROR(gpio_set_level(s_ctx.pins[relay_id], relay_level_from_state(on)),
|
||||
TAG,
|
||||
"relay set level failed");
|
||||
s_ctx.states[relay_id] = on;
|
||||
ESP_LOGI(TAG, "继电器%d -> %s (GPIO%d level=%d)",
|
||||
relay_id + 1,
|
||||
on ? "ON" : "OFF",
|
||||
s_ctx.pins[relay_id],
|
||||
relay_level_from_state(on));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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");
|
||||
ESP_RETURN_ON_ERROR(relay_validate_id(relay_id), TAG, "invalid relay id");
|
||||
|
||||
return relay_ctrl_set(relay_id, !s_ctx.states[relay_id]);
|
||||
}
|
||||
|
||||
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");
|
||||
ESP_RETURN_ON_FALSE(on_out != NULL, ESP_ERR_INVALID_ARG, TAG, "on_out is null");
|
||||
ESP_RETURN_ON_ERROR(relay_validate_id(relay_id), TAG, "invalid relay id");
|
||||
|
||||
*on_out = s_ctx.states[relay_id];
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t relay_ctrl_set_all(bool relay1_on, bool relay2_on)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_ctx.inited, ESP_ERR_INVALID_STATE, TAG, "relay not initialized");
|
||||
ESP_RETURN_ON_ERROR(relay_ctrl_set(RELAY_CTRL_ID_1, relay1_on), TAG, "set relay1 failed");
|
||||
ESP_RETURN_ON_ERROR(relay_ctrl_set(RELAY_CTRL_ID_2, relay2_on), TAG, "set relay2 failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
5
components/wifi-connect/CMakeLists.txt
Normal file
5
components/wifi-connect/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "wifi-connect.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_wifi esp_timer esp_event esp_netif nvs_flash esp_http_server lwip driver
|
||||
)
|
||||
18
components/wifi-connect/Kconfig.projbuild
Normal file
18
components/wifi-connect/Kconfig.projbuild
Normal file
@@ -0,0 +1,18 @@
|
||||
menu "WiFi 连接"
|
||||
|
||||
config WIFI_CONNECT_CONNECT_TIMEOUT_SEC
|
||||
int "Wi-Fi 连接超时 (秒)"
|
||||
range 5 180
|
||||
default 30
|
||||
|
||||
config WIFI_CONNECT_MAX_SCAN_RESULTS
|
||||
int "最大扫描结果数"
|
||||
range 5 50
|
||||
default 20
|
||||
|
||||
config WIFI_CONNECT_AP_MAX_CONNECTIONS
|
||||
int "软AP最大连接数"
|
||||
range 1 10
|
||||
default 4
|
||||
|
||||
endmenu
|
||||
165
components/wifi-connect/README.md
Normal file
165
components/wifi-connect/README.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# wifi-connect 组件说明
|
||||
|
||||
`wifi-connect` 是一个基于 ESP-IDF 的 Wi-Fi 配网组件,支持:
|
||||
|
||||
- 长按按键进入配网模式
|
||||
- 启动 SoftAP + Captive Portal(网页配网)
|
||||
- 手机连接热点后,通过网页扫描并选择路由器
|
||||
- 保存 Wi-Fi 凭据到 NVS
|
||||
- 下次开机自动重连
|
||||
- 支持两种配网模式:按键触发 / 常驻配网
|
||||
|
||||
面向最终用户的一页版操作说明见:`USER_GUIDE.md`
|
||||
现场打印张贴版(四步卡)见:`QUICK_POSTER.md`
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
- `wifi-connect.c`:组件主实现(按键、APSTA、HTTP、DNS、状态机)
|
||||
- `include/wifi-connect.h`:对外 API
|
||||
- `Kconfig.projbuild`:组件配置项
|
||||
- `CMakeLists.txt`:组件构建依赖
|
||||
|
||||
---
|
||||
|
||||
## 对外 API
|
||||
|
||||
头文件:`include/wifi-connect.h`
|
||||
|
||||
- `esp_err_t wifi_connect_init(void);`
|
||||
- 初始化组件(NVS、Wi-Fi、事件、按键任务等)
|
||||
- 尝试自动连接已保存网络
|
||||
|
||||
- `esp_err_t wifi_connect_start(void);`
|
||||
- 启动配网(APSTA + HTTP + DNS)
|
||||
|
||||
- `esp_err_t wifi_connect_stop(void);`
|
||||
- 停止配网(关闭热点与相关服务)
|
||||
|
||||
- `wifi_connect_status_t wifi_connect_get_status(void);`
|
||||
- 获取当前状态:`idle / provisioning / connecting / connected / failed / timeout`
|
||||
|
||||
- `esp_err_t wifi_connect_get_config(wifi_connect_config_t *config);`
|
||||
- 读取已保存的 Wi-Fi 凭据
|
||||
|
||||
- `esp_err_t wifi_connect_clear_config(void);`
|
||||
- 清除已保存的 Wi-Fi 凭据(SSID/密码)
|
||||
|
||||
---
|
||||
|
||||
## 快速使用
|
||||
|
||||
在 `main/main.c`:
|
||||
|
||||
```c
|
||||
#include "esp_check.h"
|
||||
#include "wifi-connect.h"
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(wifi_connect_init());
|
||||
}
|
||||
```
|
||||
|
||||
运行后:
|
||||
|
||||
1. 选择配网模式:
|
||||
- 按键触发模式:长按配置按键进入配网
|
||||
- 常驻配网模式:上电自动进入配网
|
||||
2. 手机连接 `ESP32-xxxxxx` 热点
|
||||
3. 打开 `http://192.168.4.1`
|
||||
4. 选择 Wi-Fi 并输入密码提交
|
||||
5. 配网行为:
|
||||
- 按键触发模式:连接成功后按配置自动关闭热点
|
||||
- 常驻配网模式:配网热点保持开启,不自动关闭
|
||||
|
||||
如需清空历史凭据,可在配网页面点击“清除已保存”。
|
||||
|
||||
---
|
||||
|
||||
## Kconfig 配置项
|
||||
|
||||
在 `idf.py menuconfig` 中:`WiFi Connect` 菜单
|
||||
|
||||
- `Provisioning mode`:配网模式(二选一)
|
||||
- `Button triggered`:按键触发配网(默认)
|
||||
- `Always-on provisioning`:常驻配网(上电自动进入且不自动关闭)
|
||||
|
||||
- `WIFI_CONNECT_BUTTON_GPIO`:进入配网的按键 GPIO
|
||||
- `WIFI_CONNECT_BUTTON_ACTIVE_LEVEL`:按键有效电平
|
||||
- `WIFI_CONNECT_DEBOUNCE_MS`:按键去抖时间
|
||||
- `WIFI_CONNECT_LONG_PRESS_MS`:长按触发时长
|
||||
- `WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS`:上电保护窗口(该时间内忽略长按检测)
|
||||
- `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`:松手解锁时间(先稳定松手再允许长按触发)
|
||||
- `WIFI_CONNECT_CONNECT_TIMEOUT_SEC`:连接路由器超时
|
||||
- `WIFI_CONNECT_IDLE_TIMEOUT_SEC`:配网页面空闲超时
|
||||
- `WIFI_CONNECT_MAX_SCAN_RESULTS`:扫描网络最大数量
|
||||
- `WIFI_CONNECT_AP_MAX_CONNECTIONS`:SoftAP 最大连接数
|
||||
- `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC`:联网成功后 AP 延迟关闭秒数
|
||||
|
||||
---
|
||||
|
||||
## 日志与状态说明(中文)
|
||||
|
||||
组件会输出统一中文状态日志,例如:
|
||||
|
||||
- `【状态】wifi-connect 初始化完成`
|
||||
- `【状态】检测到按键长按:开始进入配网模式`
|
||||
- `【状态】配网已启动:配网热点已开启,SSID=...`
|
||||
- `【状态】开始连接路由器:收到配网请求,目标网络=...`
|
||||
- `【状态】联网成功:已连接 ...,获取 IP=...`
|
||||
- `【状态】配网已停止:热点已关闭,设备继续以 STA 模式运行`
|
||||
|
||||
说明:ESP-IDF 驱动层(如 `wifi:`、`esp_netif_lwip:`)仍会输出英文日志,这是框架默认行为。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1) 手机连上热点但不自动弹出页面
|
||||
|
||||
- 手动访问:`http://192.168.4.1`
|
||||
- 确认手机没有强制使用 HTTPS
|
||||
- 查看串口是否有 `配网已启动`、`DNS 劫持服务已启动` 日志
|
||||
|
||||
### 2) 提交后连接失败
|
||||
|
||||
- 检查密码是否正确
|
||||
- 查看日志中的失败原因码(`连接失败,原因=...`)
|
||||
- 检查路由器是否禁用了新设备接入
|
||||
- 若曾保存过旧配置,可先在页面点击“清除已保存”后再重试
|
||||
|
||||
### 4) 按键未按下却误触发配网
|
||||
|
||||
- 常见原因是按键引脚与 LCD/外设复用,初始化期间电平抖动被误判为长按
|
||||
- 可增大 `WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS`(如 8000~10000)
|
||||
- 可增大 `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(如 300~500)
|
||||
- 若硬件允许,优先给配网按键使用独立 GPIO
|
||||
|
||||
### 5) 成功后热点消失是否正常
|
||||
|
||||
- 在按键触发模式下:正常,可通过 `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC` 调整关闭延时
|
||||
- 在常驻配网模式下:热点不会自动关闭
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
由 `CMakeLists.txt` 声明:
|
||||
|
||||
- `esp_wifi`
|
||||
- `esp_timer`
|
||||
- `esp_event`
|
||||
- `esp_netif`
|
||||
- `nvs_flash`
|
||||
- `esp_http_server`
|
||||
- `lwip`
|
||||
- `driver`
|
||||
|
||||
---
|
||||
|
||||
## 版本建议
|
||||
|
||||
- 推荐 ESP-IDF `v5.5.x`
|
||||
- 当前项目验证环境:`esp-idf v5.5.2`(ESP32-C3)
|
||||
34
components/wifi-connect/include/wifi-connect.h
Normal file
34
components/wifi-connect/include/wifi-connect.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
WIFI_CONNECT_STATUS_IDLE = 0,
|
||||
WIFI_CONNECT_STATUS_PROVISIONING,
|
||||
WIFI_CONNECT_STATUS_CONNECTING,
|
||||
WIFI_CONNECT_STATUS_CONNECTED,
|
||||
WIFI_CONNECT_STATUS_FAILED,
|
||||
WIFI_CONNECT_STATUS_TIMEOUT,
|
||||
} wifi_connect_status_t;
|
||||
|
||||
typedef struct {
|
||||
bool has_config;
|
||||
char ssid[33];
|
||||
char password[65];
|
||||
} wifi_connect_config_t;
|
||||
|
||||
esp_err_t wifi_connect_init(void);
|
||||
esp_err_t wifi_connect_start(void);
|
||||
esp_err_t wifi_connect_stop(void);
|
||||
wifi_connect_status_t wifi_connect_get_status(void);
|
||||
esp_err_t wifi_connect_get_config(wifi_connect_config_t *config);
|
||||
esp_err_t wifi_connect_clear_config(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1028
components/wifi-connect/wifi-connect.c
Normal file
1028
components/wifi-connect/wifi-connect.c
Normal file
File diff suppressed because it is too large
Load Diff
73
dependencies.lock
Normal file
73
dependencies.lock
Normal file
@@ -0,0 +1,73 @@
|
||||
dependencies:
|
||||
esp-idf-lib/dht:
|
||||
component_hash: a0a1b26519566809041bc8fccf754e3ee9b25f73b0decbdc51bfd16143dedd66
|
||||
dependencies:
|
||||
- name: esp-idf-lib/esp_idf_lib_helpers
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '*'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
targets:
|
||||
- esp32
|
||||
- esp32c2
|
||||
- esp32c3
|
||||
- esp32c5
|
||||
- esp32c6
|
||||
- esp32c61
|
||||
- esp32h2
|
||||
- esp32p4
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
version: 1.2.0
|
||||
esp-idf-lib/esp_idf_lib_helpers:
|
||||
component_hash: 689853bb8993434f9556af0f2816e808bf77b5d22100144b21f3519993daf237
|
||||
dependencies: []
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32
|
||||
- esp32c2
|
||||
- esp32c3
|
||||
- esp32c5
|
||||
- esp32c6
|
||||
- esp32c61
|
||||
- esp32h2
|
||||
- esp32p4
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
version: 1.4.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/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
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.5.2
|
||||
direct_dependencies:
|
||||
- esp-idf-lib/dht
|
||||
- espressif/cjson
|
||||
- espressif/mqtt
|
||||
- idf
|
||||
manifest_hash: 5e6fbc0e2a31ade2b187deb633c2459efe2f2401bfa17c732a0aa99c7846a05e
|
||||
target: esp32c3
|
||||
version: 2.0.0
|
||||
3
main/CMakeLists.txt
Executable file
3
main/CMakeLists.txt
Executable file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES nvs_flash esp_wifi esp_event esp_system wifi-connect agri_env bh1750 dht relay_ctrl )
|
||||
31
main/Kconfig.projbuild
Normal file
31
main/Kconfig.projbuild
Normal file
@@ -0,0 +1,31 @@
|
||||
menu "Example configuration"
|
||||
choice EXAMPLE_CHIP_TYPE
|
||||
prompt "Select chip type"
|
||||
default EXAMPLE_TYPE_AM2301
|
||||
|
||||
config EXAMPLE_TYPE_DHT11
|
||||
bool "DHT11"
|
||||
config EXAMPLE_TYPE_AM2301
|
||||
bool "DHT21/DHT22/AM2301/AM2302/AM2321"
|
||||
config EXAMPLE_TYPE_SI7021
|
||||
bool "Itead Si7021"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_DATA_GPIO
|
||||
int "Data GPIO number"
|
||||
default 4 if IDF_TARGET_ESP8266
|
||||
default 4 if IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32C61
|
||||
default 4 if IDF_TARGET_ESP32H2
|
||||
default 4 if IDF_TARGET_ESP32P4
|
||||
default 17 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
help
|
||||
GPIO number connected to DATA pin
|
||||
|
||||
config EXAMPLE_INTERNAL_PULLUP
|
||||
bool "Enable internal pull-up resistor"
|
||||
default 0
|
||||
help
|
||||
Check this option if you don't have external pull-up resistor on data GPIO.
|
||||
DHT sensors that come mounted on a PCB generally have pull-up resistors on the data pin.
|
||||
But for stable operation, it is recommended to provide an external pull-up resistor.
|
||||
endmenu
|
||||
20
main/idf_component.yml
Normal file
20
main/idf_component.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: '>=4.1.0'
|
||||
# # Put list of dependencies here
|
||||
# # For components maintained by Espressif:
|
||||
# component: "~1.0.0"
|
||||
# # For 3rd party components:
|
||||
# username/component: ">=1.0.0,<2.0.0"
|
||||
# username2/component2:
|
||||
# version: "~1.0.0"
|
||||
# # For transient dependencies `public` flag can be set.
|
||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
||||
# # All dependencies of `main` are public by default.
|
||||
# public: true
|
||||
esp-idf-lib/dht: ^1.2.0
|
||||
espressif/mqtt: ^1.0.0
|
||||
espressif/cjson: ^1.7.19~2
|
||||
|
||||
146
main/main.c
Executable file
146
main/main.c
Executable file
@@ -0,0 +1,146 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "wifi-connect.h" // 包含 Wi-Fi 配网模块头文件(提供 Wi-Fi 连接状态查询和初始化接口)
|
||||
#include "agri_env.h" // 包含农业环境模块头文件(提供 MQTT 功能接口)
|
||||
#include "bh1750_use.h" // 包含 BH1750 封装接口
|
||||
#include <dht.h>
|
||||
#include "driver/gpio.h"
|
||||
#include "relay_ctrl.h" // 包含继电器控制模块头文件(提供继电器控制接口)
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
// 等待 Wi-Fi 连接成功,超时后返回当前连接状态
|
||||
static bool wait_for_wifi_connected(TickType_t timeout_ticks)
|
||||
{
|
||||
const TickType_t start_ticks = xTaskGetTickCount();
|
||||
while ((xTaskGetTickCount() - start_ticks) < timeout_ticks)
|
||||
{
|
||||
if (wifi_connect_get_status() == WIFI_CONNECT_STATUS_CONNECTED)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
return wifi_connect_get_status() == WIFI_CONNECT_STATUS_CONNECTED;
|
||||
}
|
||||
|
||||
// 根据 menuconfig 中的选择定义传感器类型常量
|
||||
#if defined(CONFIG_EXAMPLE_TYPE_DHT11)
|
||||
#define SENSOR_TYPE DHT_TYPE_DHT11
|
||||
#elif defined(CONFIG_EXAMPLE_TYPE_AM2301)
|
||||
#define SENSOR_TYPE DHT_TYPE_AM2301
|
||||
#elif defined(CONFIG_EXAMPLE_TYPE_SI7021)
|
||||
#define SENSOR_TYPE DHT_TYPE_SI7021
|
||||
#else
|
||||
#error "未在 menuconfig 中选择任何 DHT 传感器类型!"
|
||||
#endif
|
||||
|
||||
void dht_test(void *pvParameters)
|
||||
{
|
||||
float temperature, humidity;
|
||||
const gpio_num_t dht_gpio = (gpio_num_t)CONFIG_EXAMPLE_DATA_GPIO;
|
||||
uint32_t timeout_count = 0;
|
||||
|
||||
ESP_LOGI(TAG, "正在启动 DHT 测试任务,引脚: GPIO%d", dht_gpio);
|
||||
ESP_LOGI(TAG, "使用的传感器类型: %d (0:DHT11, 1:AM2301, 2:SI7021)", SENSOR_TYPE);
|
||||
|
||||
// 传感器上电后先等待稳定,避免首读超时
|
||||
vTaskDelay(pdMS_TO_TICKS(1500));
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_INTERNAL_PULLUP
|
||||
gpio_set_pull_mode(dht_gpio, GPIO_PULLUP_ONLY);
|
||||
#endif
|
||||
|
||||
while (1)
|
||||
{
|
||||
esp_err_t res = ESP_FAIL;
|
||||
for (int attempt = 1; attempt <= 3; ++attempt)
|
||||
{
|
||||
res = dht_read_float_data(SENSOR_TYPE, dht_gpio, &humidity, &temperature);
|
||||
if (res == ESP_OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// 给总线一点恢复时间,再重试
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
|
||||
if (res == ESP_OK)
|
||||
{
|
||||
timeout_count = 0;
|
||||
ESP_LOGI(TAG, "湿度: %.1f%% 温度: %.1f°C", humidity, temperature);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res == ESP_ERR_TIMEOUT)
|
||||
{
|
||||
timeout_count++;
|
||||
}
|
||||
ESP_LOGW(TAG, "读取失败: %s", esp_err_to_name(res));
|
||||
|
||||
if (timeout_count >= 3)
|
||||
{
|
||||
ESP_LOGW(TAG, "DHT 连续超时,建议检查: 1) DATA 线上拉电阻(4.7k~10k) 2) 传感器供电与地线 3) 是否更换为 GPIO2/3 等普通 IO");
|
||||
timeout_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(wifi_connect_init()); // 初始化 Wi-Fi 配网模块
|
||||
ESP_ERROR_CHECK(bh1750_user_init()); // 初始化光照传感器
|
||||
|
||||
ESP_ERROR_CHECK(relay_ctrl_init(GPIO_NUM_6, GPIO_NUM_7, true)); //
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(2000)); // 等待系统稳定
|
||||
|
||||
|
||||
if (wait_for_wifi_connected(pdMS_TO_TICKS(60000)))
|
||||
{
|
||||
esp_err_t err = agri_env_mqtt_start();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "MQTT 启动失败: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// 测试读取光照数据
|
||||
float lux;
|
||||
if (bh1750_user_read(&lux) == ESP_OK)
|
||||
{
|
||||
ESP_LOGI(TAG, "测试读取光照强度: %.2f Lux", lux);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "Wi-Fi 连接超时,暂时跳过 MQTT 启动");
|
||||
}
|
||||
|
||||
// 启动 DHT 传感器测试任务
|
||||
if (xTaskCreate(dht_test, "dht_test", 2048, NULL, 5, NULL) != pdPASS)
|
||||
{
|
||||
ESP_LOGE(TAG, "创建 DHT 任务失败");
|
||||
}
|
||||
|
||||
// 启动一个心跳任务
|
||||
while (1)
|
||||
{
|
||||
ESP_LOGI(TAG, "系统心跳...");
|
||||
// 测试读取光照数据
|
||||
float lux;
|
||||
if (bh1750_user_read(&lux) == ESP_OK)
|
||||
{
|
||||
ESP_LOGI(TAG, "测试读取光照强度: %.2f Lux", lux);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user