#include #include #include #include #include "cJSON.h" #include "esp_check.h" #include "esp_log.h" #include "esp_mac.h" #include "mqtt_control.h" // MQTT 服务器地址(协议+域名+端口) #define MQTT_BROKER_URL "mqtt://beihong.wang:1883" // MQTT 用户名 #define MQTT_USERNAME "BotanicalBuddy" // MQTT 密码 #define MQTT_PASSWORD "YTGui8979HI" // 传感器数据发布主题 #define MQTT_SENSOR_TOPIC "topic/sensor/esp32_BotanicalBuddy_001" // 控制指令订阅主题 #define MQTT_CONTROL_TOPIC "topic/control/esp32_BotanicalBuddy_001" static const char *TAG = "mqtt_control"; // 日志标签 static esp_mqtt_client_handle_t g_mqtt_client = NULL; // 全局 MQTT 客户端句柄 static bool g_mqtt_connected = false; // MQTT 连接状态标志 static mqtt_control_command_handler_t g_cmd_handler = NULL; static void *g_cmd_user_ctx = NULL; static bool json_read_bool(cJSON *root, const char *key, bool *out) { cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); if (item == NULL) { return false; } if (cJSON_IsBool(item)) { *out = cJSON_IsTrue(item); return true; } if (cJSON_IsNumber(item)) { *out = (item->valuedouble != 0.0); return true; } if (cJSON_IsString(item) && item->valuestring != NULL) { if (strcasecmp(item->valuestring, "on") == 0 || strcasecmp(item->valuestring, "true") == 0 || strcmp(item->valuestring, "1") == 0) { *out = true; return true; } if (strcasecmp(item->valuestring, "off") == 0 || strcasecmp(item->valuestring, "false") == 0 || strcmp(item->valuestring, "0") == 0) { *out = false; return true; } } return false; } static bool json_read_float(cJSON *root, const char *key, float *out) { cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); if (!cJSON_IsNumber(item)) { return false; } *out = (float)item->valuedouble; return true; } static bool json_read_mode_auto(cJSON *root, const char *key, bool *out_auto) { cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key); if (item == NULL) { return false; } if (cJSON_IsString(item) && item->valuestring != NULL) { if (strcasecmp(item->valuestring, "auto") == 0) { *out_auto = true; return true; } if (strcasecmp(item->valuestring, "manual") == 0) { *out_auto = false; return true; } } if (cJSON_IsBool(item)) { *out_auto = cJSON_IsTrue(item); return true; } if (cJSON_IsNumber(item)) { *out_auto = (item->valuedouble != 0.0); return true; } return false; } static esp_err_t mqtt_parse_control_command(const char *data, int data_len, mqtt_control_command_t *out_cmd) { ESP_RETURN_ON_FALSE(data != NULL && data_len > 0, ESP_ERR_INVALID_ARG, TAG, "invalid mqtt data"); ESP_RETURN_ON_FALSE(out_cmd != NULL, ESP_ERR_INVALID_ARG, TAG, "out_cmd is null"); memset(out_cmd, 0, sizeof(*out_cmd)); cJSON *root = cJSON_ParseWithLength(data, (size_t)data_len); ESP_RETURN_ON_FALSE(root != NULL, ESP_ERR_INVALID_ARG, TAG, "control json parse failed"); float soil_on = 0.0f; float soil_off = 0.0f; float light_on_lux = 0.0f; float light_off_lux = 0.0f; bool has_soil_on = json_read_float(root, "soil_on", &soil_on); bool has_soil_off = json_read_float(root, "soil_off", &soil_off); bool has_light_on = json_read_float(root, "light_on", &light_on_lux); bool has_light_off = json_read_float(root, "light_off", &light_off_lux); out_cmd->has_mode = json_read_mode_auto(root, "mode", &out_cmd->auto_mode); if (has_soil_on && has_soil_off && has_light_on && has_light_off) { out_cmd->has_thresholds = true; out_cmd->soil_on_pct = soil_on; out_cmd->soil_off_pct = soil_off; out_cmd->light_on_lux = light_on_lux; out_cmd->light_off_lux = light_off_lux; } out_cmd->has_pump = json_read_bool(root, "pump", &out_cmd->pump_on); out_cmd->has_light = json_read_bool(root, "light", &out_cmd->light_on); cJSON_Delete(root); ESP_RETURN_ON_FALSE(out_cmd->has_mode || out_cmd->has_thresholds || out_cmd->has_pump || out_cmd->has_light, ESP_ERR_INVALID_ARG, TAG, "no valid control fields in payload"); return ESP_OK; } /** * @brief 判断接收到的 MQTT 主题是否与预期主题匹配 * * @param event_topic 事件中的主题字符串 * @param event_topic_len 事件中主题的长度 * @param expected 预期的主题字符串 * @return true 匹配成功;false 匹配失败 */ static bool mqtt_topic_match(const char *event_topic, int event_topic_len, const char *expected) { size_t expected_len = strlen(expected); return expected_len == (size_t)event_topic_len && strncmp(event_topic, expected, expected_len) == 0; } /** * @brief MQTT 事件处理回调函数 * * 处理连接、断开、订阅、数据接收等事件。 * * @param handler_args 用户传入的参数(未使用) * @param base 事件基类型(ESP-MQTT) * @param event_id 具体事件 ID * @param event_data 事件数据指针 */ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { (void)handler_args; ESP_LOGD(TAG, "event base=%s id=%" PRIi32, base, event_id); esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data; esp_mqtt_client_handle_t client = event->client; switch ((esp_mqtt_event_id_t)event_id) { case MQTT_EVENT_CONNECTED: { g_mqtt_connected = true; ESP_LOGI(TAG, "MQTT connected"); // 连接成功后订阅控制主题 int msg_id = esp_mqtt_client_subscribe(client, MQTT_CONTROL_TOPIC, 1); ESP_LOGI(TAG, "subscribe topic=%s msg_id=%d", MQTT_CONTROL_TOPIC, msg_id); break; } case MQTT_EVENT_DISCONNECTED: g_mqtt_connected = false; ESP_LOGW(TAG, "MQTT disconnected"); break; case MQTT_EVENT_SUBSCRIBED: ESP_LOGI(TAG, "MQTT subscribed msg_id=%d", event->msg_id); break; case MQTT_EVENT_DATA: ESP_LOGI(TAG, "MQTT data topic=%.*s data=%.*s", event->topic_len, event->topic, event->data_len, event->data); // 如果是控制主题的数据,则解析控制命令(待实现) if (mqtt_topic_match(event->topic, event->topic_len, MQTT_CONTROL_TOPIC)) { mqtt_control_command_t cmd = {0}; esp_err_t parse_ret = mqtt_parse_control_command(event->data, event->data_len, &cmd); if (parse_ret != ESP_OK) { ESP_LOGW(TAG, "控制命令解析失败: %s", esp_err_to_name(parse_ret)); break; } if (g_cmd_handler != NULL) { esp_err_t handle_ret = g_cmd_handler(&cmd, g_cmd_user_ctx); if (handle_ret != ESP_OK) { ESP_LOGW(TAG, "控制命令处理失败: %s", esp_err_to_name(handle_ret)); } } else { ESP_LOGW(TAG, "未注册控制命令处理器,忽略控制消息"); } } break; case MQTT_EVENT_ERROR: ESP_LOGE(TAG, "MQTT error type=%d", event->error_handle ? event->error_handle->error_type : -1); break; default: break; } } /** * @brief 启动 MQTT 客户端 * * 初始化客户端、注册事件回调、启动连接。 * * @return esp_err_t 启动结果,ESP_OK 表示成功 */ esp_err_t mqtt_control_start(void) { if (g_mqtt_client != NULL) { return ESP_OK; } // 生成基于 MAC 地址后三字节的唯一客户端 ID char client_id[32] = {0}; uint8_t mac[6] = {0}; ESP_RETURN_ON_ERROR(esp_read_mac(mac, ESP_MAC_WIFI_STA), TAG, "read mac failed"); snprintf(client_id, sizeof(client_id), "esp32_%02x%02x%02x", mac[3], mac[4], mac[5]); // 配置 MQTT 客户端参数 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, }; g_mqtt_client = esp_mqtt_client_init(&mqtt_cfg); ESP_RETURN_ON_FALSE(g_mqtt_client != NULL, ESP_FAIL, TAG, "mqtt client init failed"); ESP_RETURN_ON_ERROR(esp_mqtt_client_register_event(g_mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL), TAG, "register event failed"); ESP_RETURN_ON_ERROR(esp_mqtt_client_start(g_mqtt_client), TAG, "start mqtt client failed"); ESP_LOGI(TAG, "MQTT started with client_id=%s", client_id); return ESP_OK; } esp_err_t mqtt_control_register_command_handler(mqtt_control_command_handler_t handler, void *user_ctx) { g_cmd_handler = handler; g_cmd_user_ctx = user_ctx; return ESP_OK; } /** * @brief 停止并销毁 MQTT 客户端 * * @return esp_err_t 停止结果,ESP_OK 表示成功 */ esp_err_t mqtt_control_stop(void) { if (g_mqtt_client == NULL) { return ESP_OK; } esp_err_t ret = esp_mqtt_client_stop(g_mqtt_client); if (ret != ESP_OK) { return ret; } ret = esp_mqtt_client_destroy(g_mqtt_client); if (ret != ESP_OK) { return ret; } g_mqtt_client = NULL; g_mqtt_connected = false; return ESP_OK; } /** * @brief 查询 MQTT 当前连接状态 * * @return true 已连接;false 未连接 */ bool mqtt_control_is_connected(void) { return g_mqtt_connected; } /** * @brief 发布 MQTT 消息到指定主题 * * @param topic 目标主题 * @param payload 消息载荷 * @param qos 服务质量等级(0,1,2) * @param retain 是否保留消息 * @return esp_err_t 发布结果 */ esp_err_t mqtt_control_publish(const char *topic, const char *payload, int qos, int retain) { ESP_RETURN_ON_FALSE(topic != NULL, ESP_ERR_INVALID_ARG, TAG, "topic is null"); ESP_RETURN_ON_FALSE(payload != NULL, ESP_ERR_INVALID_ARG, TAG, "payload is null"); ESP_RETURN_ON_FALSE(g_mqtt_client != NULL, ESP_ERR_INVALID_STATE, TAG, "mqtt not started"); int msg_id = esp_mqtt_client_publish(g_mqtt_client, topic, payload, 0, qos, retain); ESP_RETURN_ON_FALSE(msg_id >= 0, ESP_FAIL, TAG, "publish failed"); return ESP_OK; } /** * @brief 发布传感器数据到预定义的传感器主题 * * @param payload 传感器数据字符串 * @param qos 服务质量 * @param retain 是否保留消息 * @return esp_err_t 发布结果 */ esp_err_t mqtt_control_publish_sensor(const char *payload, int qos, int retain) { return mqtt_control_publish(MQTT_SENSOR_TOPIC, payload, qos, retain); }