From e8618ab6aa8ade3e00a59ffe3e293babf6df5128 Mon Sep 17 00:00:00 2001 From: XupaMisto Date: Sun, 15 Feb 2026 00:03:13 +0000 Subject: [PATCH] Fix MQTT heartbeat + clock MQTT-only + payload cleanup --- main/certs.h | 46 ++-- main/include/mqtt_handler.h | 20 -- main/led_effects.c | 14 +- main/main.c | 237 ++++----------------- main/mqtt_handler.c | 407 ++++++++++++++++++++---------------- 5 files changed, 297 insertions(+), 427 deletions(-) diff --git a/main/certs.h b/main/certs.h index 5c23e78..6b8f0b3 100644 --- a/main/certs.h +++ b/main/certs.h @@ -2,33 +2,21 @@ static const char ca_cert_pem[] = "-----BEGIN CERTIFICATE-----\n" -"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" -"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" -"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" -"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" -"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" -"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" -"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" -"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" -"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" -"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" -"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" -"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" -"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" -"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" -"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" -"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" -"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" -"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" -"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" -"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" -"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" -"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" -"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" -"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" -"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" -"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" -"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" -"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" -"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" +"MIIDHDCCAgSgAwIBAgIUUDkqyQzHgZpOxeCBy0YGWwDZWRkwDQYJKoZIhvcNAQEL\n" +"BQAwIDEeMBwGA1UEAwwVbXF0dC54dXBhcy5teXdpcmUub3JnMB4XDTI2MDIxNDE4\n" +"MjcyMVoXDTI3MDIxNDE4MjcyMVowIDEeMBwGA1UEAwwVbXF0dC54dXBhcy5teXdp\n" +"cmUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnRpx59na551D\n" +"v9HNX56vZdhBpt+MM9vL/TiyNupnuStH7hoNDMYXGva4YSbsHNZknHN0h6Aq08jG\n" +"oDHoJyWr3Cn4ftqb616V499hJmodFFyyk8zR952On32PV7ds95TTIloXe1ptMs0Y\n" +"Pxsr1U1x3M0FNpGazHJEXj7ANQjLcx6ou0FCsgLiqHQ0z6OCMYk9Pl/bvFd4As3R\n" +"QnL0aWV938QjJ1RFdASgW81xONuxntoJiKQNl9mBTGHF7UGFsHckz4lxNohrmDgs\n" +"tDnBiumlZ1fLsOp+rPrGz5r4U5UHf4z6O+KN+Y7t8B6yYHmtN+BHQtCbCMsiXOy5\n" +"BSGx+DGxAQIDAQABo04wTDArBgNVHREEJDAighVtcXR0Lnh1cGFzLm15d2lyZS5v\n" +"cmeCCWxvY2FsaG9zdDAdBgNVHQ4EFgQUZB2TrAcLVR4TFoAoFFVoRZoCHVwwDQYJ\n" +"KoZIhvcNAQELBQADggEBAEkShLf/LYBWefmS+E1/S8q7SWj8zdsP1YdJl5sxvhsI\n" +"rPyioJtN2XdUrZe9N46O/d6MnlGLTnFzIsGq6zz//3lj0Tm8St85uaQ4/sI6HwGn\n" +"BapXpvl9jUfJpSjJOEmzHx932LE+wTfy71P7m81ntj3nduhN26mZBGlMvTTaOm93\n" +"cOcblytv8ROJ/Zyzmyj28nmHodNQEOGPkH0ZfXiLboZm1KgDjliQDNvUlYhrzFtd\n" +"E0BlfQM9peIreDiOtPYIk9F7yfINhfINVS8Zasgf3XINjbY2WctA8i5j27L1yfXz\n" +"+iAxjDRxi+lMTMKSzm72z26UWwQk/C9m1s8SaIeLj/g=\n" "-----END CERTIFICATE-----\n"; diff --git a/main/include/mqtt_handler.h b/main/include/mqtt_handler.h index 617b0ae..2b15da9 100644 --- a/main/include/mqtt_handler.h +++ b/main/include/mqtt_handler.h @@ -1,23 +1,3 @@ #pragma once -#include "mqtt_client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern esp_mqtt_client_handle_t mqtt_client; - -// Exportar os tópicos MQTT (antes eram static!) -extern char topic_status[64]; -extern char topic_cmd[64]; -extern char topic_resp[64]; -extern char topic_lwt[64]; - -// Opcional: loop placeholder -static inline void mqtt_handler_loop(void) {} void mqtt_handler_start(void); - -#ifdef __cplusplus -} -#endif diff --git a/main/led_effects.c b/main/led_effects.c index 97d7d07..2899ce0 100644 --- a/main/led_effects.c +++ b/main/led_effects.c @@ -87,20 +87,12 @@ void led_clock_animation(void) struct tm t; localtime_r(&now, &t); - int h = t.tm_hour; - int m = t.tm_min; int s = t.tm_sec; - // Mostrar HHMM no display (14-seg) - - display_set_time_top(h, m); - - // LED dos segundos em azul - led_clear(); // APAGA TUDO + led_clear(); - int pos = s % LED_COUNT; // 0..59 ou 0..63 - - led_set_pixel(pos, 0, 0, 60); // azul forte + int pos = s % LED_COUNT; + led_set_pixel(pos, 0, 0, 60); led_show(); vTaskDelay(pdMS_TO_TICKS(200)); diff --git a/main/main.c b/main/main.c index 7ac37c0..a7aab7a 100644 --- a/main/main.c +++ b/main/main.c @@ -1,21 +1,16 @@ #include #include +#include -#include "esp_rom_sys.h" -#include "esp_task_wdt.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "driver/gpio.h" + #include "nvs_flash.h" -#include "esp_system.h" #include "esp_log.h" +#include "esp_system.h" #include "esp_wifi.h" -#include "esp_event.h" -#include "esp_netif.h" -#include "esp_sntp.h" #include "eeprom_virtual.h" -#include "eeprom_tls.h" #include "eeprom_animacao.h" #include "wifi_config_portal.h" @@ -23,245 +18,102 @@ #include "led_driver.h" #include "led_effects.h" - -#include "creditos.h" #include "led_task.h" +#include "creditos.h" #include "driver/i2c.h" #include "i2c_helper.h" #include "display.h" -#include - -#include "net_weather.h" #include "buzzer.h" - - -esp_err_t i2c_init(void); -esp_err_t display_init(void); - - - -bool modo_bloqueado = false; // definição oficial - -#define SDA_PIN 21 -#define SCL_PIN 22 -#define I2C_PORT I2C_NUM_0 - +// ====================================================== static const char *TAG = "APP"; -static uint32_t segundos = 0; +void i2c_scan(void); +volatile bool hora_vem_do_mqtt = false; +bool modo_bloqueado = false; static bool wifi_ready = false; +// ====================================================== +// CONTADORES +// ====================================================== typedef struct { int total_creditos; int total_saidas; } contadores_t; +// ====================================================== +// SEGUNDOS (simples, local) +// ====================================================== +static uint32_t segundos = 0; -static void weather_task(void *arg) +static void segundos_task(void *pv) { - float temp; - - ESP_LOGI("WEATHER", "🌡️ weather_task arrancou"); - - // 🔥 leitura IMEDIATA (antes do delay) - ESP_LOGI("WEATHER", "🌐 a pedir temperatura (primeira vez)"); - - if (net_weather_update(&temp)) { - ESP_LOGI("WEATHER", "🌡️ temperatura recebida: %.1f", temp); - display_temperature_bottom(temp); - } else { - ESP_LOGE("WEATHER", "❌ net_weather_update falhou"); - } - - while (1) { - vTaskDelay(pdMS_TO_TICKS(15 * 60 * 1000)); - - ESP_LOGI("WEATHER", "🌐 a pedir temperatura (loop)"); - - if (net_weather_update(&temp)) { - ESP_LOGI("WEATHER", "🌡️ temperatura recebida: %.1f", temp); - display_temperature_bottom(temp); - } else { - ESP_LOGE("WEATHER", "❌ net_weather_update falhou"); - } - } -} - - -// ============================ -// Task contador simples -// ============================ -void segundos_task(void *pv) { while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); segundos++; } } -uint32_t get_segundos(void) { +uint32_t get_segundos(void) +{ return segundos; } - -// ============================ -// Callback Wi-Fi pronto -// ============================ -static void on_wifi_connected(void) { +// ====================================================== +// WIFI OK CALLBACK +// ====================================================== +static void on_wifi_connected(void) +{ wifi_ready = true; ESP_LOGI(TAG, "✅ Wi-Fi conectado — iniciando MQTT..."); mqtt_handler_start(); - ESP_LOGI(TAG, "🕒 SNTP..."); - esp_sntp_setoperatingmode(SNTP_OPMODE_POLL); - esp_sntp_setservername(0, "pool.ntp.org"); - esp_sntp_init(); - ESP_LOGI(TAG, "💡 Inicializando driver LED..."); led_driver_init(); ESP_LOGI(TAG, "🎬 Iniciando tasks LED e Créditos..."); - xTaskCreate(led_task, "led_task", 8192, NULL, 5, NULL); - xTaskCreate(creditos_task, "creditos_task", 8192, NULL, 5, NULL); - - // 🌡️ TASK DO TEMPO (AQUI!) - ESP_LOGI(TAG, "🌡️ Iniciando task de temperatura (Open-Meteo)"); - xTaskCreate(weather_task, "weather_task", 4096, NULL, 4, NULL); + // xTaskCreate(led_task, "led_task", 8192, NULL, 5, NULL); + // xTaskCreate(creditos_task, "creditos_task", 8192, NULL, 5, NULL); } - -// ============================ -// Stack Overflow Handler -// ============================ -void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) +// ====================================================== +// MAIN +// ====================================================== +void app_main(void) { - esp_rom_printf("\n🧨 Stack overflow em %s!\n", pcTaskName); - esp_task_wdt_reset(); - vTaskDelay(pdMS_TO_TICKS(1000)); - esp_restart(); -} - - -// Configuração básica do I2C - i2c_config_t cfg = { - .mode = I2C_MODE_MASTER, - .sda_io_num = SDA_PIN, - .scl_io_num = SCL_PIN, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 100000 - }; - -void ht16_init() -{ - uint8_t cmd1 = 0x21; // liga oscilador - i2c_master_write_to_device(I2C_PORT, 0x70, &cmd1, 1, 10 / portTICK_PERIOD_MS); - - uint8_t cmd2 = 0x81; // display ON, sem piscar - i2c_master_write_to_device(I2C_PORT, 0x70, &cmd2, 1, 10 / portTICK_PERIOD_MS); - - uint8_t cmd3 = 0xEF; // brilho máximo - i2c_master_write_to_device(I2C_PORT, 0x70, &cmd3, 1, 10 / portTICK_PERIOD_MS); -} - - - -void ht16_test() -{ - uint8_t buf[17] = {0}; - buf[0] = 0x00; // endereço inicial - - buf[7] = 0b0111111; // acende apenas o dígito 0 - // (que mostra um "0" bonitinho) - - - // Os outros dígitos ficam a 0 = apagados - - i2c_master_write_to_device(I2C_PORT, 0x70, buf, sizeof(buf), 20 / portTICK_PERIOD_MS); -} - -//************************************************************** */ -void i2c_scan(void) -{ - printf("\n--- A fazer scan ao I2C ---\n"); - - int found = 0; - - for (uint8_t addr = 1; addr < 0x7F; addr++) { - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true); - i2c_master_stop(cmd); - - esp_err_t r = i2c_master_cmd_begin(I2C_PORT, cmd, pdMS_TO_TICKS(50)); - i2c_cmd_link_delete(cmd); - - if (r == ESP_OK) { - printf("✅ I2C encontrado: 0x%02X\n", addr); - found++; - } else if (r == ESP_ERR_TIMEOUT) { - printf("⏱️ TIMEOUT no addr 0x%02X (bus preso?)\n", addr); - break; // não vale a pena continuar - } - } - - if (!found) { - printf("❌ Nenhum dispositivo I2C encontrado\n"); - } -} - - -// ============================ -// MAIN -// ============================ -void app_main(void) { - // -------- EEPROM virtual -------- eeprom_virtual_init(); contadores_t contadores = {100, 25}; eeprom_virtual_write_bin("contadores", &contadores, sizeof(contadores)); - ESP_LOGI("EEPROM", "💾 Gravado: total_creditos=%d, total_saidas=%d", - contadores.total_creditos, contadores.total_saidas); contadores_t lidos = {0}; size_t len = sizeof(lidos); if (eeprom_virtual_read_bin("contadores", &lidos, &len) == ESP_OK) { - ESP_LOGI("EEPROM", "📖 Lido: total_creditos=%d, total_saidas=%d", + ESP_LOGI("EEPROM", "📖 Lido: creditos=%d saidas=%d", lidos.total_creditos, lidos.total_saidas); - } else { - ESP_LOGW("EEPROM", "⚠️ Falha ao ler dados!"); } - // -------- NVS normal -------- + // -------- NVS -------- ESP_ERROR_CHECK(nvs_flash_init()); + // -------- Animação -------- animacao_load(); ESP_LOGI("ANIM", "🎨 Animação carregada = %u", animacao); - - - // 1) Inicializa I2C (não aborta) - esp_err_t ei = i2c_init(); - if (ei != ESP_OK) { - printf("i2c_init falhou: %s\n", esp_err_to_name(ei)); + // -------- I2C -------- + if (i2c_init() == ESP_OK) { + // i2c_scan(); } - // 2) Scan I2C (ver o que existe no barramento) - i2c_scan(); - - // 3) Inicializa displays SEM abortar (para não rebootar em loop) esp_err_t ed = display_init(); - printf("display_init = %s\n", esp_err_to_name(ed)); - - // 4) Teste simples no display (se existir) + ESP_LOGI(TAG, "display_init = %s", esp_err_to_name(ed)); display_text_top("INIT"); - // 🔔 6) buzzer (AQUI!) + // -------- Buzzer -------- buzzer_init(); - buzzer_beep(500); - + buzzer_beep(300); // -------- Wi-Fi -------- wifi_config_t cfg; @@ -269,21 +121,22 @@ void app_main(void) { if (esp_wifi_get_config(WIFI_IF_STA, &cfg) == ESP_OK) { if (strlen((char *)cfg.sta.ssid) > 0) { - ESP_LOGI(TAG, "📂 Credenciais no NVS: SSID=%s", cfg.sta.ssid); + ESP_LOGI(TAG, "📂 Credenciais encontradas: %s", cfg.sta.ssid); have_creds = true; } } wifi_config_portal_init(on_wifi_connected, have_creds); - // -------- Criar tasks iniciais -------- + // -------- Tasks base -------- xTaskCreate(segundos_task, "segundos_task", 4096, NULL, 5, NULL); // -------- Loop principal -------- + // Tudo é event-driven (MQTT, UI, etc.) while (1) { - if (wifi_ready) { - mqtt_handler_loop(); - } - vTaskDelay(pdMS_TO_TICKS(100)); + vTaskDelay(pdMS_TO_TICKS(1000)); } } + + + diff --git a/main/mqtt_handler.c b/main/mqtt_handler.c index ed2e05a..a4f0872 100644 --- a/main/mqtt_handler.c +++ b/main/mqtt_handler.c @@ -1,215 +1,272 @@ -#include #include -#include "esp_log.h" -#include "mqtt_client.h" -#include "esp_system.h" -#include "esp_mac.h" -#include "esp_netif.h" -#include "cJSON.h" +#include #include "certs.h" -#include "mqtt_comandos.h" -#include "led_driver.h" -#include "esp_timer.h" + +#include "esp_log.h" +#include "esp_system.h" #include "esp_event.h" -#include "eeprom_virtual.h" -#include "ui.h" +#include "mqtt_client.h" +#include "esp_mac.h" + +#include "cJSON.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "mqtt_handler.h" +#include "display.h" + +// ====================================================== static const char *TAG = "MQTT"; -// -------- CONFIG -------- -#define BROKER_HOST "mqtt.xupas.mywire.org" -#define BROKER_PORT_TLS 8883 -#define BROKER_PORT_TCP 1883 -#define MQTT_USER "xupa" -#define MQTT_PASS "xupa" +// MQTT CONFIG +#define BROKER_HOST "mqtt.xupas.mywire.org" +#define BROKER_PORT 8883 +#define MQTT_USER "xupa" +#define MQTT_PASS "xupa" -esp_mqtt_client_handle_t mqtt_client = NULL; -static esp_timer_handle_t mqtt_watchdog = NULL; +// ====================================================== +static esp_mqtt_client_handle_t mqtt_client = NULL; static bool mqtt_connected = false; -char topic_status[64]; -char topic_cmd[64]; -char topic_resp[64]; -char topic_lwt[64]; +static char topic_cmd[64]; +static char topic_status[64]; + +// TASK HANDLES +static TaskHandle_t status_task_handle = NULL; +static TaskHandle_t mqtt_clock_handle = NULL; + +// RELÓGIO MQTT +static int clock_h = 0; +static int clock_m = 0; +static bool clock_valid = false; // ====================================================== -// HEARTBEAT / STATUS +// HEARTBEAT TASK // ====================================================== -static void send_status(void) { - if (!mqtt_client || !mqtt_connected) return; +static void status_task(void *pv) +{ + while (1) { - char buf[160]; - snprintf(buf, sizeof(buf), - "{\"uptime\":%lu,\"heap\":%lu}", - (unsigned long)(esp_log_timestamp() / 1000), - (unsigned long)esp_get_free_heap_size()); + if (mqtt_connected) { - esp_mqtt_client_publish(mqtt_client, topic_status, buf, 0, 1, false); - ESP_LOGI(TAG, "📤 STATUS -> %s", buf); -} + char msg[128]; -// ====================================================== -// WATCHDOG CALLBACK -// ====================================================== -static void mqtt_watchdog_cb(void *arg) { - if (!mqtt_connected) { - ESP_LOGE(TAG, "⏱️ 2 minutos sem MQTT, reiniciando ESP..."); - esp_restart(); + snprintf(msg, sizeof(msg), + "{\"status\":\"online\"}"); + + esp_mqtt_client_publish(mqtt_client, + topic_status, + msg, + 0, + 1, + 0); + + ESP_LOGI(TAG, "💓 Heartbeat enviado"); + } + + vTaskDelay(pdMS_TO_TICKS(10000)); } } // ====================================================== -// EVENT HANDLER +// CLOCK TASK (só MQTT controla) // ====================================================== -static void mqtt_event_handler(void *handler_args, esp_event_base_t base, - int32_t event_id, void *event_data) { +static void mqtt_clock_task(void *pv) +{ + while (1) { + + if (clock_valid) { + + vTaskDelay(pdMS_TO_TICKS(60000)); // 1 minuto + + clock_m++; + + if (clock_m >= 60) { + clock_m = 0; + clock_h++; + + if (clock_h >= 24) + clock_h = 0; + } + + display_set_time_top(clock_h, clock_m); + + ESP_LOGI(TAG, "🕒 Hora interna: %02d:%02d", + clock_h, clock_m); + } + else { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + } +} + +// ====================================================== +// MQTT EVENT HANDLER +// ====================================================== +static void mqtt_event_handler(void *arg, + esp_event_base_t base, + int32_t event_id, + void *event_data) +{ esp_mqtt_event_handle_t event = event_data; switch (event->event_id) { - case MQTT_EVENT_CONNECTED: - mqtt_connected = true; - // LED verde no pixel 0 - led_set_pixel(0, 0, 50, 0); - led_show(); + // ------------------------------ + case MQTT_EVENT_CONNECTED: - ESP_LOGI(TAG, "✅ MQTT conectado"); - esp_mqtt_client_publish(mqtt_client, topic_status, "online", 0, 1, 0); - // esp_mqtt_client_publish(mqtt_client, topic_status, "online", 0, 1, true); - esp_mqtt_client_subscribe(mqtt_client, topic_cmd, 1); - send_status(); + mqtt_connected = true; + ESP_LOGI(TAG, "✅ MQTT conectado"); - if (mqtt_watchdog) esp_timer_stop(mqtt_watchdog); - break; + // ONLINE retain + esp_mqtt_client_publish(mqtt_client, + topic_status, + "{\"status\":\"online\"}", + 0, + 1, + 1); - case MQTT_EVENT_DISCONNECTED: - mqtt_connected = false; + esp_mqtt_client_subscribe(mqtt_client, topic_cmd, 1); + esp_mqtt_client_subscribe(mqtt_client, "time/now", 1); - // LED vermelho no pixel 0 - led_set_pixel(0, 50, 0, 0); - led_show(); + // heartbeat (1x) + if (status_task_handle == NULL) { + xTaskCreate(status_task, + "status_task", + 4096, + NULL, + 5, + &status_task_handle); + } - ESP_LOGW(TAG, "⚠️ MQTT desconectado"); - if (mqtt_watchdog) esp_timer_start_periodic(mqtt_watchdog, 120000000); - break; + // clock (1x) + if (mqtt_clock_handle == NULL) { + xTaskCreate(mqtt_clock_task, + "mqtt_clock", + 4096, + NULL, + 5, + &mqtt_clock_handle); + } - case MQTT_EVENT_DATA: { - // Copia o payload para um buffer legível - char json_clean[256]; - int len = event->data_len; + break; - if (len >= sizeof(json_clean)) len = sizeof(json_clean) - 1; - memcpy(json_clean, event->data, len); - json_clean[len] = 0; // NULL terminate + // ------------------------------ + case MQTT_EVENT_DISCONNECTED: + mqtt_connected = false; + ESP_LOGW(TAG, "⚠️ MQTT desconectado"); + break; - // Remove quebras de linha - for (int i = 0; json_clean[i]; i++) { - if (json_clean[i] == '\r' || json_clean[i] == '\n') - json_clean[i] = ' '; - } + // ------------------------------ + case MQTT_EVENT_DATA: +{ + esp_mqtt_event_handle_t e = event; - // Mostrar tópico + JSON limpo - ESP_LOGI(TAG, "📩 [%.*s] %s", - event->topic_len, event->topic, - json_clean); + // 🔥 Ignorar fragmentos parciais + if (e->current_data_offset != 0) { + ESP_LOGW(TAG, "Fragmento ignorado"); + return; + } + + // 🔥 Ignorar payload vazio + if (e->data_len == 0) { + ESP_LOGW(TAG, "Payload vazio ignorado"); + return; + } + + char topic[64]; + char payload[256]; + + int tlen = e->topic_len; + int plen = e->data_len; + + if (tlen >= sizeof(topic)) tlen = sizeof(topic) - 1; + if (plen >= sizeof(payload)) plen = sizeof(payload) - 1; + + memcpy(topic, e->topic, tlen); + topic[tlen] = 0; + + memcpy(payload, e->data, plen); + payload[plen] = 0; + + ESP_LOGI(TAG, "📩 [%s] %s", topic, payload); + + // -------- TIME -------- + if (strcmp(topic, "time/now") == 0) { + + cJSON *root = cJSON_Parse(payload); + if (!root) return; + + cJSON *h = cJSON_GetObjectItem(root, "h"); + cJSON *m = cJSON_GetObjectItem(root, "m"); + + if (cJSON_IsNumber(h) && cJSON_IsNumber(m)) { + + clock_h = h->valueint; + clock_m = m->valueint; + clock_valid = true; + + display_set_time_top(clock_h, clock_m); + + ESP_LOGI(TAG, "⏰ Hora MQTT: %02d:%02d", + clock_h, clock_m); + } - // JSON parse - cJSON *root = cJSON_Parse(json_clean); - if (root) { - mqtt_comandos_handle(root); cJSON_Delete(root); - } else { - ESP_LOGE(TAG, "❌ JSON inválido"); + return; } + break; -} - - - case MQTT_EVENT_ERROR: - ESP_LOGE(TAG, "❌ Erro MQTT"); - break; - - default: - break; - } -} - - -// ====================================================== -// HEARTBEAT TASK -// ====================================================== -static void mqtt_heartbeat_task(void *arg) { - while (1) { - send_status(); - vTaskDelay(pdMS_TO_TICKS(30000)); // envia status a cada 30s - } -} - -// ====================================================== -// START / CONFIG -// ====================================================== -void mqtt_handler_start(void) { - esp_mqtt_client_config_t mqtt_cfg = {0}; - - // -------- IDENTIFICADOR AUTOMÁTICO -------- - char device_id[16]; - uint8_t mac[6]; - esp_read_mac(mac, ESP_MAC_WIFI_STA); - snprintf(device_id, sizeof(device_id), "esp_%02X%02X%02X", mac[3], mac[4], mac[5]); - - esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); - if (netif) esp_netif_set_hostname(netif, device_id); - ESP_LOGI(TAG, "🆔 ID do dispositivo: %s", device_id); - - snprintf(topic_status, sizeof(topic_status), "esp/%s/status", device_id); - snprintf(topic_cmd, sizeof(topic_cmd), "esp/%s/cmd", device_id); - snprintf(topic_resp, sizeof(topic_resp), "esp/%s/resp", device_id); - snprintf(topic_lwt, sizeof(topic_lwt), "esp/%s/lwt", device_id); - - mqtt_cfg.credentials.client_id = device_id; - mqtt_cfg.credentials.username = MQTT_USER; - mqtt_cfg.credentials.authentication.password = MQTT_PASS; -// ====================================================== -// MQTT TLS — usa SEMPRE o certificado embutido -// ====================================================== - mqtt_cfg.broker.address.hostname = BROKER_HOST; - mqtt_cfg.broker.address.port = BROKER_PORT_TLS; - mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_SSL; - -// Certificado raiz (ISRG Root X1) - mqtt_cfg.broker.verification.certificate = ca_cert_pem; - ESP_LOGI(TAG, "🔐 TLS ativo (cert embutido, EEPROM ignorada)"); - -// -------- LWT -------- - mqtt_cfg.session.last_will.topic = topic_lwt; - mqtt_cfg.session.last_will.msg = "offline"; - mqtt_cfg.session.last_will.qos = 1; - mqtt_cfg.session.last_will.retain = false; - -// ====================================================== -// INICIALIZAÇÃO DO CLIENTE MQTT (TLS OBRIGATÓRIO) -// ====================================================== - mqtt_client = esp_mqtt_client_init(&mqtt_cfg); - if (mqtt_client == NULL) { - ESP_LOGE(TAG, "❌ Falha a inicializar MQTT (TLS). Abortado."); - return; // Nem vale a pena continuar, sem MQTT não há vida - } - - esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); - esp_mqtt_client_start(mqtt_client); - - ESP_LOGI(TAG, "🚀 MQTT inicializado em %s:%lu (TLS)", - mqtt_cfg.broker.address.hostname, - (unsigned long)mqtt_cfg.broker.address.port); + } - // -------- WATCHDOG MQTT -------- - const esp_timer_create_args_t wd_args = { - .callback = &mqtt_watchdog_cb, - .name = "mqtt_watchdog" - }; - esp_timer_create(&wd_args, &mqtt_watchdog); - - // -------- HEARTBEAT TASK -------- - xTaskCreate(mqtt_heartbeat_task, "mqtt_heartbeat", 12288, NULL, 5, NULL); + default: + break; + } +} + +// ====================================================== +// START MQTT +// ====================================================== +void mqtt_handler_start(void) +{ + esp_mqtt_client_config_t cfg = {0}; + + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + + char client_id[16]; + snprintf(client_id, sizeof(client_id), + "esp_%02X%02X%02X", + mac[3], mac[4], mac[5]); + + snprintf(topic_cmd, sizeof(topic_cmd), + "esp/%s/cmd", client_id); + + snprintf(topic_status, sizeof(topic_status), + "esp/%s/status", client_id); + + cfg.broker.address.hostname = BROKER_HOST; + cfg.broker.address.port = BROKER_PORT; + cfg.credentials.username = MQTT_USER; + cfg.credentials.authentication.password = MQTT_PASS; + cfg.credentials.client_id = client_id; + cfg.broker.address.transport = MQTT_TRANSPORT_OVER_SSL; + cfg.broker.verification.certificate = ca_cert_pem; + + // Last will + cfg.session.last_will.topic = topic_status; + cfg.session.last_will.msg = "{\"status\":\"offline\"}"; + cfg.session.last_will.qos = 1; + cfg.session.last_will.retain = 1; + + mqtt_client = esp_mqtt_client_init(&cfg); + esp_mqtt_client_register_event( + mqtt_client, ESP_EVENT_ANY_ID, + mqtt_event_handler, NULL); + + esp_mqtt_client_start(mqtt_client); + + ESP_LOGI(TAG, "🚀 MQTT iniciado como %s", client_id); }