feat: 集成 MQTT 高频上报与远程控制功能,并标准化协议文档

1. 核心功能增强:
   - 实现 1s/次的传感器数据主动上报,温湿度精度提升至小数点后两位。
   - 新增基于 cJSON 的 MQTT 控制指令解析逻辑,支持对双路继电器的远程开关控制。
   - 引入即时回执 (ACK) 机制:设备在执行控制指令后立即通过 `agri/env/ack` 主题反馈执行状态。

2. 系统架构优化:
   - 引入 `sntp_time` 组件实现自动对时,确保上报数据携带准确的 `YYYY-MM-DD HH:MM:SS` 时间戳。
   - 增加基于 eFuse 的芯片 UID 获取逻辑,结合 MAC 地址实现设备唯一标识。
   - 为传感器采集任务引入 Mutex 互斥锁,确保多任务环境下 `env_sample_data_t` 的线程安全。
   - 将所有传感器任务堆栈提升至 3072 字节,解决 cJSON 操作导致的 Stack Overflow 风险。

3. 文档与规范:
   - 新增 `README_MQTT.md` (V1.2),定义了环境报文、控制指令及 ACK 回执的完整 JSON Schema。
   - 同步更新主工程 `README.md`,明确硬件接线说明及系统功能列表。
This commit is contained in:
Wang Beihong
2026-04-19 20:34:26 +08:00
parent 8548f04733
commit d9fba1be5b
10 changed files with 587 additions and 27 deletions

View File

@@ -1,5 +1,9 @@
# AgriEnv_Monitor
## MQTT 协议文档
MQTT 上报与双继电器控制格式请参考:`README_MQTT.md`
本项目基于 ESP-IDF新增了一个统一环境采集与控制组件 `agri_env`,实现以下功能:
1. DHT11 温湿度采集DATA -> GPIO10
@@ -7,10 +11,14 @@
3. MQ135 数据采集AO -> GPIO1
4. 双路继电器控制
5. 统一数据结构与获取接口
6. MQTT 连接MQTT 3.1 + 用户名密码
7. MQTT 订阅主题与发布主题
8. 定时任务采样、打印和上报
9. cJSON 数据包构建用于上报
6. MQTT 连接MQTT 3.1.1,自动获取 UID/MAC/时间
7. MQTT 集成控制指令及即时回执ACK
8. 高频环境上报1 秒一次2 位精度)
9. cJSON 数据包序列化与上报
## 协议文档
详细的 MQTT 报文格式及指令说明请参考:[README_MQTT.md](README_MQTT.md)
## 目录说明

88
README_MQTT.md Normal file
View File

@@ -0,0 +1,88 @@
# AgriEnv_Monitor MQTT 通信协议文档 (V1.2)
本文档面向上位机(控制端)开发,定义了与 AgriEnv_Monitor 硬件终端的交互格式。
## 1. 通信主题 (Topic)
| 功能 | 主题 (Default) | 说明 |
| :--- | :--- | :--- |
| **环境上报** | `agri/env/data` | 硬件每 1 秒主动推送到此主题 |
| **设备控制** | `agri/env/cmd` | 上位机发送控制指令到此主题 |
| **执行回执** | `agri/env/ack` | 设备执行指令后的状态反馈 |
---
## 2. 环境数据上报 (Device -> Cloud/Client)
硬件会在任务循环中自动采集并打包传感器数据,格式如下:
### 报文示例 (JSON)
```json
{
"uid": "C5E171EDA59400F8",
"mac": "D8:85:AC:5D:EE:30",
"time": "2026-04-19 20:12:48",
"data": {
"temp": 29.0,
"humi": 64.0,
"lux": 109.17,
"gas_do": 1
},
"status": {
"r1": 0,
"r2": 1
}
}
```
### 字段定义
- **uid**: (String) 芯片唯一 ID用于区分不同终端。
- **mac**: (String) 硬件 MAC 地址。
- **time**: (String) 时间戳字符串 (`YYYY-MM-DD HH:MM:SS`)。
- **data**: (Object) 传感器实时数据:
- **temp**: (Float) 温度,单位 °C。
- **humi**: (Float) 相对湿度,单位 %。
- **lux**: (Float) 光照强度,单位 Lux。
- **gas_do**: (Int) MQ135 数字输出电平1: 正常0: 触发报警)。
- **status**: (Object) 执行器当前状态:
- **r1**: (Int) 继电器 1 状态 (0: 关, 1: 开)。
- **r2**: (Int) 继电器 2 状态 (0: 关, 1: 开)。
---
## 3. 设备远程控制 (Client -> Device)
上位机通过发布 JSON 指令到 `agri/env/cmd` 来控制继电器。
### 指令示例 (JSON)
```json
{
"relay": 1,
"state": 1
}
```
### 字段说明
- **relay**: (Int) 目标继电器编号。可选值:`1``2`
- **state**: (Int) 目标状态。可选值:`1` (开启), `0` (关闭)。
---
## 4. 控制执行回执 (Device -> Client)
当设备成功解析并执行控制指令后,会立即向 `agri/env/ack` 主题发布一条回执消息。
### 回执示例 (JSON)
```json
{
"relay": 1,
"state": 1,
"result": "success"
}
```
### 字段说明
- **relay**: (Int) 已控制的继电器编号。
- **state**: (Int) 执行后的状态。
- **result**: (String) 执行结果,固定为 "success"。

View File

@@ -1,5 +1,5 @@
idf_component_register(
SRCS "agri_env.c"
INCLUDE_DIRS "include"
REQUIRES driver mqtt cjson esp_timer esp_event
REQUIRES driver mqtt cjson esp_timer esp_event relay_ctrl
)

View File

@@ -9,6 +9,8 @@
#include "esp_event.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "cJSON.h"
#include "relay_ctrl.h"
#include "agri_env.h"
@@ -130,6 +132,34 @@ static void agri_env_mqtt_event_handler(void *handler_args, esp_event_base_t bas
event->topic,
event->data_len,
event->data);
// 解析控制指令并执行
cJSON *root = cJSON_ParseWithLength(event->data, event->data_len);
if (root != NULL) {
cJSON *relay = cJSON_GetObjectItem(root, "relay");
cJSON *state = cJSON_GetObjectItem(root, "state");
if (cJSON_IsNumber(relay) && cJSON_IsNumber(state)) {
int r_id = relay->valueint;
bool r_on = (state->valueint != 0);
relay_ctrl_set(r_id == 1 ? RELAY_CTRL_ID_1 : RELAY_CTRL_ID_2, r_on);
ESP_LOGI(TAG, "MQTT 指令执行: Relay %d -> %s", r_id, r_on ? "ON" : "OFF");
// 发送即时执行回执 (ACK)
cJSON *ack = cJSON_CreateObject();
if (ack != NULL) {
cJSON_AddNumberToObject(ack, "relay", r_id);
cJSON_AddNumberToObject(ack, "state", r_on ? 1 : 0);
cJSON_AddStringToObject(ack, "result", "success");
char *ack_str = cJSON_PrintUnformatted(ack);
if (ack_str != NULL) {
agri_env_mqtt_publish("agri/env/ack", ack_str, 1, 0);
free(ack_str);
}
cJSON_Delete(ack);
}
}
cJSON_Delete(root);
}
}
}
@@ -239,15 +269,21 @@ bool agri_env_mqtt_is_connected(void)
* @param retain 保留消息标识
* @return esp_err_t 成功返回 ESP_OK失败返回相应错误码
*/
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain)
esp_err_t agri_env_mqtt_publish(const char *topic, const char *payload, int qos, int retain)
{
ESP_RETURN_ON_FALSE(topic != NULL && topic[0] != '\0', ESP_ERR_INVALID_ARG, TAG, "主题为空");
ESP_RETURN_ON_FALSE(payload != NULL, ESP_ERR_INVALID_ARG, TAG, "内容为空");
ESP_RETURN_ON_FALSE(s_ctx.mqtt_client != NULL, ESP_ERR_INVALID_STATE, TAG, "MQTT 客户端未启动");
ESP_RETURN_ON_FALSE(agri_env_mqtt_is_connected(), ESP_ERR_INVALID_STATE, TAG, "MQTT 未连接");
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;
}
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain)
{
static const char *payload = "{\"mode\":\"mqtt_only\"}";
return agri_env_mqtt_publish(topic, payload, qos, retain);
}

View File

@@ -15,6 +15,8 @@ esp_err_t agri_env_mqtt_start(void);
esp_err_t agri_env_mqtt_stop(void);
/* 查询 MQTT 当前是否已连接。 */
bool agri_env_mqtt_is_connected(void);
/* 发布指定的 JSON 载荷到指定主题。 */
esp_err_t agri_env_mqtt_publish(const char *topic, const char *payload, int qos, int retain);
/* 发布固定 MQTT-only 心跳载荷到指定主题。 */
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain);

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "sntp_time.c"
INCLUDE_DIRS "include"
REQUIRES esp_timer esp_event esp_netif lwip)

View 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

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

View File

@@ -1,3 +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 )
REQUIRES nvs_flash sntp_time esp_wifi esp_event esp_system wifi-connect agri_env bh1750 dht relay_ctrl efuse )

View File

@@ -2,6 +2,7 @@
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "esp_check.h"
@@ -12,8 +13,201 @@
#include <dht.h>
#include "driver/gpio.h"
#include "relay_ctrl.h" // 包含继电器控制模块头文件(提供继电器控制接口)
#include "sntp_time.h" // 包含 SNTP 时间模块头文件(提供时间同步接口)
#include "esp_mac.h"
#include "esp_system.h"
#include "esp_efuse.h"
#include "esp_efuse_table.h"
#include <time.h>
#include <string.h>
#include <cJSON.h>
static const char *TAG = "main";
static const gpio_num_t MQ135_DO_GPIO = GPIO_NUM_10;
typedef struct
{
float temperature_c;
float humidity_percent;
float illuminance_lux;
int mq135_do_level;
bool relay1_on;
bool relay2_on;
char time_str[32];
char mac_str[20];
char uid_str[20];
} env_sample_data_t;
static env_sample_data_t s_env_data = {
.temperature_c = 0.0f,
.humidity_percent = 0.0f,
.illuminance_lux = 0.0f,
.mq135_do_level = -1,
.relay1_on = false,
.relay2_on = false,
.time_str = "N/A",
.mac_str = "N/A",
.uid_str = "N/A",
};
static SemaphoreHandle_t s_env_data_lock = NULL;
static void env_data_update_dht(float humidity, float temperature)
{
if (s_env_data_lock == NULL)
{
return;
}
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.humidity_percent = humidity;
s_env_data.temperature_c = temperature;
xSemaphoreGive(s_env_data_lock);
}
static void env_data_update_lux(float lux)
{
if (s_env_data_lock == NULL)
{
return;
}
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.illuminance_lux = lux;
xSemaphoreGive(s_env_data_lock);
}
static void env_data_update_mq135(int level)
{
if (s_env_data_lock == NULL)
{
return;
}
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.mq135_do_level = level;
xSemaphoreGive(s_env_data_lock);
}
static void env_data_update_relays(void)
{
if (s_env_data_lock == NULL)
{
return;
}
bool relay1_on = false;
bool relay2_on = false;
if (relay_ctrl_get(RELAY_CTRL_ID_1, &relay1_on) != ESP_OK)
{
relay1_on = false;
}
if (relay_ctrl_get(RELAY_CTRL_ID_2, &relay2_on) != ESP_OK)
{
relay2_on = false;
}
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.relay1_on = relay1_on;
s_env_data.relay2_on = relay2_on;
xSemaphoreGive(s_env_data_lock);
}
static void env_data_update_system_info(void)
{
if (s_env_data_lock == NULL)
{
return;
}
// 获取当前时间
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
char time_buf[32];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
// 获取 MAC 地址 (Base MAC)
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char mac_buf[20];
snprintf(mac_buf, sizeof(mac_buf), "%02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// 获取 UID (基于核心 ID)
uint8_t chip_id[8];
esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, chip_id, 64);
char uid_buf[20];
if (err == ESP_OK) {
snprintf(uid_buf, sizeof(uid_buf), "%02X%02X%02X%02X%02X%02X%02X%02X",
chip_id[0], chip_id[1], chip_id[2], chip_id[3],
chip_id[4], chip_id[5], chip_id[6], chip_id[7]);
} else {
snprintf(uid_buf, sizeof(uid_buf), "UNKNOWN");
}
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
strncpy(s_env_data.time_str, time_buf, sizeof(s_env_data.time_str));
strncpy(s_env_data.mac_str, mac_buf, sizeof(s_env_data.mac_str));
strncpy(s_env_data.uid_str, uid_buf, sizeof(s_env_data.uid_str));
xSemaphoreGive(s_env_data_lock);
}
static void env_data_log_snapshot(void)
{
if (s_env_data_lock == NULL)
{
return;
}
env_data_update_system_info(); // 打印前更新一次系统信息
env_sample_data_t snapshot;
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
snapshot = s_env_data;
xSemaphoreGive(s_env_data_lock);
ESP_LOGI(TAG,
"采集汇总 | 时间:%s | UID:%s | MAC:%s | 温度:%.2f°C | 湿度:%.2f%% | 光照:%.2fLux | MQ135:%d | R1:%d | R2:%d",
snapshot.time_str,
snapshot.uid_str,
snapshot.mac_str,
snapshot.temperature_c,
snapshot.humidity_percent,
snapshot.illuminance_lux,
snapshot.mq135_do_level,
snapshot.relay1_on,
snapshot.relay2_on);
// 将数据构建为 cJSON 格式并通过 MQTT 发送
cJSON *root = cJSON_CreateObject();
if (root != NULL) {
cJSON_AddStringToObject(root, "uid", snapshot.uid_str);
cJSON_AddStringToObject(root, "mac", snapshot.mac_str);
cJSON_AddStringToObject(root, "time", snapshot.time_str);
cJSON *data = cJSON_AddObjectToObject(root, "data");
cJSON_AddNumberToObject(data, "temp", snapshot.temperature_c);
cJSON_AddNumberToObject(data, "humi", snapshot.humidity_percent);
cJSON_AddNumberToObject(data, "lux", snapshot.illuminance_lux);
cJSON_AddNumberToObject(data, "gas_do", snapshot.mq135_do_level);
cJSON *status = cJSON_AddObjectToObject(root, "status");
cJSON_AddNumberToObject(status, "r1", snapshot.relay1_on ? 1 : 0);
cJSON_AddNumberToObject(status, "r2", snapshot.relay2_on ? 1 : 0);
char *json_str = cJSON_PrintUnformatted(root);
if (json_str != NULL) {
if (agri_env_mqtt_is_connected()) {
agri_env_mqtt_publish("agri/env/data", json_str, 1, 0);
}
free(json_str);
}
cJSON_Delete(root);
}
}
// 等待 Wi-Fi 连接成功,超时后返回当前连接状态
static bool wait_for_wifi_connected(TickType_t timeout_ticks)
@@ -47,8 +241,7 @@ void dht_test(void *pvParameters)
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);
ESP_LOGI(TAG, "正在启动 DHT 任务,引脚: GPIO%d", dht_gpio);
// 传感器上电后先等待稳定,避免首读超时
vTaskDelay(pdMS_TO_TICKS(1500));
@@ -74,7 +267,7 @@ void dht_test(void *pvParameters)
if (res == ESP_OK)
{
timeout_count = 0;
ESP_LOGI(TAG, "湿度: %.1f%% 温度: %.1f°C", humidity, temperature);
env_data_update_dht(humidity, temperature);
}
else
{
@@ -95,16 +288,65 @@ void dht_test(void *pvParameters)
}
}
static void mq135_do_task(void *pvParameters)
{
(void)pvParameters;
ESP_LOGI(TAG, "正在启动 MQ135 DO 任务,引脚: GPIO%d", MQ135_DO_GPIO);
while (1)
{
int level = gpio_get_level(MQ135_DO_GPIO);
env_data_update_mq135(level);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
static void bh1750_task(void *pvParameters)
{
(void)pvParameters;
ESP_LOGI(TAG, "正在启动 BH1750 任务");
while (1)
{
float lux;
if (bh1750_user_read(&lux) == ESP_OK)
{
env_data_update_lux(lux);
}
env_data_log_snapshot();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
s_env_data_lock = xSemaphoreCreateMutex();
if (s_env_data_lock == NULL)
{
ESP_LOGE(TAG, "采集数据互斥锁创建失败");
return;
}
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)); //
const gpio_config_t mq135_do_cfg = {
.pin_bit_mask = (1ULL << MQ135_DO_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&mq135_do_cfg));
ESP_LOGI(TAG, "MQ135 DO 输入已配置: GPIO%d", MQ135_DO_GPIO);
// 初始化继电器控制模块继电器1连接 GPIO6继电器2连接 GPIO7且为高电平吸合
ESP_ERROR_CHECK(relay_ctrl_init(GPIO_NUM_6, GPIO_NUM_7, true));
env_data_update_relays();
vTaskDelay(pdMS_TO_TICKS(2000)); // 等待系统稳定
if (wait_for_wifi_connected(pdMS_TO_TICKS(60000)))
{
esp_err_t err = agri_env_mqtt_start();
@@ -112,12 +354,11 @@ void app_main(void)
{
ESP_LOGW(TAG, "MQTT 启动失败: %s", esp_err_to_name(err));
}
// 测试读取光照数据
float lux;
if (bh1750_user_read(&lux) == ESP_OK)
// Wi-Fi 连通后做一次 SNTP 对时,失败不阻断后续业务。
esp_err_t sntp_ret = sntp_timp_sync_time(12000);
if (sntp_ret != ESP_OK)
{
ESP_LOGI(TAG, "测试读取光照强度: %.2f Lux", lux);
ESP_LOGW(TAG, "SNTP 对时未完成: %s", esp_err_to_name(sntp_ret));
}
}
else
@@ -126,21 +367,26 @@ void app_main(void)
}
// 启动 DHT 传感器测试任务
if (xTaskCreate(dht_test, "dht_test", 2048, NULL, 5, NULL) != pdPASS)
if (xTaskCreate(dht_test, "dht_test", 3072, NULL, 5, NULL) != pdPASS)
{
ESP_LOGE(TAG, "创建 DHT 任务失败");
}
// 启动一个心跳任务
// 启动 MQ135 DO 电平检测任务
if (xTaskCreate(mq135_do_task, "mq135_do", 3072, NULL, 5, NULL) != pdPASS)
{
ESP_LOGE(TAG, "创建 MQ135 DO 任务失败");
}
// 启动 BH1750 光照检测任务
if (xTaskCreate(bh1750_task, "bh1750_task", 3072, NULL, 5, NULL) != pdPASS)
{
ESP_LOGE(TAG, "创建 BH1750 任务失败");
}
// 主任务保持存活,不再打印系统心跳
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));
}
}