Fix MQTT heartbeat + clock MQTT-only + payload cleanup

This commit is contained in:
XupaMisto 2026-02-15 00:03:13 +00:00
parent a26b421d75
commit e8618ab6aa
5 changed files with 297 additions and 427 deletions

View File

@ -2,33 +2,21 @@
static const char ca_cert_pem[] =
"-----BEGIN CERTIFICATE-----\n"
"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\n"
"-----END CERTIFICATE-----\n";

View File

@ -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

View File

@ -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));

View File

@ -1,21 +1,16 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#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 <stdbool.h>
#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));
}
}

View File

@ -1,215 +1,272 @@
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "mqtt_client.h"
#include "esp_system.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include "cJSON.h"
#include <stdio.h>
#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);
}