Compare commits
No commits in common. "main" and "portal-top" have entirely different histories.
main
...
portal-top
@ -16,8 +16,6 @@ idf_component_register(
|
|||||||
"i2c_helper.c"
|
"i2c_helper.c"
|
||||||
"display.c"
|
"display.c"
|
||||||
"ui.c"
|
"ui.c"
|
||||||
"net_weather.c"
|
|
||||||
"buzzer.c"
|
|
||||||
INCLUDE_DIRS
|
INCLUDE_DIRS
|
||||||
"include"
|
"include"
|
||||||
|
|
||||||
@ -30,6 +28,5 @@ idf_component_register(
|
|||||||
json
|
json
|
||||||
driver
|
driver
|
||||||
esp_http_server
|
esp_http_server
|
||||||
esp_http_client
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
#include "driver/gpio.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "esp_rom_sys.h"
|
|
||||||
|
|
||||||
#define BUZ_GPIO GPIO_NUM_19
|
|
||||||
|
|
||||||
void buzzer_init(void)
|
|
||||||
{
|
|
||||||
gpio_reset_pin(BUZ_GPIO);
|
|
||||||
gpio_set_direction(BUZ_GPIO, GPIO_MODE_OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void buzzer_beep(uint32_t ms)
|
|
||||||
{
|
|
||||||
const int period_us = 500; // ~2 kHz
|
|
||||||
|
|
||||||
int cycles = (ms * 1000) / period_us;
|
|
||||||
|
|
||||||
for (int i = 0; i < cycles; i++) {
|
|
||||||
gpio_set_level(BUZ_GPIO, 1);
|
|
||||||
esp_rom_delay_us(period_us / 2);
|
|
||||||
gpio_set_level(BUZ_GPIO, 0);
|
|
||||||
esp_rom_delay_us(period_us / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
46
main/certs.h
46
main/certs.h
@ -2,21 +2,33 @@
|
|||||||
|
|
||||||
static const char ca_cert_pem[] =
|
static const char ca_cert_pem[] =
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
"MIIDHDCCAgSgAwIBAgIUUDkqyQzHgZpOxeCBy0YGWwDZWRkwDQYJKoZIhvcNAQEL\n"
|
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
|
||||||
"BQAwIDEeMBwGA1UEAwwVbXF0dC54dXBhcy5teXdpcmUub3JnMB4XDTI2MDIxNDE4\n"
|
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
|
||||||
"MjcyMVoXDTI3MDIxNDE4MjcyMVowIDEeMBwGA1UEAwwVbXF0dC54dXBhcy5teXdp\n"
|
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
|
||||||
"cmUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnRpx59na551D\n"
|
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
|
||||||
"v9HNX56vZdhBpt+MM9vL/TiyNupnuStH7hoNDMYXGva4YSbsHNZknHN0h6Aq08jG\n"
|
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
|
||||||
"oDHoJyWr3Cn4ftqb616V499hJmodFFyyk8zR952On32PV7ds95TTIloXe1ptMs0Y\n"
|
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
|
||||||
"Pxsr1U1x3M0FNpGazHJEXj7ANQjLcx6ou0FCsgLiqHQ0z6OCMYk9Pl/bvFd4As3R\n"
|
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
|
||||||
"QnL0aWV938QjJ1RFdASgW81xONuxntoJiKQNl9mBTGHF7UGFsHckz4lxNohrmDgs\n"
|
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
|
||||||
"tDnBiumlZ1fLsOp+rPrGz5r4U5UHf4z6O+KN+Y7t8B6yYHmtN+BHQtCbCMsiXOy5\n"
|
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
|
||||||
"BSGx+DGxAQIDAQABo04wTDArBgNVHREEJDAighVtcXR0Lnh1cGFzLm15d2lyZS5v\n"
|
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
|
||||||
"cmeCCWxvY2FsaG9zdDAdBgNVHQ4EFgQUZB2TrAcLVR4TFoAoFFVoRZoCHVwwDQYJ\n"
|
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
|
||||||
"KoZIhvcNAQELBQADggEBAEkShLf/LYBWefmS+E1/S8q7SWj8zdsP1YdJl5sxvhsI\n"
|
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
|
||||||
"rPyioJtN2XdUrZe9N46O/d6MnlGLTnFzIsGq6zz//3lj0Tm8St85uaQ4/sI6HwGn\n"
|
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
|
||||||
"BapXpvl9jUfJpSjJOEmzHx932LE+wTfy71P7m81ntj3nduhN26mZBGlMvTTaOm93\n"
|
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
|
||||||
"cOcblytv8ROJ/Zyzmyj28nmHodNQEOGPkH0ZfXiLboZm1KgDjliQDNvUlYhrzFtd\n"
|
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
|
||||||
"E0BlfQM9peIreDiOtPYIk9F7yfINhfINVS8Zasgf3XINjbY2WctA8i5j27L1yfXz\n"
|
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
|
||||||
"+iAxjDRxi+lMTMKSzm72z26UWwQk/C9m1s8SaIeLj/g=\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"
|
||||||
"-----END CERTIFICATE-----\n";
|
"-----END CERTIFICATE-----\n";
|
||||||
|
|||||||
465
main/display.c
465
main/display.c
@ -1,37 +1,19 @@
|
|||||||
// display.c (HT16K33 14-seg / 4 dígitos)
|
|
||||||
// TOP = 0x71 | BOTTOM = 0x70
|
|
||||||
// I2C_PORT = I2C_NUM_0
|
|
||||||
//
|
|
||||||
// MODO "SAFE":
|
|
||||||
// - Se I2C não estiver instalado, ou se não existir display, ignora tudo (sem spam)
|
|
||||||
// - display_init() NUNCA aborta o firmware (devolve ESP_OK e deixa display disabled)
|
|
||||||
|
|
||||||
#include "driver/i2c.h"
|
#include "driver/i2c.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_err.h"
|
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#define I2C_PORT I2C_NUM_0
|
#define I2C_PORT I2C_NUM_0
|
||||||
|
|
||||||
// Endereços reais
|
// Endereços reais
|
||||||
#define DISP_TOP_ADDR 0x71
|
#define DISP_TOP_ADDR 0x71 // display superior
|
||||||
#define DISP_BOTTOM_ADDR 0x70
|
#define DISP_BOTTOM_ADDR 0x70 // display inferior
|
||||||
|
static uint16_t rotate180(uint16_t m);
|
||||||
|
|
||||||
static const char *TAG = "DISPLAY";
|
const char *TAG = "DISPLAY";
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// FLAG GLOBAL
|
// MAPA DE SEGMENTOS (mantido igual ao teu)
|
||||||
// =======================================================
|
|
||||||
static bool s_display_enabled = false;
|
|
||||||
void display_temperature_bottom(float temp);
|
|
||||||
|
|
||||||
void display_set_enabled(bool en) { s_display_enabled = en; }
|
|
||||||
bool display_is_enabled(void) { return s_display_enabled; }
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// MAPA DE SEGMENTOS (igual ao teu)
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
|
|
||||||
#define SEG_A (1 << 0)
|
#define SEG_A (1 << 0)
|
||||||
@ -56,264 +38,131 @@ bool display_is_enabled(void) { return s_display_enabled; }
|
|||||||
|
|
||||||
#define SEG_G (SEG_ML | SEG_MR)
|
#define SEG_G (SEG_ML | SEG_MR)
|
||||||
|
|
||||||
static uint16_t charset(char c);
|
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// FUNÇÕES I2C (internas)
|
// FUNÇÕES GENÉRICAS PARA QUALQUER DISPLAY
|
||||||
// =======================================================
|
// =======================================================
|
||||||
|
|
||||||
static inline bool i2c_driver_is_installed(void)
|
static void disp_send_cmd(uint8_t addr, uint8_t cmd)
|
||||||
{
|
{
|
||||||
// truque simples: tenta criar um cmd e dar begin "vazio" é overkill.
|
i2c_master_write_to_device(I2C_PORT, addr, &cmd, 1, 20 / portTICK_PERIOD_MS);
|
||||||
// Mais seguro: tenta uma escrita curta e ver se dá INVALID_STATE.
|
|
||||||
// Aqui vamos só confiar na primeira escrita do display_init().
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t disp_send_cmd(uint8_t addr, uint8_t cmd)
|
static void disp_raw(uint8_t addr, int pos, uint16_t mask)
|
||||||
{
|
{
|
||||||
return i2c_master_write_to_device(I2C_PORT, addr, &cmd, 1, pdMS_TO_TICKS(50));
|
if (pos < 0 || pos > 3) return;
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t disp_raw(uint8_t addr, int pos, uint16_t mask)
|
|
||||||
{
|
|
||||||
if (pos < 0 || pos > 3) return ESP_ERR_INVALID_ARG;
|
|
||||||
|
|
||||||
uint8_t buf[3];
|
uint8_t buf[3];
|
||||||
buf[0] = (uint8_t)(pos * 2);
|
buf[0] = pos * 2;
|
||||||
buf[1] = (uint8_t)(mask & 0xFF);
|
buf[1] = mask & 0xFF;
|
||||||
buf[2] = (uint8_t)((mask >> 8) & 0xFF);
|
buf[2] = (mask >> 8) & 0xFF;
|
||||||
|
|
||||||
return i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), pdMS_TO_TICKS(50));
|
i2c_master_write_to_device(I2C_PORT, addr, buf, 3, 20 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t disp_clear(uint8_t addr)
|
static void disp_clear(uint8_t addr)
|
||||||
{
|
{
|
||||||
uint8_t buf[17] = {0};
|
uint8_t buf[17] = {0};
|
||||||
buf[0] = 0x00;
|
buf[0] = 0x00;
|
||||||
return i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), pdMS_TO_TICKS(50));
|
i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), 20 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// INIT (SAFE)
|
// INIT PARA OS DOIS DISPLAYS (TOP 0x71 | BOTTOM 0x70)
|
||||||
// =======================================================
|
// =======================================================
|
||||||
|
|
||||||
esp_err_t display_init(void)
|
void display_init(void)
|
||||||
{
|
{
|
||||||
// por defeito: desligado
|
uint8_t cmd1 = 0x21; // oscillator ON
|
||||||
s_display_enabled = false;
|
uint8_t cmd2 = 0x81; // display ON, blink OFF
|
||||||
|
uint8_t cmd3 = 0xEF; // brightness MAX
|
||||||
// Se o driver I2C não estiver instalado, a primeira chamada vai dar INVALID_STATE.
|
|
||||||
// Nós tratamos isso como "não há display nesta board" e seguimos sem logs.
|
|
||||||
const uint8_t cmd1 = 0x21; // oscillator ON
|
|
||||||
const uint8_t cmd2 = 0x81; // display ON, blink OFF
|
|
||||||
const uint8_t cmd3 = 0xEF; // brightness MAX
|
|
||||||
|
|
||||||
esp_err_t err;
|
|
||||||
|
|
||||||
// TOP
|
// TOP
|
||||||
err = disp_send_cmd(DISP_TOP_ADDR, cmd1);
|
disp_send_cmd(DISP_TOP_ADDR, cmd1);
|
||||||
if (err == ESP_ERR_INVALID_STATE) return ESP_OK; // I2C não instalado -> ignora
|
disp_send_cmd(DISP_TOP_ADDR, cmd2);
|
||||||
if (err != ESP_OK) return ESP_OK; // display ausente/cabos -> ignora
|
disp_send_cmd(DISP_TOP_ADDR, cmd3);
|
||||||
|
disp_clear(DISP_TOP_ADDR);
|
||||||
err = disp_send_cmd(DISP_TOP_ADDR, cmd2);
|
|
||||||
if (err != ESP_OK) return ESP_OK;
|
|
||||||
|
|
||||||
err = disp_send_cmd(DISP_TOP_ADDR, cmd3);
|
|
||||||
if (err != ESP_OK) return ESP_OK;
|
|
||||||
|
|
||||||
err = disp_clear(DISP_TOP_ADDR);
|
|
||||||
if (err != ESP_OK) return ESP_OK;
|
|
||||||
|
|
||||||
// BOTTOM
|
// BOTTOM
|
||||||
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd1);
|
disp_send_cmd(DISP_BOTTOM_ADDR, cmd1);
|
||||||
if (err != ESP_OK) return ESP_OK;
|
disp_send_cmd(DISP_BOTTOM_ADDR, cmd2);
|
||||||
|
disp_send_cmd(DISP_BOTTOM_ADDR, cmd3);
|
||||||
|
disp_clear(DISP_BOTTOM_ADDR);
|
||||||
|
|
||||||
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd2);
|
ESP_LOGI(TAG, "📟 Displays inicializados: TOP=0x71 BOTTOM=0x70");
|
||||||
if (err != ESP_OK) return ESP_OK;
|
|
||||||
|
|
||||||
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd3);
|
|
||||||
if (err != ESP_OK) return ESP_OK;
|
|
||||||
|
|
||||||
err = disp_clear(DISP_BOTTOM_ADDR);
|
|
||||||
if (err != ESP_OK) return ESP_OK;
|
|
||||||
|
|
||||||
// OK -> ativa display
|
|
||||||
s_display_enabled = true;
|
|
||||||
ESP_LOGI(TAG, "📟 Displays OK: TOP=0x%02X BOTTOM=0x%02X", DISP_TOP_ADDR, DISP_BOTTOM_ADDR);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// RAW / CLEAR (com guard)
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
|
// =======================================================
|
||||||
|
// RAW PARA TOP E BOTTOM
|
||||||
|
// =======================================================
|
||||||
void display_raw_top(int pos, uint16_t mask)
|
void display_raw_top(int pos, uint16_t mask)
|
||||||
{
|
{
|
||||||
if (!s_display_enabled) return;
|
int p = 3 - pos; // inverter ordem dos dígitos
|
||||||
|
uint16_t m = rotate180(mask); // rodar segmentos
|
||||||
esp_err_t err = disp_raw(DISP_TOP_ADDR, pos, mask);
|
disp_raw(DISP_TOP_ADDR, p, m);
|
||||||
if (err != ESP_OK) {
|
|
||||||
// se o driver desaparecer a meio, desativa e cala
|
|
||||||
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
||||||
// opcional: não logar para evitar spam
|
|
||||||
// ESP_LOGE(TAG, "raw_top falhou: %s", esp_err_to_name(err));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void display_raw_bottom(int pos, uint16_t mask)
|
void display_raw_bottom(int pos, uint16_t mask)
|
||||||
{
|
{
|
||||||
if (!s_display_enabled) return;
|
disp_raw(DISP_BOTTOM_ADDR, pos, mask);
|
||||||
|
|
||||||
esp_err_t err = disp_raw(DISP_BOTTOM_ADDR, pos, mask);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
||||||
// opcional: não logar
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void display_clear_top(void)
|
void display_clear_top(void)
|
||||||
{
|
{
|
||||||
if (!s_display_enabled) return;
|
disp_clear(DISP_TOP_ADDR);
|
||||||
|
|
||||||
esp_err_t err = disp_clear(DISP_TOP_ADDR);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void display_clear_bottom(void)
|
void display_clear_bottom(void)
|
||||||
{
|
{
|
||||||
if (!s_display_enabled) return;
|
disp_clear(DISP_BOTTOM_ADDR);
|
||||||
|
|
||||||
esp_err_t err = disp_clear(DISP_BOTTOM_ADDR);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// ROTATE 180 para 14 segmentos REAL
|
||||||
|
// (mapa correto para o teu HT16K33)
|
||||||
|
// =====================
|
||||||
|
static uint16_t rotate180(uint16_t m)
|
||||||
|
{
|
||||||
|
uint16_t r = 0;
|
||||||
|
|
||||||
|
// A (topo) <-> D (baixo)
|
||||||
|
if (m & SEG_A) r |= SEG_D;
|
||||||
|
if (m & SEG_D) r |= SEG_A;
|
||||||
|
|
||||||
|
// B (top-right) <-> E (bottom-left)
|
||||||
|
if (m & SEG_B) r |= SEG_E;
|
||||||
|
if (m & SEG_E) r |= SEG_B;
|
||||||
|
|
||||||
|
// C (bottom-right) <-> F (top-left)
|
||||||
|
if (m & SEG_C) r |= SEG_F;
|
||||||
|
if (m & SEG_F) r |= SEG_C;
|
||||||
|
|
||||||
|
// Meio vertical ML ↔ MR
|
||||||
|
if (m & SEG_ML) r |= SEG_MR;
|
||||||
|
if (m & SEG_MR) r |= SEG_ML;
|
||||||
|
|
||||||
|
// Alfanuméricos topo TL/TM/TR ↔ BL/BM/BR
|
||||||
|
if (m & SEG_TL) r |= SEG_BL;
|
||||||
|
if (m & SEG_BL) r |= SEG_TL;
|
||||||
|
|
||||||
|
if (m & SEG_TM) r |= SEG_BM;
|
||||||
|
if (m & SEG_BM) r |= SEG_TM;
|
||||||
|
|
||||||
|
if (m & SEG_TR) r |= SEG_BR;
|
||||||
|
if (m & SEG_BR) r |= SEG_TR;
|
||||||
|
|
||||||
|
// DP é DP (ponto)
|
||||||
|
if (m & SEG_DP) r |= SEG_DP;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// TEXTO / CHARS
|
// CHARSET — mantido EXACTAMENTE como o teu
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
void display_char_top(int pos, char c)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
display_raw_top(pos, charset(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_char_bottom(int pos, char c)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
display_raw_bottom(pos, charset(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_text_top(const char *txt)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
char c = (txt && txt[i]) ? txt[i] : ' ';
|
|
||||||
display_char_top(i, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_text_bottom(const char *txt)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
char c = (txt && txt[i]) ? txt[i] : ' ';
|
|
||||||
display_char_bottom(i, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// NÚMEROS
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
static const uint16_t digit_mask[10] =
|
|
||||||
{
|
|
||||||
[0] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,
|
|
||||||
[1] = SEG_B | SEG_C,
|
|
||||||
[2] = SEG_A | SEG_B | SEG_G | SEG_E | SEG_D,
|
|
||||||
[3] = SEG_A | SEG_B | SEG_G | SEG_C | SEG_D,
|
|
||||||
[4] = SEG_F | SEG_G | SEG_B | SEG_C,
|
|
||||||
[5] = SEG_A | SEG_F | SEG_G | SEG_C | SEG_D,
|
|
||||||
[6] = SEG_A | SEG_F | SEG_G | SEG_E | SEG_D | SEG_C,
|
|
||||||
[7] = SEG_A | SEG_B | SEG_C,
|
|
||||||
[8] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G,
|
|
||||||
[9] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G,
|
|
||||||
};
|
|
||||||
|
|
||||||
void display_digit_top(int pos, uint8_t val)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
if (val > 9) val = 0;
|
|
||||||
display_raw_top(pos, digit_mask[val]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_digit_bottom(int pos, uint8_t val)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
if (val > 9) val = 0;
|
|
||||||
display_raw_bottom(pos, digit_mask[val]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_number_top(int num)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
|
|
||||||
if (num < 0) num = 0;
|
|
||||||
if (num > 9999) num = 9999;
|
|
||||||
|
|
||||||
display_digit_top(3, (uint8_t)(num % 10));
|
|
||||||
display_digit_top(2, (uint8_t)((num / 10) % 10));
|
|
||||||
display_digit_top(1, (uint8_t)((num / 100) % 10));
|
|
||||||
display_digit_top(0, (uint8_t)((num / 1000) % 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_number_bottom(int num)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
|
|
||||||
if (num < 0) num = 0;
|
|
||||||
if (num > 9999) num = 9999;
|
|
||||||
|
|
||||||
display_digit_bottom(3, (uint8_t)(num % 10));
|
|
||||||
display_digit_bottom(2, (uint8_t)((num / 10) % 10));
|
|
||||||
display_digit_bottom(1, (uint8_t)((num / 100) % 10));
|
|
||||||
display_digit_bottom(0, (uint8_t)((num / 1000) % 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// RELÓGIO (HH:MM com DP no meio)
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
void display_set_time_top(int horas, int minutos)
|
|
||||||
{
|
|
||||||
if (!s_display_enabled) return;
|
|
||||||
|
|
||||||
if (horas < 0) horas = 0;
|
|
||||||
if (horas > 99) horas = 99;
|
|
||||||
if (minutos < 0) minutos = 0;
|
|
||||||
if (minutos > 59) minutos = 59;
|
|
||||||
|
|
||||||
int h1 = horas / 10;
|
|
||||||
int h2 = horas % 10;
|
|
||||||
int m1 = minutos / 10;
|
|
||||||
int m2 = minutos % 10;
|
|
||||||
|
|
||||||
uint16_t mid = digit_mask[h2] | SEG_DP;
|
|
||||||
|
|
||||||
display_digit_top(0, (uint8_t)h1);
|
|
||||||
display_raw_top(1, mid);
|
|
||||||
display_digit_top(2, (uint8_t)m1);
|
|
||||||
display_digit_top(3, (uint8_t)m2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// CHARSET (igual ao teu)
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
|
|
||||||
static uint16_t charset(char c)
|
static uint16_t charset(char c)
|
||||||
@ -332,7 +181,7 @@ static uint16_t charset(char c)
|
|||||||
case '8': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G;
|
case '8': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G;
|
||||||
case '9': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G;
|
case '9': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G;
|
||||||
|
|
||||||
// LETRAS
|
// LETRAS COMPLETAS (mantidas)
|
||||||
case 'A': case 'a': return SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G;
|
case 'A': case 'a': return SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G;
|
||||||
case 'B': case 'b': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G;
|
case 'B': case 'b': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G;
|
||||||
case 'C': case 'c': return SEG_A | SEG_F | SEG_E | SEG_D;
|
case 'C': case 'c': return SEG_A | SEG_F | SEG_E | SEG_D;
|
||||||
@ -370,55 +219,113 @@ static uint16_t charset(char c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void display_debug_segment(uint16_t bitmask)
|
|
||||||
{
|
|
||||||
(void)bitmask;
|
|
||||||
// opcional: podes mostrar um padrão fixo se display estiver ativo
|
|
||||||
// if (!display_is_enabled()) return;
|
|
||||||
// display_raw_top(0, bitmask);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// TEMPERATURA (BOTTOM) ex: 23.5
|
// DISPLAY CARACTER / TEXTO
|
||||||
// =======================================================
|
// =======================================================
|
||||||
void display_temperature_bottom(float temp)
|
|
||||||
|
void display_char_top(int pos, char c)
|
||||||
{
|
{
|
||||||
if (!s_display_enabled) return;
|
display_raw_top(pos, charset(c));
|
||||||
|
}
|
||||||
|
|
||||||
// limites práticos
|
void display_char_bottom(int pos, char c)
|
||||||
if (temp > 99.9f) temp = 99.9f;
|
{
|
||||||
if (temp < -9.9f) temp = -9.9f;
|
disp_raw(DISP_BOTTOM_ADDR, pos, charset(c));
|
||||||
|
}
|
||||||
|
|
||||||
bool neg = false;
|
void display_text_top(const char *txt)
|
||||||
if (temp < 0) {
|
{
|
||||||
neg = true;
|
for (int i = 0; i < 4; i++) {
|
||||||
temp = -temp;
|
char c = txt[i] ? txt[i] : ' ';
|
||||||
}
|
display_char_top(i, c);
|
||||||
|
|
||||||
int inteiro = (int)temp;
|
|
||||||
int decimal = (int)((temp - inteiro) * 10 + 0.5f);
|
|
||||||
|
|
||||||
display_clear_bottom();
|
|
||||||
|
|
||||||
if (neg) {
|
|
||||||
// formato: C-5.2 (raro, mas pronto)
|
|
||||||
display_char_bottom(0, 'C');
|
|
||||||
display_raw_bottom(1, SEG_G); // '-'
|
|
||||||
display_raw_bottom(2, digit_mask[inteiro] | SEG_DP);
|
|
||||||
display_digit_bottom(3, decimal);
|
|
||||||
} else {
|
|
||||||
if (inteiro >= 10) {
|
|
||||||
// formato: C13.8
|
|
||||||
display_char_bottom(0, 'C');
|
|
||||||
display_digit_bottom(1, inteiro / 10);
|
|
||||||
display_raw_bottom(2, digit_mask[inteiro % 10] | SEG_DP);
|
|
||||||
display_digit_bottom(3, decimal);
|
|
||||||
} else {
|
|
||||||
// formato: C 7.2
|
|
||||||
display_char_bottom(0, 'C');
|
|
||||||
display_raw_bottom(1, 0);
|
|
||||||
display_raw_bottom(2, digit_mask[inteiro] | SEG_DP);
|
|
||||||
display_digit_bottom(3, decimal);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void display_text_bottom(const char *txt)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
char c = txt[i] ? txt[i] : ' ';
|
||||||
|
display_char_bottom(i, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =======================================================
|
||||||
|
// DISPLAY DE NÚMEROS (igual ao teu)
|
||||||
|
// =======================================================
|
||||||
|
|
||||||
|
static const uint16_t digit_mask[10] =
|
||||||
|
{
|
||||||
|
[0] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,
|
||||||
|
[1] = SEG_B | SEG_C,
|
||||||
|
[2] = SEG_A | SEG_B | SEG_G | SEG_E | SEG_D,
|
||||||
|
[3] = SEG_A | SEG_B | SEG_G | SEG_C | SEG_D,
|
||||||
|
[4] = SEG_F | SEG_G | SEG_B | SEG_C,
|
||||||
|
[5] = SEG_A | SEG_F | SEG_G | SEG_C | SEG_D,
|
||||||
|
[6] = SEG_A | SEG_F | SEG_G | SEG_E | SEG_D | SEG_C,
|
||||||
|
[7] = SEG_A | SEG_B | SEG_C,
|
||||||
|
[8] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G,
|
||||||
|
[9] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G,
|
||||||
|
};
|
||||||
|
|
||||||
|
void display_digit_top(int pos, uint8_t val)
|
||||||
|
{
|
||||||
|
if (val > 9) val = 0;
|
||||||
|
display_raw_top(pos, digit_mask[val]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void display_digit_bottom(int pos, uint8_t val)
|
||||||
|
{
|
||||||
|
if (val > 9) val = 0;
|
||||||
|
disp_raw(DISP_BOTTOM_ADDR, pos, digit_mask[val]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_number_top(int num)
|
||||||
|
{
|
||||||
|
if (num < 0) num = 0;
|
||||||
|
if (num > 9999) num = 9999;
|
||||||
|
|
||||||
|
display_digit_top(3, num % 10);
|
||||||
|
display_digit_top(2, (num / 10) % 10);
|
||||||
|
display_digit_top(1, (num / 100) % 10);
|
||||||
|
display_digit_top(0, (num / 1000) % 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void display_number_bottom(int num)
|
||||||
|
{
|
||||||
|
if (num < 0) num = 0;
|
||||||
|
if (num > 9999) num = 9999;
|
||||||
|
|
||||||
|
display_digit_bottom(3, num % 10);
|
||||||
|
display_digit_bottom(2, (num/10) % 10);
|
||||||
|
display_digit_bottom(1, (num/100) % 10);
|
||||||
|
display_digit_bottom(0, (num/1000) % 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =======================================================
|
||||||
|
// display_set_time() — mantém SEG_DP no sítio certo
|
||||||
|
// =======================================================
|
||||||
|
void display_set_time_top(int horas, int minutos)
|
||||||
|
{
|
||||||
|
if (horas < 0) horas = 0;
|
||||||
|
if (horas > 99) horas = 99;
|
||||||
|
if (minutos < 0) minutos = 0;
|
||||||
|
if (minutos > 59) minutos = 59;
|
||||||
|
|
||||||
|
int h1 = horas / 10;
|
||||||
|
int h2 = horas % 10;
|
||||||
|
int m1 = minutos / 10;
|
||||||
|
int m2 = minutos % 10;
|
||||||
|
|
||||||
|
uint16_t mid = digit_mask[h2] | SEG_DP;
|
||||||
|
|
||||||
|
display_digit_top(0, h1);
|
||||||
|
display_raw_top(1, mid);
|
||||||
|
display_digit_top(2, m1);
|
||||||
|
display_digit_top(3, m2);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,86 +1,20 @@
|
|||||||
#include "driver/i2c.h"
|
#include "driver/i2c.h"
|
||||||
#include "esp_err.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "esp_rom_sys.h"
|
|
||||||
|
|
||||||
#define SDA_PIN 21
|
#define SDA_PIN 21
|
||||||
#define SCL_PIN 22
|
#define SCL_PIN 22
|
||||||
#define I2C_PORT I2C_NUM_0
|
#define I2C_PORT I2C_NUM_0
|
||||||
|
|
||||||
static const char *TAG_I2C = "I2C";
|
void i2c_init(void)
|
||||||
|
|
||||||
static void i2c_bus_reset(void)
|
|
||||||
{
|
{
|
||||||
// Solta o bus: gera 9 clocks em SCL para libertar SDA se algum escravo ficou preso
|
|
||||||
gpio_config_t io = {
|
|
||||||
.pin_bit_mask = (1ULL << SDA_PIN) | (1ULL << SCL_PIN),
|
|
||||||
.mode = GPIO_MODE_OUTPUT_OD,
|
|
||||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
||||||
.intr_type = GPIO_INTR_DISABLE
|
|
||||||
};
|
|
||||||
gpio_config(&io);
|
|
||||||
|
|
||||||
gpio_set_level(SDA_PIN, 1);
|
|
||||||
gpio_set_level(SCL_PIN, 1);
|
|
||||||
esp_rom_delay_us(5);
|
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < 9; i++) {
|
|
||||||
gpio_set_level(SCL_PIN, 0);
|
|
||||||
esp_rom_delay_us(5);
|
|
||||||
|
|
||||||
gpio_set_level(SCL_PIN, 1);
|
|
||||||
esp_rom_delay_us(5);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// STOP
|
|
||||||
gpio_set_level(SDA_PIN, 0);
|
|
||||||
esp_rom_delay_us(5);
|
|
||||||
|
|
||||||
gpio_set_level(SCL_PIN, 1);
|
|
||||||
esp_rom_delay_us(5);
|
|
||||||
|
|
||||||
gpio_set_level(SDA_PIN, 1);
|
|
||||||
esp_rom_delay_us(5);
|
|
||||||
|
|
||||||
|
|
||||||
// devolve os pinos ao I2C driver depois
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t i2c_init(void)
|
|
||||||
{
|
|
||||||
// se já estava instalado, limpa e volta a instalar
|
|
||||||
i2c_driver_delete(I2C_PORT);
|
|
||||||
|
|
||||||
i2c_bus_reset();
|
|
||||||
|
|
||||||
i2c_config_t cfg = {
|
i2c_config_t cfg = {
|
||||||
.mode = I2C_MODE_MASTER,
|
.mode = I2C_MODE_MASTER,
|
||||||
.sda_io_num = SDA_PIN,
|
.sda_io_num = SDA_PIN,
|
||||||
.scl_io_num = SCL_PIN,
|
.scl_io_num = SCL_PIN,
|
||||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||||
.master.clk_speed = 100000, // começa seguro
|
.master.clk_speed = 400000
|
||||||
.clk_flags = 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_err_t err;
|
i2c_param_config(I2C_PORT, &cfg);
|
||||||
|
i2c_driver_install(I2C_PORT, cfg.mode, 0, 0, 0);
|
||||||
err = i2c_param_config(I2C_PORT, &cfg);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG_I2C, "i2c_param_config falhou: %s", esp_err_to_name(err));
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = i2c_driver_install(I2C_PORT, cfg.mode, 0, 0, 0);
|
|
||||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
||||||
ESP_LOGE(TAG_I2C, "i2c_driver_install falhou: %s", esp_err_to_name(err));
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG_I2C, "I2C OK (SDA=%d SCL=%d) freq=100kHz", SDA_PIN, SCL_PIN);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// inicializa o buzzer (PWM / GPIO19)
|
|
||||||
void buzzer_init(void);
|
|
||||||
|
|
||||||
// bip simples em milissegundos
|
|
||||||
void buzzer_beep(uint32_t ms);
|
|
||||||
@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "esp_err.h"
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// ENDEREÇOS I2C (definidos no .c)
|
// ENDEREÇOS I2C (definidos no .c)
|
||||||
@ -10,7 +8,7 @@
|
|||||||
// BOTTOM = 0x70
|
// BOTTOM = 0x70
|
||||||
|
|
||||||
// Inicialização dos dois displays
|
// Inicialização dos dois displays
|
||||||
esp_err_t display_init(void);
|
void display_init(void);
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// RAW ACCESS (usa 16 bits de segmentos)
|
// RAW ACCESS (usa 16 bits de segmentos)
|
||||||
@ -20,8 +18,6 @@ void display_raw_bottom(int pos, uint16_t mask);
|
|||||||
|
|
||||||
void display_clear_top(void);
|
void display_clear_top(void);
|
||||||
void display_clear_bottom(void);
|
void display_clear_bottom(void);
|
||||||
void display_set_enabled(bool en);
|
|
||||||
bool display_is_enabled(void);
|
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// TEXTO E CARACTERES
|
// TEXTO E CARACTERES
|
||||||
@ -45,7 +41,6 @@ void display_number_bottom(int num);
|
|||||||
// RELÓGIO (HH:MM com DP entre horas)
|
// RELÓGIO (HH:MM com DP entre horas)
|
||||||
// =======================================================
|
// =======================================================
|
||||||
void display_set_time_top(int horas, int minutos);
|
void display_set_time_top(int horas, int minutos);
|
||||||
void display_temperature_bottom(float temp);
|
|
||||||
|
|
||||||
// =======================================================
|
// =======================================================
|
||||||
// DEBUG
|
// DEBUG
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "esp_err.h"
|
|
||||||
|
|
||||||
esp_err_t i2c_init(void);
|
void i2c_init(void);
|
||||||
|
|||||||
@ -1,3 +1,23 @@
|
|||||||
#pragma once
|
#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);
|
void mqtt_handler_start(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
bool net_weather_update(float *out_temp);
|
|
||||||
@ -87,12 +87,20 @@ void led_clock_animation(void)
|
|||||||
struct tm t;
|
struct tm t;
|
||||||
localtime_r(&now, &t);
|
localtime_r(&now, &t);
|
||||||
|
|
||||||
|
int h = t.tm_hour;
|
||||||
|
int m = t.tm_min;
|
||||||
int s = t.tm_sec;
|
int s = t.tm_sec;
|
||||||
|
|
||||||
led_clear();
|
// Mostrar HHMM no display (14-seg)
|
||||||
|
|
||||||
int pos = s % LED_COUNT;
|
display_set_time_top(h, m);
|
||||||
led_set_pixel(pos, 0, 0, 60);
|
|
||||||
|
// LED dos segundos em azul
|
||||||
|
led_clear(); // APAGA TUDO
|
||||||
|
|
||||||
|
int pos = s % LED_COUNT; // 0..59 ou 0..63
|
||||||
|
|
||||||
|
led_set_pixel(pos, 0, 0, 60); // azul forte
|
||||||
led_show();
|
led_show();
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
|||||||
177
main/main.c
177
main/main.c
@ -1,16 +1,21 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
#include "esp_log.h"
|
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_netif.h"
|
||||||
|
#include "esp_sntp.h"
|
||||||
|
|
||||||
#include "eeprom_virtual.h"
|
#include "eeprom_virtual.h"
|
||||||
|
#include "eeprom_tls.h"
|
||||||
#include "eeprom_animacao.h"
|
#include "eeprom_animacao.h"
|
||||||
|
|
||||||
#include "wifi_config_portal.h"
|
#include "wifi_config_portal.h"
|
||||||
@ -18,102 +23,175 @@
|
|||||||
|
|
||||||
#include "led_driver.h"
|
#include "led_driver.h"
|
||||||
#include "led_effects.h"
|
#include "led_effects.h"
|
||||||
#include "led_task.h"
|
|
||||||
#include "creditos.h"
|
#include "creditos.h"
|
||||||
|
#include "led_task.h"
|
||||||
|
|
||||||
#include "driver/i2c.h"
|
#include "driver/i2c.h"
|
||||||
#include "i2c_helper.h"
|
#include "i2c_helper.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "buzzer.h"
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
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 const char *TAG = "APP";
|
||||||
void i2c_scan(void);
|
static uint32_t segundos = 0;
|
||||||
volatile bool hora_vem_do_mqtt = false;
|
|
||||||
bool modo_bloqueado = false;
|
|
||||||
static bool wifi_ready = false;
|
static bool wifi_ready = false;
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// CONTADORES
|
|
||||||
// ======================================================
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int total_creditos;
|
int total_creditos;
|
||||||
int total_saidas;
|
int total_saidas;
|
||||||
} contadores_t;
|
} contadores_t;
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// SEGUNDOS (simples, local)
|
|
||||||
// ======================================================
|
|
||||||
static uint32_t segundos = 0;
|
|
||||||
|
|
||||||
static void segundos_task(void *pv)
|
// ============================
|
||||||
{
|
// Task contador simples
|
||||||
|
// ============================
|
||||||
|
void segundos_task(void *pv) {
|
||||||
while (1) {
|
while (1) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
segundos++;
|
segundos++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t get_segundos(void)
|
uint32_t get_segundos(void) {
|
||||||
{
|
|
||||||
return segundos;
|
return segundos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// WIFI OK CALLBACK
|
// ============================
|
||||||
// ======================================================
|
// Callback Wi-Fi pronto
|
||||||
static void on_wifi_connected(void)
|
// ============================
|
||||||
{
|
static void on_wifi_connected(void) {
|
||||||
wifi_ready = true;
|
wifi_ready = true;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "✅ Wi-Fi conectado — iniciando MQTT...");
|
ESP_LOGI(TAG, "✅ Wi-Fi conectado — iniciando MQTT...");
|
||||||
mqtt_handler_start();
|
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...");
|
ESP_LOGI(TAG, "💡 Inicializando driver LED...");
|
||||||
led_driver_init();
|
led_driver_init();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "🎬 Iniciando tasks LED e Créditos...");
|
ESP_LOGI(TAG, "🎬 Iniciando tasks LED e Créditos...");
|
||||||
// xTaskCreate(led_task, "led_task", 8192, NULL, 5, NULL);
|
xTaskCreate(led_task, "led_task", 8192, NULL, 5, NULL);
|
||||||
// xTaskCreate(creditos_task, "creditos_task", 8192, NULL, 5, NULL);
|
xTaskCreate(creditos_task, "creditos_task", 8192, NULL, 5, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// MAIN
|
// ============================
|
||||||
// ======================================================
|
// Stack Overflow Handler
|
||||||
void app_main(void)
|
// ============================
|
||||||
|
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
|
||||||
{
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
printf("\n--- A fazer scan ao I2C ---\n");
|
||||||
|
// --- SCAN I2C ---
|
||||||
|
for (uint8_t addr = 1; addr < 127; 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, 20 / portTICK_PERIOD_MS);
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
|
||||||
|
if (r == ESP_OK) {
|
||||||
|
printf("ENCONTRADO I2C: 0x%02X\n", addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// MAIN
|
||||||
|
// ============================
|
||||||
|
void app_main(void) {
|
||||||
|
|
||||||
// -------- EEPROM virtual --------
|
// -------- EEPROM virtual --------
|
||||||
eeprom_virtual_init();
|
eeprom_virtual_init();
|
||||||
|
|
||||||
contadores_t contadores = {100, 25};
|
contadores_t contadores = {100, 25};
|
||||||
eeprom_virtual_write_bin("contadores", &contadores, sizeof(contadores));
|
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};
|
contadores_t lidos = {0};
|
||||||
size_t len = sizeof(lidos);
|
size_t len = sizeof(lidos);
|
||||||
if (eeprom_virtual_read_bin("contadores", &lidos, &len) == ESP_OK) {
|
if (eeprom_virtual_read_bin("contadores", &lidos, &len) == ESP_OK) {
|
||||||
ESP_LOGI("EEPROM", "📖 Lido: creditos=%d saidas=%d",
|
ESP_LOGI("EEPROM", "📖 Lido: total_creditos=%d, total_saidas=%d",
|
||||||
lidos.total_creditos, lidos.total_saidas);
|
lidos.total_creditos, lidos.total_saidas);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW("EEPROM", "⚠️ Falha ao ler dados!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- NVS --------
|
// -------- NVS normal --------
|
||||||
ESP_ERROR_CHECK(nvs_flash_init());
|
ESP_ERROR_CHECK(nvs_flash_init());
|
||||||
|
|
||||||
// -------- Animação --------
|
|
||||||
animacao_load();
|
animacao_load();
|
||||||
ESP_LOGI("ANIM", "🎨 Animação carregada = %u", animacao);
|
ESP_LOGI("ANIM", "🎨 Animação carregada = %u", animacao);
|
||||||
|
|
||||||
// -------- I2C --------
|
|
||||||
if (i2c_init() == ESP_OK) {
|
|
||||||
// i2c_scan();
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t ed = display_init();
|
|
||||||
ESP_LOGI(TAG, "display_init = %s", esp_err_to_name(ed));
|
i2c_init(); // <- o helper entra aqui
|
||||||
|
display_init(); // <- inicializa o HT16K33
|
||||||
|
i2c_scan();
|
||||||
display_text_top("INIT");
|
display_text_top("INIT");
|
||||||
|
|
||||||
// -------- Buzzer --------
|
|
||||||
buzzer_init();
|
|
||||||
buzzer_beep(300);
|
|
||||||
|
|
||||||
// -------- Wi-Fi --------
|
// -------- Wi-Fi --------
|
||||||
wifi_config_t cfg;
|
wifi_config_t cfg;
|
||||||
@ -121,22 +199,21 @@ void app_main(void)
|
|||||||
|
|
||||||
if (esp_wifi_get_config(WIFI_IF_STA, &cfg) == ESP_OK) {
|
if (esp_wifi_get_config(WIFI_IF_STA, &cfg) == ESP_OK) {
|
||||||
if (strlen((char *)cfg.sta.ssid) > 0) {
|
if (strlen((char *)cfg.sta.ssid) > 0) {
|
||||||
ESP_LOGI(TAG, "📂 Credenciais encontradas: %s", cfg.sta.ssid);
|
ESP_LOGI(TAG, "📂 Credenciais no NVS: SSID=%s", cfg.sta.ssid);
|
||||||
have_creds = true;
|
have_creds = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wifi_config_portal_init(on_wifi_connected, have_creds);
|
wifi_config_portal_init(on_wifi_connected, have_creds);
|
||||||
|
|
||||||
// -------- Tasks base --------
|
// -------- Criar tasks iniciais --------
|
||||||
xTaskCreate(segundos_task, "segundos_task", 4096, NULL, 5, NULL);
|
xTaskCreate(segundos_task, "segundos_task", 4096, NULL, 5, NULL);
|
||||||
|
|
||||||
// -------- Loop principal --------
|
// -------- Loop principal --------
|
||||||
// Tudo é event-driven (MQTT, UI, etc.)
|
|
||||||
while (1) {
|
while (1) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
if (wifi_ready) {
|
||||||
|
mqtt_handler_loop();
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,272 +1,215 @@
|
|||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "certs.h"
|
#include <string.h>
|
||||||
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_system.h"
|
|
||||||
#include "esp_event.h"
|
|
||||||
#include "mqtt_client.h"
|
#include "mqtt_client.h"
|
||||||
|
#include "esp_system.h"
|
||||||
#include "esp_mac.h"
|
#include "esp_mac.h"
|
||||||
|
#include "esp_netif.h"
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "certs.h"
|
||||||
#include "freertos/task.h"
|
#include "mqtt_comandos.h"
|
||||||
|
#include "led_driver.h"
|
||||||
#include "mqtt_handler.h"
|
#include "esp_timer.h"
|
||||||
#include "display.h"
|
#include "esp_event.h"
|
||||||
|
#include "eeprom_virtual.h"
|
||||||
// ======================================================
|
#include "ui.h"
|
||||||
static const char *TAG = "MQTT";
|
static const char *TAG = "MQTT";
|
||||||
|
|
||||||
// MQTT CONFIG
|
// -------- CONFIG --------
|
||||||
#define BROKER_HOST "mqtt.xupas.mywire.org"
|
#define BROKER_HOST "mqtt.xupas.mywire.org"
|
||||||
#define BROKER_PORT 8883
|
#define BROKER_PORT_TLS 8883
|
||||||
|
#define BROKER_PORT_TCP 1883
|
||||||
#define MQTT_USER "xupa"
|
#define MQTT_USER "xupa"
|
||||||
#define MQTT_PASS "xupa"
|
#define MQTT_PASS "xupa"
|
||||||
|
|
||||||
// ======================================================
|
esp_mqtt_client_handle_t mqtt_client = NULL;
|
||||||
static esp_mqtt_client_handle_t mqtt_client = NULL;
|
static esp_timer_handle_t mqtt_watchdog = NULL;
|
||||||
static bool mqtt_connected = false;
|
static bool mqtt_connected = false;
|
||||||
|
|
||||||
static char topic_cmd[64];
|
char topic_status[64];
|
||||||
static char topic_status[64];
|
char topic_cmd[64];
|
||||||
|
char topic_resp[64];
|
||||||
// TASK HANDLES
|
char topic_lwt[64];
|
||||||
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 TASK
|
// HEARTBEAT / STATUS
|
||||||
// ======================================================
|
// ======================================================
|
||||||
static void status_task(void *pv)
|
static void send_status(void) {
|
||||||
{
|
if (!mqtt_client || !mqtt_connected) return;
|
||||||
while (1) {
|
|
||||||
|
|
||||||
if (mqtt_connected) {
|
char buf[160];
|
||||||
|
snprintf(buf, sizeof(buf),
|
||||||
|
"{\"uptime\":%lu,\"heap\":%lu}",
|
||||||
|
(unsigned long)(esp_log_timestamp() / 1000),
|
||||||
|
(unsigned long)esp_get_free_heap_size());
|
||||||
|
|
||||||
char msg[128];
|
esp_mqtt_client_publish(mqtt_client, topic_status, buf, 0, 1, false);
|
||||||
|
ESP_LOGI(TAG, "📤 STATUS -> %s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
snprintf(msg, sizeof(msg),
|
// ======================================================
|
||||||
"{\"status\":\"online\"}");
|
// WATCHDOG CALLBACK
|
||||||
|
// ======================================================
|
||||||
esp_mqtt_client_publish(mqtt_client,
|
static void mqtt_watchdog_cb(void *arg) {
|
||||||
topic_status,
|
if (!mqtt_connected) {
|
||||||
msg,
|
ESP_LOGE(TAG, "⏱️ 2 minutos sem MQTT, reiniciando ESP...");
|
||||||
0,
|
esp_restart();
|
||||||
1,
|
|
||||||
0);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "💓 Heartbeat enviado");
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================
|
// ======================================================
|
||||||
// CLOCK TASK (só MQTT controla)
|
// EVENT HANDLER
|
||||||
// ======================================================
|
// ======================================================
|
||||||
static void mqtt_clock_task(void *pv)
|
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
||||||
{
|
int32_t event_id, void *event_data) {
|
||||||
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;
|
esp_mqtt_event_handle_t event = event_data;
|
||||||
|
|
||||||
switch (event->event_id) {
|
switch (event->event_id) {
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
case MQTT_EVENT_CONNECTED:
|
case MQTT_EVENT_CONNECTED:
|
||||||
|
|
||||||
mqtt_connected = true;
|
mqtt_connected = true;
|
||||||
|
|
||||||
|
// LED verde no pixel 0
|
||||||
|
led_set_pixel(0, 0, 50, 0);
|
||||||
|
led_show();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "✅ MQTT conectado");
|
ESP_LOGI(TAG, "✅ MQTT conectado");
|
||||||
|
esp_mqtt_client_publish(mqtt_client, topic_status, "online", 0, 1, 0);
|
||||||
// ONLINE retain
|
// esp_mqtt_client_publish(mqtt_client, topic_status, "online", 0, 1, true);
|
||||||
esp_mqtt_client_publish(mqtt_client,
|
|
||||||
topic_status,
|
|
||||||
"{\"status\":\"online\"}",
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
1);
|
|
||||||
|
|
||||||
esp_mqtt_client_subscribe(mqtt_client, topic_cmd, 1);
|
esp_mqtt_client_subscribe(mqtt_client, topic_cmd, 1);
|
||||||
esp_mqtt_client_subscribe(mqtt_client, "time/now", 1);
|
send_status();
|
||||||
|
|
||||||
// heartbeat (1x)
|
|
||||||
if (status_task_handle == NULL) {
|
|
||||||
xTaskCreate(status_task,
|
|
||||||
"status_task",
|
|
||||||
4096,
|
|
||||||
NULL,
|
|
||||||
5,
|
|
||||||
&status_task_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clock (1x)
|
|
||||||
if (mqtt_clock_handle == NULL) {
|
|
||||||
xTaskCreate(mqtt_clock_task,
|
|
||||||
"mqtt_clock",
|
|
||||||
4096,
|
|
||||||
NULL,
|
|
||||||
5,
|
|
||||||
&mqtt_clock_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (mqtt_watchdog) esp_timer_stop(mqtt_watchdog);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// ------------------------------
|
|
||||||
case MQTT_EVENT_DISCONNECTED:
|
case MQTT_EVENT_DISCONNECTED:
|
||||||
mqtt_connected = false;
|
mqtt_connected = false;
|
||||||
|
|
||||||
|
// LED vermelho no pixel 0
|
||||||
|
led_set_pixel(0, 50, 0, 0);
|
||||||
|
led_show();
|
||||||
|
|
||||||
ESP_LOGW(TAG, "⚠️ MQTT desconectado");
|
ESP_LOGW(TAG, "⚠️ MQTT desconectado");
|
||||||
|
if (mqtt_watchdog) esp_timer_start_periodic(mqtt_watchdog, 120000000);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// ------------------------------
|
case MQTT_EVENT_DATA: {
|
||||||
case MQTT_EVENT_DATA:
|
// Copia o payload para um buffer legível
|
||||||
{
|
char json_clean[256];
|
||||||
esp_mqtt_event_handle_t e = event;
|
int len = event->data_len;
|
||||||
|
|
||||||
// 🔥 Ignorar fragmentos parciais
|
if (len >= sizeof(json_clean)) len = sizeof(json_clean) - 1;
|
||||||
if (e->current_data_offset != 0) {
|
memcpy(json_clean, event->data, len);
|
||||||
ESP_LOGW(TAG, "Fragmento ignorado");
|
json_clean[len] = 0; // NULL terminate
|
||||||
return;
|
|
||||||
|
// Remove quebras de linha
|
||||||
|
for (int i = 0; json_clean[i]; i++) {
|
||||||
|
if (json_clean[i] == '\r' || json_clean[i] == '\n')
|
||||||
|
json_clean[i] = ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 Ignorar payload vazio
|
// Mostrar tópico + JSON limpo
|
||||||
if (e->data_len == 0) {
|
ESP_LOGI(TAG, "📩 [%.*s] %s",
|
||||||
ESP_LOGW(TAG, "Payload vazio ignorado");
|
event->topic_len, event->topic,
|
||||||
return;
|
json_clean);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
cJSON_Delete(root);
|
||||||
return;
|
} else {
|
||||||
|
ESP_LOGE(TAG, "❌ JSON inválido");
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case MQTT_EVENT_ERROR:
|
||||||
|
ESP_LOGE(TAG, "❌ Erro MQTT");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// START MQTT
|
|
||||||
// ======================================================
|
|
||||||
void mqtt_handler_start(void)
|
|
||||||
{
|
|
||||||
esp_mqtt_client_config_t cfg = {0};
|
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// 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];
|
uint8_t mac[6];
|
||||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||||
|
snprintf(device_id, sizeof(device_id), "esp_%02X%02X%02X", mac[3], mac[4], mac[5]);
|
||||||
|
|
||||||
char client_id[16];
|
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||||
snprintf(client_id, sizeof(client_id),
|
if (netif) esp_netif_set_hostname(netif, device_id);
|
||||||
"esp_%02X%02X%02X",
|
ESP_LOGI(TAG, "🆔 ID do dispositivo: %s", device_id);
|
||||||
mac[3], mac[4], mac[5]);
|
|
||||||
|
|
||||||
snprintf(topic_cmd, sizeof(topic_cmd),
|
snprintf(topic_status, sizeof(topic_status), "esp/%s/status", device_id);
|
||||||
"esp/%s/cmd", client_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);
|
||||||
|
|
||||||
snprintf(topic_status, sizeof(topic_status),
|
mqtt_cfg.credentials.client_id = device_id;
|
||||||
"esp/%s/status", client_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;
|
||||||
|
|
||||||
cfg.broker.address.hostname = BROKER_HOST;
|
// Certificado raiz (ISRG Root X1)
|
||||||
cfg.broker.address.port = BROKER_PORT;
|
mqtt_cfg.broker.verification.certificate = ca_cert_pem;
|
||||||
cfg.credentials.username = MQTT_USER;
|
ESP_LOGI(TAG, "🔐 TLS ativo (cert embutido, EEPROM ignorada)");
|
||||||
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
|
// -------- LWT --------
|
||||||
cfg.session.last_will.topic = topic_status;
|
mqtt_cfg.session.last_will.topic = topic_lwt;
|
||||||
cfg.session.last_will.msg = "{\"status\":\"offline\"}";
|
mqtt_cfg.session.last_will.msg = "offline";
|
||||||
cfg.session.last_will.qos = 1;
|
mqtt_cfg.session.last_will.qos = 1;
|
||||||
cfg.session.last_will.retain = 1;
|
mqtt_cfg.session.last_will.retain = false;
|
||||||
|
|
||||||
mqtt_client = esp_mqtt_client_init(&cfg);
|
// ======================================================
|
||||||
esp_mqtt_client_register_event(
|
// INICIALIZAÇÃO DO CLIENTE MQTT (TLS OBRIGATÓRIO)
|
||||||
mqtt_client, ESP_EVENT_ANY_ID,
|
// ======================================================
|
||||||
mqtt_event_handler, NULL);
|
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_mqtt_client_start(mqtt_client);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "🚀 MQTT iniciado como %s", client_id);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,80 +0,0 @@
|
|||||||
#include "esp_http_client.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include "buzzer.h"
|
|
||||||
|
|
||||||
static const char *TAG = "WEATHER";
|
|
||||||
|
|
||||||
static bool parse_temperature_from_json(const char *json, float *out_temp)
|
|
||||||
{
|
|
||||||
if (!json || !out_temp) return false;
|
|
||||||
|
|
||||||
// 1) ir para o bloco "current"
|
|
||||||
const char *p = strstr(json, "\"current\"");
|
|
||||||
if (!p) return false;
|
|
||||||
|
|
||||||
// 2) procurar temperature_2m DENTRO de current
|
|
||||||
p = strstr(p, "\"temperature_2m\"");
|
|
||||||
if (!p) return false;
|
|
||||||
|
|
||||||
// 3) ir para o valor
|
|
||||||
p = strchr(p, ':');
|
|
||||||
if (!p) return false;
|
|
||||||
|
|
||||||
*out_temp = strtof(p + 1, NULL);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool net_weather_update(float *out_temp)
|
|
||||||
{
|
|
||||||
char buf[512];
|
|
||||||
|
|
||||||
esp_http_client_config_t cfg = {
|
|
||||||
.url = "http://api.open-meteo.com/v1/forecast?latitude=38.75&longitude=-9.125¤t=temperature_2m",
|
|
||||||
.method = HTTP_METHOD_GET,
|
|
||||||
.transport_type = HTTP_TRANSPORT_OVER_TCP,
|
|
||||||
.timeout_ms = 10000,
|
|
||||||
.user_agent = "esp32-weather",
|
|
||||||
};
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "➡️ HTTP init");
|
|
||||||
|
|
||||||
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
|
||||||
if (!client) {
|
|
||||||
ESP_LOGE(TAG, "❌ http_client_init falhou");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t err = esp_http_client_open(client, 0);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "❌ http_client_open falhou: %s", esp_err_to_name(err));
|
|
||||||
esp_http_client_cleanup(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int content_length = esp_http_client_fetch_headers(client);
|
|
||||||
ESP_LOGI(TAG, "📦 content-length = %d", content_length);
|
|
||||||
|
|
||||||
int len = esp_http_client_read(client, buf, sizeof(buf) - 1);
|
|
||||||
if (len <= 0) {
|
|
||||||
ESP_LOGE(TAG, "❌ http_client_read falhou");
|
|
||||||
esp_http_client_close(client);
|
|
||||||
esp_http_client_cleanup(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[len] = 0;
|
|
||||||
ESP_LOGI(TAG, "📩 JSON: %s", buf);
|
|
||||||
|
|
||||||
esp_http_client_close(client);
|
|
||||||
esp_http_client_cleanup(client);
|
|
||||||
|
|
||||||
if (!parse_temperature_from_json(buf, out_temp)) {
|
|
||||||
ESP_LOGE(TAG, "❌ parse_temperature_from_json falhou");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@ -602,7 +602,7 @@ CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
|
|||||||
# ESP HTTP client
|
# ESP HTTP client
|
||||||
#
|
#
|
||||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
|
CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
|
||||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
|
# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
|
||||||
# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set
|
# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set
|
||||||
# end of ESP HTTP client
|
# end of ESP HTTP client
|
||||||
|
|
||||||
|
|||||||
@ -317,14 +317,14 @@ CONFIG_ESPTOOLPY_FLASHFREQ_40M=y
|
|||||||
# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set
|
# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set
|
||||||
CONFIG_ESPTOOLPY_FLASHFREQ="40m"
|
CONFIG_ESPTOOLPY_FLASHFREQ="40m"
|
||||||
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||||
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
|
||||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
|
||||||
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||||
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||||
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||||
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||||
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||||
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
CONFIG_ESPTOOLPY_FLASHSIZE="2MB"
|
||||||
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
|
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
|
||||||
CONFIG_ESPTOOLPY_BEFORE_RESET=y
|
CONFIG_ESPTOOLPY_BEFORE_RESET=y
|
||||||
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
|
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
|
||||||
|
|||||||
3
tatus
3
tatus
@ -1,3 +0,0 @@
|
|||||||
[33mc5aeddc[m[33m ([m[1;36mHEAD[m[33m -> [m[1;32mmain[m[33m, [m[1;33mtag: [m[1;33mportal-top[m[33m, [m[1;31morigin/main[m[33m, [m[1;31morigin/HEAD[m[33m)[m chore: ignore vscode settings
|
|
||||||
[33m0d828ca[m wifi: portal Wi-Fi top com scan, clear_wifi MQTT e fluxo limpo STA/AP
|
|
||||||
[33m4d28d62[m commit final pc2
|
|
||||||
Loading…
Reference in New Issue
Block a user