commit 00d1b0ae4ada44d5b7df1ad75bfb696c33ddb7bb Author: Andrey Aleksandrov Date: Tue Dec 30 21:48:51 2025 +0200 Initial commit diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8625ef8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v1.0.0] - 2024-12-30 + +### Added + +- Repository diff --git a/library.json b/library.json new file mode 100644 index 0000000..03691bf --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "mqtt_manager", + "version": "1.0.0", + "description": "MQTT client library with auto-reconnection and sensor data publishing for ESP32", + "keywords": "mqtt, esp32, iot, publish, subscribe, reconnect, sensor", + "authors": { + "name": "ESP32C6-DHT22 Project" + }, + "license": "MIT", + "frameworks": [ + "espidf" + ], + "platforms": [ + "espressif32" + ] +} \ No newline at end of file diff --git a/mqtt_manager.c b/mqtt_manager.c new file mode 100644 index 0000000..3652c7f --- /dev/null +++ b/mqtt_manager.c @@ -0,0 +1,400 @@ +#include "mqtt_manager.h" +#include "esp_log.h" +#include +#include + +static const char *TAG = "MQTT_MANAGER"; + +// MQTT manager state +static bool is_initialized = false; +static mqtt_status_t current_status = MQTT_STATUS_DISCONNECTED; +static esp_mqtt_client_handle_t mqtt_client = NULL; +static mqtt_event_callback_t user_callback = NULL; + +// MQTT event handler +static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + esp_mqtt_event_handle_t event = event_data; + + switch ((esp_mqtt_event_id_t)event_id) + { + case MQTT_EVENT_BEFORE_CONNECT: + ESP_LOGI(TAG, "MQTT connecting to broker..."); + current_status = MQTT_STATUS_CONNECTING; + if (user_callback) + { + user_callback(current_status, NULL, NULL, 0); + } + break; + + case MQTT_EVENT_CONNECTED: + ESP_LOGI(TAG, "MQTT connected to broker"); + current_status = MQTT_STATUS_CONNECTED; + if (user_callback) + { + user_callback(current_status, NULL, NULL, 0); + } + break; + + case MQTT_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "MQTT disconnected from broker"); + current_status = MQTT_STATUS_DISCONNECTED; + if (user_callback) + { + user_callback(current_status, NULL, NULL, 0); + } + break; + + case MQTT_EVENT_PUBLISHED: + ESP_LOGD(TAG, "MQTT message published, msg_id=%d", event->msg_id); + break; + + case MQTT_EVENT_DATA: + ESP_LOGD(TAG, "MQTT data received - Topic: %.*s, Data: %.*s", + event->topic_len, event->topic, + event->data_len, event->data); + if (user_callback) + { + // Null-terminate the topic and data for callback + char topic_str[256]; + char data_str[512]; + int topic_len = (event->topic_len < sizeof(topic_str) - 1) ? event->topic_len : sizeof(topic_str) - 1; + int data_len = (event->data_len < sizeof(data_str) - 1) ? event->data_len : sizeof(data_str) - 1; + + memcpy(topic_str, event->topic, topic_len); + topic_str[topic_len] = '\0'; + + memcpy(data_str, event->data, data_len); + data_str[data_len] = '\0'; + + user_callback(current_status, topic_str, data_str, data_len); + } + break; + + case MQTT_EVENT_ERROR: + ESP_LOGE(TAG, "MQTT error occurred, error_handle->error_type: %d", event->error_handle->error_type); + current_status = MQTT_STATUS_ERROR; + if (user_callback) + { + user_callback(current_status, NULL, NULL, 0); + } + break; + + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGD(TAG, "MQTT subscribed, msg_id=%d", event->msg_id); + break; + + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGD(TAG, "MQTT unsubscribed, msg_id=%d", event->msg_id); + break; + + case MQTT_EVENT_DELETED: + ESP_LOGD(TAG, "MQTT client deleted"); + break; + + case MQTT_USER_EVENT: + case MQTT_EVENT_ANY: + default: + ESP_LOGD(TAG, "MQTT event: %d", event_id); + break; + } +} + +esp_err_t mqtt_manager_init(const mqtt_config_t *config, mqtt_event_callback_t event_callback) +{ + if (is_initialized) + { + ESP_LOGW(TAG, "MQTT manager already initialized"); + return ESP_OK; + } + + if (config == NULL || config->broker_url == NULL || config->client_id == NULL) + { + ESP_LOGE(TAG, "Invalid MQTT configuration"); + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGI(TAG, "Initializing MQTT manager"); + ESP_LOGI(TAG, "MQTT Broker: %s:%d", config->broker_url, config->broker_port); + ESP_LOGI(TAG, "MQTT Client ID: %s", config->client_id); + + user_callback = event_callback; + current_status = MQTT_STATUS_DISCONNECTED; + + // Configure MQTT client + esp_mqtt_client_config_t mqtt_cfg = { + .broker.address.uri = config->broker_url, + .credentials.client_id = config->client_id, + .network.timeout_ms = config->network_timeout_ms > 0 ? config->network_timeout_ms : 10000, + .network.refresh_connection_after_ms = 20000, + .session.keepalive = config->keepalive > 0 ? config->keepalive : 60, + }; + + // Set port if specified + if (config->broker_port > 0) + { + mqtt_cfg.broker.address.port = config->broker_port; + } + + // Set credentials if provided + if (config->username && strlen(config->username) > 0) + { + mqtt_cfg.credentials.username = config->username; + } + if (config->password && strlen(config->password) > 0) + { + mqtt_cfg.credentials.authentication.password = config->password; + } + + // Initialize MQTT client + mqtt_client = esp_mqtt_client_init(&mqtt_cfg); + if (mqtt_client == NULL) + { + ESP_LOGE(TAG, "Failed to initialize MQTT client"); + return ESP_ERR_NO_MEM; + } + + // Register event handler + esp_err_t ret = esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to register MQTT event handler: %s", esp_err_to_name(ret)); + esp_mqtt_client_destroy(mqtt_client); + mqtt_client = NULL; + return ret; + } + + is_initialized = true; + ESP_LOGI(TAG, "MQTT manager initialized successfully"); + return ESP_OK; +} + +esp_err_t mqtt_manager_start(void) +{ + if (!is_initialized || mqtt_client == NULL) + { + ESP_LOGE(TAG, "MQTT manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGI(TAG, "Starting MQTT client"); + esp_err_t ret = esp_mqtt_client_start(mqtt_client); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to start MQTT client: %s", esp_err_to_name(ret)); + current_status = MQTT_STATUS_ERROR; + return ret; + } + + return ESP_OK; +} + +esp_err_t mqtt_manager_stop(void) +{ + if (!is_initialized || mqtt_client == NULL) + { + ESP_LOGE(TAG, "MQTT manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGI(TAG, "Stopping MQTT client"); + current_status = MQTT_STATUS_DISCONNECTED; + + esp_err_t ret = esp_mqtt_client_stop(mqtt_client); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to stop MQTT client: %s", esp_err_to_name(ret)); + return ret; + } + + return ESP_OK; +} + +mqtt_status_t mqtt_manager_get_status(void) +{ + return current_status; +} + +int mqtt_manager_publish(const char *topic, const char *data, int len, int qos, int retain) +{ + if (!is_initialized || mqtt_client == NULL) + { + ESP_LOGE(TAG, "MQTT manager not initialized"); + return -1; + } + + if (current_status != MQTT_STATUS_CONNECTED) + { + ESP_LOGW(TAG, "MQTT not connected, cannot publish"); + return -1; + } + + if (topic == NULL || data == NULL) + { + ESP_LOGE(TAG, "Topic or data is NULL"); + return -1; + } + + int data_len = (len == 0) ? strlen(data) : len; + int msg_id = esp_mqtt_client_publish(mqtt_client, topic, data, data_len, qos, retain); + + if (msg_id >= 0) + { + ESP_LOGD(TAG, "Published to topic '%s', msg_id=%d", topic, msg_id); + } + else + { + ESP_LOGE(TAG, "Failed to publish to topic '%s'", topic); + } + + return msg_id; +} + +int mqtt_manager_publish_string(const char *topic, const char *message, int qos, int retain) +{ + return mqtt_manager_publish(topic, message, 0, qos, retain); +} + +int mqtt_manager_publish_sensor_data(const char *topic, float temperature, float humidity, const char *device_id, int qos, int retain) +{ + if (topic == NULL || device_id == NULL) + { + ESP_LOGE(TAG, "Topic or device_id is NULL"); + return -1; + } + + char json_data[256]; + int len = snprintf(json_data, sizeof(json_data), + "{\"temperature\":%.2f,\"humidity\":%.2f,\"device\":\"%s\"}", + temperature, humidity, device_id); + + if (len >= sizeof(json_data)) + { + ESP_LOGW(TAG, "JSON data truncated"); + } + + ESP_LOGI(TAG, "Publishing sensor data - Temp: %.2f°C, Humidity: %.2f%%", temperature, humidity); + return mqtt_manager_publish(topic, json_data, 0, qos, retain); +} + +int mqtt_manager_subscribe(const char *topic, int qos) +{ + if (!is_initialized || mqtt_client == NULL) + { + ESP_LOGE(TAG, "MQTT manager not initialized"); + return -1; + } + + if (current_status != MQTT_STATUS_CONNECTED) + { + ESP_LOGW(TAG, "MQTT not connected, cannot subscribe"); + return -1; + } + + if (topic == NULL) + { + ESP_LOGE(TAG, "Topic is NULL"); + return -1; + } + + int msg_id = esp_mqtt_client_subscribe(mqtt_client, topic, qos); + + if (msg_id >= 0) + { + ESP_LOGI(TAG, "Subscribed to topic '%s', msg_id=%d", topic, msg_id); + } + else + { + ESP_LOGE(TAG, "Failed to subscribe to topic '%s'", topic); + } + + return msg_id; +} + +int mqtt_manager_unsubscribe(const char *topic) +{ + if (!is_initialized || mqtt_client == NULL) + { + ESP_LOGE(TAG, "MQTT manager not initialized"); + return -1; + } + + if (current_status != MQTT_STATUS_CONNECTED) + { + ESP_LOGW(TAG, "MQTT not connected, cannot unsubscribe"); + return -1; + } + + if (topic == NULL) + { + ESP_LOGE(TAG, "Topic is NULL"); + return -1; + } + + int msg_id = esp_mqtt_client_unsubscribe(mqtt_client, topic); + + if (msg_id >= 0) + { + ESP_LOGI(TAG, "Unsubscribed from topic '%s', msg_id=%d", topic, msg_id); + } + else + { + ESP_LOGE(TAG, "Failed to unsubscribe from topic '%s'", topic); + } + + return msg_id; +} + +bool mqtt_manager_is_connected(void) +{ + return current_status == MQTT_STATUS_CONNECTED; +} + +esp_err_t mqtt_manager_reconnect(void) +{ + if (!is_initialized || mqtt_client == NULL) + { + ESP_LOGE(TAG, "MQTT manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGI(TAG, "Forcing MQTT reconnection"); + + // Stop and restart the client + esp_err_t ret = mqtt_manager_stop(); + if (ret != ESP_OK) + { + return ret; + } + + // Small delay before restarting + vTaskDelay(pdMS_TO_TICKS(1000)); + + return mqtt_manager_start(); +} + +esp_err_t mqtt_manager_deinit(void) +{ + if (!is_initialized) + { + ESP_LOGW(TAG, "MQTT manager not initialized"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Deinitializing MQTT manager"); + + // Stop client if running + if (mqtt_client != NULL) + { + esp_mqtt_client_stop(mqtt_client); + esp_mqtt_client_destroy(mqtt_client); + mqtt_client = NULL; + } + + current_status = MQTT_STATUS_DISCONNECTED; + user_callback = NULL; + is_initialized = false; + + ESP_LOGI(TAG, "MQTT manager deinitialized"); + return ESP_OK; +} \ No newline at end of file diff --git a/mqtt_manager.h b/mqtt_manager.h new file mode 100644 index 0000000..51b658f --- /dev/null +++ b/mqtt_manager.h @@ -0,0 +1,155 @@ +#ifndef MQTT_MANAGER_H +#define MQTT_MANAGER_H + +#include "mqtt_client.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief MQTT connection status + */ + typedef enum + { + MQTT_STATUS_DISCONNECTED = 0, + MQTT_STATUS_CONNECTING, + MQTT_STATUS_CONNECTED, + MQTT_STATUS_ERROR + } mqtt_status_t; + + /** + * @brief MQTT configuration structure + */ + typedef struct + { + const char *broker_url; /**< MQTT broker URL (e.g., "mqtt://192.168.1.100") */ + int broker_port; /**< MQTT broker port (usually 1883 for non-secure) */ + const char *client_id; /**< Unique client identifier */ + const char *username; /**< Username (can be NULL if not required) */ + const char *password; /**< Password (can be NULL if not required) */ + int keepalive; /**< Keepalive interval in seconds (0 for default) */ + int network_timeout_ms; /**< Network timeout in milliseconds (0 for default) */ + } mqtt_config_t; + + /** + * @brief MQTT event callback function type + * + * @param status Current MQTT status + * @param topic Topic of received message (only valid for data events) + * @param data Message data (only valid for data events) + * @param data_len Length of message data + */ + typedef void (*mqtt_event_callback_t)(mqtt_status_t status, const char *topic, const char *data, int data_len); + + /** + * @brief Initialize MQTT manager + * + * @param config MQTT configuration + * @param event_callback Optional callback function for MQTT events + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t mqtt_manager_init(const mqtt_config_t *config, mqtt_event_callback_t event_callback); + + /** + * @brief Start MQTT client + * + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t mqtt_manager_start(void); + + /** + * @brief Stop MQTT client + * + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t mqtt_manager_stop(void); + + /** + * @brief Get current MQTT connection status + * + * @return mqtt_status_t Current MQTT status + */ + mqtt_status_t mqtt_manager_get_status(void); + + /** + * @brief Publish a message to a topic + * + * @param topic Topic to publish to + * @param data Message data + * @param len Length of data (0 for string length) + * @param qos Quality of Service (0, 1, or 2) + * @param retain Retain flag + * @return int Message ID on success, -1 on error + */ + int mqtt_manager_publish(const char *topic, const char *data, int len, int qos, int retain); + + /** + * @brief Publish a string message to a topic + * + * @param topic Topic to publish to + * @param message String message to publish + * @param qos Quality of Service (0, 1, or 2) + * @param retain Retain flag + * @return int Message ID on success, -1 on error + */ + int mqtt_manager_publish_string(const char *topic, const char *message, int qos, int retain); + + /** + * @brief Publish sensor data as JSON + * + * @param topic Topic to publish to + * @param temperature Temperature value + * @param humidity Humidity value + * @param device_id Device identifier + * @param qos Quality of Service (0, 1, or 2) + * @param retain Retain flag + * @return int Message ID on success, -1 on error + */ + int mqtt_manager_publish_sensor_data(const char *topic, float temperature, float humidity, const char *device_id, int qos, int retain); + + /** + * @brief Subscribe to a topic + * + * @param topic Topic to subscribe to + * @param qos Quality of Service (0, 1, or 2) + * @return int Message ID on success, -1 on error + */ + int mqtt_manager_subscribe(const char *topic, int qos); + + /** + * @brief Unsubscribe from a topic + * + * @param topic Topic to unsubscribe from + * @return int Message ID on success, -1 on error + */ + int mqtt_manager_unsubscribe(const char *topic); + + /** + * @brief Check if MQTT is connected + * + * @return true if connected, false otherwise + */ + bool mqtt_manager_is_connected(void); + + /** + * @brief Force reconnect to MQTT broker + * + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t mqtt_manager_reconnect(void); + + /** + * @brief Deinitialize MQTT manager + * + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t mqtt_manager_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif // MQTT_MANAGER_H \ No newline at end of file