mirror of
https://git.beihong.wang/wangbeihong/iot-bedroom-environment-controller.git
synced 2026-04-23 16:43:03 +08:00
1179 lines
61 KiB
C
1179 lines
61 KiB
C
#include "mqtt_manager.h"
|
||
#include "esp_log.h"
|
||
#include "mqtt_client.h"
|
||
#include "cJSON.h"
|
||
#include "esp_mac.h"
|
||
#include "esp_err.h"
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "freertos/semphr.h"
|
||
#include <time.h>
|
||
#include <math.h>
|
||
#include <string.h>
|
||
#include <inttypes.h>
|
||
|
||
#include "sensors.h"
|
||
#include "common.h"
|
||
#include "time_alarm.h"
|
||
#include "app_state.h"
|
||
#include "vars.h"
|
||
|
||
static const char *TAG = "mqtt_manager";
|
||
|
||
// MQTT 配置
|
||
#define MQTT_BROKER_URL "mqtt://beihong.wang:1883"
|
||
#define MQTT_USERNAME "esp_mqtt_client"
|
||
#define MQTT_CLIENT_ID "esp_mqtt_client"
|
||
#define MQTT_PASSWORD "664hd78gas97"
|
||
|
||
#define MQTT_PUBLISH_TOPIC_QOS0 "topic/sensor/esp32_iothome_001"
|
||
#define MQTT_NOTIFY_TOPIC "topic/control/esp32_iothome_001"
|
||
#define MQTT_UNSUBSCRIBE_TOPIC "topic/control/esp32_iothome_001"
|
||
|
||
#define MQTT_MAX_RETRY_COUNT 5
|
||
#define MQTT_RETRY_DELAY_MS 5000
|
||
|
||
esp_mqtt_client_handle_t g_mqtt_client = NULL;
|
||
|
||
// 全局静态 JSON 缓冲区(4KB),用于所有 JSON 打印,防止堆碎片化
|
||
static char GLOBAL_MQTT_JSON_BUFFER[4096];
|
||
static bool g_mqtt_connected = false;
|
||
static int g_mqtt_retry_count = 0;
|
||
|
||
// 静态任务缓冲区:使用静态创建避免在堆上分配任务栈/TCB
|
||
|
||
|
||
// app_state.h 提供共享状态的 extern 声明;保留跨模块函数声明
|
||
extern sensor_data_t g_sensor_data;
|
||
extern void alarm_set_time(uint8_t alarm_idx, uint8_t hour, uint8_t minute, uint8_t second);
|
||
extern void alarm_set_enable(uint8_t alarm_idx, bool enable);
|
||
|
||
// MQTT 发布任务:构建 JSON 并发布到 broker
|
||
void mqtt_publish_task(void *pvParameters)
|
||
{
|
||
TickType_t last_wake_time = xTaskGetTickCount();
|
||
|
||
// 初始化设备消息
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
strcpy(g_device_message.device_id, "esp32_bedroom_001");
|
||
strcpy(g_device_message.device_type, "bedroom_controller");
|
||
|
||
time_t now;
|
||
time(&now);
|
||
g_device_message.timestamp = (uint64_t)(now * 1000ULL);
|
||
|
||
g_device_message.state.online = true;
|
||
strcpy(g_device_message.state.current_mode, "night_mode");
|
||
g_device_message.state.home_status = true;
|
||
g_device_message.state.standby_mode = false;
|
||
g_device_message.state.error_code = 0;
|
||
g_device_message.state.auto_mode = false; // 默认手动模式
|
||
set_var_mode("Manual"); // 初始化屏幕模式显示
|
||
|
||
g_device_message.telemetry.temperature = 0;
|
||
g_device_message.telemetry.humidity = 0;
|
||
g_device_message.telemetry.light_intensity = 0;
|
||
strcpy(g_device_message.telemetry.curtain_state, "close");
|
||
strcpy(g_device_message.telemetry.led_state, "close");
|
||
g_device_message.telemetry.led_power = 0;
|
||
strcpy(g_device_message.telemetry.fan_state, "close");
|
||
strcpy(g_device_message.telemetry.buzzer_state, "close");
|
||
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
|
||
while (1)
|
||
{
|
||
// 只有在MQTT连接时才更新传感器数据并生成JSON消息
|
||
if (g_mqtt_connected)
|
||
{
|
||
if (xSensorDataMutex != NULL && xSemaphoreTake(xSensorDataMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
if (g_sensor_data.ahtxx_valid)
|
||
{
|
||
g_device_message.telemetry.temperature = g_sensor_data.temperature;
|
||
g_device_message.telemetry.humidity = g_sensor_data.humidity;
|
||
}
|
||
if (g_sensor_data.bh1750_valid)
|
||
{
|
||
g_device_message.telemetry.light_intensity = g_sensor_data.lux;
|
||
}
|
||
if (g_sensor_data.sgp30_valid)
|
||
{
|
||
g_device_message.telemetry.co2_ppm = g_sensor_data.co2_ppm;
|
||
g_device_message.telemetry.tvoc_ppb = g_sensor_data.tvoc_ppb;
|
||
}
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
xSemaphoreGive(xSensorDataMutex);
|
||
}
|
||
}
|
||
|
||
char *json_message = NULL;
|
||
bool json_heap_alloc = false;
|
||
// 只有在MQTT连接时才生成JSON消息
|
||
if (g_mqtt_connected && xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
time_t now;
|
||
time(&now);
|
||
g_device_message.timestamp = (uint64_t)(now * 1000ULL);
|
||
|
||
cJSON *root = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(root, "type", "device_message");
|
||
cJSON_AddStringToObject(root, "device_id", g_device_message.device_id);
|
||
cJSON_AddStringToObject(root, "device_type", g_device_message.device_type);
|
||
// 使用字符串表示时间戳,避免浮点数转换的栈溢出问题
|
||
char timestamp_str[32];
|
||
snprintf(timestamp_str, sizeof(timestamp_str), "%llu", g_device_message.timestamp);
|
||
cJSON_AddStringToObject(root, "timestamp", timestamp_str);
|
||
cJSON_AddStringToObject(root, "message_type", "sensor_data");
|
||
|
||
cJSON *data_obj = cJSON_CreateObject();
|
||
cJSON_AddItemToObject(root, "data", data_obj);
|
||
|
||
cJSON *state_obj = cJSON_CreateObject();
|
||
cJSON_AddBoolToObject(state_obj, "online", g_device_message.state.online);
|
||
cJSON_AddStringToObject(state_obj, "current_mode", g_device_message.state.current_mode);
|
||
cJSON_AddBoolToObject(state_obj, "standby_mode", g_device_message.state.standby_mode);
|
||
cJSON_AddNumberToObject(state_obj, "error_code", g_device_message.state.error_code);
|
||
cJSON_AddBoolToObject(state_obj, "auto_mode", g_device_message.state.auto_mode);
|
||
cJSON_AddItemToObject(data_obj, "state", state_obj);
|
||
|
||
cJSON *telemetry_obj = cJSON_CreateObject();
|
||
// 使用字符串表示浮点数,避免栈溢出问题
|
||
char temp_str[16];
|
||
char humidity_str[16];
|
||
char light_str[16];
|
||
snprintf(temp_str, sizeof(temp_str), "%.2f", roundf(g_device_message.telemetry.temperature * 100) / 100);
|
||
snprintf(humidity_str, sizeof(humidity_str), "%.2f", roundf(g_device_message.telemetry.humidity * 100) / 100);
|
||
snprintf(light_str, sizeof(light_str), "%.2f", roundf(g_device_message.telemetry.light_intensity * 100) / 100);
|
||
cJSON_AddStringToObject(telemetry_obj, "temperature", temp_str);
|
||
cJSON_AddStringToObject(telemetry_obj, "humidity", humidity_str);
|
||
cJSON_AddStringToObject(telemetry_obj, "light_intensity", light_str);
|
||
cJSON_AddStringToObject(telemetry_obj, "curtain_state", g_device_message.telemetry.curtain_state);
|
||
cJSON_AddStringToObject(telemetry_obj, "led_state", g_device_message.telemetry.led_state);
|
||
cJSON_AddNumberToObject(telemetry_obj, "led_power", g_device_message.telemetry.led_power);
|
||
cJSON_AddStringToObject(telemetry_obj, "fan_state", g_device_message.telemetry.fan_state);
|
||
cJSON_AddStringToObject(telemetry_obj, "buzzer_state", g_device_message.telemetry.buzzer_state);
|
||
// 添加 SGP30 传感器数据
|
||
cJSON_AddNumberToObject(telemetry_obj, "co2_ppm", g_device_message.telemetry.co2_ppm);
|
||
cJSON_AddNumberToObject(telemetry_obj, "tvoc_ppb", g_device_message.telemetry.tvoc_ppb);
|
||
cJSON_AddItemToObject(data_obj, "telemetry", telemetry_obj);
|
||
|
||
// 优先将 JSON 打印到全局静态缓冲区,避免堆分配;若缓冲不足再回退到 cJSON_Print
|
||
bool printed = cJSON_PrintPreallocated(root, GLOBAL_MQTT_JSON_BUFFER, sizeof(GLOBAL_MQTT_JSON_BUFFER), 0);
|
||
if (printed)
|
||
{
|
||
json_message = GLOBAL_MQTT_JSON_BUFFER; // 直接使用全局内存
|
||
json_heap_alloc = false;
|
||
}
|
||
else
|
||
{
|
||
json_message = cJSON_Print(root);
|
||
json_heap_alloc = (json_message != NULL);
|
||
}
|
||
cJSON_Delete(root);
|
||
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
|
||
if (json_message != NULL)
|
||
{
|
||
ESP_LOGI(TAG, "准备发布MQTT消息:");
|
||
ESP_LOGI(TAG, "Topic: %s", MQTT_PUBLISH_TOPIC_QOS0);
|
||
if (g_mqtt_client != NULL && g_mqtt_connected)
|
||
{
|
||
int msg_id = esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message, 0, 0, 0);
|
||
ESP_LOGI(TAG, "MQTT消息已发布, msg_id=%d", msg_id);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "MQTT客户端未连接,跳过发布");
|
||
}
|
||
if (json_heap_alloc)
|
||
{
|
||
free(json_message);
|
||
}
|
||
}
|
||
|
||
uint32_t delay_ms = 3000;
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, 10) == pdTRUE)
|
||
{
|
||
if (!g_device_message.state.home_status)
|
||
{
|
||
delay_ms = 50000;
|
||
}
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
|
||
vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(delay_ms));
|
||
}
|
||
}
|
||
|
||
void mqtt_manager_publish_feedback(void)
|
||
{
|
||
if (g_mqtt_client == NULL || !g_mqtt_connected)
|
||
return;
|
||
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
time_t now;
|
||
time(&now);
|
||
g_device_message.timestamp = (uint64_t)(now * 1000ULL);
|
||
|
||
cJSON *root = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(root, "type", "device_message");
|
||
cJSON_AddStringToObject(root, "device_id", g_device_message.device_id);
|
||
cJSON_AddStringToObject(root, "device_type", g_device_message.device_type);
|
||
// 使用字符串表示时间戳,避免浮点数转换的栈溢出问题
|
||
char timestamp_str[32];
|
||
snprintf(timestamp_str, sizeof(timestamp_str), "%llu", g_device_message.timestamp);
|
||
cJSON_AddStringToObject(root, "timestamp", timestamp_str);
|
||
cJSON_AddStringToObject(root, "message_type", "control_feedback");
|
||
|
||
cJSON *data_obj = cJSON_CreateObject();
|
||
cJSON_AddItemToObject(root, "data", data_obj);
|
||
|
||
cJSON *state_obj = cJSON_CreateObject();
|
||
cJSON_AddBoolToObject(state_obj, "online", g_device_message.state.online);
|
||
cJSON_AddStringToObject(state_obj, "current_mode", g_device_message.state.current_mode);
|
||
cJSON_AddBoolToObject(state_obj, "standby_mode", g_device_message.state.standby_mode);
|
||
cJSON_AddNumberToObject(state_obj, "error_code", g_device_message.state.error_code);
|
||
cJSON_AddBoolToObject(state_obj, "auto_mode", g_device_message.state.auto_mode);
|
||
cJSON_AddItemToObject(data_obj, "state", state_obj);
|
||
|
||
cJSON *telemetry_obj = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(telemetry_obj, "fan_state", g_device_message.telemetry.fan_state);
|
||
cJSON_AddStringToObject(telemetry_obj, "led_state", g_device_message.telemetry.led_state);
|
||
cJSON_AddNumberToObject(telemetry_obj, "led_power", g_device_message.telemetry.led_power);
|
||
cJSON_AddStringToObject(telemetry_obj, "curtain_state", g_device_message.telemetry.curtain_state);
|
||
cJSON_AddStringToObject(telemetry_obj, "buzzer_state", g_device_message.telemetry.buzzer_state);
|
||
// 添加 SGP30 传感器数据
|
||
cJSON_AddNumberToObject(telemetry_obj, "co2_ppm", g_device_message.telemetry.co2_ppm);
|
||
cJSON_AddNumberToObject(telemetry_obj, "tvoc_ppb", g_device_message.telemetry.tvoc_ppb);
|
||
cJSON_AddItemToObject(data_obj, "telemetry", telemetry_obj);
|
||
|
||
// 添加闹钟状态信息
|
||
cJSON *alarms_obj = cJSON_CreateArray();
|
||
for (int i = 0; i < ALARM_MAX_NUM; i++)
|
||
{
|
||
uint8_t hour, minute, second;
|
||
bool enabled, triggered;
|
||
alarm_get_status(i, &hour, &minute, &second, &enabled, &triggered);
|
||
|
||
cJSON *alarm_obj = cJSON_CreateObject();
|
||
char alarm_name[32]; // 增加缓冲区大小以避免截断
|
||
snprintf(alarm_name, sizeof(alarm_name), "alarm%d", i + 1);
|
||
cJSON_AddStringToObject(alarm_obj, "name", alarm_name);
|
||
|
||
char time_str[32]; // 增加缓冲区大小以避免截断
|
||
snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d", hour, minute, second);
|
||
cJSON_AddStringToObject(alarm_obj, "time", time_str);
|
||
cJSON_AddBoolToObject(alarm_obj, "enabled", enabled);
|
||
cJSON_AddBoolToObject(alarm_obj, "triggered", triggered);
|
||
|
||
cJSON_AddItemToArray(alarms_obj, alarm_obj);
|
||
}
|
||
cJSON_AddItemToObject(data_obj, "alarms", alarms_obj);
|
||
|
||
// 优先写入静态缓冲,减少堆分配
|
||
char *json_message_fb = NULL;
|
||
bool json_fb_heap = false;
|
||
bool printed_fb = cJSON_PrintPreallocated(root, GLOBAL_MQTT_JSON_BUFFER, sizeof(GLOBAL_MQTT_JSON_BUFFER), 0);
|
||
if (printed_fb)
|
||
{
|
||
json_message_fb = GLOBAL_MQTT_JSON_BUFFER;
|
||
json_fb_heap = false;
|
||
}
|
||
else
|
||
{
|
||
json_message_fb = cJSON_Print(root);
|
||
json_fb_heap = (json_message_fb != NULL);
|
||
}
|
||
cJSON_Delete(root);
|
||
if (json_message_fb)
|
||
{
|
||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message_fb, 0, 0, 0);
|
||
if (json_fb_heap)
|
||
{
|
||
free(json_message_fb);
|
||
}
|
||
}
|
||
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
}
|
||
|
||
// 发布闹钟状态消息
|
||
void mqtt_manager_publish_alarm_status(int alarm_idx, const char *alarm_status, bool triggered)
|
||
{
|
||
if (g_mqtt_client == NULL || !g_mqtt_connected)
|
||
return;
|
||
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
time_t now;
|
||
time(&now);
|
||
g_device_message.timestamp = (uint64_t)(now * 1000ULL);
|
||
|
||
cJSON *root = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(root, "type", "device_message");
|
||
cJSON_AddStringToObject(root, "device_id", g_device_message.device_id);
|
||
cJSON_AddStringToObject(root, "device_type", g_device_message.device_type);
|
||
// 使用字符串表示时间戳,避免浮点数转换的栈溢出问题
|
||
char timestamp_str[32];
|
||
snprintf(timestamp_str, sizeof(timestamp_str), "%llu", g_device_message.timestamp);
|
||
cJSON_AddStringToObject(root, "timestamp", timestamp_str);
|
||
cJSON_AddStringToObject(root, "message_type", "alarm_status");
|
||
|
||
cJSON *data_obj = cJSON_CreateObject();
|
||
cJSON_AddItemToObject(root, "data", data_obj);
|
||
|
||
cJSON *alarm_obj = cJSON_CreateObject();
|
||
char alarm_name[32]; // 增加缓冲区大小以避免截断
|
||
snprintf(alarm_name, sizeof(alarm_name), "alarm%d", alarm_idx + 1);
|
||
cJSON_AddStringToObject(alarm_obj, "alarm_name", alarm_name);
|
||
cJSON_AddStringToObject(alarm_obj, "status", alarm_status);
|
||
cJSON_AddBoolToObject(alarm_obj, "triggered", triggered);
|
||
cJSON_AddItemToObject(data_obj, "alarm", alarm_obj);
|
||
|
||
// 包含当前设备状态
|
||
cJSON *state_obj = cJSON_CreateObject();
|
||
cJSON_AddBoolToObject(state_obj, "online", g_device_message.state.online);
|
||
cJSON_AddStringToObject(state_obj, "current_mode", g_device_message.state.current_mode);
|
||
cJSON_AddBoolToObject(state_obj, "standby_mode", g_device_message.state.standby_mode);
|
||
cJSON_AddNumberToObject(state_obj, "error_code", g_device_message.state.error_code);
|
||
cJSON_AddBoolToObject(state_obj, "auto_mode", g_device_message.state.auto_mode);
|
||
cJSON_AddItemToObject(data_obj, "state", state_obj);
|
||
|
||
// 包含当前设备遥测数据
|
||
cJSON *telemetry_obj = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(telemetry_obj, "fan_state", g_device_message.telemetry.fan_state);
|
||
cJSON_AddStringToObject(telemetry_obj, "led_state", g_device_message.telemetry.led_state);
|
||
cJSON_AddNumberToObject(telemetry_obj, "led_power", g_device_message.telemetry.led_power);
|
||
cJSON_AddStringToObject(telemetry_obj, "curtain_state", g_device_message.telemetry.curtain_state);
|
||
cJSON_AddStringToObject(telemetry_obj, "buzzer_state", g_device_message.telemetry.buzzer_state);
|
||
cJSON_AddItemToObject(data_obj, "telemetry", telemetry_obj);
|
||
|
||
// 优先写入静态缓冲,减少堆分配
|
||
char *json_message_alarm = NULL;
|
||
bool json_alarm_heap = false;
|
||
bool printed_alarm = cJSON_PrintPreallocated(root, GLOBAL_MQTT_JSON_BUFFER, sizeof(GLOBAL_MQTT_JSON_BUFFER), 0);
|
||
if (printed_alarm)
|
||
{
|
||
json_message_alarm = GLOBAL_MQTT_JSON_BUFFER;
|
||
json_alarm_heap = false;
|
||
}
|
||
else
|
||
{
|
||
json_message_alarm = cJSON_Print(root);
|
||
json_alarm_heap = (json_message_alarm != NULL);
|
||
}
|
||
cJSON_Delete(root);
|
||
if (json_message_alarm)
|
||
{
|
||
ESP_LOGI(TAG, "发布闹钟状态: alarm%d %s (triggered=%d)", alarm_idx + 1, alarm_status, triggered);
|
||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message_alarm, 0, 0, 0);
|
||
if (json_alarm_heap)
|
||
{
|
||
free(json_message_alarm);
|
||
}
|
||
}
|
||
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
}
|
||
|
||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||
{
|
||
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
|
||
esp_mqtt_event_handle_t event = event_data;
|
||
esp_mqtt_client_handle_t client = event->client;
|
||
int msg_id;
|
||
|
||
switch ((esp_mqtt_event_id_t)event_id)
|
||
{
|
||
case MQTT_EVENT_CONNECTED:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||
g_mqtt_connected = true;
|
||
g_mqtt_retry_count = 0; // 重置重试计数器
|
||
msg_id = esp_mqtt_client_subscribe(client, MQTT_NOTIFY_TOPIC, 2);
|
||
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
|
||
break;
|
||
|
||
case MQTT_EVENT_DISCONNECTED:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||
g_mqtt_connected = false;
|
||
|
||
// 如果不是手动停止,尝试重连
|
||
if (g_mqtt_client != NULL && g_mqtt_retry_count < MQTT_MAX_RETRY_COUNT)
|
||
{
|
||
g_mqtt_retry_count++;
|
||
ESP_LOGI(TAG, "MQTT断开连接,%d秒后尝试重连 (第%d次)",
|
||
MQTT_RETRY_DELAY_MS/1000, g_mqtt_retry_count);
|
||
|
||
// 延迟后重连
|
||
vTaskDelay(pdMS_TO_TICKS(MQTT_RETRY_DELAY_MS));
|
||
esp_mqtt_client_reconnect(g_mqtt_client);
|
||
}
|
||
else if (g_mqtt_retry_count >= MQTT_MAX_RETRY_COUNT)
|
||
{
|
||
ESP_LOGW(TAG, "MQTT重连次数已达上限(%d次),停止重试", MQTT_MAX_RETRY_COUNT);
|
||
}
|
||
break;
|
||
|
||
case MQTT_EVENT_SUBSCRIBED:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
|
||
msg_id = esp_mqtt_client_publish(client, MQTT_PUBLISH_TOPIC_QOS0, "data", 0, 0, 0);
|
||
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
|
||
break;
|
||
|
||
case MQTT_EVENT_UNSUBSCRIBED:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
|
||
break;
|
||
|
||
case MQTT_EVENT_PUBLISHED:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
|
||
break;
|
||
|
||
case MQTT_EVENT_DATA:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
|
||
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
|
||
printf("DATA=%.*s\r\n", event->data_len, event->data);
|
||
|
||
if (strncmp(event->topic, MQTT_NOTIFY_TOPIC, event->topic_len) == 0)
|
||
{
|
||
cJSON *root = cJSON_ParseWithLength(event->data, event->data_len);
|
||
if (root == NULL)
|
||
{
|
||
ESP_LOGE(TAG, "Failed to parse JSON data");
|
||
break;
|
||
}
|
||
|
||
cJSON *type_obj = cJSON_GetObjectItem(root, "type");
|
||
if (type_obj != NULL && cJSON_IsString(type_obj) && strcmp(type_obj->valuestring, "status") == 0)
|
||
{
|
||
cJSON *subtype_obj = cJSON_GetObjectItem(root, "subtype");
|
||
cJSON *status_obj = cJSON_GetObjectItem(root, "status");
|
||
const char *status_str = NULL;
|
||
if (subtype_obj != NULL && cJSON_IsString(subtype_obj))
|
||
{
|
||
status_str = cJSON_GetStringValue(subtype_obj);
|
||
}
|
||
else if (status_obj != NULL && cJSON_IsString(status_obj))
|
||
{
|
||
status_str = cJSON_GetStringValue(status_obj);
|
||
}
|
||
|
||
if (status_str != NULL && strcmp(status_str, "online") == 0)
|
||
{
|
||
ESP_LOGI(TAG, "Received status online message, starting reporting");
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
g_device_message.state.online = true;
|
||
g_device_message.state.home_status = true;
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
cJSON_Delete(root);
|
||
break;
|
||
}
|
||
|
||
if (type_obj == NULL || !cJSON_IsString(type_obj))
|
||
{
|
||
ESP_LOGE(TAG, "Invalid message type");
|
||
cJSON_Delete(root);
|
||
break;
|
||
}
|
||
|
||
cJSON *device_id_obj = cJSON_GetObjectItem(root, "device_id");
|
||
cJSON *device_type_obj = cJSON_GetObjectItem(root, "device_type");
|
||
if (device_id_obj == NULL || !cJSON_IsString(device_id_obj) ||
|
||
device_type_obj == NULL || !cJSON_IsString(device_type_obj))
|
||
{
|
||
ESP_LOGE(TAG, "Missing device identification");
|
||
cJSON_Delete(root);
|
||
break;
|
||
}
|
||
|
||
if (strcmp(cJSON_GetStringValue(device_id_obj), "esp32_bedroom_001") != 0 ||
|
||
strcmp(cJSON_GetStringValue(device_type_obj), "bedroom_controller") != 0)
|
||
{
|
||
ESP_LOGE(TAG, "Device identification does not match");
|
||
cJSON_Delete(root);
|
||
break;
|
||
}
|
||
|
||
if (strcmp(type_obj->valuestring, "control_command") == 0)
|
||
{
|
||
cJSON *request_id_obj = cJSON_GetObjectItem(root, "request_id");
|
||
if (request_id_obj != NULL && cJSON_IsString(request_id_obj))
|
||
{
|
||
ESP_LOGI(TAG, "Received request_id: %s", request_id_obj->valuestring);
|
||
}
|
||
|
||
cJSON *message_type_obj = cJSON_GetObjectItem(root, "message_type");
|
||
if (message_type_obj != NULL && cJSON_IsString(message_type_obj))
|
||
{
|
||
const char *message_type = cJSON_GetStringValue(message_type_obj);
|
||
|
||
if (strcmp(message_type, "presence_request") == 0)
|
||
{
|
||
cJSON *data_obj = cJSON_GetObjectItem(root, "data");
|
||
if (data_obj != NULL && cJSON_IsObject(data_obj))
|
||
{
|
||
cJSON *presence_obj = cJSON_GetObjectItem(data_obj, "presence");
|
||
if (presence_obj != NULL && cJSON_IsString(presence_obj))
|
||
{
|
||
const char *presence_status = cJSON_GetStringValue(presence_obj);
|
||
ESP_LOGI(TAG, "Received presence status: %s", presence_status);
|
||
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
if (strcmp(presence_status, "home") == 0)
|
||
{
|
||
g_device_message.state.home_status = true;
|
||
led_backlight_on = true;
|
||
ESP_LOGI(TAG, "Device is present at home");
|
||
}
|
||
else if (strcmp(presence_status, "away") == 0)
|
||
{
|
||
g_device_message.state.home_status = false;
|
||
led_backlight_on = false;
|
||
ESP_LOGI(TAG, "Device is away from home");
|
||
}
|
||
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (strcmp(message_type, "control_request") == 0)
|
||
{
|
||
ESP_LOGI(TAG, "Received control_request message");
|
||
|
||
// 首先检查是否有config对象,用于设置自动/手动模式
|
||
cJSON *data_obj = cJSON_GetObjectItem(root, "data");
|
||
if (data_obj != NULL && cJSON_IsObject(data_obj))
|
||
{
|
||
// 检查是否有config.auto_mode字段
|
||
cJSON *config_obj = cJSON_GetObjectItem(data_obj, "config");
|
||
if (config_obj != NULL && cJSON_IsObject(config_obj))
|
||
{
|
||
cJSON *auto_mode_obj = cJSON_GetObjectItem(config_obj, "auto_mode");
|
||
if (auto_mode_obj != NULL && cJSON_IsBool(auto_mode_obj))
|
||
{
|
||
bool auto_mode = cJSON_IsTrue(auto_mode_obj);
|
||
ESP_LOGI(TAG, "Setting auto_mode to: %s", auto_mode ? "true (Auto)" : "false (Manual)");
|
||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
g_device_message.state.auto_mode = auto_mode;
|
||
if (auto_mode) {
|
||
strcpy(g_device_message.state.current_mode, "auto");
|
||
set_var_mode("Auto");
|
||
} else {
|
||
strcpy(g_device_message.state.current_mode, "manual");
|
||
set_var_mode("Manual");
|
||
}
|
||
xSemaphoreGive(xMqttMessageMutex);
|
||
}
|
||
|
||
// 自动/手动模式联动自动功能
|
||
if (auto_mode) {
|
||
g_cooling_mode_enabled = true;
|
||
g_use_time_period = true;
|
||
cooling_mode_save_to_nvs();
|
||
time_period_save_to_nvs();
|
||
ESP_LOGI(TAG, "自动模式: 已开启降温、时间段等自动功能");
|
||
} else {
|
||
g_cooling_mode_enabled = false;
|
||
g_use_time_period = false;
|
||
cooling_mode_save_to_nvs();
|
||
time_period_save_to_nvs();
|
||
ESP_LOGI(TAG, "手动模式: 已关闭降温、时间段等自动功能");
|
||
}
|
||
|
||
// 发布反馈消息
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "auto_mode field not found or not boolean in config");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 如果没有config对象,检查是否有controls对象
|
||
// 这样control_request消息也可以用于设备控制
|
||
cJSON *controls_obj = cJSON_GetObjectItem(data_obj, "controls");
|
||
if (controls_obj != NULL && cJSON_IsObject(controls_obj))
|
||
{
|
||
ESP_LOGI(TAG, "Processing control command in control_request");
|
||
// 直接在这里处理controls对象,避免goto带来的作用域问题
|
||
// 处理风扇控制
|
||
cJSON *fan_power_obj = cJSON_GetObjectItem(controls_obj, "fan_state");
|
||
if (fan_power_obj != NULL && cJSON_IsString(fan_power_obj))
|
||
{
|
||
const char *fan_power_str = cJSON_GetStringValue(fan_power_obj);
|
||
|
||
if (strcmp(fan_power_str, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.fan_state, "open");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
fan_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Fan turned OPEN");
|
||
}
|
||
else if (strcmp(fan_power_str, "close") == 0 || strcmp(fan_power_str, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.fan_state, "close");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
fan_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Fan turned CLOSE");
|
||
}
|
||
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
// 处理窗帘控制
|
||
cJSON *curtain_obj = cJSON_GetObjectItem(controls_obj, "curtain_state");
|
||
if (curtain_obj != NULL && cJSON_IsString(curtain_obj))
|
||
{
|
||
const char *curtain_str = cJSON_GetStringValue(curtain_obj);
|
||
|
||
if (strcmp(curtain_str, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.curtain_state, "open");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
servo_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Curtain opened");
|
||
}
|
||
else if (strcmp(curtain_str, "close") == 0 || strcmp(curtain_str, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.curtain_state, "close");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
servo_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Curtain closed");
|
||
}
|
||
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
// 处理蜂鸣器控制
|
||
cJSON *buzzer_power_obj = cJSON_GetObjectItem(controls_obj, "buzzer_state");
|
||
if (buzzer_power_obj != NULL && cJSON_IsString(buzzer_power_obj))
|
||
{
|
||
const char *buzzer_power_str = cJSON_GetStringValue(buzzer_power_obj);
|
||
|
||
if (strcmp(buzzer_power_str, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.buzzer_state, "open");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Buzzer turned OPEN");
|
||
}
|
||
else if (strcmp(buzzer_power_str, "close") == 0 || strcmp(buzzer_power_str, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.buzzer_state, "close");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Buzzer turned CLOSE");
|
||
}
|
||
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
// 处理LED控制
|
||
cJSON *led_state_obj = cJSON_GetObjectItem(controls_obj, "led_state");
|
||
cJSON *led_power_obj = cJSON_GetObjectItem(controls_obj, "led_power");
|
||
bool led_changed = false;
|
||
|
||
if (led_power_obj != NULL && cJSON_IsNumber(led_power_obj))
|
||
{
|
||
int power = cJSON_GetNumberValue(led_power_obj);
|
||
if (power < 0)
|
||
power = 0;
|
||
if (power > 100)
|
||
power = 100;
|
||
|
||
g_device_message.telemetry.led_power = (uint8_t)power;
|
||
strcpy(g_device_message.telemetry.led_state, power > 0 ? "open" : "close");
|
||
|
||
led_brightness_value = (uint8_t)power;
|
||
light_source_control_flag = (power > 0);
|
||
|
||
ESP_LOGI(TAG, "LED power set to %d", power);
|
||
led_changed = true;
|
||
}
|
||
else if (led_state_obj != NULL && cJSON_IsString(led_state_obj))
|
||
{
|
||
const char *state = cJSON_GetStringValue(led_state_obj);
|
||
|
||
if (strcmp(state, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.led_state, "open");
|
||
g_device_message.telemetry.led_power = 100;
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
led_brightness_value = 100;
|
||
light_source_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "LED turned OPEN");
|
||
led_changed = true;
|
||
}
|
||
else if (strcmp(state, "close") == 0 || strcmp(state, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.led_state, "close");
|
||
g_device_message.telemetry.led_power = 0;
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
led_brightness_value = 0;
|
||
light_source_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "LED turned CLOSE");
|
||
led_changed = true;
|
||
}
|
||
}
|
||
|
||
if (led_changed)
|
||
{
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
// 处理闹钟设置
|
||
ESP_LOGI(TAG, "Starting alarm processing in control_request");
|
||
for (int ai = 1; ai <= ALARM_MAX_NUM; ai++)
|
||
{
|
||
char key_time[32];
|
||
char key_enable[32];
|
||
snprintf(key_time, sizeof(key_time), "alarm%d_time", ai);
|
||
snprintf(key_enable, sizeof(key_enable), "alarm%d_enable", ai);
|
||
|
||
ESP_LOGI(TAG, "Checking for alarm keys: %s, %s", key_time, key_enable);
|
||
|
||
cJSON *alarm_time_obj = cJSON_GetObjectItem(controls_obj, key_time);
|
||
if (alarm_time_obj != NULL && cJSON_IsString(alarm_time_obj))
|
||
{
|
||
const char *time_str = cJSON_GetStringValue(alarm_time_obj);
|
||
ESP_LOGI(TAG, "Found alarm%d_time: %s", ai, time_str);
|
||
int hh = 0, mm = 0, ss = 0;
|
||
int parts = 0;
|
||
parts = sscanf(time_str, "%d:%d:%d", &hh, &mm, &ss);
|
||
ESP_LOGI(TAG, "Parsed time: parts=%d, hh=%d, mm=%d, ss=%d", parts, hh, mm, ss);
|
||
if (parts >= 2)
|
||
{
|
||
if (parts == 2)
|
||
ss = 0;
|
||
ESP_LOGI(TAG, "Set alarm%d time to %02d:%02d:%02d", ai, hh, mm, ss);
|
||
alarm_set_time(ai - 1, hh, mm, ss);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "Failed to parse alarm time: %s", time_str);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI(TAG, "No alarm%d_time found or not a string", ai);
|
||
}
|
||
|
||
cJSON *alarm_enable_obj = cJSON_GetObjectItem(controls_obj, key_enable);
|
||
if (alarm_enable_obj != NULL && cJSON_IsString(alarm_enable_obj))
|
||
{
|
||
const char *enable_str = cJSON_GetStringValue(alarm_enable_obj);
|
||
ESP_LOGI(TAG, "Found alarm%d_enable: %s", ai, enable_str);
|
||
bool enable = false;
|
||
if (strcasecmp(enable_str, "on") == 0 || strcasecmp(enable_str, "true") == 0 || strcasecmp(enable_str, "enable") == 0)
|
||
enable = true;
|
||
else
|
||
enable = false;
|
||
|
||
ESP_LOGI(TAG, "Set alarm%d enable = %s", ai, enable ? "true" : "false");
|
||
alarm_set_enable(ai - 1, enable);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI(TAG, "No alarm%d_enable found or not a string", ai);
|
||
}
|
||
|
||
if ((alarm_time_obj != NULL && cJSON_IsString(alarm_time_obj)) || (alarm_enable_obj != NULL && cJSON_IsString(alarm_enable_obj)))
|
||
{
|
||
ESP_LOGI(TAG, "Alarm settings changed, publishing feedback");
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "Neither config nor controls object found in data");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "data object not found in control_request");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 对于其他类型的消息,假设是control_command类型
|
||
cJSON *data_obj = cJSON_GetObjectItem(root, "data");
|
||
if (data_obj != NULL && cJSON_IsObject(data_obj))
|
||
{
|
||
cJSON *controls_obj = cJSON_GetObjectItem(data_obj, "controls");
|
||
if (controls_obj != NULL && cJSON_IsObject(controls_obj))
|
||
{
|
||
ESP_LOGI(TAG, "Processing control command");
|
||
|
||
// 处理风扇控制
|
||
cJSON *fan_power_obj = cJSON_GetObjectItem(controls_obj, "fan_state");
|
||
if (fan_power_obj != NULL && cJSON_IsString(fan_power_obj))
|
||
{
|
||
const char *fan_power_str = cJSON_GetStringValue(fan_power_obj);
|
||
|
||
if (strcmp(fan_power_str, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.fan_state, "open");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
fan_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Fan turned OPEN");
|
||
}
|
||
else if (strcmp(fan_power_str, "close") == 0 || strcmp(fan_power_str, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.fan_state, "close");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
fan_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Fan turned CLOSE");
|
||
}
|
||
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
cJSON *curtain_obj = cJSON_GetObjectItem(controls_obj, "curtain_state");
|
||
if (curtain_obj != NULL && cJSON_IsString(curtain_obj))
|
||
{
|
||
const char *curtain_str = cJSON_GetStringValue(curtain_obj);
|
||
|
||
if (strcmp(curtain_str, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.curtain_state, "open");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
servo_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Curtain opened");
|
||
}
|
||
else if (strcmp(curtain_str, "close") == 0 || strcmp(curtain_str, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.curtain_state, "close");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
servo_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Curtain closed");
|
||
}
|
||
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
cJSON *buzzer_power_obj = cJSON_GetObjectItem(controls_obj, "buzzer_state");
|
||
if (buzzer_power_obj != NULL && cJSON_IsString(buzzer_power_obj))
|
||
{
|
||
const char *buzzer_power_str = cJSON_GetStringValue(buzzer_power_obj);
|
||
|
||
if (strcmp(buzzer_power_str, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.buzzer_state, "open");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Buzzer turned OPEN");
|
||
}
|
||
else if (strcmp(buzzer_power_str, "close") == 0 || strcmp(buzzer_power_str, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.buzzer_state, "close");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Buzzer turned CLOSE");
|
||
}
|
||
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
cJSON *led_state_obj = cJSON_GetObjectItem(controls_obj, "led_state");
|
||
cJSON *led_power_obj = cJSON_GetObjectItem(controls_obj, "led_power");
|
||
|
||
if (led_power_obj != NULL && cJSON_IsNumber(led_power_obj))
|
||
{
|
||
int power = cJSON_GetNumberValue(led_power_obj);
|
||
if (power < 0)
|
||
power = 0;
|
||
if (power > 100)
|
||
power = 100;
|
||
|
||
g_device_message.telemetry.led_power = (uint8_t)power;
|
||
strcpy(g_device_message.telemetry.led_state, power > 0 ? "open" : "close");
|
||
|
||
led_brightness_value = (uint8_t)power;
|
||
light_source_control_flag = (power > 0);
|
||
|
||
ESP_LOGI(TAG, "LED power set to %d", power);
|
||
}
|
||
else if (led_state_obj != NULL && cJSON_IsString(led_state_obj))
|
||
{
|
||
const char *state = cJSON_GetStringValue(led_state_obj);
|
||
|
||
if (strcmp(state, "open") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.led_state, "open");
|
||
g_device_message.telemetry.led_power = 100;
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
led_brightness_value = 100;
|
||
light_source_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "LED turned OPEN");
|
||
}
|
||
else if (strcmp(state, "close") == 0 || strcmp(state, "closed") == 0)
|
||
{
|
||
strcpy(g_device_message.telemetry.led_state, "close");
|
||
g_device_message.telemetry.led_power = 0;
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
led_brightness_value = 0;
|
||
light_source_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "LED turned CLOSE");
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "Invalid LED command: %s", state);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
cJSON *day_period_start_obj = cJSON_GetObjectItem(controls_obj, "day_period_start");
|
||
cJSON *day_period_end_obj = cJSON_GetObjectItem(controls_obj, "day_period_end");
|
||
cJSON *night_period_start_obj = cJSON_GetObjectItem(controls_obj, "night_period_start");
|
||
cJSON *night_period_end_obj = cJSON_GetObjectItem(controls_obj, "night_period_end");
|
||
|
||
bool period_changed = false;
|
||
|
||
if (day_period_start_obj != NULL && cJSON_IsString(day_period_start_obj) &&
|
||
day_period_end_obj != NULL && cJSON_IsString(day_period_end_obj))
|
||
{
|
||
const char *start_str = cJSON_GetStringValue(day_period_start_obj);
|
||
const char *end_str = cJSON_GetStringValue(day_period_end_obj);
|
||
int start_hh = 0, start_mm = 0, end_hh = 0, end_mm = 0;
|
||
|
||
if (sscanf(start_str, "%d:%d", &start_hh, &start_mm) >= 2 &&
|
||
sscanf(end_str, "%d:%d", &end_hh, &end_mm) >= 2)
|
||
{
|
||
ESP_LOGI(TAG, "Set day period: %02d:%02d - %02d:%02d",
|
||
start_hh, start_mm, end_hh, end_mm);
|
||
time_period_set(TIME_PERIOD_DAY, start_hh, start_mm, end_hh, end_mm);
|
||
period_changed = true;
|
||
}
|
||
}
|
||
|
||
if (night_period_start_obj != NULL && cJSON_IsString(night_period_start_obj) &&
|
||
night_period_end_obj != NULL && cJSON_IsString(night_period_end_obj))
|
||
{
|
||
const char *start_str = cJSON_GetStringValue(night_period_start_obj);
|
||
const char *end_str = cJSON_GetStringValue(night_period_end_obj);
|
||
int start_hh = 0, start_mm = 0, end_hh = 0, end_mm = 0;
|
||
|
||
if (sscanf(start_str, "%d:%d", &start_hh, &start_mm) >= 2 &&
|
||
sscanf(end_str, "%d:%d", &end_hh, &end_mm) >= 2)
|
||
{
|
||
ESP_LOGI(TAG, "Set night period: %02d:%02d - %02d:%02d",
|
||
start_hh, start_mm, end_hh, end_mm);
|
||
time_period_set(TIME_PERIOD_NIGHT, start_hh, start_mm, end_hh, end_mm);
|
||
period_changed = true;
|
||
}
|
||
}
|
||
|
||
if (period_changed)
|
||
{
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
// 处理时间段启用控制
|
||
cJSON *use_time_period_obj = cJSON_GetObjectItem(controls_obj, "use_time_period");
|
||
if (use_time_period_obj != NULL && cJSON_IsBool(use_time_period_obj))
|
||
{
|
||
g_use_time_period = cJSON_IsTrue(use_time_period_obj) ? true : false;
|
||
time_period_save_to_nvs();
|
||
ESP_LOGI(TAG, "时间段控制 %s", g_use_time_period ? "启用" : "禁用");
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
cJSON *temp_threshold_obj = cJSON_GetObjectItem(controls_obj, "temperature_threshold");
|
||
if (temp_threshold_obj != NULL && cJSON_IsNumber(temp_threshold_obj))
|
||
{
|
||
float threshold = (float)cJSON_GetNumberValue(temp_threshold_obj);
|
||
if (threshold < 10.0f)
|
||
threshold = 10.0f;
|
||
if (threshold > 40.0f)
|
||
threshold = 40.0f;
|
||
|
||
g_temperature_threshold = threshold;
|
||
g_cooling_mode_enabled = true;
|
||
cooling_mode_save_to_nvs();
|
||
ESP_LOGI(TAG, "Cooling mode temperature threshold set to %.1f°C", g_temperature_threshold);
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
cJSON_Delete(root);
|
||
}
|
||
}
|
||
break;
|
||
case MQTT_EVENT_ERROR:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
|
||
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
|
||
{
|
||
ESP_LOGI(TAG, "Last error code reported from esp-tls: 0x%x", event->error_handle->esp_tls_last_esp_err);
|
||
ESP_LOGI(TAG, "Last tls stack error number: 0x%x", event->error_handle->esp_tls_stack_err);
|
||
ESP_LOGI(TAG, "Last captured errno : %d (%s)", event->error_handle->esp_transport_sock_errno,
|
||
strerror(event->error_handle->esp_transport_sock_errno));
|
||
|
||
// 检查是否是DNS解析错误
|
||
if (event->error_handle->esp_tls_last_esp_err == 0x8002 || // ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME
|
||
event->error_handle->esp_transport_sock_errno == 202 || // EAI_NONAME
|
||
event->error_handle->esp_transport_sock_errno == 203) // EAI_SERVICE
|
||
{
|
||
ESP_LOGE(TAG, "DNS解析失败!请检查MQTT服务器地址: %s", MQTT_BROKER_URL);
|
||
ESP_LOGE(TAG, "可能的解决方案:");
|
||
ESP_LOGE(TAG, "1. 检查域名 %s 是否正确", "beihong.wang");
|
||
ESP_LOGE(TAG, "2. 检查网络连接");
|
||
ESP_LOGE(TAG, "3. 尝试使用IP地址代替域名");
|
||
}
|
||
}
|
||
else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED)
|
||
{
|
||
ESP_LOGI(TAG, "Connection refused error: 0x%x", event->error_handle->connect_return_code);
|
||
ESP_LOGI(TAG, "Possible reasons: wrong username/password, server refused connection");
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW(TAG, "Unknown error type: 0x%x", event->error_handle->error_type);
|
||
}
|
||
|
||
// 记录错误后尝试重连
|
||
if (g_mqtt_client != NULL && g_mqtt_retry_count < MQTT_MAX_RETRY_COUNT)
|
||
{
|
||
g_mqtt_retry_count++;
|
||
ESP_LOGI(TAG, "MQTT error, will retry in %d seconds (attempt %d/%d)",
|
||
MQTT_RETRY_DELAY_MS/1000, g_mqtt_retry_count, MQTT_MAX_RETRY_COUNT);
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(MQTT_RETRY_DELAY_MS));
|
||
esp_mqtt_client_reconnect(g_mqtt_client);
|
||
}
|
||
break;
|
||
|
||
case MQTT_EVENT_ANY:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_ANY");
|
||
break;
|
||
case MQTT_EVENT_BEFORE_CONNECT:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_BEFORE_CONNECT");
|
||
break;
|
||
case MQTT_EVENT_DELETED:
|
||
ESP_LOGI(TAG, "MQTT_EVENT_DELETED");
|
||
break;
|
||
case MQTT_USER_EVENT:
|
||
ESP_LOGI(TAG, "MQTT_USER_EVENT");
|
||
break;
|
||
|
||
default:
|
||
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
|
||
break;
|
||
}
|
||
}
|
||
|
||
esp_err_t mqtt_manager_init(void)
|
||
{
|
||
ESP_LOGI(TAG, "mqtt_manager_init");
|
||
return ESP_OK;
|
||
}
|
||
|
||
esp_err_t mqtt_manager_start(void)
|
||
{
|
||
// 生成基于MAC地址的唯一客户端ID
|
||
char client_id[32];
|
||
uint8_t mac[6];
|
||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||
sprintf(client_id, "esp32_%02x%02x%02x", mac[3], mac[4], mac[5]);
|
||
ESP_LOGI(TAG, "Generated Client ID: %s", client_id);
|
||
|
||
esp_mqtt_client_config_t mqtt_cfg = {
|
||
.broker.address.uri = MQTT_BROKER_URL,
|
||
.credentials.username = MQTT_USERNAME,
|
||
.credentials.client_id = client_id,
|
||
.credentials.authentication.password = MQTT_PASSWORD,
|
||
};
|
||
|
||
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
|
||
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
||
esp_mqtt_client_start(client);
|
||
|
||
g_mqtt_client = client;
|
||
|
||
xTaskCreate(mqtt_publish_task, "mqtt_publish_task", 3046, NULL, 5, NULL);
|
||
return ESP_OK;
|
||
}
|
||
|
||
esp_err_t mqtt_manager_stop(void)
|
||
{
|
||
if (g_mqtt_client != NULL)
|
||
{
|
||
esp_mqtt_client_stop(g_mqtt_client);
|
||
esp_mqtt_client_destroy(g_mqtt_client);
|
||
g_mqtt_client = NULL;
|
||
}
|
||
return ESP_OK;
|
||
}
|