添加农业环境模块,集成 MQTT 客户端功能,支持配置参数和数据发布

This commit is contained in:
Wang Beihong
2026-04-22 01:21:26 +08:00
parent ffdb7065e3
commit 811b47d274
8 changed files with 425 additions and 38 deletions

View File

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

View File

@@ -0,0 +1,26 @@
menu "MQTT 配置参数"
config AGRI_ENV_MQTT_BROKER_URI
string "MQTT 服务器地址"
default ""
config AGRI_ENV_MQTT_USERNAME
string "MQTT 用户名"
default ""
config AGRI_ENV_MQTT_PASSWORD
string "MQTT 密码"
default ""
config AGRI_ENV_MQTT_CLIENT_ID
string "MQTT Client ID"
default "agri-env-monitor"
config AGRI_ENV_MQTT_PUBLISH_TOPIC
string "MQTT 发布主题"
default "agri/env/data"
config AGRI_ENV_MQTT_SUBSCRIBE_TOPIC
string "MQTT 订阅主题"
default "agri/env/cmd"
endmenu

View File

@@ -0,0 +1,261 @@
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "cJSON.h"
#include "relay_ctrl.h"
#include "agri_env.h"
static const char *TAG = "agri_env";
/**
* @brief 农业环境模块上下文结构体
* 包含 MQTT 客户端句柄、连接状态以及用于线程安全的互斥锁
*/
typedef struct {
SemaphoreHandle_t lock; /*!< 互斥锁,保护共享资源 */
esp_mqtt_client_handle_t mqtt_client; /*!< ESP MQTT 客户端句柄 */
bool mqtt_connected; /*!< MQTT 连接状态标识 */
} agri_env_ctx_t;
static agri_env_ctx_t s_ctx;
/**
* @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(const char *topic, const char *payload, int qos, int retain)
{
ESP_RETURN_ON_FALSE(topic != NULL && topic[0] != '\0', ESP_ERR_INVALID_ARG, TAG, "主题为空");
ESP_RETURN_ON_FALSE(payload != NULL, ESP_ERR_INVALID_ARG, TAG, "内容为空");
ESP_RETURN_ON_FALSE(s_ctx.mqtt_client != NULL, ESP_ERR_INVALID_STATE, TAG, "MQTT 客户端未启动");
ESP_RETURN_ON_FALSE(agri_env_mqtt_is_connected(), ESP_ERR_INVALID_STATE, TAG, "MQTT 未连接");
int msg_id = esp_mqtt_client_publish(s_ctx.mqtt_client, topic, payload, 0, qos, retain);
ESP_RETURN_ON_FALSE(msg_id >= 0, ESP_FAIL, TAG, "MQTT 发布失败");
return ESP_OK;
}
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain)
{
static const char *payload = "{\"mode\":\"mqtt_only\"}";
return agri_env_mqtt_publish(topic, payload, qos, retain);
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 启动 MQTT 客户端连接。 */
esp_err_t agri_env_mqtt_start(void);
/* 停止 MQTT 客户端连接并释放资源。 */
esp_err_t agri_env_mqtt_stop(void);
/* 查询 MQTT 当前是否已连接。 */
bool agri_env_mqtt_is_connected(void);
/* 发布指定的 JSON 载荷到指定主题。 */
esp_err_t agri_env_mqtt_publish(const char *topic, const char *payload, int qos, int retain);
/* 发布固定 MQTT-only 心跳载荷到指定主题。 */
esp_err_t agri_env_mqtt_publish_latest(const char *topic, int qos, int retain);
#ifdef __cplusplus
}
#endif

View File

@@ -13,6 +13,16 @@ dependencies:
registry_url: https://components.espressif.com/ registry_url: https://components.espressif.com/
type: service type: service
version: 1.0.0 version: 1.0.0
espressif/cjson:
component_hash: e788323270d90738662d66fffa910bfe1fba019bba087f01557e70c40485b469
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.7.19~2
espressif/cmake_utilities: espressif/cmake_utilities:
component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
dependencies: dependencies:
@@ -51,6 +61,16 @@ dependencies:
registry_url: https://components.espressif.com registry_url: https://components.espressif.com
type: service type: service
version: 1.5.1 version: 1.5.1
espressif/mqtt:
component_hash: ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342
dependencies:
- name: idf
require: private
version: '>=5.3'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.0
espressif/sensor_hub: espressif/sensor_hub:
component_hash: b3e72f3e2d10e165b972b17fff889f294b1a685769efacf73ec8f1e92152d1d5 component_hash: b3e72f3e2d10e165b972b17fff889f294b1a685769efacf73ec8f1e92152d1d5
dependencies: dependencies:
@@ -92,9 +112,11 @@ dependencies:
version: 1.1.3 version: 1.1.3
direct_dependencies: direct_dependencies:
- espressif/aht30 - espressif/aht30
- espressif/cjson
- espressif/esp_lvgl_port - espressif/esp_lvgl_port
- espressif/mqtt
- idf - idf
- supcik/hx711 - supcik/hx711
manifest_hash: 09736a2502ce314a6733743671e919311d43e602eb74410732a63f10ed3aa60a manifest_hash: f379725444325394085b05956aaab66b9a797466dc72d898cd7645dbe6cb359c
target: esp32s3 target: esp32s3
version: 2.0.0 version: 2.0.0

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "main.cpp" idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES nvs_flash esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2 JW01 human_door fire_sensor hx711 su-03t) REQUIRES nvs_flash agri_env esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2 JW01 human_door fire_sensor hx711 su-03t)

View File

@@ -17,3 +17,5 @@ dependencies:
espressif/esp_lvgl_port: ^2.7.2 espressif/esp_lvgl_port: ^2.7.2
espressif/aht30: ^1.0.0 espressif/aht30: ^1.0.0
supcik/hx711: ^1.1.3 supcik/hx711: ^1.1.3
espressif/mqtt: ^1.0.0
espressif/cjson: ^1.7.19~2

View File

@@ -37,6 +37,7 @@
#include "fire_sensor.h" #include "fire_sensor.h"
#include "hx711.hpp" #include "hx711.hpp"
#include "su-03t.h" #include "su-03t.h"
#include "agri_env.h"
#define TAG "MAIN" #define TAG "MAIN"
#define CO2_SPOILAGE_THRESHOLD_PPM 1500.0f #define CO2_SPOILAGE_THRESHOLD_PPM 1500.0f
@@ -97,7 +98,8 @@ static void reconfigure_twdt(uint32_t timeout_ms, uint32_t idle_core_mask)
}; };
esp_err_t ret = esp_task_wdt_reconfigure(&twdt_cfg); esp_err_t ret = esp_task_wdt_reconfigure(&twdt_cfg);
if (ret != ESP_OK) { if (ret != ESP_OK)
{
ESP_LOGW(TAG, "TWDT reconfigure failed: %s", esp_err_to_name(ret)); ESP_LOGW(TAG, "TWDT reconfigure failed: %s", esp_err_to_name(ret));
} }
} }
@@ -227,33 +229,41 @@ static void relay_status_task(void *arg)
{ {
bool relay_on = false; bool relay_on = false;
if (relay_ctrl_get(RELAY_CTRL_ID_1, &relay_on) == ESP_OK) { if (relay_ctrl_get(RELAY_CTRL_ID_1, &relay_on) == ESP_OK)
{
set_var_fan_status(relay_status_text(relay_on)); set_var_fan_status(relay_status_text(relay_on));
if (s_env_data_lock) { if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY); xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.fan_on = relay_on; s_env_data.fan_on = relay_on;
xSemaphoreGive(s_env_data_lock); xSemaphoreGive(s_env_data_lock);
} }
} }
if (relay_ctrl_get(RELAY_CTRL_ID_2, &relay_on) == ESP_OK) { if (relay_ctrl_get(RELAY_CTRL_ID_2, &relay_on) == ESP_OK)
{
set_var_light_status(relay_status_text(relay_on)); set_var_light_status(relay_status_text(relay_on));
if (s_env_data_lock) { if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY); xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.light_on = relay_on; s_env_data.light_on = relay_on;
xSemaphoreGive(s_env_data_lock); xSemaphoreGive(s_env_data_lock);
} }
} }
if (relay_ctrl_get(RELAY_CTRL_ID_3, &relay_on) == ESP_OK) { if (relay_ctrl_get(RELAY_CTRL_ID_3, &relay_on) == ESP_OK)
{
set_var_cool_status(relay_status_text(relay_on)); set_var_cool_status(relay_status_text(relay_on));
if (s_env_data_lock) { if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY); xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.cool_on = relay_on; s_env_data.cool_on = relay_on;
xSemaphoreGive(s_env_data_lock); xSemaphoreGive(s_env_data_lock);
} }
} }
if (relay_ctrl_get(RELAY_CTRL_ID_4, &relay_on) == ESP_OK) { if (relay_ctrl_get(RELAY_CTRL_ID_4, &relay_on) == ESP_OK)
{
set_var_hot_status(relay_status_text(relay_on)); set_var_hot_status(relay_status_text(relay_on));
if (s_env_data_lock) { if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY); xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.hot_on = relay_on; s_env_data.hot_on = relay_on;
xSemaphoreGive(s_env_data_lock); xSemaphoreGive(s_env_data_lock);
@@ -272,9 +282,11 @@ static void sntp_task(void *arg)
{ {
(void)arg; (void)arg;
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000))) { if (wait_for_wifi_connected(pdMS_TO_TICKS(15000)))
{
esp_err_t sntp_ret = sntp_timp_sync_time(10000); esp_err_t sntp_ret = sntp_timp_sync_time(10000);
if (sntp_ret != ESP_OK) { if (sntp_ret != ESP_OK)
{
ESP_LOGW(TAG, "SNTP sync failed: %s", esp_err_to_name(sntp_ret)); ESP_LOGW(TAG, "SNTP sync failed: %s", esp_err_to_name(sntp_ret));
} }
} }
@@ -289,20 +301,24 @@ static void sntp_task(void *arg)
static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx) static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx)
{ {
(void)user_ctx; (void)user_ctx;
if (frame == NULL) { if (frame == NULL)
{
return; return;
} }
char hex_buf[256]; char hex_buf[256];
size_t pos = 0; size_t pos = 0;
for (size_t i = 0; i < frame->params_len && pos + 4 < sizeof(hex_buf); ++i) { for (size_t i = 0; i < frame->params_len && pos + 4 < sizeof(hex_buf); ++i)
{
int n = snprintf(&hex_buf[pos], sizeof(hex_buf) - pos, "%02X ", frame->params[i]); int n = snprintf(&hex_buf[pos], sizeof(hex_buf) - pos, "%02X ", frame->params[i]);
if (n <= 0) { if (n <= 0)
{
break; break;
} }
pos += (size_t)n; pos += (size_t)n;
} }
if (pos == 0) { if (pos == 0)
{
snprintf(hex_buf, sizeof(hex_buf), "(no params)"); snprintf(hex_buf, sizeof(hex_buf), "(no params)");
} }
@@ -311,7 +327,8 @@ static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx)
(unsigned)frame->params_len, (unsigned)frame->params_len,
hex_buf); hex_buf);
if (s_env_data_lock) { if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY); xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.su03t_last_msgno = frame->msgno; s_env_data.su03t_last_msgno = frame->msgno;
s_env_data.su03t_rx_count++; s_env_data.su03t_rx_count++;
@@ -332,9 +349,11 @@ static void hx711_task(void *arg)
int tare_ok_count = 0; int tare_ok_count = 0;
// 上电空载自动去皮:当前重量作为 0g 基准 // 上电空载自动去皮:当前重量作为 0g 基准
for (int i = 0; i < HX711_TARE_SAMPLES; ++i) { for (int i = 0; i < HX711_TARE_SAMPLES; ++i)
{
int32_t raw = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS)); int32_t raw = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS));
if (raw != HX711::kUndefined) { if (raw != HX711::kUndefined)
{
tare_sum += raw; tare_sum += raw;
tare_ok_count++; tare_ok_count++;
} }
@@ -342,10 +361,13 @@ static void hx711_task(void *arg)
} }
int32_t tare_offset = 0; int32_t tare_offset = 0;
if (tare_ok_count > 0) { if (tare_ok_count > 0)
{
tare_offset = (int32_t)(tare_sum / tare_ok_count); tare_offset = (int32_t)(tare_sum / tare_ok_count);
ESP_LOGI(TAG, "HX711 tare done: raw0=%ld, samples=%d", (long)tare_offset, tare_ok_count); ESP_LOGI(TAG, "HX711 tare done: raw0=%ld, samples=%d", (long)tare_offset, tare_ok_count);
} else { }
else
{
ESP_LOGW(TAG, "HX711 tare failed, use 0 as offset"); ESP_LOGW(TAG, "HX711 tare failed, use 0 as offset");
} }
@@ -355,14 +377,18 @@ static void hx711_task(void *arg)
bool display_locked = false; bool display_locked = false;
uint32_t stable_count = 0; uint32_t stable_count = 0;
uint32_t err_cnt = 0; uint32_t err_cnt = 0;
for (;;) { for (;;)
{
int32_t value = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS)); int32_t value = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS));
if (value != HX711::kUndefined) { if (value != HX711::kUndefined)
{
float weight_g = ((float)(value - tare_offset)) / HX711_COUNTS_PER_GRAM; float weight_g = ((float)(value - tare_offset)) / HX711_COUNTS_PER_GRAM;
if (fabsf(weight_g) < HX711_ZERO_DEADBAND_G) { if (fabsf(weight_g) < HX711_ZERO_DEADBAND_G)
{
weight_g = 0.0f; weight_g = 0.0f;
} }
if (weight_g < 0.0f) { if (weight_g < 0.0f)
{
weight_g = 0.0f; weight_g = 0.0f;
} }
@@ -370,7 +396,8 @@ static void hx711_task(void *arg)
filtered_weight_g = filtered_weight_g * (1.0f - HX711_FILTER_ALPHA) + weight_g * HX711_FILTER_ALPHA; filtered_weight_g = filtered_weight_g * (1.0f - HX711_FILTER_ALPHA) + weight_g * HX711_FILTER_ALPHA;
float rounded_weight_g = roundf(filtered_weight_g * 100.0f) / 100.0f; float rounded_weight_g = roundf(filtered_weight_g * 100.0f) / 100.0f;
if (!display_initialized) { if (!display_initialized)
{
display_weight_g = rounded_weight_g; display_weight_g = rounded_weight_g;
display_initialized = true; display_initialized = true;
} }
@@ -378,40 +405,53 @@ static void hx711_task(void *arg)
float diff_from_display = fabsf(rounded_weight_g - display_weight_g); float diff_from_display = fabsf(rounded_weight_g - display_weight_g);
// 稳定后锁定显示,重量明显变化时再解锁并继续更新 // 稳定后锁定显示,重量明显变化时再解锁并继续更新
if (display_locked) { if (display_locked)
if (diff_from_display >= HX711_UNLOCK_DELTA_G) { {
if (diff_from_display >= HX711_UNLOCK_DELTA_G)
{
display_locked = false; display_locked = false;
stable_count = 0; stable_count = 0;
} }
} }
if (!display_locked) { if (!display_locked)
if (diff_from_display <= HX711_STABLE_BAND_G) { {
if (stable_count < HX711_STABLE_SAMPLES) { if (diff_from_display <= HX711_STABLE_BAND_G)
{
if (stable_count < HX711_STABLE_SAMPLES)
{
stable_count++; stable_count++;
} }
if (stable_count >= HX711_STABLE_SAMPLES) { if (stable_count >= HX711_STABLE_SAMPLES)
{
display_locked = true; display_locked = true;
} }
} else { }
else
{
stable_count = 0; stable_count = 0;
} }
if (diff_from_display >= HX711_UPDATE_MIN_STEP_G) { if (diff_from_display >= HX711_UPDATE_MIN_STEP_G)
{
display_weight_g = rounded_weight_g; display_weight_g = rounded_weight_g;
} }
} }
set_var_ice_weight(display_weight_g); set_var_ice_weight(display_weight_g);
if (s_env_data_lock) { if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY); xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.ice_weight = display_weight_g; s_env_data.ice_weight = display_weight_g;
xSemaphoreGive(s_env_data_lock); xSemaphoreGive(s_env_data_lock);
} }
err_cnt = 0; err_cnt = 0;
} else { }
if ((++err_cnt % 20) == 0) { else
{
if ((++err_cnt % 20) == 0)
{
ESP_LOGW(TAG, "HX711 read timeout, check DOUT/SCK wiring and power"); ESP_LOGW(TAG, "HX711 read timeout, check DOUT/SCK wiring and power");
} }
} }
@@ -449,6 +489,12 @@ extern "C" void app_main(void)
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000))) if (wait_for_wifi_connected(pdMS_TO_TICKS(15000)))
{ {
set_var_system_ip(wifi_connect_get_ip()); set_var_system_ip(wifi_connect_get_ip());
esp_err_t err = agri_env_mqtt_start();
if (err != ESP_OK)
{
ESP_LOGW(TAG, "MQTT 启动失败: %s", esp_err_to_name(err));
}
} }
// 4. 独立 SNTP 对时任务 // 4. 独立 SNTP 对时任务