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

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