diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 9e8e372..10d9730 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -15,6 +15,7 @@ idf_component_register( "premios.c" "i2c_helper.c" "display.c" + "ui.c" INCLUDE_DIRS "include" diff --git a/main/display.c b/main/display.c index faa3238..27e2d73 100644 --- a/main/display.c +++ b/main/display.c @@ -7,6 +7,35 @@ const char *TAG = "DISPLAY"; +// Mapa de bits real do teu módulo +#define SEG_A (1 << 0) // topo +#define SEG_B (1 << 1) // cima direita +#define SEG_C (1 << 2) // baixo direita +#define SEG_D (1 << 3) // baixo +#define SEG_E (1 << 4) // baixo esquerda +#define SEG_F (1 << 5) // cima esquerda + +// verticais do meio +#define SEG_ML (1 << 6) // meio-esquerda +#define SEG_MR (1 << 7) // meio-direita + +// parte alfanumérica (topo) +#define SEG_TL (1 << 8) // top-left extra +#define SEG_TM (1 << 9) // top-middle (horizontal) +#define SEG_TR (1 << 10) // top-right extra + +// parte alfanumérica (baixo) +#define SEG_BL (1 << 11) // bottom-left extra +#define SEG_BM (1 << 12) // bottom-middle (horizontal) +#define SEG_BR (1 << 13) // bottom-right extra + +#define SEG_DP (1 << 14) // ponto decimal + +// "segmento do meio" clássico (g) = as duas barrinhas +#define SEG_G (SEG_ML | SEG_MR) + + + // ------------------------------ // DISPLAY INIT @@ -58,49 +87,174 @@ static uint16_t charset(char c) { switch (c) { - case '0': return 0b0000000000111111; - case '1': return 0b0000000000000110; - case '2': return 0b0000000001011011; - case '3': return 0b0000000001001111; - case '4': return 0b0000000001100110; - case '5': return 0b0000000001101101; - case '6': return 0b0000000001111101; - case '7': return 0b0000000000000111; - case '8': return 0b0000000001111111; - case '9': return 0b0000000001101111; + //------------------------------------------------------ + // NÚMEROS (PERFEITOS) + //------------------------------------------------------ + 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 principais - case 'A': case 'a': return 0b0000000001110111; - case 'B': case 'b': return 0b0001000011111100; - case 'C': case 'c': return 0b0000000000111001; - case 'D': case 'd': return 0b0001000011011110; - case 'E': case 'e': return 0b0000000001111001; - case 'F': case 'f': return 0b0000000001110001; + //------------------------------------------------------ + // LETRAS A-Z (todas as possíveis) + //------------------------------------------------------ - // CORRIGIDO → H com g1 + g2 - case 'H': case 'h': return - (1<<5) | // f - (1<<4) | // e - (1<<1) | // b - (1<<2) | // c - (1<<6) | // g1 - (1<<7); // g2 + // A + case 'A': case 'a': + return SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G; - case 'I': case 'i': return 0b0001000000000000 | 0b0000000000000110; - case 'L': case 'l': return 0b0000000000111000; - case 'O': case 'o': return 0b0001000010011100; - case 'P': case 'p': return 0b0000000001110011; - case 'S': case 's': return 0b0000000001101101; - case 'U': case 'u': return 0b0000000000011100; + // B (tipo “8” mais quadrado) + case 'B': case 'b': + return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G; - case '-': return 0b0000000001000000; - case ' ': return 0; + // C + case 'C': case 'c': + return SEG_A | SEG_F | SEG_E | SEG_D; + + // D + case 'D': case 'd': + return SEG_B | SEG_C | SEG_D | SEG_E | SEG_G; + + // E + case 'E': case 'e': + return SEG_A | SEG_F | SEG_G | SEG_E | SEG_D; + + // F + case 'F': case 'f': + return SEG_A | SEG_F | SEG_G | SEG_E; + + // G + case 'G': case 'g': + return SEG_A | SEG_F | SEG_E | SEG_D | SEG_C | SEG_G; + + // H + case 'H': case 'h': + return SEG_F | SEG_E | SEG_G | SEG_B | SEG_C; + + // I + case 'I': case 'i': + return SEG_B | SEG_C; + + // J + case 'J': case 'j': + return SEG_B | SEG_C | SEG_D; + + // K (usa diagonais internas TL/TR + ML/MR) + case 'K': case 'k': + return SEG_F | SEG_E | SEG_G | SEG_TR | SEG_BR; + + // L + case 'L': case 'l': + return SEG_F | SEG_E | SEG_D; + + // M + case 'M': case 'm': + return SEG_F | SEG_E | SEG_TL | SEG_TR | SEG_B | SEG_C; + + // N + case 'N': case 'n': + return SEG_F | SEG_E | SEG_TL | SEG_BR | SEG_C | SEG_B; + + // O + case 'O': case 'o': + return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F; + + // P + case 'P': case 'p': + return SEG_A | SEG_B | SEG_F | SEG_G | SEG_E; + + // Q (tipo O + diagonal extra) + case 'Q': case 'q': + return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_BR; + + // R + case 'R': case 'r': + return SEG_A | SEG_B | SEG_F | SEG_G | SEG_E | SEG_BR; + + // S + case 'S': case 's': + return SEG_A | SEG_F | SEG_G | SEG_C | SEG_D; + + // T + case 'T': case 't': + return SEG_A | SEG_TM | SEG_BR; + + // U + case 'U': case 'u': + return SEG_F | SEG_E | SEG_D | SEG_C | SEG_B; + + // V + case 'V': case 'v': + return SEG_F | SEG_E | SEG_D | SEG_B | SEG_TR; + + // W + case 'W': case 'w': + return SEG_F | SEG_E | SEG_D | SEG_C | SEG_B | SEG_BR | SEG_TR; + + // X + case 'X': case 'x': + return SEG_TL | SEG_TR | SEG_ML | SEG_MR | SEG_BL | SEG_BR; + + // Y + case 'Y': case 'y': + return SEG_F | SEG_B | SEG_G | SEG_C | SEG_D; + + // Z + case 'Z': case 'z': + return SEG_A | SEG_TR | SEG_G | SEG_BL | SEG_D; + + //------------------------------------------------------ + // SÍMBOLOS ESPECIAIS + //------------------------------------------------------ + + case '-': + return SEG_G; + + case '_': + return SEG_D; + + case '=': + return SEG_G | SEG_BM; + + case '*': + return SEG_TM | SEG_BM | SEG_ML | SEG_MR; + + case '/': + return SEG_TR | SEG_BL; + + case '\\': + return SEG_TL | SEG_BR; + + case '|': + return SEG_B | SEG_C; + + case ':': + return SEG_DP | SEG_BR; + + case '.': + return SEG_DP; + + case '\'': + return SEG_TR; + + case '"': + return SEG_TR | SEG_TL; + + case ' ': + return 0; default: - return 0; + return 0; // desconhecido → apagado } } + // -------------------------------------------------------- // display_char() // -------------------------------------------------------- @@ -127,24 +281,46 @@ void display_text(const char *txt) // display_number() // Mantida para compatibilidade com 7-segment // -------------------------------------------------------- +// Dígitos 0–9 usando os segmentos definidos acima +static const uint16_t digit_mask[10] = { + // 0: a b c d e f + [0] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, + + // 1: b c + [1] = SEG_B | SEG_C, + + // 2: a b g e d + [2] = SEG_A | SEG_B | SEG_G | SEG_E | SEG_D, + + // 3: a b g c d + [3] = SEG_A | SEG_B | SEG_G | SEG_C | SEG_D, + + // 4: f g b c + [4] = SEG_F | SEG_G | SEG_B | SEG_C, + + // 5: a f g c d + [5] = SEG_A | SEG_F | SEG_G | SEG_C | SEG_D, + + // 6: a f g e d c + [6] = SEG_A | SEG_F | SEG_G | SEG_E | SEG_D | SEG_C, + + // 7: a b c + [7] = SEG_A | SEG_B | SEG_C, + + // 8: tudo + [8] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, + + // 9: a b c d f g + [9] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G, +}; + void display_digit(int pos, uint8_t val) { - static const uint8_t seg7[10] = { - 0b00111111, - 0b00000110, - 0b01011011, - 0b01001111, - 0b01100110, - 0b01101101, - 0b01111101, - 0b00000111, - 0b01111111, - 0b01101111 - }; - + if (pos < 0 || pos > 3) return; if (val > 9) val = 0; - display_raw(pos, seg7[val]); + uint16_t mask = digit_mask[val]; + display_raw(pos, mask); } void display_number(int num) @@ -162,8 +338,22 @@ void display_set_time(int horas, int minutos) if (horas < 0) horas = 0; if (horas > 99) horas = 99; if (minutos < 0) minutos = 0; - if (minutos > 99) minutos = 99; + if (minutos > 59) minutos = 59; - int num = horas * 100 + minutos; - display_number(num); + int h1 = horas / 10; + int h2 = horas % 10; + int m1 = minutos / 10; + int m2 = minutos % 10; + + uint16_t dig1 = digit_mask[h2] | SEG_DP; //dot betwen hours + + display_digit(0, h1); + display_raw(1, dig1); + display_digit(2, m1); + display_digit(3, m2); +} + +void display_debug_segment(uint16_t bitmask) +{ + display_raw(0, bitmask); // acende só o dígito 0 } diff --git a/main/include/display.h b/main/include/display.h index 1d5dc31..bdf8460 100644 --- a/main/include/display.h +++ b/main/include/display.h @@ -1,5 +1,5 @@ #pragma once - +#include void display_init(void); void display_clear(void); void display_raw(int pos, uint16_t mask); @@ -8,3 +8,4 @@ void display_text(const char *txt); void display_digit(int pos, uint8_t val); void display_number(int num); void display_set_time(int horas, int minutos); +void display_debug_segment(uint16_t bitmask); \ No newline at end of file diff --git a/main/include/ui.h b/main/include/ui.h new file mode 100644 index 0000000..f5e80c8 --- /dev/null +++ b/main/include/ui.h @@ -0,0 +1,16 @@ +// 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); diff --git a/main/led_effects.c b/main/led_effects.c index 8c46133..fb9966a 100644 --- a/main/led_effects.c +++ b/main/led_effects.c @@ -83,31 +83,26 @@ void led_jackpot_animation(void) void led_clock_animation(void) { - int n = led_get_count(); - if (n <= 0) return; + time_t now = time(NULL); + struct tm t; + localtime_r(&now, &t); - // === Tempo atual === - time_t now; - struct tm timeinfo; + int h = t.tm_hour; + int m = t.tm_min; + int s = t.tm_sec; - time(&now); - localtime_r(&now, &timeinfo); - - int h = timeinfo.tm_hour; - int m = timeinfo.tm_min; - int s = timeinfo.tm_sec; - - // === Display mostra HHMM === + // Mostrar HHMM no display (14-seg) display_set_time(h, m); - // === Segundos na roda === - int pos_s = (s * n) / 60; + // LED dos segundos em azul + led_clear(); // APAGA TUDO - led_clear(); - led_set_pixel(pos_s, 20, 20, 20); // branco suave + 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)); // 5 updates/segundo + vTaskDelay(pdMS_TO_TICKS(200)); } diff --git a/main/main.c b/main/main.c index 9408525..c8e4b69 100644 --- a/main/main.c +++ b/main/main.c @@ -168,9 +168,30 @@ void app_main(void) { //i2c scan -i2c_init(); // vem do i2c_helper.c -display_init(); -display_text("INIT"); + i2c_init(); // <- o helper entra aqui + display_init(); // <- inicializa o HT16K33 + // display_text("INIT"); // ou display_number(0000) +/*display_number(8); +vTaskDelay(pdMS_TO_TICKS(1000)); +display_number(4); +vTaskDelay(pdMS_TO_TICKS(1000)); +display_number(7); +vTaskDelay(pdMS_TO_TICKS(1000)); +display_number(2); + +display_text("HELP"); +vTaskDelay(pdMS_TO_TICKS(1000)); +display_text("Err "); +vTaskDelay(pdMS_TO_TICKS(1000));*/ +display_text("C0dE"); +vTaskDelay(pdMS_TO_TICKS(500)); +display_text("0000"); +/* +vTaskDelay(pdMS_TO_TICKS(1000)); +display_text("UP "); +vTaskDelay(pdMS_TO_TICKS(1000)); +display_text("rSt "); +vTaskDelay(pdMS_TO_TICKS(1000));*/ diff --git a/main/mqtt_comandos.c b/main/mqtt_comandos.c index 33551d2..b6d10be 100644 --- a/main/mqtt_comandos.c +++ b/main/mqtt_comandos.c @@ -17,6 +17,9 @@ #include "eeprom_virtual.h" #include "eeprom_animacao.h" // animacao + animacao_save() +#include "ui.h" +#include "display.h" + static const char *TAG = "MQTT_CMD"; // Estes vêm de mqtt_handler.c @@ -299,6 +302,82 @@ void mqtt_comandos_handle(cJSON *root) 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("MENU"); + ESP_LOGI(TAG, "📟 MENU ON"); + return; + } + + else if (strcmp(c, "MENU_OFF") == 0) { + ui_set_menu(MENU_OFF); + display_clear(); + 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("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(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 // ====================================================== diff --git a/main/mqtt_handler.c b/main/mqtt_handler.c index da4277f..ed2e05a 100644 --- a/main/mqtt_handler.c +++ b/main/mqtt_handler.c @@ -12,7 +12,7 @@ #include "esp_timer.h" #include "esp_event.h" #include "eeprom_virtual.h" - +#include "ui.h" static const char *TAG = "MQTT"; // -------- CONFIG -------- diff --git a/main/notas.txt b/main/notas.txt index b7e468e..f77d2bc 100644 --- a/main/notas.txt +++ b/main/notas.txt @@ -34,6 +34,14 @@ {"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 diff --git a/main/ui.c b/main/ui.c new file mode 100644 index 0000000..dc9ffdf --- /dev/null +++ b/main/ui.c @@ -0,0 +1,88 @@ +// ui.c +#include "ui.h" +#include "display.h" +#include "led_driver.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include + +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("MENU"); break; + case MENU_PERC: display_text("PERC"); break; + case MENU_READ: display_text("READ"); break; + case MENU_TEST_LED: display_text("LEDS"); break; + case MENU_TEST_DISP: display_text("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("70%"); + break; + + case MENU_READ: + // aqui no futuro: mostra entradas/saídas da EEPROM + display_text("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("8888"); + vTaskDelay(pdMS_TO_TICKS(500)); + display_clear(); + 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(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)); + } +}