Compare commits
No commits in common. "419be3b9292027235a87558c2867c1863527c309" and "ee73633857949f548e7a3448c60e50a9337b6185" have entirely different histories.
419be3b929
...
ee73633857
4
.vscode/c_cpp_properties.json
vendored
4
.vscode/c_cpp_properties.json
vendored
@ -2,11 +2,11 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "ESP-IDF",
|
"name": "ESP-IDF",
|
||||||
"compilerPath": "C:/esp/tools/xtensa-esp32-elf/esp-12.2.0_20230208/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc.exe",
|
"compilerPath": "C:/esp/espressif/tools/xtensa-esp32-elf/esp-12.2.0_20230208/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc.exe",
|
||||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||||
"includePath": [
|
"includePath": [
|
||||||
"${workspaceFolder}/**",
|
"${workspaceFolder}/**",
|
||||||
"C:/ESP/frameworks/v5.1.6/esp-idf/components/**"
|
"C:/esp/esp-idf/components/**"
|
||||||
],
|
],
|
||||||
"defines": [
|
"defines": [
|
||||||
"ESP_PLATFORM",
|
"ESP_PLATFORM",
|
||||||
|
|||||||
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@ -3,14 +3,8 @@
|
|||||||
"*.c": "c",
|
"*.c": "c",
|
||||||
"*.h": "c"
|
"*.h": "c"
|
||||||
},
|
},
|
||||||
|
|
||||||
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||||
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
"C_Cpp.default.compileCommands": "Z:/varios/LED_shit/build/compile_commands.json",
|
||||||
|
|
||||||
"C_Cpp.default.includePath": [
|
|
||||||
"${workspaceFolder}/**",
|
|
||||||
"C:/ESP/frameworks/v5.1.6/esp-idf/components/**"
|
|
||||||
],
|
|
||||||
|
|
||||||
"idf.espIdfPath": "C:/Users/carec/esp/v5.1.6/esp-idf",
|
"idf.espIdfPath": "C:/Users/carec/esp/v5.1.6/esp-idf",
|
||||||
"idf.pythonBinPath": "C:/Users/carec/.espressif/python_env/idf5.1_py3.11_env/Scripts/python.exe",
|
"idf.pythonBinPath": "C:/Users/carec/.espressif/python_env/idf5.1_py3.11_env/Scripts/python.exe",
|
||||||
@ -20,9 +14,8 @@
|
|||||||
"idf.adapterTargetName": "esp32",
|
"idf.adapterTargetName": "esp32",
|
||||||
"idf.portWin": "COM3",
|
"idf.portWin": "COM3",
|
||||||
"idf.flashType": "UART",
|
"idf.flashType": "UART",
|
||||||
|
"idf.buildDir": "C:/esp_build_temp",
|
||||||
"idf.buildDir": "${workspaceFolder}/build",
|
"idf.flashDir": "C:/esp_build_temp",
|
||||||
"idf.flashDir": "${workspaceFolder}/build",
|
|
||||||
|
|
||||||
"cmake.buildDirectory": "${workspaceFolder}/build",
|
"cmake.buildDirectory": "${workspaceFolder}/build",
|
||||||
"cmake.configureSettings": {
|
"cmake.configureSettings": {
|
||||||
|
|||||||
@ -4,20 +4,20 @@ idf_component_register(
|
|||||||
"wifi_config_portal.c"
|
"wifi_config_portal.c"
|
||||||
"mqtt_handler.c"
|
"mqtt_handler.c"
|
||||||
"mqtt_comandos.c"
|
"mqtt_comandos.c"
|
||||||
|
"fs_handler.c"
|
||||||
"eeprom_tls.c"
|
"eeprom_tls.c"
|
||||||
"eeprom_virtual.c"
|
"eeprom_virtual.c"
|
||||||
"dns_server.c"
|
"dns_server.c"
|
||||||
"led_driver.c"
|
|
||||||
"led_effects.c"
|
# LED driver
|
||||||
"eeprom_animacao.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/led_driver.c"
|
||||||
"led_task.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/led_strip/led_strip.c"
|
||||||
"creditos.c"
|
"${CMAKE_CURRENT_SOURCE_DIR}/led_strip/led_strip_encoder.c"
|
||||||
"premios.c"
|
|
||||||
"i2c_helper.c"
|
|
||||||
"display.c"
|
|
||||||
"ui.c"
|
|
||||||
INCLUDE_DIRS
|
INCLUDE_DIRS
|
||||||
|
"."
|
||||||
"include"
|
"include"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/led_strip"
|
||||||
|
|
||||||
REQUIRES
|
REQUIRES
|
||||||
esp_wifi
|
esp_wifi
|
||||||
@ -26,6 +26,7 @@ idf_component_register(
|
|||||||
nvs_flash
|
nvs_flash
|
||||||
mqtt
|
mqtt
|
||||||
json
|
json
|
||||||
|
spiffs
|
||||||
driver
|
driver
|
||||||
esp_http_server
|
esp_http_server
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// certs.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
static const char ca_cert_pem[] =
|
static const char ca_cert_pem[] =
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
#include "creditos.h"
|
|
||||||
#include "led_driver.h"
|
|
||||||
#include "esp_random.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "esp_random.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
|
|
||||||
volatile bool tem_creditos = false;
|
|
||||||
static const char *TAG = "CREDITOS";
|
|
||||||
|
|
||||||
void creditos_dar(void) {
|
|
||||||
tem_creditos = true;
|
|
||||||
ESP_LOGI(TAG, "💰 Crédito recebido");
|
|
||||||
}
|
|
||||||
|
|
||||||
void creditos_task(void *pv) {
|
|
||||||
while (1) {
|
|
||||||
|
|
||||||
if (tem_creditos) {
|
|
||||||
uint16_t pos = esp_random() % LED_COUNT;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "🎯 SPIN -> posição %u", pos);
|
|
||||||
|
|
||||||
led_spin_to(pos, 2, 12, 80);
|
|
||||||
|
|
||||||
tem_creditos = false; // crédito consumido
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(50));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
331
main/display.c
331
main/display.c
@ -1,331 +0,0 @@
|
|||||||
#include "driver/i2c.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "display.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define I2C_PORT I2C_NUM_0
|
|
||||||
|
|
||||||
// Endereços reais
|
|
||||||
#define DISP_TOP_ADDR 0x71 // display superior
|
|
||||||
#define DISP_BOTTOM_ADDR 0x70 // display inferior
|
|
||||||
static uint16_t rotate180(uint16_t m);
|
|
||||||
|
|
||||||
const char *TAG = "DISPLAY";
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// MAPA DE SEGMENTOS (mantido igual ao teu)
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
#define SEG_A (1 << 0)
|
|
||||||
#define SEG_B (1 << 1)
|
|
||||||
#define SEG_C (1 << 2)
|
|
||||||
#define SEG_D (1 << 3)
|
|
||||||
#define SEG_E (1 << 4)
|
|
||||||
#define SEG_F (1 << 5)
|
|
||||||
|
|
||||||
#define SEG_ML (1 << 6)
|
|
||||||
#define SEG_MR (1 << 7)
|
|
||||||
|
|
||||||
#define SEG_TL (1 << 8)
|
|
||||||
#define SEG_TM (1 << 9)
|
|
||||||
#define SEG_TR (1 << 10)
|
|
||||||
|
|
||||||
#define SEG_BL (1 << 11)
|
|
||||||
#define SEG_BM (1 << 12)
|
|
||||||
#define SEG_BR (1 << 13)
|
|
||||||
|
|
||||||
#define SEG_DP (1 << 14)
|
|
||||||
|
|
||||||
#define SEG_G (SEG_ML | SEG_MR)
|
|
||||||
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// FUNÇÕES GENÉRICAS PARA QUALQUER DISPLAY
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
static void disp_send_cmd(uint8_t addr, uint8_t cmd)
|
|
||||||
{
|
|
||||||
i2c_master_write_to_device(I2C_PORT, addr, &cmd, 1, 20 / portTICK_PERIOD_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void disp_raw(uint8_t addr, int pos, uint16_t mask)
|
|
||||||
{
|
|
||||||
if (pos < 0 || pos > 3) return;
|
|
||||||
|
|
||||||
uint8_t buf[3];
|
|
||||||
buf[0] = pos * 2;
|
|
||||||
buf[1] = mask & 0xFF;
|
|
||||||
buf[2] = (mask >> 8) & 0xFF;
|
|
||||||
|
|
||||||
i2c_master_write_to_device(I2C_PORT, addr, buf, 3, 20 / portTICK_PERIOD_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void disp_clear(uint8_t addr)
|
|
||||||
{
|
|
||||||
uint8_t buf[17] = {0};
|
|
||||||
buf[0] = 0x00;
|
|
||||||
i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), 20 / portTICK_PERIOD_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// INIT PARA OS DOIS DISPLAYS (TOP 0x71 | BOTTOM 0x70)
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
void display_init(void)
|
|
||||||
{
|
|
||||||
uint8_t cmd1 = 0x21; // oscillator ON
|
|
||||||
uint8_t cmd2 = 0x81; // display ON, blink OFF
|
|
||||||
uint8_t cmd3 = 0xEF; // brightness MAX
|
|
||||||
|
|
||||||
// TOP
|
|
||||||
disp_send_cmd(DISP_TOP_ADDR, cmd1);
|
|
||||||
disp_send_cmd(DISP_TOP_ADDR, cmd2);
|
|
||||||
disp_send_cmd(DISP_TOP_ADDR, cmd3);
|
|
||||||
disp_clear(DISP_TOP_ADDR);
|
|
||||||
|
|
||||||
// BOTTOM
|
|
||||||
disp_send_cmd(DISP_BOTTOM_ADDR, cmd1);
|
|
||||||
disp_send_cmd(DISP_BOTTOM_ADDR, cmd2);
|
|
||||||
disp_send_cmd(DISP_BOTTOM_ADDR, cmd3);
|
|
||||||
disp_clear(DISP_BOTTOM_ADDR);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "📟 Displays inicializados: TOP=0x71 BOTTOM=0x70");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// RAW PARA TOP E BOTTOM
|
|
||||||
// =======================================================
|
|
||||||
void display_raw_top(int pos, uint16_t mask)
|
|
||||||
{
|
|
||||||
int p = 3 - pos; // inverter ordem dos dígitos
|
|
||||||
uint16_t m = rotate180(mask); // rodar segmentos
|
|
||||||
disp_raw(DISP_TOP_ADDR, p, m);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void display_raw_bottom(int pos, uint16_t mask)
|
|
||||||
{
|
|
||||||
disp_raw(DISP_BOTTOM_ADDR, pos, mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_clear_top(void)
|
|
||||||
{
|
|
||||||
disp_clear(DISP_TOP_ADDR);
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_clear_bottom(void)
|
|
||||||
{
|
|
||||||
disp_clear(DISP_BOTTOM_ADDR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =====================
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// CHARSET — mantido EXACTAMENTE como o teu
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
static uint16_t charset(char c)
|
|
||||||
{
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
// NÚMEROS
|
|
||||||
case '0': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F;
|
|
||||||
case '1': return SEG_B | SEG_C;
|
|
||||||
case '2': return SEG_A | SEG_B | SEG_G | SEG_E | SEG_D;
|
|
||||||
case '3': return SEG_A | SEG_B | SEG_G | SEG_C | SEG_D;
|
|
||||||
case '4': return SEG_F | SEG_G | SEG_B | SEG_C;
|
|
||||||
case '5': return SEG_A | SEG_F | SEG_G | SEG_C | SEG_D;
|
|
||||||
case '6': return SEG_A | SEG_F | SEG_G | SEG_E | SEG_D | SEG_C;
|
|
||||||
case '7': return SEG_A | SEG_B | SEG_C;
|
|
||||||
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;
|
|
||||||
|
|
||||||
// LETRAS COMPLETAS (mantidas)
|
|
||||||
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 'C': case 'c': return SEG_A | SEG_F | SEG_E | SEG_D;
|
|
||||||
case 'D': case 'd': return SEG_B | SEG_C | SEG_D | SEG_E | SEG_G;
|
|
||||||
case 'E': case 'e': return SEG_A | SEG_F | SEG_G | SEG_E | SEG_D;
|
|
||||||
case 'F': case 'f': return SEG_A | SEG_F | SEG_G | SEG_E;
|
|
||||||
case 'G': case 'g': return SEG_A | SEG_F | SEG_E | SEG_D | SEG_C | SEG_G;
|
|
||||||
case 'H': case 'h': return SEG_F | SEG_E | SEG_G | SEG_B | SEG_C;
|
|
||||||
case 'I': case 'i': return SEG_B | SEG_C;
|
|
||||||
case 'J': case 'j': return SEG_B | SEG_C | SEG_D;
|
|
||||||
case 'K': case 'k': return SEG_F | SEG_E | SEG_G | SEG_TR | SEG_BR;
|
|
||||||
case 'L': case 'l': return SEG_F | SEG_E | SEG_D;
|
|
||||||
case 'M': case 'm': return SEG_F | SEG_E | SEG_TL | SEG_TR | SEG_B | SEG_C;
|
|
||||||
case 'N': case 'n': return SEG_F | SEG_E | SEG_TL | SEG_BR | SEG_C | SEG_B;
|
|
||||||
case 'O': case 'o': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F;
|
|
||||||
case 'P': case 'p': return SEG_A | SEG_B | SEG_F | SEG_G | SEG_E;
|
|
||||||
case 'Q': case 'q': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_BR;
|
|
||||||
case 'R': case 'r': return SEG_A | SEG_B | SEG_F | SEG_G | SEG_E | SEG_BR;
|
|
||||||
case 'S': case 's': return SEG_A | SEG_F | SEG_G | SEG_C | SEG_D;
|
|
||||||
case 'T': case 't': return SEG_A | SEG_TM | SEG_BR;
|
|
||||||
case 'U': case 'u': return SEG_F | SEG_E | SEG_D | SEG_C | SEG_B;
|
|
||||||
case 'V': case 'v': return SEG_F | SEG_E | SEG_D | SEG_B | SEG_TR;
|
|
||||||
case 'W': case 'w': return SEG_F | SEG_E | SEG_D | SEG_C | SEG_B | SEG_BR | SEG_TR;
|
|
||||||
case 'X': case 'x': return SEG_TL | SEG_TR | SEG_ML | SEG_MR | SEG_BL | SEG_BR;
|
|
||||||
case 'Y': case 'y': return SEG_F | SEG_B | SEG_G | SEG_C | SEG_D;
|
|
||||||
case 'Z': case 'z': return SEG_A | SEG_TR | SEG_G | SEG_BL | SEG_D;
|
|
||||||
|
|
||||||
// símbolos
|
|
||||||
case '-': return SEG_G;
|
|
||||||
case '.': return SEG_DP;
|
|
||||||
case '_': return SEG_D;
|
|
||||||
case ' ': return 0;
|
|
||||||
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// DISPLAY CARACTER / TEXTO
|
|
||||||
// =======================================================
|
|
||||||
|
|
||||||
void display_char_top(int pos, char c)
|
|
||||||
{
|
|
||||||
display_raw_top(pos, charset(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_char_bottom(int pos, char c)
|
|
||||||
{
|
|
||||||
disp_raw(DISP_BOTTOM_ADDR, pos, charset(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
void display_text_top(const char *txt)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
char c = txt[i] ? txt[i] : ' ';
|
|
||||||
display_char_top(i, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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,48 +0,0 @@
|
|||||||
#include "eeprom_animacao.h"
|
|
||||||
#include "nvs.h"
|
|
||||||
#include "nvs_flash.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
static const char *TAG = "EEPROM_ANIM";
|
|
||||||
|
|
||||||
#define NAMESPACE "storage"
|
|
||||||
#define KEY_ANIM "animacao"
|
|
||||||
|
|
||||||
// valor em RAM, sempre igual ao guardado
|
|
||||||
uint8_t animacao = 0;
|
|
||||||
|
|
||||||
void animacao_load(void)
|
|
||||||
{
|
|
||||||
nvs_handle_t handle;
|
|
||||||
esp_err_t err = nvs_open(NAMESPACE, NVS_READONLY, &handle);
|
|
||||||
|
|
||||||
if (err == ESP_OK) {
|
|
||||||
err = nvs_get_u8(handle, KEY_ANIM, &animacao);
|
|
||||||
nvs_close(handle);
|
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
animacao = 0; // default
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
animacao = 0; // default
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Carregado animacao = %u", animacao);
|
|
||||||
}
|
|
||||||
|
|
||||||
void animacao_save(uint8_t v)
|
|
||||||
{
|
|
||||||
nvs_handle_t handle;
|
|
||||||
esp_err_t err = nvs_open(NAMESPACE, NVS_READWRITE, &handle);
|
|
||||||
|
|
||||||
if (err == ESP_OK) {
|
|
||||||
nvs_set_u8(handle, KEY_ANIM, v);
|
|
||||||
nvs_commit(handle);
|
|
||||||
nvs_close(handle);
|
|
||||||
|
|
||||||
animacao = v; // atualiza RAM também
|
|
||||||
ESP_LOGI(TAG, "Gravado animacao = %u", v);
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Falha ao abrir NVS p/ animacao");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
main/fs_handler.c
Normal file
54
main/fs_handler.c
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_spiffs.h"
|
||||||
|
|
||||||
|
static const char *TAG = "FS";
|
||||||
|
|
||||||
|
// --- MONTAR SPIFFS ---
|
||||||
|
void fs_init(void) {
|
||||||
|
esp_vfs_spiffs_conf_t conf = {
|
||||||
|
.base_path = "/spiffs",
|
||||||
|
.partition_label = "storage",
|
||||||
|
.max_files = 5,
|
||||||
|
.format_if_mount_failed = true
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "❌ Falha ao montar SPIFFS (%s)", esp_err_to_name(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t total = 0, used = 0;
|
||||||
|
ret = esp_spiffs_info(conf.partition_label, &total, &used);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "❌ Erro ao obter info SPIFFS (%s)", esp_err_to_name(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "📂 SPIFFS montado: total=%d KB, usado=%d KB",
|
||||||
|
(int)(total / 1024), (int)(used / 1024));
|
||||||
|
|
||||||
|
// Criar ficheiro de exemplo se não existir
|
||||||
|
FILE *f = fopen("/spiffs/config.json", "r");
|
||||||
|
if (!f) {
|
||||||
|
ESP_LOGW(TAG, "⚠️ config.json não existe, a criar...");
|
||||||
|
f = fopen("/spiffs/config.json", "w");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "{ \"ssid\": \"ALQAEDA\", \"pass\": \"1q2w3e4r5t\" }");
|
||||||
|
fclose(f);
|
||||||
|
ESP_LOGI(TAG, "✅ config.json criado.");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "❌ Erro a criar config.json");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DESMONTAR SPIFFS ---
|
||||||
|
void fs_deinit(void) {
|
||||||
|
ESP_LOGI(TAG, "🧹 Desmontando SPIFFS...");
|
||||||
|
esp_vfs_spiffs_unregister(NULL);
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
#include "driver/i2c.h"
|
|
||||||
|
|
||||||
#define SDA_PIN 21
|
|
||||||
#define SCL_PIN 22
|
|
||||||
#define I2C_PORT I2C_NUM_0
|
|
||||||
|
|
||||||
void i2c_init(void)
|
|
||||||
{
|
|
||||||
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 = 400000
|
|
||||||
};
|
|
||||||
|
|
||||||
i2c_param_config(I2C_PORT, &cfg);
|
|
||||||
i2c_driver_install(I2C_PORT, cfg.mode, 0, 0, 0);
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
extern volatile bool tem_creditos;
|
|
||||||
|
|
||||||
void creditos_task(void *pv);
|
|
||||||
void creditos_dar(void); // função para marcar crédito
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// ENDEREÇOS I2C (definidos no .c)
|
|
||||||
// =======================================================
|
|
||||||
// TOP = 0x71
|
|
||||||
// BOTTOM = 0x70
|
|
||||||
|
|
||||||
// Inicialização dos dois displays
|
|
||||||
void display_init(void);
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// RAW ACCESS (usa 16 bits de segmentos)
|
|
||||||
// =======================================================
|
|
||||||
void display_raw_top(int pos, uint16_t mask);
|
|
||||||
void display_raw_bottom(int pos, uint16_t mask);
|
|
||||||
|
|
||||||
void display_clear_top(void);
|
|
||||||
void display_clear_bottom(void);
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// TEXTO E CARACTERES
|
|
||||||
// =======================================================
|
|
||||||
void display_char_top(int pos, char c);
|
|
||||||
void display_char_bottom(int pos, char c);
|
|
||||||
|
|
||||||
void display_text_top(const char *txt); // 4 chars
|
|
||||||
void display_text_bottom(const char *txt); // 4 chars
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// NÚMEROS (0–9999)
|
|
||||||
// =======================================================
|
|
||||||
void display_digit_top(int pos, uint8_t val);
|
|
||||||
void display_digit_bottom(int pos, uint8_t val);
|
|
||||||
|
|
||||||
void display_number_top(int num);
|
|
||||||
void display_number_bottom(int num);
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// RELÓGIO (HH:MM com DP entre horas)
|
|
||||||
// =======================================================
|
|
||||||
void display_set_time_top(int horas, int minutos);
|
|
||||||
|
|
||||||
// =======================================================
|
|
||||||
// DEBUG
|
|
||||||
// =======================================================
|
|
||||||
void display_debug_segment(uint16_t bitmask);
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// byte carregado da EEPROM
|
|
||||||
extern uint8_t animacao;
|
|
||||||
|
|
||||||
// lê o byte guardado
|
|
||||||
void animacao_load(void);
|
|
||||||
|
|
||||||
// grava o byte
|
|
||||||
void animacao_save(uint8_t v);
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
void i2c_init(void);
|
|
||||||
@ -1,25 +1,24 @@
|
|||||||
#ifndef LED_DRIVER_H
|
#pragma once
|
||||||
#define LED_DRIVER_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
#define LED_COUNT 60
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
// inicialização do driver
|
#endif
|
||||||
void led_driver_init(void);
|
|
||||||
// número de LEDs
|
// === Configurações principais ===
|
||||||
int led_get_count(void);
|
#define LED_PIN 18
|
||||||
// envia os dados para o anel
|
#define LED_COUNT 60
|
||||||
void led_show(void);
|
#define LED_RES_HZ 10000000 // 10 MHz de resolução (0.1 µs por tick)
|
||||||
// limpa todos os LEDs
|
|
||||||
void led_clear(void);
|
// === Interface pública ===
|
||||||
// define um LED individual
|
esp_err_t led_init(void);
|
||||||
void led_set_pixel(int index, uint8_t r, uint8_t g, uint8_t b);
|
esp_err_t led_clear(void);
|
||||||
void led_all_on(uint8_t r, uint8_t g, uint8_t b);
|
esp_err_t led_set(uint16_t index, uint8_t r, uint8_t g, uint8_t b);
|
||||||
void led_all_off(void);
|
esp_err_t led_show(void);
|
||||||
|
void led_rainbow(uint16_t offset);
|
||||||
// spin da sorte
|
void led_spin_to(uint16_t target, uint16_t rounds, uint16_t delay_start, uint16_t delay_end);
|
||||||
void led_spin_to(uint16_t target, int min_spins, int max_spins, int base_delay_ms);
|
void led_idle_animation(void);
|
||||||
void led_set_global_brightness(uint8_t level);
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
void led_all_on(uint8_t r, uint8_t g, uint8_t b);
|
|
||||||
void led_all_off(void);
|
|
||||||
void led_anim_03(void);
|
|
||||||
|
|
||||||
void led_idle_animation(void);
|
|
||||||
void led_jackpot_animation(void);
|
|
||||||
void led_clock_animation(void); // <-- NOVA ANIMAÇÃO
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
void led_task(void *pv);
|
|
||||||
@ -1,20 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include "mqtt_client.h"
|
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
|
#include "esp_err.h"
|
||||||
// Variáveis globais
|
|
||||||
extern esp_mqtt_client_handle_t mqtt_client;
|
|
||||||
extern char topic_resp[64];
|
|
||||||
|
|
||||||
extern uint8_t hora_r, hora_g, hora_b;
|
|
||||||
extern uint8_t min_r, min_g, min_b;
|
|
||||||
extern uint8_t sec_r, sec_g, sec_b;
|
|
||||||
|
|
||||||
extern int clock_speed_ms;
|
|
||||||
|
|
||||||
extern bool demo_mode;
|
|
||||||
extern char current_mode[16];
|
|
||||||
|
|
||||||
void mqtt_comandos_handle(cJSON *root);
|
void mqtt_comandos_handle(cJSON *root);
|
||||||
|
|||||||
@ -5,16 +5,10 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern esp_mqtt_client_handle_t mqtt_client;
|
extern esp_mqtt_client_handle_t mqtt_client; // ✅ agora visível em outros .c
|
||||||
|
static inline void mqtt_handler_loop(void) {
|
||||||
// Exportar os tópicos MQTT (antes eram static!)
|
// placeholder — sem função neste build
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
extern uint16_t premios[60];
|
|
||||||
|
|
||||||
uint16_t premio_da_posicao(int pos);
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
// ui.h
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
MENU_OFF = 0,
|
|
||||||
MENU_MAIN,
|
|
||||||
MENU_PERC, // percentagem prémios
|
|
||||||
MENU_READ, // leitura contadores
|
|
||||||
MENU_TEST_LED, // teste roda de LEDs
|
|
||||||
MENU_TEST_DISP, // teste display
|
|
||||||
} menu_state_t;
|
|
||||||
|
|
||||||
void ui_set_menu(menu_state_t s);
|
|
||||||
void ui_menu_next(void);
|
|
||||||
void ui_menu_ok(void);
|
|
||||||
void ui_task(void *pv);
|
|
||||||
@ -1,197 +1,147 @@
|
|||||||
#include "led_driver.h"
|
#include "led_driver.h"
|
||||||
#include "driver/rmt_tx.h"
|
#include "driver/rmt_tx.h"
|
||||||
#include "driver/gpio.h"
|
#include "led_strip_encoder.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "esp_random.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "premios.h"
|
#include "esp_rom_sys.h"
|
||||||
|
|
||||||
|
static const char *TAG = "LED";
|
||||||
|
static bool led_ready = false; // 🚦 flag de inicialização
|
||||||
|
static rmt_channel_handle_t led_chan = NULL;
|
||||||
|
static rmt_encoder_handle_t led_enc = NULL;
|
||||||
|
static uint8_t led_pixels[LED_COUNT * 3];
|
||||||
|
|
||||||
#define LED_PIN 18 // <-- CERTIFICA-TE disto
|
// --- Conversão HSV → RGB ---
|
||||||
#define RMT_RESOLUTION_HZ 10000000 // 10 MHz (1 tick = 0.1us)
|
static void hsv2rgb(uint32_t h, uint32_t s, uint32_t v,
|
||||||
|
uint8_t *r, uint8_t *g, uint8_t *b)
|
||||||
typedef struct {
|
|
||||||
uint8_t r,g,b;
|
|
||||||
} led_color_t;
|
|
||||||
|
|
||||||
static led_color_t leds[LED_COUNT];
|
|
||||||
|
|
||||||
static rmt_channel_handle_t rmt_chan = NULL;
|
|
||||||
static rmt_encoder_handle_t encoder = NULL;
|
|
||||||
|
|
||||||
static rmt_transmit_config_t tx_config = {
|
|
||||||
.loop_count = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint8_t global_brightness = 255;
|
|
||||||
|
|
||||||
void led_set_global_brightness(uint8_t level)
|
|
||||||
{
|
{
|
||||||
global_brightness = level;
|
h %= 360;
|
||||||
|
uint32_t rgb_max = v * 255 / 100;
|
||||||
|
uint32_t rgb_min = rgb_max * (100 - s) / 100;
|
||||||
|
uint32_t i = h / 60, diff = h % 60;
|
||||||
|
uint32_t adj = (rgb_max - rgb_min) * diff / 60;
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case 0: *r = rgb_max; *g = rgb_min + adj; *b = rgb_min; break;
|
||||||
|
case 1: *r = rgb_max - adj; *g = rgb_max; *b = rgb_min; break;
|
||||||
|
case 2: *r = rgb_min; *g = rgb_max; *b = rgb_min + adj; break;
|
||||||
|
case 3: *r = rgb_min; *g = rgb_max - adj; *b = rgb_max; break;
|
||||||
|
case 4: *r = rgb_min + adj; *g = rgb_min; *b = rgb_max; break;
|
||||||
|
default:*r = rgb_max; *g = rgb_min; *b = rgb_max - adj; break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Inicialização ---
|
||||||
|
esp_err_t led_init(void)
|
||||||
void led_driver_init(void)
|
|
||||||
{
|
{
|
||||||
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
|
ESP_LOGI(TAG, "Inicializando LEDs no GPIO%d (%d LEDs)", LED_PIN, LED_COUNT);
|
||||||
|
|
||||||
rmt_tx_channel_config_t chan_cfg = {
|
rmt_tx_channel_config_t tx_cfg = {
|
||||||
.gpio_num = LED_PIN,
|
.gpio_num = LED_PIN,
|
||||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
.resolution_hz = RMT_RESOLUTION_HZ,
|
|
||||||
.mem_block_symbols = 64,
|
.mem_block_symbols = 64,
|
||||||
.trans_queue_depth = 1,
|
.resolution_hz = LED_RES_HZ,
|
||||||
|
.trans_queue_depth = 4,
|
||||||
};
|
};
|
||||||
|
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_cfg, &led_chan));
|
||||||
|
|
||||||
ESP_ERROR_CHECK(rmt_new_tx_channel(&chan_cfg, &rmt_chan));
|
led_strip_encoder_config_t enc_cfg = {.resolution = LED_RES_HZ};
|
||||||
ESP_ERROR_CHECK(rmt_enable(rmt_chan));
|
ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&enc_cfg, &led_enc));
|
||||||
|
ESP_ERROR_CHECK(rmt_enable(led_chan));
|
||||||
|
|
||||||
// WS2812B timings (EXATOS)
|
memset(led_pixels, 0, sizeof(led_pixels));
|
||||||
rmt_bytes_encoder_config_t enc_cfg = {
|
|
||||||
.bit0 = {
|
|
||||||
.duration0 = 4, .level0 = 1, // 0.4us HIGH
|
|
||||||
.duration1 = 9, .level1 = 0 // 0.9us LOW
|
|
||||||
},
|
|
||||||
.bit1 = {
|
|
||||||
.duration0 = 9, .level0 = 1, // 0.9us HIGH
|
|
||||||
.duration1 = 4, .level1 = 0 // 0.4us LOW
|
|
||||||
},
|
|
||||||
.flags.msb_first = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(rmt_new_bytes_encoder(&enc_cfg, &encoder));
|
led_ready = true; // ✅ marca como pronto
|
||||||
|
led_show(); // apaga tudo ao iniciar
|
||||||
memset(leds, 0, sizeof(leds));
|
return ESP_OK;
|
||||||
led_show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void led_set_pixel(int index, uint8_t r, uint8_t g, uint8_t b)
|
// --- Apagar todos os LEDs ---
|
||||||
|
esp_err_t led_clear(void)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= LED_COUNT) return;
|
if (!led_ready) return ESP_ERR_INVALID_STATE;
|
||||||
leds[index].r = r;
|
memset(led_pixels, 0, sizeof(led_pixels));
|
||||||
leds[index].g = g;
|
return led_show();
|
||||||
leds[index].b = b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void led_clear(void)
|
// --- Definir cor individual ---
|
||||||
|
esp_err_t led_set(uint16_t index, uint8_t r, uint8_t g, uint8_t b)
|
||||||
{
|
{
|
||||||
memset(leds, 0, sizeof(leds));
|
if (index >= LED_COUNT) return ESP_ERR_INVALID_ARG;
|
||||||
|
led_pixels[index * 3 + 0] = g; // WS2812 usa ordem GRB
|
||||||
|
led_pixels[index * 3 + 1] = r;
|
||||||
|
led_pixels[index * 3 + 2] = b;
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hw_update(void)
|
// --- Enviar buffer para o anel ---
|
||||||
|
esp_err_t led_show(void)
|
||||||
{
|
{
|
||||||
uint8_t buf[LED_COUNT * 3];
|
if (!led_ready || !led_chan || !led_enc) {
|
||||||
|
ESP_LOGW(TAG, "⚠️ led_show() chamado antes da inicialização — ignorado");
|
||||||
// Previne divisão por zero
|
return ESP_ERR_INVALID_STATE;
|
||||||
uint8_t br = (global_brightness == 0) ? 1 : global_brightness;
|
|
||||||
|
|
||||||
for (int i = 0; i < LED_COUNT; i++) {
|
|
||||||
|
|
||||||
// Escala (r,g,b) com brilho global (0..255)
|
|
||||||
uint8_t r = (leds[i].r * br) >> 8;
|
|
||||||
uint8_t g = (leds[i].g * br) >> 8;
|
|
||||||
uint8_t b = (leds[i].b * br) >> 8;
|
|
||||||
|
|
||||||
// GRB
|
|
||||||
buf[i*3 + 0] = g;
|
|
||||||
buf[i*3 + 1] = r;
|
|
||||||
buf[i*3 + 2] = b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_ERROR_CHECK(rmt_transmit(rmt_chan, encoder, buf, sizeof(buf), &tx_config));
|
rmt_transmit_config_t tx_cfg = {.loop_count = 0};
|
||||||
rmt_tx_wait_all_done(rmt_chan, -1);
|
esp_err_t err = rmt_transmit(led_chan, led_enc,
|
||||||
|
led_pixels, sizeof(led_pixels), &tx_cfg);
|
||||||
esp_rom_delay_us(60); // reset 60us
|
if (err != ESP_OK) {
|
||||||
}
|
ESP_LOGE(TAG, "❌ rmt_transmit falhou (%s)", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
|
||||||
void led_show(void)
|
|
||||||
{
|
|
||||||
hw_update();
|
|
||||||
}
|
|
||||||
|
|
||||||
int led_get_count(void)
|
|
||||||
{
|
|
||||||
return LED_COUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_spin_to(uint16_t target, int min_spins, int max_spins, int base_delay_ms)
|
|
||||||
{
|
|
||||||
int n = LED_COUNT;
|
|
||||||
if (min_spins < 1) min_spins = 1;
|
|
||||||
if (max_spins < min_spins) max_spins = min_spins;
|
|
||||||
|
|
||||||
// passos aleatórios entre min e max
|
|
||||||
int extra = 0;
|
|
||||||
if (max_spins > min_spins)
|
|
||||||
extra = rand() % (max_spins - min_spins + 1);
|
|
||||||
|
|
||||||
// número total de passos até ao LED final
|
|
||||||
int total_steps = (min_spins + extra) * n + (target % n);
|
|
||||||
int delay = base_delay_ms;
|
|
||||||
|
|
||||||
for (int step = 0; step <= total_steps; step++) {
|
|
||||||
int pos = step % n;
|
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
if (i == pos)
|
|
||||||
led_set_pixel(i, 0, 40, 0); // cursor verde
|
|
||||||
else
|
|
||||||
led_set_pixel(i, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = rmt_tx_wait_all_done(led_chan, portMAX_DELAY);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "❌ rmt_tx_wait_all_done falhou (%s)", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
// --- Animação de rotação até posição alvo ---
|
||||||
|
void led_spin_to(uint16_t target, uint16_t rounds, uint16_t delay_start, uint16_t delay_end)
|
||||||
|
{
|
||||||
|
if (!led_ready) return;
|
||||||
|
|
||||||
|
uint32_t total_steps = rounds * LED_COUNT + target;
|
||||||
|
for (uint32_t i = 0; i < total_steps; i++) {
|
||||||
|
uint16_t pos = i % LED_COUNT;
|
||||||
|
|
||||||
|
// Limpa e acende o LED atual
|
||||||
|
led_clear();
|
||||||
|
led_set(pos, 255, 150, 0);
|
||||||
led_show();
|
led_show();
|
||||||
|
|
||||||
// aceleração
|
// Calcula delay progressivo (aceleração -> desaceleração)
|
||||||
if (step < total_steps / 3 && delay > 3)
|
uint32_t delay = delay_start + ((delay_end - delay_start) * i) / total_steps;
|
||||||
delay--;
|
|
||||||
|
|
||||||
// desaceleração
|
|
||||||
else if (step > (2 * total_steps) / 3)
|
|
||||||
delay++;
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(delay));
|
vTaskDelay(pdMS_TO_TICKS(delay));
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================
|
// Flash final
|
||||||
// POSIÇÃO FINAL
|
for (int j = 0; j < 3; j++) {
|
||||||
// =======================================
|
led_clear();
|
||||||
|
|
||||||
int pos_final = total_steps % n;
|
|
||||||
|
|
||||||
// LED final destacado
|
|
||||||
led_set_pixel(pos_final, 0, 60, 0);
|
|
||||||
led_show();
|
led_show();
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
vTaskDelay(pdMS_TO_TICKS(80));
|
||||||
|
for (int k = 0; k < LED_COUNT; k++)
|
||||||
// =======================================
|
led_set(k, 255, 200, 50);
|
||||||
// PRÉMIO FINAL
|
|
||||||
// =======================================
|
|
||||||
|
|
||||||
uint16_t premio = premio_da_posicao(pos_final);
|
|
||||||
|
|
||||||
if (premio > 0) {
|
|
||||||
// piscar o LED que deu prémio
|
|
||||||
for (int k = 0; k < 8; k++) {
|
|
||||||
if (k & 1)
|
|
||||||
led_set_pixel(pos_final, 0, 0, 0);
|
|
||||||
else
|
|
||||||
led_set_pixel(pos_final, 0, 0, 60); // azul prémio
|
|
||||||
led_show();
|
led_show();
|
||||||
vTaskDelay(pdMS_TO_TICKS(120));
|
vTaskDelay(pdMS_TO_TICKS(80));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// sem prémio → pisca vermelho suave
|
|
||||||
for (int k = 0; k < 4; k++) {
|
// --- Animação idle (arco-íris lento) ---
|
||||||
if (k & 1)
|
void led_idle_animation(void)
|
||||||
led_set_pixel(pos_final, 0, 0, 0);
|
{
|
||||||
else
|
if (!led_ready) return;
|
||||||
led_set_pixel(pos_final, 40, 0, 0);
|
|
||||||
led_show();
|
static uint16_t hue = 0;
|
||||||
vTaskDelay(pdMS_TO_TICKS(120));
|
for (int i = 0; i < LED_COUNT; i++) {
|
||||||
}
|
uint8_t r, g, b;
|
||||||
}
|
hsv2rgb((hue + i * 6) % 360, 100, 10, &r, &g, &b);
|
||||||
|
led_set(i, r, g, b);
|
||||||
// (aqui no futuro vais mandar MQTT do prémio)
|
}
|
||||||
|
led_show();
|
||||||
|
hue = (hue + 2) % 360;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,166 +0,0 @@
|
|||||||
#include "led_effects.h"
|
|
||||||
#include "led_driver.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include "mqtt_comandos.h"
|
|
||||||
#include "premios.h"
|
|
||||||
#include "display.h" // onde está a tua display_number()
|
|
||||||
|
|
||||||
|
|
||||||
void led_all_on(uint8_t r, uint8_t g, uint8_t b)
|
|
||||||
{
|
|
||||||
int n = led_get_count();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
led_set_pixel(i, r, g, b);
|
|
||||||
}
|
|
||||||
led_show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_all_off(void)
|
|
||||||
{
|
|
||||||
led_clear();
|
|
||||||
led_show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_idle_animation(void)
|
|
||||||
{
|
|
||||||
static int pos = 0;
|
|
||||||
int n = led_get_count();
|
|
||||||
if (n <= 0) return;
|
|
||||||
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
led_set_pixel(i, 0, 0, 0);
|
|
||||||
|
|
||||||
// pontinho azul
|
|
||||||
led_set_pixel(pos, 0, 0, 16);
|
|
||||||
led_show();
|
|
||||||
|
|
||||||
pos = (pos + 1) % n;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_jackpot_animation(void)
|
|
||||||
{
|
|
||||||
static int state = 0;
|
|
||||||
static int step = 0;
|
|
||||||
int n = led_get_count();
|
|
||||||
|
|
||||||
if (state == 0) {
|
|
||||||
// Piscas laranja
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
led_set_pixel(i, (step & 1) ? 32 : 0, (step & 1) ? 16 : 0, 0);
|
|
||||||
|
|
||||||
led_show();
|
|
||||||
|
|
||||||
step++;
|
|
||||||
if (step >= 12) { // 6 ON + 6 OFF
|
|
||||||
step = 0;
|
|
||||||
state = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (state == 1) {
|
|
||||||
// Arco-íris progressivo
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
int x = (i + step) % n;
|
|
||||||
uint8_t r = (x * 5) & 0xFF;
|
|
||||||
uint8_t g = ((x * 3) & 0xFF) >> 1;
|
|
||||||
uint8_t b = ((x * 7) & 0xFF) >> 2;
|
|
||||||
led_set_pixel(i, r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
led_show();
|
|
||||||
|
|
||||||
step++;
|
|
||||||
if (step >= n * 2) {
|
|
||||||
step = 0;
|
|
||||||
state = 0; // repete a animação
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_clock_animation(void)
|
|
||||||
{
|
|
||||||
time_t now = time(NULL);
|
|
||||||
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
|
|
||||||
|
|
||||||
int pos = s % LED_COUNT; // 0..59 ou 0..63
|
|
||||||
|
|
||||||
led_set_pixel(pos, 0, 0, 60); // azul forte
|
|
||||||
led_show();
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void led_anim_03(void)
|
|
||||||
{
|
|
||||||
static int left = 0;
|
|
||||||
static int right = LED_COUNT - 1;
|
|
||||||
static bool meet = false;
|
|
||||||
|
|
||||||
int n = LED_COUNT;
|
|
||||||
|
|
||||||
// ------- Lista de prémios -------
|
|
||||||
// coloca as posições reais depois
|
|
||||||
static const int premios[] = { 3, 8, 15, 22, 31, 40, 47, 53 };
|
|
||||||
static const int total_premios = sizeof(premios) / sizeof(premios[0]);
|
|
||||||
|
|
||||||
// limpar tudo primeiro
|
|
||||||
led_clear();
|
|
||||||
|
|
||||||
if (!meet) {
|
|
||||||
|
|
||||||
// acende esquerda e direita
|
|
||||||
led_set_pixel(left, 0, 40, 0); // verde
|
|
||||||
led_set_pixel(right, 0, 40, 0);
|
|
||||||
|
|
||||||
left++;
|
|
||||||
right--;
|
|
||||||
|
|
||||||
// encontraram-se?
|
|
||||||
if (left >= right) {
|
|
||||||
meet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// ---- PISCAR PRÉMIOS ----
|
|
||||||
static bool on = false;
|
|
||||||
on = !on;
|
|
||||||
|
|
||||||
for (int i = 0; i < total_premios; i++) {
|
|
||||||
int p = premios[i] % n;
|
|
||||||
if (on)
|
|
||||||
led_set_pixel(p, 0, 0, 60); // azul forte
|
|
||||||
else
|
|
||||||
led_set_pixel(p, 0, 0, 10); // azul fraco
|
|
||||||
}
|
|
||||||
|
|
||||||
// durante alguns ciclos pisca antes de resetar
|
|
||||||
static int count = 0;
|
|
||||||
count++;
|
|
||||||
if (count > 20) { // ajusta aqui a duração do piscar
|
|
||||||
count = 0;
|
|
||||||
meet = false;
|
|
||||||
left = 0;
|
|
||||||
right = n - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
led_show();
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(50));
|
|
||||||
}
|
|
||||||
65
main/led_strip/led_strip.c
Normal file
65
main/led_strip/led_strip.c
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include "led_strip.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
static const char *TAG = "led_strip";
|
||||||
|
|
||||||
|
struct led_strip_t {
|
||||||
|
uint32_t length;
|
||||||
|
uint8_t *buffer;
|
||||||
|
rmt_channel_handle_t channel;
|
||||||
|
uint32_t resolution;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t b;
|
||||||
|
} __attribute__((packed)) grb_pixel_t;
|
||||||
|
|
||||||
|
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *config, led_strip_handle_t *ret_strip) {
|
||||||
|
if (!config || !ret_strip) return ESP_ERR_INVALID_ARG;
|
||||||
|
|
||||||
|
led_strip_handle_t strip = calloc(1, sizeof(struct led_strip_t));
|
||||||
|
if (!strip) return ESP_ERR_NO_MEM;
|
||||||
|
|
||||||
|
strip->length = config->strip_length;
|
||||||
|
strip->resolution = config->resolution_hz;
|
||||||
|
strip->channel = config->rmt_channel;
|
||||||
|
strip->buffer = calloc(strip->length, sizeof(grb_pixel_t));
|
||||||
|
if (!strip->buffer) {
|
||||||
|
free(strip);
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ret_strip = strip;
|
||||||
|
ESP_LOGI(TAG, "Novo LED strip criado (%lu LEDs)", (unsigned long)strip->length);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint8_t red, uint8_t green, uint8_t blue) {
|
||||||
|
if (!strip || index >= strip->length) return ESP_ERR_INVALID_ARG;
|
||||||
|
grb_pixel_t *pixels = (grb_pixel_t *)strip->buffer;
|
||||||
|
pixels[index].g = green;
|
||||||
|
pixels[index].r = red;
|
||||||
|
pixels[index].b = blue;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t led_strip_refresh(led_strip_handle_t strip) {
|
||||||
|
if (!strip) return ESP_ERR_INVALID_ARG;
|
||||||
|
// Envia os dados via RMT
|
||||||
|
rmt_transmit_config_t tx_conf = { .loop_count = 0 };
|
||||||
|
ESP_ERROR_CHECK(rmt_transmit(strip->channel, NULL, strip->buffer, strip->length * sizeof(grb_pixel_t), &tx_conf));
|
||||||
|
ESP_ERROR_CHECK(rmt_tx_wait_all_done(strip->channel, portMAX_DELAY));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t led_strip_clear(led_strip_handle_t strip) {
|
||||||
|
if (!strip) return ESP_ERR_INVALID_ARG;
|
||||||
|
memset(strip->buffer, 0, strip->length * sizeof(grb_pixel_t));
|
||||||
|
return led_strip_refresh(strip);
|
||||||
|
}
|
||||||
46
main/led_strip/led_strip.h
Normal file
46
main/led_strip/led_strip.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "driver/rmt_tx.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle para a instância de LED strip
|
||||||
|
*/
|
||||||
|
typedef struct led_strip_t *led_strip_handle_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configuração para criar um LED strip
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t strip_length; // número de LEDs
|
||||||
|
uint32_t resolution_hz; // resolução do RMT
|
||||||
|
rmt_channel_handle_t rmt_channel;// canal RMT a usar
|
||||||
|
} led_strip_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cria uma nova instância de LED strip
|
||||||
|
*/
|
||||||
|
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *config, led_strip_handle_t *ret_strip);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Define a cor de um LED (index base 0)
|
||||||
|
*/
|
||||||
|
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint8_t red, uint8_t green, uint8_t blue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Atualiza os LEDs com as cores definidas
|
||||||
|
*/
|
||||||
|
esp_err_t led_strip_refresh(led_strip_handle_t strip);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Limpa todos os LEDs (define para preto)
|
||||||
|
*/
|
||||||
|
esp_err_t led_strip_clear(led_strip_handle_t strip);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
124
main/led_strip/led_strip_encoder.c
Normal file
124
main/led_strip/led_strip_encoder.c
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "led_strip_encoder.h"
|
||||||
|
|
||||||
|
static const char *TAG = "led_encoder";
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
rmt_encoder_t base;
|
||||||
|
rmt_encoder_t *bytes_encoder;
|
||||||
|
rmt_encoder_t *copy_encoder;
|
||||||
|
int state;
|
||||||
|
rmt_symbol_word_t reset_code;
|
||||||
|
} rmt_led_strip_encoder_t;
|
||||||
|
|
||||||
|
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
|
||||||
|
{
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||||
|
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
|
||||||
|
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
|
||||||
|
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
|
||||||
|
rmt_encode_state_t state = RMT_ENCODING_RESET;
|
||||||
|
size_t encoded_symbols = 0;
|
||||||
|
switch (led_encoder->state) {
|
||||||
|
case 0: // send RGB data
|
||||||
|
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
|
||||||
|
if (session_state & RMT_ENCODING_COMPLETE) {
|
||||||
|
led_encoder->state = 1; // switch to next state when current encoding session finished
|
||||||
|
}
|
||||||
|
if (session_state & RMT_ENCODING_MEM_FULL) {
|
||||||
|
state |= RMT_ENCODING_MEM_FULL;
|
||||||
|
goto out; // yield if there's no free space for encoding artifacts
|
||||||
|
}
|
||||||
|
// fall-through
|
||||||
|
case 1: // send reset code
|
||||||
|
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
|
||||||
|
sizeof(led_encoder->reset_code), &session_state);
|
||||||
|
if (session_state & RMT_ENCODING_COMPLETE) {
|
||||||
|
led_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session
|
||||||
|
state |= RMT_ENCODING_COMPLETE;
|
||||||
|
}
|
||||||
|
if (session_state & RMT_ENCODING_MEM_FULL) {
|
||||||
|
state |= RMT_ENCODING_MEM_FULL;
|
||||||
|
goto out; // yield if there's no free space for encoding artifacts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
*ret_state = state;
|
||||||
|
return encoded_symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
|
||||||
|
{
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||||
|
rmt_del_encoder(led_encoder->bytes_encoder);
|
||||||
|
rmt_del_encoder(led_encoder->copy_encoder);
|
||||||
|
free(led_encoder);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
|
||||||
|
{
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||||
|
rmt_encoder_reset(led_encoder->bytes_encoder);
|
||||||
|
rmt_encoder_reset(led_encoder->copy_encoder);
|
||||||
|
led_encoder->state = RMT_ENCODING_RESET;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
rmt_led_strip_encoder_t *led_encoder = NULL;
|
||||||
|
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||||
|
led_encoder = rmt_alloc_encoder_mem(sizeof(rmt_led_strip_encoder_t));
|
||||||
|
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
|
||||||
|
led_encoder->base.encode = rmt_encode_led_strip;
|
||||||
|
led_encoder->base.del = rmt_del_led_strip_encoder;
|
||||||
|
led_encoder->base.reset = rmt_led_strip_encoder_reset;
|
||||||
|
// different led strip might have its own timing requirements, following parameter is for WS2812
|
||||||
|
rmt_bytes_encoder_config_t bytes_encoder_config = {
|
||||||
|
.bit0 = {
|
||||||
|
.level0 = 1,
|
||||||
|
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
|
||||||
|
},
|
||||||
|
.bit1 = {
|
||||||
|
.level0 = 1,
|
||||||
|
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
|
||||||
|
},
|
||||||
|
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
|
||||||
|
};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
|
||||||
|
rmt_copy_encoder_config_t copy_encoder_config = {};
|
||||||
|
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
|
||||||
|
|
||||||
|
uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us
|
||||||
|
led_encoder->reset_code = (rmt_symbol_word_t) {
|
||||||
|
.level0 = 0,
|
||||||
|
.duration0 = reset_ticks,
|
||||||
|
.level1 = 0,
|
||||||
|
.duration1 = reset_ticks,
|
||||||
|
};
|
||||||
|
*ret_encoder = &led_encoder->base;
|
||||||
|
return ESP_OK;
|
||||||
|
err:
|
||||||
|
if (led_encoder) {
|
||||||
|
if (led_encoder->bytes_encoder) {
|
||||||
|
rmt_del_encoder(led_encoder->bytes_encoder);
|
||||||
|
}
|
||||||
|
if (led_encoder->copy_encoder) {
|
||||||
|
rmt_del_encoder(led_encoder->copy_encoder);
|
||||||
|
}
|
||||||
|
free(led_encoder);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
28
main/led_strip/led_strip_encoder.h
Normal file
28
main/led_strip/led_strip_encoder.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "driver/rmt_tx.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configuração do encoder de LED strip (usado pelo RMT)
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t resolution; // resolução em Hz
|
||||||
|
} led_strip_encoder_config_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cria um novo encoder para enviar dados WS2812 via RMT.
|
||||||
|
*
|
||||||
|
* @param config Configuração do encoder (resolução)
|
||||||
|
* @param ret_encoder Ponteiro de retorno do handle do encoder
|
||||||
|
*
|
||||||
|
* @return ESP_OK se criado com sucesso
|
||||||
|
*/
|
||||||
|
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config,
|
||||||
|
rmt_encoder_handle_t *ret_encoder);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,45 +0,0 @@
|
|||||||
#include "led_task.h"
|
|
||||||
#include "led_effects.h"
|
|
||||||
#include "eeprom_animacao.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
static const char *TAG = "LED_TASK";
|
|
||||||
|
|
||||||
void led_task(void *pv)
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "💡 LED task iniciada");
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
|
|
||||||
switch (animacao) {
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
// modo idle (pontinho azul)
|
|
||||||
led_idle_animation();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
// modo relógio
|
|
||||||
led_clock_animation();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
// jackpot non-blocking
|
|
||||||
led_jackpot_animation();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
led_anim_03(); // <-- AGORA É A PRINCIPAL
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// fallback seguro
|
|
||||||
led_idle_animation();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// controla a velocidade de TODAS as animações
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(40)); // ~25 FPS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
184
main/main.c
184
main/main.c
@ -1,6 +1,5 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "esp_rom_sys.h"
|
#include "esp_rom_sys.h"
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
@ -12,49 +11,31 @@
|
|||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
|
#include "esp_spiffs.h"
|
||||||
#include "esp_sntp.h"
|
#include "esp_sntp.h"
|
||||||
|
|
||||||
#include "eeprom_virtual.h"
|
|
||||||
#include "eeprom_tls.h"
|
|
||||||
#include "eeprom_animacao.h"
|
|
||||||
|
|
||||||
#include "wifi_config_portal.h"
|
#include "wifi_config_portal.h"
|
||||||
#include "mqtt_handler.h"
|
#include "mqtt_handler.h"
|
||||||
|
|
||||||
#include "led_driver.h"
|
#include "led_driver.h"
|
||||||
#include "led_effects.h"
|
#include "esp_random.h"
|
||||||
|
#include "eeprom_virtual.h"
|
||||||
|
#include "eeprom_tls.h"
|
||||||
|
|
||||||
#include "creditos.h"
|
|
||||||
#include "led_task.h"
|
|
||||||
|
|
||||||
#include "driver/i2c.h"
|
|
||||||
#include "i2c_helper.h"
|
|
||||||
#include "display.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 uint32_t segundos = 0;
|
|
||||||
static bool wifi_ready = false;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int total_creditos;
|
int total_creditos;
|
||||||
int total_saidas;
|
int total_saidas;
|
||||||
} contadores_t;
|
} contadores_t;
|
||||||
|
|
||||||
|
static const char *TAG = "APP";
|
||||||
|
static uint32_t segundos = 0;
|
||||||
|
static bool wifi_ready = false;
|
||||||
|
|
||||||
// ============================
|
// === Contador simples de debug ===
|
||||||
// Task contador simples
|
|
||||||
// ============================
|
|
||||||
void segundos_task(void *pv) {
|
void segundos_task(void *pv) {
|
||||||
while (1) {
|
while (1) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
segundos++;
|
segundos++;
|
||||||
|
// ESP_LOGI("TIMER", "⏱ %lu segundos", segundos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,105 +43,55 @@ uint32_t get_segundos(void) {
|
|||||||
return segundos;
|
return segundos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Task de LEDs ===
|
||||||
|
void led_task(void *pv) {
|
||||||
|
bool tem_creditos = false;
|
||||||
|
while (1) {
|
||||||
|
if (wifi_ready && tem_creditos) {
|
||||||
|
uint16_t target = esp_random() % LED_COUNT;
|
||||||
|
led_spin_to(target, 2, 12, 80);
|
||||||
|
tem_creditos = false;
|
||||||
|
} else if (wifi_ready) {
|
||||||
|
led_idle_animation();
|
||||||
|
} else {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100)); // espera Wi-Fi
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================
|
// === Callback quando Wi-Fi e IP estão prontos ===
|
||||||
// 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, IP obtido — iniciando MQTT e LEDs...");
|
||||||
|
|
||||||
ESP_LOGI(TAG, "✅ Wi-Fi conectado — iniciando MQTT...");
|
|
||||||
mqtt_handler_start();
|
mqtt_handler_start();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "🕒 SNTP...");
|
ESP_LOGI(TAG, "🕒 Inicializando SNTP...");
|
||||||
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||||||
esp_sntp_setservername(0, "pool.ntp.org");
|
esp_sntp_setservername(0, "pool.ntp.org");
|
||||||
esp_sntp_init();
|
esp_sntp_init();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "💡 Inicializando driver LED...");
|
ESP_LOGI(TAG, "💡 Inicializando LEDs...");
|
||||||
led_driver_init();
|
led_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
|
|
||||||
// ============================
|
|
||||||
// Stack Overflow Handler
|
|
||||||
// ============================
|
|
||||||
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
|
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
|
||||||
{
|
{
|
||||||
esp_rom_printf("\n🧨 Stack overflow em %s!\n", pcTaskName);
|
esp_rom_printf("\n🧨 Stack overflow em %s!\n", pcTaskName);
|
||||||
|
// NÃO reinicia aqui — isso bloqueia o WiFi event task e causa WDT
|
||||||
|
// apenas marca para reiniciar depois
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
esp_restart();
|
esp_restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Configuração básica do I2C
|
// === Função principal ===
|
||||||
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) {
|
void app_main(void) {
|
||||||
|
// ---------- EEPROM ----------
|
||||||
// -------- EEPROM virtual --------
|
|
||||||
eeprom_virtual_init();
|
eeprom_virtual_init();
|
||||||
|
|
||||||
contadores_t contadores = {100, 25};
|
contadores_t contadores = {100, 25};
|
||||||
@ -177,42 +108,49 @@ void app_main(void) {
|
|||||||
ESP_LOGW("EEPROM", "⚠️ Falha ao ler dados!");
|
ESP_LOGW("EEPROM", "⚠️ Falha ao ler dados!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- NVS normal --------
|
// ---------- NVS ----------
|
||||||
ESP_ERROR_CHECK(nvs_flash_init());
|
ESP_ERROR_CHECK(nvs_flash_init());
|
||||||
|
|
||||||
animacao_load();
|
// ---------- LOG ----------
|
||||||
ESP_LOGI("ANIM", "🎨 Animação carregada = %u", animacao);
|
esp_log_level_set("wifi", ESP_LOG_WARN);
|
||||||
|
|
||||||
|
// ---------- SPIFFS ----------
|
||||||
|
ESP_LOGI(TAG, "🔧 Montando SPIFFS...");
|
||||||
|
esp_vfs_spiffs_conf_t spiffs_conf = {
|
||||||
|
.base_path = "/spiffs",
|
||||||
|
.partition_label = "storage",
|
||||||
|
.max_files = 5,
|
||||||
|
.format_if_mount_failed = true
|
||||||
|
};
|
||||||
|
esp_err_t ret = esp_vfs_spiffs_register(&spiffs_conf);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "❌ Falha ao montar SPIFFS (%s)", esp_err_to_name(ret));
|
||||||
|
} else {
|
||||||
|
size_t total = 0, used = 0;
|
||||||
|
esp_spiffs_info(spiffs_conf.partition_label, &total, &used);
|
||||||
|
ESP_LOGI(TAG, "📦 SPIFFS OK: total=%d bytes, usado=%d", total, used);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Wi-Fi ----------
|
||||||
i2c_init(); // <- o helper entra aqui
|
|
||||||
display_init(); // <- inicializa o HT16K33
|
|
||||||
i2c_scan();
|
|
||||||
display_text_top("INIT");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -------- Wi-Fi --------
|
|
||||||
wifi_config_t cfg;
|
wifi_config_t cfg;
|
||||||
bool have_creds = false;
|
bool have_creds = false;
|
||||||
|
|
||||||
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 no NVS: SSID=%s", cfg.sta.ssid);
|
ESP_LOGI(TAG, "📂 Credenciais encontradas 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);
|
||||||
|
|
||||||
// -------- Criar tasks iniciais --------
|
// ---------- Tasks ----------
|
||||||
xTaskCreate(segundos_task, "segundos_task", 4096, NULL, 5, NULL);
|
xTaskCreate(segundos_task, "segundos_task", 12288, NULL, 5, NULL);
|
||||||
|
xTaskCreate(led_task, "led_task", 12288, NULL, 5, NULL);
|
||||||
|
|
||||||
// -------- Loop principal --------
|
// ---------- Loop principal (livre) ----------
|
||||||
while (1) {
|
while (1) {
|
||||||
if (wifi_ready) {
|
if (wifi_ready) {
|
||||||
mqtt_handler_loop();
|
mqtt_handler_loop(); // mantém MQTT ativo
|
||||||
}
|
}
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,409 +1,89 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <sys/stat.h>
|
||||||
#include <stdbool.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_system.h"
|
|
||||||
#include "esp_random.h"
|
|
||||||
#include "mqtt_client.h"
|
#include "mqtt_client.h"
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
|
|
||||||
#include "led_driver.h"
|
|
||||||
#include "led_effects.h"
|
|
||||||
#include "eeprom_virtual.h"
|
#include "eeprom_virtual.h"
|
||||||
#include "eeprom_animacao.h" // animacao + animacao_save()
|
|
||||||
|
|
||||||
#include "ui.h"
|
|
||||||
#include "display.h"
|
|
||||||
|
|
||||||
static const char *TAG = "MQTT_CMD";
|
static const char *TAG = "MQTT_CMD";
|
||||||
|
|
||||||
// Estes vêm de mqtt_handler.c
|
|
||||||
extern esp_mqtt_client_handle_t mqtt_client;
|
extern esp_mqtt_client_handle_t mqtt_client;
|
||||||
extern char topic_resp[64];
|
|
||||||
|
|
||||||
// Variável global declarada noutro ficheiro
|
#define TOPIC_RESP "esp/esp32-002/resp"
|
||||||
extern bool modo_bloqueado;
|
|
||||||
|
|
||||||
// Modo atual
|
void mqtt_comandos_handle(cJSON *root) {
|
||||||
bool demo_mode = false;
|
cJSON *id = cJSON_GetObjectItem(root, "id");
|
||||||
char current_mode[16] = "IDLE";
|
|
||||||
|
|
||||||
// Relógio
|
|
||||||
uint8_t hora_r = 10, hora_g = 40, hora_b = 10;
|
|
||||||
uint8_t min_r = 10, min_g = 10, min_b = 60;
|
|
||||||
uint8_t sec_r = 5, sec_g = 5, sec_b = 5;
|
|
||||||
int clock_speed_ms = 150;
|
|
||||||
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// FUNÇÃO PRINCIPAL
|
|
||||||
// ======================================================
|
|
||||||
void mqtt_comandos_handle(cJSON *root)
|
|
||||||
{
|
|
||||||
cJSON *cmd = cJSON_GetObjectItem(root, "cmd");
|
cJSON *cmd = cJSON_GetObjectItem(root, "cmd");
|
||||||
if (!cJSON_IsString(cmd)) {
|
if (!cJSON_IsNumber(id) || !cJSON_IsString(cmd)) {
|
||||||
ESP_LOGW(TAG, "⚠️ Comando inválido (sem cmd)");
|
ESP_LOGW(TAG, "⚠️ Comando inválido");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *c = cmd->valuestring;
|
int req_id = id->valueint;
|
||||||
ESP_LOGI(TAG, "📩 CMD: %s", c);
|
const char *comando = cmd->valuestring;
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// LED_ON
|
|
||||||
// --------------------------------------------------
|
|
||||||
if (strcmp(c, "LED_ON") == 0) {
|
|
||||||
led_all_on(50, 50, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
else if (strcmp(c, "LED_OFF") == 0) {
|
|
||||||
led_all_off();
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(c, "CLEAR") == 0) {
|
|
||||||
led_all_off();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
else if (strcmp(c, "SPIN") == 0) {
|
|
||||||
int n = led_get_count();
|
|
||||||
if (n > 0) {
|
|
||||||
uint16_t p = esp_random() % n;
|
|
||||||
led_spin_to(p, 2, 12, 80);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
else if (strcmp(c, "JACKPOT") == 0) {
|
|
||||||
led_jackpot_animation();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
else if (strcmp(c, "REBOOT") == 0) {
|
|
||||||
ESP_LOGW(TAG, "🔁 REBOOT pedido via MQTT");
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
|
||||||
esp_restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
else if (strcmp(c, "STATUS") == 0) {
|
|
||||||
char resp[160];
|
|
||||||
snprintf(resp, sizeof(resp),
|
|
||||||
"{\"uptime\":%lu,\"heap\":%lu}",
|
|
||||||
(unsigned long)(esp_log_timestamp()/1000),
|
|
||||||
(unsigned long)esp_get_free_heap_size());
|
|
||||||
|
|
||||||
esp_mqtt_client_publish(mqtt_client, topic_resp,
|
|
||||||
resp, 0, 1, false);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "📤 STATUS enviado");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// BLOQUEIO
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "BLOCK") == 0) {
|
|
||||||
|
|
||||||
cJSON *v = cJSON_GetObjectItem(root, "value");
|
|
||||||
if (!cJSON_IsNumber(v)) {
|
|
||||||
ESP_LOGW(TAG, "BLOCK sem valor numérico");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t novo = (uint8_t)v->valueint;
|
|
||||||
|
|
||||||
uint8_t val = novo;
|
|
||||||
eeprom_virtual_write_bin("blocked", &val, 1);
|
|
||||||
|
|
||||||
extern bool modo_bloqueado;
|
|
||||||
modo_bloqueado = novo;
|
|
||||||
|
|
||||||
ESP_LOGW(TAG, "%s", novo ? "BLOQUEADO" : "DESBLOQUEADO");
|
|
||||||
|
|
||||||
char resp[64];
|
|
||||||
snprintf(resp, sizeof(resp), "{\"blocked\":%u}", novo);
|
|
||||||
esp_mqtt_client_publish(mqtt_client, topic_resp,
|
|
||||||
resp, 0, 1, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// SET_ANIM
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "SET_ANIM") == 0) {
|
|
||||||
|
|
||||||
cJSON *v = cJSON_GetObjectItem(root, "value");
|
|
||||||
if (!cJSON_IsNumber(v)) {
|
|
||||||
ESP_LOGW(TAG, "❌ SET_ANIM sem value");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t novo = v->valueint;
|
|
||||||
animacao_save(novo);
|
|
||||||
animacao = novo;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "🎨 Animação alterada para %u", novo);
|
|
||||||
|
|
||||||
char resp[64];
|
|
||||||
snprintf(resp, sizeof(resp), "{\"anim\":%u}", novo);
|
|
||||||
esp_mqtt_client_publish(mqtt_client, topic_resp, resp, 0, 1, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// SET_COLOR
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "SET_COLOR") == 0) {
|
|
||||||
|
|
||||||
cJSON *r = cJSON_GetObjectItem(root, "r");
|
|
||||||
cJSON *g = cJSON_GetObjectItem(root, "g");
|
|
||||||
cJSON *b = cJSON_GetObjectItem(root, "b");
|
|
||||||
|
|
||||||
if (!cJSON_IsNumber(r) || !cJSON_IsNumber(g) || !cJSON_IsNumber(b)) {
|
|
||||||
ESP_LOGW(TAG, "❌ SET_COLOR sem r/g/b");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int n = led_get_count();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
led_set_pixel(i, r->valueint, g->valueint, b->valueint);
|
|
||||||
}
|
|
||||||
led_show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// SET_BRIGHT
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "SET_BRIGHT") == 0) {
|
|
||||||
|
|
||||||
cJSON *v = cJSON_GetObjectItem(root, "value");
|
|
||||||
if (!cJSON_IsNumber(v)) {
|
|
||||||
ESP_LOGW(TAG, "❌ SET_BRIGHT sem value");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
led_set_global_brightness(v->valueint);
|
|
||||||
ESP_LOGI(TAG, "💡 Brightness = %u", v->valueint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// LED_PIXEL
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "LED_PIXEL") == 0) {
|
|
||||||
|
|
||||||
cJSON *n = cJSON_GetObjectItem(root, "n");
|
|
||||||
cJSON *r = cJSON_GetObjectItem(root, "r");
|
|
||||||
cJSON *g = cJSON_GetObjectItem(root, "g");
|
|
||||||
cJSON *b = cJSON_GetObjectItem(root, "b");
|
|
||||||
|
|
||||||
if (!cJSON_IsNumber(n) || !cJSON_IsNumber(r) ||
|
|
||||||
!cJSON_IsNumber(g) || !cJSON_IsNumber(b)) {
|
|
||||||
ESP_LOGW(TAG, "❌ LED_PIXEL sem dados");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
led_set_pixel(n->valueint, r->valueint, g->valueint, b->valueint);
|
|
||||||
led_show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// DEMO
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "DEMO_ON") == 0) {
|
|
||||||
demo_mode = true;
|
|
||||||
strncpy(current_mode, "DEMO", sizeof(current_mode));
|
|
||||||
ESP_LOGI(TAG, "▶ Demo ON");
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(c, "DEMO_OFF") == 0) {
|
|
||||||
demo_mode = false;
|
|
||||||
strncpy(current_mode, "NORMAL", sizeof(current_mode));
|
|
||||||
ESP_LOGI(TAG, "⏹ Demo OFF");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// SET_MODE
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "SET_MODE") == 0) {
|
|
||||||
|
|
||||||
cJSON *v = cJSON_GetObjectItem(root, "value");
|
|
||||||
if (!cJSON_IsString(v)) {
|
|
||||||
ESP_LOGW(TAG, "❌ SET_MODE sem string");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(current_mode, v->valuestring, sizeof(current_mode));
|
|
||||||
current_mode[sizeof(current_mode)-1] = '\0';
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "🎛 Modo = %s", current_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// GET_INFO
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "GET_INFO") == 0) {
|
|
||||||
|
|
||||||
char resp[256];
|
|
||||||
snprintf(resp, sizeof(resp),
|
|
||||||
"{\"uptime\":%lu,\"heap\":%lu,\"anim\":%u,\"mode\":\"%s\"}",
|
|
||||||
(unsigned long)(esp_log_timestamp()/1000),
|
|
||||||
(unsigned long)esp_get_free_heap_size(),
|
|
||||||
animacao,
|
|
||||||
current_mode
|
|
||||||
);
|
|
||||||
|
|
||||||
esp_mqtt_client_publish(mqtt_client, topic_resp, resp, 0, 1, false);
|
|
||||||
ESP_LOGI(TAG, "📤 INFO enviado");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// CORES DO RELÓGIO
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "SET_HOUR_COLOR") == 0 ||
|
|
||||||
strcmp(c, "SET_MIN_COLOR") == 0 ||
|
|
||||||
strcmp(c, "SET_SEC_COLOR") == 0) {
|
|
||||||
|
|
||||||
cJSON *r = cJSON_GetObjectItem(root, "r");
|
|
||||||
cJSON *g = cJSON_GetObjectItem(root, "g");
|
|
||||||
cJSON *b = cJSON_GetObjectItem(root, "b");
|
|
||||||
|
|
||||||
if (!cJSON_IsNumber(r) || !cJSON_IsNumber(g) || !cJSON_IsNumber(b)) {
|
|
||||||
ESP_LOGW(TAG, "❌ SET_*_COLOR inválido");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(c, "SET_HOUR_COLOR") == 0) {
|
|
||||||
hora_r = r->valueint; hora_g = g->valueint; hora_b = b->valueint;
|
|
||||||
}
|
|
||||||
else if (strcmp(c, "SET_MIN_COLOR") == 0) {
|
|
||||||
min_r = r->valueint; min_g = g->valueint; min_b = b->valueint;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sec_r = r->valueint; sec_g = g->valueint; sec_b = b->valueint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// SET_CLOCK_SPEED
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "SET_CLOCK_SPEED") == 0) {
|
|
||||||
|
|
||||||
cJSON *v = cJSON_GetObjectItem(root, "ms");
|
|
||||||
if (!cJSON_IsNumber(v)) {
|
|
||||||
ESP_LOGW(TAG, "❌ SET_CLOCK_SPEED sem ms");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clock_speed_ms = v->valueint;
|
|
||||||
ESP_LOGI(TAG, "⏰ Clock speed = %d ms", clock_speed_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// MENU
|
|
||||||
// ======================================================
|
|
||||||
|
|
||||||
else if (strcmp(c, "MENU_ON") == 0) {
|
|
||||||
ui_set_menu(MENU_MAIN);
|
|
||||||
display_text_top("MENU");
|
|
||||||
ESP_LOGI(TAG, "📟 MENU ON");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(c, "MENU_OFF") == 0) {
|
|
||||||
ui_set_menu(MENU_OFF);
|
|
||||||
display_clear_top();
|
|
||||||
ESP_LOGI(TAG, "📟 MENU OFF");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(c, "MENU_NEXT") == 0) {
|
|
||||||
ui_menu_next();
|
|
||||||
ESP_LOGI(TAG, "📟 MENU NEXT");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(c, "MENU_OK") == 0) {
|
|
||||||
ui_menu_ok();
|
|
||||||
ESP_LOGI(TAG, "📟 MENU OK");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(c, "DISP_TEST") == 0) {
|
|
||||||
display_text_top("8888");
|
|
||||||
ESP_LOGI(TAG, "🖥️ TESTE DISPLAY");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (strcmp(c, "LED_TEST") == 0) {
|
|
||||||
int n = led_get_count();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
led_set_pixel(i, 0, 0, 40);
|
|
||||||
led_show();
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(8));
|
|
||||||
}
|
|
||||||
led_all_off();
|
|
||||||
ESP_LOGI(TAG, "💡 TESTE LED");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// SET_PERC (percentagem)
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "SET_PERC") == 0) {
|
|
||||||
|
|
||||||
cJSON *v = cJSON_GetObjectItem(root, "val");
|
|
||||||
if (!cJSON_IsNumber(v)) {
|
|
||||||
ESP_LOGW(TAG, "❌ SET_PERC sem valor");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t p = v->valueint;
|
|
||||||
|
|
||||||
// guarda na EEPROM virtual
|
|
||||||
eeprom_virtual_write_bin("percentagem", &p, 1);
|
|
||||||
|
|
||||||
// mostra no display
|
|
||||||
display_number_bottom(p);
|
|
||||||
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "🎯 Percentagem definida = %u%%", p);
|
|
||||||
|
|
||||||
char resp[64];
|
|
||||||
snprintf(resp, sizeof(resp), "{\"percent\":%u}", p);
|
|
||||||
esp_mqtt_client_publish(mqtt_client, topic_resp, resp, 0, 1, false);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// TEST_RING
|
|
||||||
// ======================================================
|
|
||||||
else if (strcmp(c, "TEST_RING") == 0) {
|
|
||||||
|
|
||||||
int n = led_get_count();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
led_clear();
|
|
||||||
led_set_pixel(i, 0, 50, 0);
|
|
||||||
led_show();
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(80));
|
|
||||||
}
|
|
||||||
led_all_off();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================================================
|
|
||||||
// COMANDO DESCONHECIDO
|
|
||||||
// ======================================================
|
|
||||||
else {
|
|
||||||
ESP_LOGW(TAG, "⚠️ Comando desconhecido: %s", c);
|
|
||||||
|
|
||||||
|
// -------- FS_LIST --------
|
||||||
|
if (strcmp(comando, "FS_LIST") == 0) {
|
||||||
|
DIR *dir = opendir("/spiffs");
|
||||||
|
if (!dir) {
|
||||||
char resp[128];
|
char resp[128];
|
||||||
snprintf(resp, sizeof(resp),
|
snprintf(resp, sizeof(resp), "{\"id\":%d,\"error\":\"fs_not_mounted\"}", req_id);
|
||||||
"{\"error\":\"unknown_cmd\",\"cmd\":\"%s\"}", c);
|
esp_mqtt_client_publish(mqtt_client, TOPIC_RESP, resp, 0, 1, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
esp_mqtt_client_publish(mqtt_client, topic_resp, resp, 0, 1, false);
|
char json[2048];
|
||||||
|
snprintf(json, sizeof(json), "{\"id\":%d,\"files\":[", req_id);
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int first = 1;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
char path[512];
|
||||||
|
snprintf(path, sizeof(path), "/spiffs/%.255s", entry->d_name);
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path, &st) == 0) {
|
||||||
|
// 🔧 Correção: buffer maior e limite de nome seguro
|
||||||
|
char file_json[320];
|
||||||
|
snprintf(file_json, sizeof(file_json),
|
||||||
|
"{\"name\":\"%.200s\",\"size\":%ld}", entry->d_name, (long)st.st_size);
|
||||||
|
if (!first) strlcat(json, ",", sizeof(json));
|
||||||
|
strlcat(json, file_json, sizeof(json));
|
||||||
|
first = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
strlcat(json, "]}", sizeof(json));
|
||||||
|
esp_mqtt_client_publish(mqtt_client, TOPIC_RESP, json, 0, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- CERT_REQ --------
|
||||||
|
else if (strcmp(comando, "CERT_REQ") == 0) {
|
||||||
|
char json[128];
|
||||||
|
snprintf(json, sizeof(json), "{\"id\":%d,\"cmd\":\"CERT_REQ\"}", req_id);
|
||||||
|
esp_mqtt_client_publish(mqtt_client, TOPIC_RESP, json, 0, 1, false);
|
||||||
|
ESP_LOGI(TAG, "📤 Pedido de certificado enviado");
|
||||||
|
|
||||||
|
// -------- CERT_SAVE --------
|
||||||
|
} else if (strcmp(comando, "CERT_SAVE") == 0) {
|
||||||
|
cJSON *cert = cJSON_GetObjectItem(root, "cert");
|
||||||
|
if (!cJSON_IsString(cert)) {
|
||||||
|
char resp[128];
|
||||||
|
snprintf(resp, sizeof(resp), "{\"id\":%d,\"error\":\"missing_cert\"}", req_id);
|
||||||
|
esp_mqtt_client_publish(mqtt_client, TOPIC_RESP, resp, 0, 1, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *cert_str = cert->valuestring;
|
||||||
|
|
||||||
|
// 🔧 Correção: usar versão binária (string + terminador)
|
||||||
|
if (eeprom_virtual_write_bin("cert", cert_str, strlen(cert_str) + 1) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "💾 Certificado gravado na EEPROM (%d bytes)", (int)strlen(cert_str));
|
||||||
|
char resp[128];
|
||||||
|
snprintf(resp, sizeof(resp), "{\"id\":%d,\"cmd\":\"CERT_SAVE\",\"ok\":true}", req_id);
|
||||||
|
esp_mqtt_client_publish(mqtt_client, TOPIC_RESP, resp, 0, 1, false);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "❌ Falha ao gravar certificado");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,11 +12,11 @@
|
|||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "eeprom_virtual.h"
|
#include "eeprom_virtual.h"
|
||||||
#include "ui.h"
|
|
||||||
static const char *TAG = "MQTT";
|
static const char *TAG = "MQTT";
|
||||||
|
|
||||||
// -------- CONFIG --------
|
// -------- CONFIG --------
|
||||||
#define BROKER_HOST "mqtt.xupas.mywire.org"
|
#define BROKER_HOST "mtqq.xupas.mywire.org"
|
||||||
#define BROKER_PORT_TLS 8883
|
#define BROKER_PORT_TLS 8883
|
||||||
#define BROKER_PORT_TCP 1883
|
#define BROKER_PORT_TCP 1883
|
||||||
#define MQTT_USER "xupa"
|
#define MQTT_USER "xupa"
|
||||||
@ -26,10 +26,10 @@ esp_mqtt_client_handle_t mqtt_client = NULL;
|
|||||||
static esp_timer_handle_t mqtt_watchdog = NULL;
|
static esp_timer_handle_t mqtt_watchdog = NULL;
|
||||||
static bool mqtt_connected = false;
|
static bool mqtt_connected = false;
|
||||||
|
|
||||||
char topic_status[64];
|
static char topic_status[64];
|
||||||
char topic_cmd[64];
|
static char topic_cmd[64];
|
||||||
char topic_resp[64];
|
static char topic_resp[64];
|
||||||
char topic_lwt[64];
|
static char topic_lwt[64];
|
||||||
|
|
||||||
// ======================================================
|
// ======================================================
|
||||||
// HEARTBEAT / STATUS
|
// HEARTBEAT / STATUS
|
||||||
@ -43,7 +43,7 @@ static void send_status(void) {
|
|||||||
(unsigned long)(esp_log_timestamp() / 1000),
|
(unsigned long)(esp_log_timestamp() / 1000),
|
||||||
(unsigned long)esp_get_free_heap_size());
|
(unsigned long)esp_get_free_heap_size());
|
||||||
|
|
||||||
esp_mqtt_client_publish(mqtt_client, topic_status, buf, 0, 1, false);
|
esp_mqtt_client_publish(mqtt_client, topic_status, buf, 0, 1, true);
|
||||||
ESP_LOGI(TAG, "📤 STATUS -> %s", buf);
|
ESP_LOGI(TAG, "📤 STATUS -> %s", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,14 +67,10 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
|||||||
switch (event->event_id) {
|
switch (event->event_id) {
|
||||||
case MQTT_EVENT_CONNECTED:
|
case MQTT_EVENT_CONNECTED:
|
||||||
mqtt_connected = true;
|
mqtt_connected = true;
|
||||||
|
led_set(0, 0, 50, 0);
|
||||||
// LED verde no pixel 0
|
|
||||||
led_set_pixel(0, 0, 50, 0);
|
|
||||||
led_show();
|
led_show();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "✅ MQTT conectado");
|
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_publish(mqtt_client, topic_status, "online", 0, 1, true);
|
|
||||||
esp_mqtt_client_subscribe(mqtt_client, topic_cmd, 1);
|
esp_mqtt_client_subscribe(mqtt_client, topic_cmd, 1);
|
||||||
send_status();
|
send_status();
|
||||||
|
|
||||||
@ -83,37 +79,18 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
|||||||
|
|
||||||
case MQTT_EVENT_DISCONNECTED:
|
case MQTT_EVENT_DISCONNECTED:
|
||||||
mqtt_connected = false;
|
mqtt_connected = false;
|
||||||
|
led_set(0, 50, 0, 0);
|
||||||
// LED vermelho no pixel 0
|
|
||||||
led_set_pixel(0, 50, 0, 0);
|
|
||||||
led_show();
|
led_show();
|
||||||
|
|
||||||
ESP_LOGW(TAG, "⚠️ MQTT desconectado");
|
ESP_LOGW(TAG, "⚠️ MQTT desconectado");
|
||||||
if (mqtt_watchdog) esp_timer_start_periodic(mqtt_watchdog, 120000000);
|
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
|
ESP_LOGI(TAG, "📩 [%.*s] %.*s",
|
||||||
char json_clean[256];
|
|
||||||
int len = event->data_len;
|
|
||||||
|
|
||||||
if (len >= sizeof(json_clean)) len = sizeof(json_clean) - 1;
|
|
||||||
memcpy(json_clean, event->data, len);
|
|
||||||
json_clean[len] = 0; // NULL terminate
|
|
||||||
|
|
||||||
// Remove quebras de linha
|
|
||||||
for (int i = 0; json_clean[i]; i++) {
|
|
||||||
if (json_clean[i] == '\r' || json_clean[i] == '\n')
|
|
||||||
json_clean[i] = ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mostrar tópico + JSON limpo
|
|
||||||
ESP_LOGI(TAG, "📩 [%.*s] %s",
|
|
||||||
event->topic_len, event->topic,
|
event->topic_len, event->topic,
|
||||||
json_clean);
|
event->data_len, event->data);
|
||||||
|
|
||||||
// JSON parse
|
cJSON *root = cJSON_ParseWithLength(event->data, event->data_len);
|
||||||
cJSON *root = cJSON_Parse(json_clean);
|
|
||||||
if (root) {
|
if (root) {
|
||||||
mqtt_comandos_handle(root);
|
mqtt_comandos_handle(root);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
@ -121,8 +98,7 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
|||||||
ESP_LOGE(TAG, "❌ JSON inválido");
|
ESP_LOGE(TAG, "❌ JSON inválido");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case MQTT_EVENT_ERROR:
|
case MQTT_EVENT_ERROR:
|
||||||
ESP_LOGE(TAG, "❌ Erro MQTT");
|
ESP_LOGE(TAG, "❌ Erro MQTT");
|
||||||
@ -133,7 +109,6 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ======================================================
|
// ======================================================
|
||||||
// HEARTBEAT TASK
|
// HEARTBEAT TASK
|
||||||
// ======================================================
|
// ======================================================
|
||||||
@ -168,40 +143,65 @@ void mqtt_handler_start(void) {
|
|||||||
mqtt_cfg.credentials.client_id = device_id;
|
mqtt_cfg.credentials.client_id = device_id;
|
||||||
mqtt_cfg.credentials.username = MQTT_USER;
|
mqtt_cfg.credentials.username = MQTT_USER;
|
||||||
mqtt_cfg.credentials.authentication.password = MQTT_PASS;
|
mqtt_cfg.credentials.authentication.password = MQTT_PASS;
|
||||||
// ======================================================
|
|
||||||
// MQTT TLS — usa SEMPRE o certificado embutido
|
// ======================================================
|
||||||
// ======================================================
|
// TENTA CARREGAR CERTIFICADO E USAR TLS
|
||||||
|
// ======================================================
|
||||||
|
bool tls_ok = false;
|
||||||
|
static char cert_buf[4096];
|
||||||
|
size_t cert_len = sizeof(cert_buf) - 1;
|
||||||
|
|
||||||
|
if (eeprom_virtual_read_bin("cert", cert_buf, &cert_len) == ESP_OK && cert_len > 0) {
|
||||||
|
cert_buf[cert_len] = '\0';
|
||||||
|
mqtt_cfg.broker.verification.certificate = strdup(cert_buf);
|
||||||
mqtt_cfg.broker.address.hostname = BROKER_HOST;
|
mqtt_cfg.broker.address.hostname = BROKER_HOST;
|
||||||
mqtt_cfg.broker.address.port = BROKER_PORT_TLS;
|
mqtt_cfg.broker.address.port = BROKER_PORT_TLS;
|
||||||
mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||||
|
tls_ok = true;
|
||||||
|
ESP_LOGI(TAG, "🔐 Certificado TLS carregado da EEPROM (%d bytes)", (int)cert_len);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "⚠️ Certificado TLS não encontrado — vai tentar padrão embutido...");
|
||||||
|
mqtt_cfg.broker.verification.certificate = (const char *)ca_cert_pem;
|
||||||
|
mqtt_cfg.broker.address.hostname = BROKER_HOST;
|
||||||
|
mqtt_cfg.broker.address.port = BROKER_PORT_TLS;
|
||||||
|
mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||||
|
tls_ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Certificado raiz (ISRG Root X1)
|
// ======================================================
|
||||||
mqtt_cfg.broker.verification.certificate = ca_cert_pem;
|
// TESTE: se TLS falhar, cai para TCP (sem SSL)
|
||||||
ESP_LOGI(TAG, "🔐 TLS ativo (cert embutido, EEPROM ignorada)");
|
// ======================================================
|
||||||
|
if (!tls_ok) {
|
||||||
|
ESP_LOGW(TAG, "⚠️ TLS indisponível — a usar MQTT sem SSL.");
|
||||||
|
mqtt_cfg.broker.address.hostname = BROKER_HOST;
|
||||||
|
mqtt_cfg.broker.address.port = BROKER_PORT_TCP;
|
||||||
|
mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||||
|
}
|
||||||
|
|
||||||
// -------- LWT --------
|
// -------- LWT --------
|
||||||
mqtt_cfg.session.last_will.topic = topic_lwt;
|
mqtt_cfg.session.last_will.topic = topic_lwt;
|
||||||
mqtt_cfg.session.last_will.msg = "offline";
|
mqtt_cfg.session.last_will.msg = "offline";
|
||||||
mqtt_cfg.session.last_will.qos = 1;
|
mqtt_cfg.session.last_will.qos = 1;
|
||||||
mqtt_cfg.session.last_will.retain = false;
|
mqtt_cfg.session.last_will.retain = true;
|
||||||
|
|
||||||
// ======================================================
|
// ======================================================
|
||||||
// INICIALIZAÇÃO DO CLIENTE MQTT (TLS OBRIGATÓRIO)
|
// INICIALIZAÇÃO DO CLIENTE
|
||||||
// ======================================================
|
// ======================================================
|
||||||
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
|
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
|
||||||
if (mqtt_client == NULL) {
|
if (mqtt_client == NULL) {
|
||||||
ESP_LOGE(TAG, "❌ Falha a inicializar MQTT (TLS). Abortado.");
|
ESP_LOGE(TAG, "❌ Falha a inicializar MQTT — a tentar fallback TCP...");
|
||||||
return; // Nem vale a pena continuar, sem MQTT não há vida
|
mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||||
|
mqtt_cfg.broker.address.port = BROKER_PORT_TCP;
|
||||||
|
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
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 inicializado em %s:%lu (TLS)",
|
ESP_LOGI(TAG, "🚀 MQTT inicializado em %s:%lu (%s)",
|
||||||
mqtt_cfg.broker.address.hostname,
|
mqtt_cfg.broker.address.hostname,
|
||||||
(unsigned long)mqtt_cfg.broker.address.port);
|
(unsigned long)mqtt_cfg.broker.address.port,
|
||||||
|
mqtt_cfg.broker.address.transport == MQTT_TRANSPORT_OVER_SSL ? "TLS" : "TCP");
|
||||||
|
|
||||||
|
|
||||||
// -------- WATCHDOG MQTT --------
|
// -------- WATCHDOG MQTT --------
|
||||||
const esp_timer_create_args_t wd_args = {
|
const esp_timer_create_args_t wd_args = {
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
{"cmd":"STATUS"} // pede o estado: uptime + heap
|
|
||||||
{"cmd":"LED_ON"} // liga todos os LEDs (branco fraco)
|
|
||||||
{"cmd":"LED_OFF"} // desliga tudo
|
|
||||||
{"cmd":"SPIN"} // rotação rápida até posição aleatória
|
|
||||||
{"cmd":"JACKPOT"} // animação jackpot (flash + arco-íris)
|
|
||||||
{"cmd":"REBOOT"} // reinicia o ESP
|
|
||||||
{"cmd":"SET_ANIM","value":0} // animação idle (pontinho azul)
|
|
||||||
{"cmd":"SET_ANIM","value":1} // relógio (horas/minutos/segundos)
|
|
||||||
{"cmd":"SET_ANIM","value":2} // jackpot em loop
|
|
||||||
|
|
||||||
{"cmd":"STATUS"} // estado: uptime + heap
|
|
||||||
{"cmd":"LED_ON"} // liga tudo
|
|
||||||
{"cmd":"LED_OFF"} // desliga tudo
|
|
||||||
{"cmd":"CLEAR"} // igual ao LED_OFF mas sem ser idiota
|
|
||||||
{"cmd":"SPIN"} // spin aleatório
|
|
||||||
{"cmd":"JACKPOT"} // animação jackpot
|
|
||||||
{"cmd":"REBOOT"} // reinicia o ESP
|
|
||||||
{"cmd":"SET_ANIM","value":X} // animação permanente (0,1,2...)
|
|
||||||
|
|
||||||
{"cmd":"SET_COLOR","r":X,"g":Y,"b":Z} // cor estática
|
|
||||||
{"cmd":"SET_BRIGHT","value":X"} // brilho global
|
|
||||||
{"cmd":"LED_PIXEL","n":i,"r":X,"g":Y,"b":Z} // LED individual
|
|
||||||
|
|
||||||
{"cmd":"DEMO_ON"} // liga demo
|
|
||||||
{"cmd":"DEMO_OFF"} // desliga demo
|
|
||||||
|
|
||||||
{"cmd":"SET_MODE","value":"RELOGIO"} // modos gerais (STRING)
|
|
||||||
{"cmd":"GET_INFO"} // resposta JSON completa
|
|
||||||
|
|
||||||
{"cmd":"SET_HOUR_COLOR","r":X,"g":Y,"b":Z} // ponteiro horas
|
|
||||||
{"cmd":"SET_MIN_COLOR","r":X,"g":Y,"b":Z} // ponteiro minutos
|
|
||||||
{"cmd":"SET_SEC_COLOR","r":X,"g":Y,"b":Z} // ponteiro segundos
|
|
||||||
|
|
||||||
{"cmd":"SET_CLOCK_SPEED","ms":X} // velocidade do relógio (delay)
|
|
||||||
{"cmd":"TEST_RING"} // acender LED a LED
|
|
||||||
|
|
||||||
{ "cmd": "MENU_ON" }
|
|
||||||
{ "cmd": "MENU_NEXT" }
|
|
||||||
{ "cmd": "MENU_OK" }
|
|
||||||
{ "cmd": "MENU_OFF" }
|
|
||||||
|
|
||||||
{ "cmd": "DISP_TEST" }
|
|
||||||
{ "cmd": "LED_TEST" }
|
|
||||||
{ "cmd": "SET_PERC", "val": 70 }
|
|
||||||
|
|
||||||
|
|
||||||
esp/esp_BBC9A4/cmd // onde envias os comandos
|
|
||||||
esp/esp_BBC9A4/resp // respostas aos comandos
|
|
||||||
esp/esp_BBC9A4/status // heartbeat 30s
|
|
||||||
esp/esp_BBC9A4/lwt // "offline" caso morra
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
#include "premios.h"
|
|
||||||
|
|
||||||
// ===============================
|
|
||||||
// Tabela de prémios (60 LEDs)
|
|
||||||
// ===============================
|
|
||||||
// 0 = sem prémio
|
|
||||||
// Aqui só mete valores de teste. Depois ajustamos tudo bonito.
|
|
||||||
//
|
|
||||||
uint16_t premios[60] = {
|
|
||||||
0, 0, 5, 0, 10, 0, 0, 20,
|
|
||||||
0, 0, 50, 0, 0,100, 0, 0,
|
|
||||||
5, 0, 0, 10, 0, 0,200, 0,
|
|
||||||
0, 0, 20, 0, 0, 0, 50, 0,
|
|
||||||
0, 0, 5, 0, 0,100, 0, 0,
|
|
||||||
10, 0, 0, 0, 20, 0, 0, 50,
|
|
||||||
0, 0,100, 0, 0,200, 0, 0
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// Retorna o prémio associado a uma posição
|
|
||||||
// ============================================
|
|
||||||
uint16_t premio_da_posicao(int pos)
|
|
||||||
{
|
|
||||||
if (pos < 0 || pos >= 60)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return premios[pos];
|
|
||||||
}
|
|
||||||
88
main/ui.c
88
main/ui.c
@ -1,88 +0,0 @@
|
|||||||
// ui.c
|
|
||||||
#include "ui.h"
|
|
||||||
#include "display.h"
|
|
||||||
#include "led_driver.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
static menu_state_t g_menu = MENU_OFF;
|
|
||||||
|
|
||||||
void ui_set_menu(menu_state_t s)
|
|
||||||
{
|
|
||||||
g_menu = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ui_menu_next(void)
|
|
||||||
{
|
|
||||||
if (g_menu == MENU_OFF) return;
|
|
||||||
|
|
||||||
g_menu++;
|
|
||||||
if (g_menu > MENU_TEST_DISP)
|
|
||||||
g_menu = MENU_MAIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ui_render_menu(void)
|
|
||||||
{
|
|
||||||
switch (g_menu) {
|
|
||||||
case MENU_MAIN: display_text_top("MENU"); break;
|
|
||||||
case MENU_PERC: display_text_top("PERC"); break;
|
|
||||||
case MENU_READ: display_text_top("READ"); break;
|
|
||||||
case MENU_TEST_LED: display_text_top("LEDS"); break;
|
|
||||||
case MENU_TEST_DISP: display_text_top("DISP"); break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ui_menu_ok(void)
|
|
||||||
{
|
|
||||||
switch (g_menu) {
|
|
||||||
case MENU_PERC:
|
|
||||||
// aqui no futuro: mostra % atual, muda via MQTT, etc
|
|
||||||
display_text_top("70%");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_READ:
|
|
||||||
// aqui no futuro: mostra entradas/saídas da EEPROM
|
|
||||||
display_text_top("CNT ");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_TEST_LED:
|
|
||||||
// efeito simples na roda
|
|
||||||
led_clear();
|
|
||||||
for (int i = 0; i < led_get_count(); i++) {
|
|
||||||
led_set_pixel(i, 0, 0, 40);
|
|
||||||
led_show();
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MENU_TEST_DISP:
|
|
||||||
display_text_top("8888");
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
|
||||||
display_clear_top();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ui_task(void *pv)
|
|
||||||
{
|
|
||||||
while (1) {
|
|
||||||
if (g_menu == MENU_OFF) {
|
|
||||||
// modo normal → relógio + LED do segundo
|
|
||||||
time_t now = time(NULL);
|
|
||||||
struct tm t;
|
|
||||||
localtime_r(&now, &t);
|
|
||||||
|
|
||||||
display_set_time_top(t.tm_hour, t.tm_min);
|
|
||||||
// aqui podes chamar a animação da roda (se quiseres relógio visual)
|
|
||||||
} else {
|
|
||||||
ui_render_menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -338,12 +338,12 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
|
|||||||
#
|
#
|
||||||
# Partition Table
|
# Partition Table
|
||||||
#
|
#
|
||||||
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
|
CONFIG_PARTITION_TABLE_SINGLE_APP=y
|
||||||
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
|
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
|
||||||
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
|
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
|
||||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
# CONFIG_PARTITION_TABLE_CUSTOM is not set
|
||||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv"
|
||||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||||
CONFIG_PARTITION_TABLE_MD5=y
|
CONFIG_PARTITION_TABLE_MD5=y
|
||||||
# end of Partition Table
|
# end of Partition Table
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
"board/esp32-wrover-kit-3.3v.cfg"
|
"board/esp32-wrover-kit-3.3v.cfg"
|
||||||
],
|
],
|
||||||
"idf.port": "COM3",
|
"idf.port": "COM3",
|
||||||
"idf.flashBaudRate": "921600",
|
"idf.flashBaudRate": 921600,
|
||||||
"idf.monitorBaudRate": "115200",
|
"idf.monitorBaudRate": 115200,
|
||||||
"cmake.sourceDirectory": "${workspaceFolder}"
|
"cmake.sourceDirectory": "${workspaceFolder}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user