功能:添加 Wi-Fi 连接管理组件
在 wifi-connect.c 中实现了新的 Wi-Fi 连接管理模块,负责配网、连接及状态上报。 添加了用于 Wi-Fi 配置和状态显示的 HTML 界面。 集成了 NVS 用于存储 Wi-Fi 凭证。 更新了 CMakeLists.txt 以包含新模块的依赖项。 修改了 main.c 以初始化 Wi-Fi 连接管理并等待连接成功。
This commit is contained in:
52
README.md
52
README.md
@@ -0,0 +1,52 @@
|
|||||||
|
## 引脚分配
|
||||||
|
|
||||||
|
### IIC设备
|
||||||
|
|
||||||
|
这类设备有:BH1750光照传感器、AHT30温湿度传感器
|
||||||
|
|
||||||
|
| 设备名称 | SCL引脚 | SDA引脚 |
|
||||||
|
| -------- | ------- | ------- |
|
||||||
|
| BH1750 | GPIO1 | GPIO2 |
|
||||||
|
| AHT30 | GPIO1 | GPIO2 |
|
||||||
|
|
||||||
|
### 继电器
|
||||||
|
|
||||||
|
一共有4个继电器,分别控制4个设备的开关,从1号到4号依次为:风扇、照明灯、制冷片、加热器。
|
||||||
|
|
||||||
|
| 设备名称 | 继电器编号 | GPIO引脚 |
|
||||||
|
| -------- | ---------- | -------- |
|
||||||
|
| 风扇 | 1 | GPIO37 |
|
||||||
|
| 照明灯 | 2 | GPIO38 |
|
||||||
|
| 制冷片 | 3 | GPIO39 |
|
||||||
|
| 加热器 | 4 | GPIO40 |
|
||||||
|
|
||||||
|
### ADC传感器
|
||||||
|
|
||||||
|
这个用的是ADC输入,连接到GPIO3引脚。
|
||||||
|
|
||||||
|
| 设备名称 | GPIO引脚 |
|
||||||
|
| ---------- | -------- |
|
||||||
|
| 火焰传感器 | GPIO3 |
|
||||||
|
| MQ-2 | GPIO8 |
|
||||||
|
|
||||||
|
|
||||||
|
### 串口设备
|
||||||
|
|
||||||
|
| 设备名称 | TX引脚 | RX引脚 |
|
||||||
|
| -------- | ------ | ------ |
|
||||||
|
| 气体传感器 | GPIO4 | GPIO5 |
|
||||||
|
| 语音模块 | GPIO6 | GPIO7 |
|
||||||
|
|
||||||
|
### IO设备
|
||||||
|
|
||||||
|
| 设备名称 | GPIO引脚 |
|
||||||
|
| -------- | -------- |
|
||||||
|
| HC-SR312 | GPIO16 |
|
||||||
|
| 微动开关 | GPIO17 |
|
||||||
|
|
||||||
|
|
||||||
|
### 称重模块
|
||||||
|
|
||||||
|
| 设备名称 | GPIO引脚 |
|
||||||
|
| -------- | -------- |
|
||||||
|
| HX711 | GPIO35 | GPIO36 |
|
||||||
|
|||||||
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 1 // ESP32-S3 建议引脚
|
||||||
|
#define BH1750_I2C_SDA_IO 2 // ESP32-S3 建议引脚
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
63
components/relay_ctrl/include/relay_ctrl.h
Normal file
63
components/relay_ctrl/include/relay_ctrl.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#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_3 = 2,
|
||||||
|
RELAY_CTRL_ID_4 = 3,
|
||||||
|
RELAY_CTRL_ID_MAX,
|
||||||
|
} relay_ctrl_id_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化四路继电器控制模块。
|
||||||
|
*
|
||||||
|
* @param relay1_gpio 继电器1控制引脚
|
||||||
|
* @param relay2_gpio 继电器2控制引脚
|
||||||
|
* @param relay3_gpio 继电器3控制引脚
|
||||||
|
* @param relay4_gpio 继电器4控制引脚
|
||||||
|
* @param active_high 继电器有效电平,true=高电平吸合,false=低电平吸合
|
||||||
|
*/
|
||||||
|
esp_err_t relay_ctrl_init(gpio_num_t relay1_gpio,
|
||||||
|
gpio_num_t relay2_gpio,
|
||||||
|
gpio_num_t relay3_gpio,
|
||||||
|
gpio_num_t relay4_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, bool relay3_on, bool relay4_on);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
128
components/relay_ctrl/relay_ctrl.c
Normal file
128
components/relay_ctrl/relay_ctrl.c
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#include "relay_ctrl.h"
|
||||||
|
|
||||||
|
#include <stdint.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,
|
||||||
|
gpio_num_t relay3_gpio,
|
||||||
|
gpio_num_t relay4_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");
|
||||||
|
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(relay3_gpio), ESP_ERR_INVALID_ARG, TAG, "relay3 gpio invalid");
|
||||||
|
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(relay4_gpio), ESP_ERR_INVALID_ARG, TAG, "relay4 gpio invalid");
|
||||||
|
|
||||||
|
const gpio_num_t relay_gpios[RELAY_CTRL_ID_MAX] = {
|
||||||
|
relay1_gpio,
|
||||||
|
relay2_gpio,
|
||||||
|
relay3_gpio,
|
||||||
|
relay4_gpio,
|
||||||
|
};
|
||||||
|
|
||||||
|
s_ctx.active_high = active_high;
|
||||||
|
|
||||||
|
uint64_t pin_bit_mask = 0;
|
||||||
|
for (int i = 0; i < RELAY_CTRL_ID_MAX; ++i) {
|
||||||
|
s_ctx.pins[i] = relay_gpios[i];
|
||||||
|
pin_bit_mask |= (1ULL << relay_gpios[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gpio_config_t io_cfg = {
|
||||||
|
.pin_bit_mask = pin_bit_mask,
|
||||||
|
.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");
|
||||||
|
|
||||||
|
// 默认上电全部断开
|
||||||
|
for (int i = 0; i < RELAY_CTRL_ID_MAX; ++i) {
|
||||||
|
s_ctx.states[i] = false;
|
||||||
|
ESP_RETURN_ON_ERROR(gpio_set_level(relay_gpios[i], relay_level_from_state(false)),
|
||||||
|
TAG,
|
||||||
|
"relay set init level failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
s_ctx.inited = true;
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"继电器初始化完成: relay1=GPIO%d relay2=GPIO%d relay3=GPIO%d relay4=GPIO%d active_high=%d",
|
||||||
|
relay1_gpio,
|
||||||
|
relay2_gpio,
|
||||||
|
relay3_gpio,
|
||||||
|
relay4_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, bool relay3_on, bool relay4_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");
|
||||||
|
ESP_RETURN_ON_ERROR(relay_ctrl_set(RELAY_CTRL_ID_3, relay3_on), TAG, "set relay3 failed");
|
||||||
|
ESP_RETURN_ON_ERROR(relay_ctrl_set(RELAY_CTRL_ID_4, relay4_on), TAG, "set relay4 failed");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
3
components/sntp_time/CMakeLists.txt
Normal file
3
components/sntp_time/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
idf_component_register(SRCS "sntp_time.c"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES esp_timer esp_event esp_netif lwip)
|
||||||
20
components/sntp_time/include/sntp_time.h
Normal file
20
components/sntp_time/include/sntp_time.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 SNTP 并等待首次对时完成
|
||||||
|
*
|
||||||
|
* @param timeout_ms 等待首次同步的超时时间(毫秒)
|
||||||
|
* @return esp_err_t ESP_OK 表示已完成同步
|
||||||
|
*/
|
||||||
|
esp_err_t sntp_timp_sync_time(uint32_t timeout_ms);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
157
components/sntp_time/sntp_time.c
Normal file
157
components/sntp_time/sntp_time.c
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "sntp_time.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_idf_version.h"
|
||||||
|
#include "esp_sntp.h"
|
||||||
|
#include "esp_netif_sntp.h"
|
||||||
|
#include "sys/time.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
|
||||||
|
static const char *TAG = "sntp_timp";
|
||||||
|
|
||||||
|
#define SNTP_TIME_VALID_UNIX_TS 1700000000
|
||||||
|
#define SNTP_WAIT_POLL_MS 200
|
||||||
|
#define SNTP_REFRESH_PERIOD_MS 1000
|
||||||
|
|
||||||
|
extern void set_var_sntp_time(const char *value) __attribute__((weak));
|
||||||
|
|
||||||
|
static TaskHandle_t s_time_refresh_task = NULL;
|
||||||
|
|
||||||
|
static time_t get_current_time(void);
|
||||||
|
|
||||||
|
static void publish_sntp_time_var(const char *value)
|
||||||
|
{
|
||||||
|
if (set_var_sntp_time != NULL) {
|
||||||
|
set_var_sntp_time(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void format_current_time(char *buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
time_t now = get_current_time();
|
||||||
|
struct tm timeinfo;
|
||||||
|
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S", &timeinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sntp_time_refresh_task(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
char time_text[32];
|
||||||
|
for (;;) {
|
||||||
|
format_current_time(time_text, sizeof(time_text));
|
||||||
|
publish_sntp_time_var(time_text);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(SNTP_REFRESH_PERIOD_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================== 时间相关函数 ===========================
|
||||||
|
static void set_timezone(void)
|
||||||
|
{
|
||||||
|
// 设置中国标准时间(北京时间)
|
||||||
|
setenv("TZ", "CST-8", 1);
|
||||||
|
tzset();
|
||||||
|
ESP_LOGI(TAG, "时区设置为北京时间 (CST-8)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static time_t get_current_time(void)
|
||||||
|
{
|
||||||
|
// 使用POSIX函数获取时间
|
||||||
|
return time(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_current_time(void)
|
||||||
|
{
|
||||||
|
char buffer[64];
|
||||||
|
format_current_time(buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "当前时间: %s", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t start_time_refresh_task_if_needed(void)
|
||||||
|
{
|
||||||
|
if (s_time_refresh_task != NULL) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType_t ok = xTaskCreate(sntp_time_refresh_task,
|
||||||
|
"sntp_time",
|
||||||
|
3072,
|
||||||
|
NULL,
|
||||||
|
3,
|
||||||
|
&s_time_refresh_task);
|
||||||
|
return (ok == pdPASS) ? ESP_OK : ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void configure_sntp_servers(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "初始化SNTP服务");
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
|
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||||||
|
esp_sntp_setservername(0, "cn.pool.ntp.org"); // 中国 NTP 服务器
|
||||||
|
esp_sntp_setservername(1, "ntp1.aliyun.com"); // 阿里云 NTP 服务器
|
||||||
|
#else
|
||||||
|
sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||||||
|
sntp_setservername(0, "cn.pool.ntp.org");
|
||||||
|
sntp_setservername(1, "cn.pool.ntp.org");
|
||||||
|
sntp_setservername(2, "ntp1.aliyun.com");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t wait_for_time_sync(uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
int64_t start_ms = esp_timer_get_time() / 1000;
|
||||||
|
for (;;) {
|
||||||
|
time_t now = get_current_time();
|
||||||
|
if (now >= SNTP_TIME_VALID_UNIX_TS) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t elapsed_ms = (esp_timer_get_time() / 1000) - start_ms;
|
||||||
|
if (elapsed_ms >= (int64_t)timeout_ms) {
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(SNTP_WAIT_POLL_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t sntp_timp_sync_time(uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
if (timeout_ms == 0) {
|
||||||
|
timeout_ms = 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_timezone();
|
||||||
|
|
||||||
|
if (esp_sntp_enabled()) {
|
||||||
|
esp_sntp_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_sntp_servers();
|
||||||
|
esp_sntp_init();
|
||||||
|
|
||||||
|
esp_err_t ret = wait_for_time_sync(timeout_ms);
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
print_current_time();
|
||||||
|
char time_text[32];
|
||||||
|
format_current_time(time_text, sizeof(time_text));
|
||||||
|
publish_sntp_time_var(time_text);
|
||||||
|
|
||||||
|
esp_err_t task_ret = start_time_refresh_task_if_needed();
|
||||||
|
if (task_ret != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "创建时间刷新任务失败: %s", esp_err_to_name(task_ret));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "SNTP 对时超时(%lu ms)", (unsigned long)timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
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
@@ -1,2 +1,3 @@
|
|||||||
idf_component_register(SRCS "main.c"
|
idf_component_register(SRCS "main.c"
|
||||||
INCLUDE_DIRS ".")
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES nvs_flash esp_wifi esp_event esp_system wifi-connect)
|
||||||
|
|||||||
21
main/main.c
21
main/main.c
@@ -1,6 +1,25 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include "wifi-connect.h"
|
||||||
|
|
||||||
|
// 等待 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;
|
||||||
|
}
|
||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
{
|
{
|
||||||
|
ESP_ERROR_CHECK(wifi_connect_init()); // 初始化 Wi-Fi 配网模块
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user