Hi, I have a 128x64 display controlled by an ESP32 S3 that should display my interface data, but when re-rendering, the 16x16 icons are split in half, and the other half is delayed from the first. I'll attach an image and a video of what an early stage of the code looked like. By the way, it will interact with a 4x4 keyboard, which, when I press the buttons, shows me the following mapping on the serial monitor:
1=>1
2=>1
3=>1
4=>1
5=>2
6=>2
7=>3
8=>3
9=>3
0=>3
#=>One
A=>4
B=>5
C=>6
D=>A
So I can't interact with the interface as expected. Here's the code:
/* Secadora_solar_128x64.ino v5.6.5 - CORREGIDO
* Código principal para el control de la secadora solar con menú y pantalla OLED.
* Integración de la lógica de secado modularizada.
* Incluye funcionalidad de envío de datos a Google Sheets (Excel_Base_Datos.h/cpp)
* y múltiples modos de secado.
*
* ADECUADO PARA ESP32 S3 DEV KIT.
*
* CORRECCIONES APLICADAS:
* 1. Cambiado drawBitmapP() por drawXBM() que es el método correcto para U8g2
* 2. Ajustado el cálculo de ancho en bytes para drawXBM()
* 3. Corregido el manejo del teclado matricial con debounce adecuado
* 4. Optimizado el loop principal para mejor respuesta
*/
#include <Arduino.h>
#include "U8g2lib.h" // Incluye la librería U8glib para la pantalla OLED
#include <Keypad.h> // Incluye la librería Keypad para el teclado
#include <HTTPClient.h> // (Asegúrate de tener este include arriba)
#include <WiFi.h> // Necesario para la conectividad WiFi del ESP32
#include <Wire.h> // Para comunicación I2C (RTC y OLED)
#include <RTClib.h> // Librería para el módulo RTC DS3231
#include <DHT.h>
#include "logging.h"
#include "watchdog.h"
// --- Inclusión de los módulos de secado ---
#include "Secado_Customizado.h" // Incluye el archivo de cabecera para el secado personalizado
#include "Secado_Frutas.h" // Incluye el archivo de cabecera para el secado Frutas
#include "Secado_Indefinido.h" // Incluye el archivo de cabecera para el secado indefinido
#include "Secado_Tabaco_Virginia.h"// Incluye el archivo de cabecera para Tabaco Virginia
#include "Secado_Tabaco_Burley.h" // Incluye el archivo de cabecera para Tabaco Burley
#include "Secado_Tabaco_Habano.h" // Incluye el archivo de cabecera para Tabaco Habano
#include "Secado_Tabaco.h" // Incluye el archivo de cabecera para Tabaco (General)
#include "Secado_General.h" // Incluye el archivo de cabecera para Secado General (Incubación)
#include "Secado_Harinas.h" // Incluye el archivo de cabecera para Secado Harinas
#include "Secadora_Standard.h" // Incluye el archivo de cabecera para Secadora Estándar
#include "Excel_Base_Datos.h" // Para enviar datos a Google Sheets (ESP32)
// --- Incluir el archivo de cabecera para los bitmaps grandes ---
#include "Bitmaps_Grandes.h"
unsigned long previousMillis = 0; // Para guardar la última vez que algo se actualizó
const long interval = 100; // Intervalo reducido para mejor respuesta del teclado
//Maquina de estado para el wifi:
enum WifiState { WIFI_IDLE, WIFI_CONNECTING, WIFI_CONNECTED, WIFI_FAILED };
WifiState wifiState = WIFI_IDLE;
unsigned long wifiStartTime = 0;
const unsigned long WIFI_TIMEOUT = 15000; // 15 segundos
// Definición global para el nombre del modo de secado activo
String activeDryingModeName = "";
// --- Configuracion para el envio de datos a Google Sheets ---
// ¡¡¡CAMBIA ESTOS VALORES CON TUS CREDENCIALES REALES!!!
const char* WIFI_SSID = " "; // add later
const char* WIFI_PASSWORD = " "; // add later
const char* GOOGLE_SCRIPT_URL = " "; //add later
// U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // Default I2C constructor
// Use this line for I2C connection. If you have issues, try U8G2_R1, U8G2_R2, U8G2_R3 for rotation.
// The constructor below assumes your SSD1306 is 128x64 and uses hardware I2C.
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// Keypad setup for a 4x4 matrix keypad
const byte ROWS = 4; // four rows
const byte COLS = 4; // four columns
// Define the keymap for your 4x4 keypad
// Adjust these characters based on your keypad layout and desired functionality
char keys[ROWS][COLS] = {
{'1','2','3','A'}, // 'A' for UP
{'4','5','6','B'}, // 'B' for DOWN
{'7','8','9','C'}, // 'C' for SELECT
{'*','0','#','D'} // '*' for BACK, '#' for UP, 'D' for DOWN, '0' for UP
};
// Define the row and column pins connected to your Arduino
// Ensure these match your physical wiring
byte rowPins[ROWS] = {39, 40, 41, 42}; // Connect to the row pinouts of the keypad
byte colPins[COLS] = {4, 5, 6, 7}; // Connect to the column pinouts of the keypad
// Initialize the Keypad object
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// All the arrays below are generated from images using image2cpp website
// Note: U8g2 uses drawXBM for XBM format bitmaps. The dimensions are in pixels.
// 'Secado General', 16x16px
const unsigned char epd_bitmap_Secado_General [] PROGMEM = {
0x07, 0xe0, 0x18, 0x18, 0x21, 0x24, 0x50, 0x02, 0x48, 0x0a, 0x84, 0x01, 0x83, 0x81, 0xa2, 0x45,
0x82, 0x41, 0x81, 0x81, 0xa0, 0x85, 0x40, 0x82, 0x48, 0x12, 0x21, 0x84, 0x18, 0x18, 0x07, 0xe0
};
// 'Secado de Tabaco', 16x16px (This icon will be used for "Secado Luxury" in the main menu)
const unsigned char epd_bitmap_Secado_de_Tabaco [] PROGMEM = {
0x00, 0x00, 0xf8, 0x00, 0x86, 0x00, 0x81, 0x80, 0x90, 0x40, 0x88, 0x20, 0x84, 0x2f, 0x44, 0xb1,
0x42, 0xc1, 0x42, 0x85, 0x25, 0x89, 0x11, 0x91, 0x08, 0xa1, 0x07, 0xc2, 0x00, 0xfc, 0x00, 0x00
};
// 'Secado Luxury', 16x16px (This icon will be used for tobacco types in the submenu)
const unsigned char epd_bitmap_Secado_Luxury [] PROGMEM = {
0x00, 0x0e, 0x07, 0xf1, 0x18, 0x01, 0x20, 0x01, 0x40, 0x01, 0x43, 0xf1, 0x84, 0x4e, 0x8a, 0xa0,
0x89, 0x22, 0x8a, 0xa2, 0x84, 0x42, 0x43, 0x84, 0x40, 0x04, 0x20, 0x08, 0x18, 0x30, 0x07, 0xc0
};
// 'Secado Indefinido', 16x16px
const unsigned char epd_bitmap_Secado_Indefinido [] PROGMEM = {
0x00, 0x00, 0x03, 0xf0, 0x00, 0x08, 0x01, 0xe4, 0x00, 0x12, 0x00, 0xca, 0x06, 0x2a, 0x07, 0x2a,
0x07, 0x8a, 0x07, 0xc2, 0x07, 0xc0, 0x0a, 0x00, 0x1f, 0x00, 0x20, 0x80, 0x7f, 0xc0, 0x00, 0x00
};
// 'Icono secado harinas', 16x16px
const unsigned char epd_bitmap_Icono_secado_harinas [] PROGMEM = {
0x40, 0x05, 0xe0, 0x02, 0x40, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x20, 0xe1,
0x91, 0x12, 0x2a, 0x4c, 0x04, 0x04, 0x4a, 0x12, 0x10, 0x42, 0x09, 0x0d, 0xf6, 0x30, 0x03, 0xc7
};
// 'guineo icon', 16x16px (Secado Frutas)
const unsigned char epd_bitmap_guineo_icon [] PROGMEM = {
0xa0, 0x08, 0x70, 0x1c, 0x20, 0x1e, 0x00, 0x3e, 0x00, 0x3e, 0x0e, 0x3c, 0x1f, 0x7e, 0x3f, 0x7e,
0x42, 0xfc, 0x01, 0x81, 0x02, 0x5d, 0x0f, 0xde, 0x3f, 0xee, 0x7f, 0xee, 0x7f, 0x84, 0xbe, 0x08
};
// 'Secado Personalizado', 16x16px
const unsigned char epd_bitmap_Secado_Personalizado [] PROGMEM = {
0x00, 0x00, 0x7f, 0xfe, 0x40, 0x02, 0x7f, 0xfe, 0x40, 0x02, 0x43, 0xe2, 0x40, 0x22, 0x40, 0x42,
0x40, 0x42, 0x40, 0x82, 0x40, 0x82, 0x41, 0x1e, 0x41, 0x14, 0x40, 0x18, 0x7f, 0xf2, 0x00, 0x00
};
// 'Creditos', 16x16px
const unsigned char epd_bitmap_Creditos [] PROGMEM = {
0x00, 0x00, 0x00, 0x08, 0x00, 0x94, 0x10, 0x08, 0x10, 0x00, 0x6c, 0x00, 0x10, 0x10, 0x10, 0x10,
0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x00, 0x10, 0x04, 0x10, 0x0a, 0x00, 0x04, 0x00, 0x00, 0x00
};
// IMPORTANTE: Si agregas o quitas elementos en los menús, actualiza también NUM_MAIN_ITEMS, NUM_LUXURY_ITEMS y el tamaño de los arrays para evitar errores de desbordamiento.
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 288)
const int epd_bitmap_allArray_LEN = 8;
const unsigned char* bitmap_icons[8] = {
epd_bitmap_Secado_General,
epd_bitmap_Secado_de_Tabaco, // This will be the icon for "Secado Luxury" in main menu
epd_bitmap_Secado_Luxury, // This will be the icon for tobacco types in submenu
epd_bitmap_Secado_Indefinido,
epd_bitmap_Icono_secado_harinas,
epd_bitmap_guineo_icon,
epd_bitmap_Secado_Personalizado,
epd_bitmap_Creditos
};
// 'scrollbar_background', 8x64px
const unsigned char bitmap_scrollbar_background [] PROGMEM = {
0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02,
0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00
};
// 'item_sel_outline', 128x21px
const unsigned char bitmap_item_sel_outline [] PROGMEM = {
0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0,
0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0
};
// ------------------ end generated bitmaps from image2cpp ---------------------------------
// IMPORTANTE: Si agregas o quitas elementos en los menús, actualiza también NUM_MAIN_ITEMS, NUM_LUXURY_ITEMS y el tamaño de los arrays para evitar errores de desbordamiento.
// Main menu items
const int NUM_MAIN_ITEMS = 8; // number of items in the main menu
const int MAX_ITEM_LENGTH = 20; // maximum characters for the item name
char main_menu_items [NUM_MAIN_ITEMS] [MAX_ITEM_LENGTH] = { // array with main menu item names
{ "Secado General" },
{ "Secado Tabaco" }, // This will be the entry point to the luxury submenu
{ "Secado Luxury" }, // This item will be replaced by the submenu entry
{ "S. Indefinido" },
{ "Secado Harinas" },
{ "Secado Frutas" },
{ "S. Customizado" },
{ "Creditos" },
};
// Submenu for "Secado Luxury"
const int LUXURY_SUBMENU_INDEX = 2; // Index of "Secado Luxury" in main_menu_items
const int NUM_LUXURY_ITEMS = 3; // number of items in the luxury submenu
char luxury_submenu_items [NUM_LUXURY_ITEMS] [MAX_ITEM_LENGTH] = { // array with luxury submenu item names
{ "Virginia" },
{ "Burley" },
{ "Habano" },
};
// Define the index for the "Creditos" item
const int CREDITS_ITEM_INDEX = 7; // "Creditos" is the 8th item (index 7) in the main menu
int item_selected = 0; // which item in the current menu is selected
int current_screen = 0; // 0 = menu, 1 = data/logic screen, 2 = credits image 2, 3 = credits image 3
int current_menu_level = 0; // 0 = main menu, 1 = luxury submenu
// Variables para el debounce del teclado
unsigned long lastKeyTime = 0;
const unsigned long DEBOUNCE_DELAY = 50; // 50ms debounce delay
// Placeholder for buzzer state (assuming it's defined elsewhere or will be)
bool isBuzzerPlaying = false;
void playBuzzerTone(unsigned int frequency, unsigned long duration = 0) {
// tone(BUZZER, frequency); // Uncomment and define BUZZER pin if using
isBuzzerPlaying = true;
if (duration > 0) {
// Para tonos de duración finita, apagar después del tiempo.
// OJO: Usar 'delay' bloqueará el loop. Para no bloquear,
// se podría usar un temporizador con millis() en el loop principal.
// Aquí se usa delay para simplicidad en la finalización de procesos.
delay(duration);
// noTone(BUZZER); // Uncomment if using
isBuzzerPlaying = false;
}
}
void stopBuzzer() {
// noTone(BUZZER); // Uncomment if using
isBuzzerPlaying = false;
}
// Forward declarations for drawing and logic execution functions
void drawMainMenu();
void drawLuxurySubmenu();
void drawDryingModeScreen(int selected_mode_index);
void drawCreditsScreen(int screen_mode);
// Placeholder functions for executing drying logic
void executeSecadoGeneral() {
runSecado_General ();
// Call function from Secado_General.cpp/.h, e.g., runSecadoGeneral();
}
void executeSecadoTabacoVirginia() {
runSecadoTabacoVirginiaLogic ();
// Call function from Secado_Tabaco_Virginia.cpp/.h, e.g., runSecadoTabacoVirginia();
}
void executeSecadoTabacoBurley() {
runSecadoTabacoBurleyLogic ();
// Call function from Secado_Tabaco_Burley.cpp/.h, e.g., runSecadoTabacoBurley();
}
void executeSecadoTabacoHabano() {
runSecadoTabacoHabanoLogic ();
// Call function from Secado_Tabaco_Habano.cpp/.h, e.g., runSecadoTabacoHabano();
}
void executeSecadoIndefinido() {
runSecado_Indefinido ();
// Call function from Secado_Indefinido.cpp/.h, e.g., runSecadoIndefinido();
}
void executeSecadoHarinas() {
runSecado_Harinas ();
// Call function from Secado_Harinas.cpp/.h, e.g., runSecadoHarinas();
}
void executeSecadoFrutas() {
runSecado_Frutas ();
// Call function from Secado_Frutas.cpp/.h, e.g., runSecadoFrutas();
}
void executeSecadoCustomizado() {
runSecado_Customizado ();
// Call function from Secado_Customizado.cpp/.h, e.g., runSecadoCustomizado();
}
void sendDataToGoogleSheets(const String& data) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
String url = String(GOOGLE_SCRIPT_URL) + "?data=" + data;
http.begin(url);
int httpCode = http.GET();
if (httpCode > 0) {
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
} else {
Serial.println("WiFi no conectado, no se puede enviar a Google Sheets.");
}
}
void setup() {
Serial.begin(115200);
u8g2.begin();
u8g2.setColorIndex(1);
setupWatchdog();
logInfo("Iniciando setup...");
wifiState = WIFI_CONNECTING;
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
wifiStartTime = millis();
}
void loop() {
feedWatchdog(); // Alimenta el watchdog para evitar reinicio
unsigned long currentMillis = millis();
// Máquina de estado para conexión WiFi no bloqueante
if (wifiState == WIFI_CONNECTING) {
if (WiFi.status() == WL_CONNECTED) {
logInfo("WiFi conectado");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
wifiState = WIFI_CONNECTED;
} else if (currentMillis - wifiStartTime > WIFI_TIMEOUT) {
logError("Fallo la conexión WiFi (timeout)");
wifiState = WIFI_FAILED;
// Aquí puedes reintentar, entrar en modo offline, etc.
}
// No return, el resto del loop puede seguir ejecutándose
}
// Solo procesar la entrada del teclado y actualizar la pantalla si ha pasado el intervalo
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// Leer tecla con debounce mejorado
char key = keypad.getKey();
if (key) {
Serial.println("Tecla detectada: " + String(key));
}
if (key && (currentMillis - lastKeyTime > DEBOUNCE_DELAY)) {
lastKeyTime = currentMillis;
Serial.println("Tecla procesada: " + String(key));
if (current_screen == 0) { // Actualmente en un menú (principal o submenú)
if (current_menu_level == 0) { // Navegación del menú principal
if (key == 'A' || key == '#' || key == '0') { // Teclas para ARRIBA
item_selected = (item_selected - 1 + NUM_MAIN_ITEMS) % NUM_MAIN_ITEMS;
Serial.println("Tecla ARRIBA presionada, item_selected: " + String(item_selected));
} else if (key == 'B' || key == 'D') { // Teclas para ABAJO
item_selected = (item_selected + 1) % NUM_MAIN_ITEMS;
Serial.println("Tecla ABAJO presionada, item_selected: " + String(item_selected));
} else if (key == 'C') { // 'C' para SELECCIONAR
Serial.println("Tecla SELECCIONAR presionada en item: " + String(item_selected));
if (item_selected == LUXURY_SUBMENU_INDEX) {
current_menu_level = 1; // Entrar al submenú de lujo
item_selected = 0; // Reiniciar la selección para el submenú
Serial.println("Entrando al submenu luxury");
} else if (item_selected == CREDITS_ITEM_INDEX) {
current_screen = 1; // Ir a la primera imagen de créditos
Serial.println("Entrando a creditos");
} else {
current_screen = 1; // Ir a la pantalla de modo de secado
Serial.println("Entrando a modo de secado");
}
}
} else if (current_menu_level == 1) { // Navegación del submenú de lujo
if (key == 'A' || key == '#' || key == '0') { // Teclas para ARRIBA
item_selected = (item_selected - 1 + NUM_LUXURY_ITEMS) % NUM_LUXURY_ITEMS;
Serial.println("Submenu - Tecla ARRIBA presionada, item_selected: " + String(item_selected));
} else if (key == 'B' || key == 'D') { // Teclas para ABAJO
item_selected = (item_selected + 1) % NUM_LUXURY_ITEMS;
Serial.println("Submenu - Tecla ABAJO presionada, item_selected: " + String(item_selected));
} else if (key == 'C') { // 'C' para SELECCIONAR
current_screen = 1; // Ir a la pantalla del modo de secado de lujo seleccionado
Serial.println("Submenu - Seleccionando item: " + String(item_selected));
} else if (key == '*') { // '*' para ATRÁS
current_menu_level = 0; // Volver al menú principal
item_selected = LUXURY_SUBMENU_INDEX; // Seleccionar "Secado Luxury" en el menú principal
Serial.println("Volviendo al menu principal");
}
}
} else { // Actualmente en una pantalla de modo de secado o imagen de créditos
if (key == '*') { // '*' para ATRÁS
current_screen = 0; // Volver al menú anterior
Serial.println("Volviendo al menu desde pantalla de modo");
} else if (item_selected == CREDITS_ITEM_INDEX && key == 'C') { // Ciclar imágenes de créditos
current_screen = (current_screen % 3) + 1; // Cicla entre 1, 2, 3
Serial.println("Cambiando imagen de creditos a: " + String(current_screen));
}
}
}
u8g2.clearBuffer(); // Limpiar el búfer de la pantalla para el nuevo fotograma
if (current_screen == 0) { // Mostrando un menú
if (current_menu_level == 0) {
drawMainMenu();
} else if (current_menu_level == 1) {
drawLuxurySubmenu();
}
} else { // Mostrando un modo de secado o pantalla de créditos
if (current_menu_level == 0) { // Elemento del menú principal seleccionado (modo de secado o créditos)
if (item_selected == CREDITS_ITEM_INDEX) {
drawCreditsScreen(current_screen);
} else {
drawDryingModeScreen(item_selected);
// Ejecutar la lógica para el modo de secado del menú principal seleccionado
switch (item_selected) {
case 0: executeSecadoGeneral(); break;
case 1: // Este caso es "Secado Tabaco" en el menú principal, que ahora es la entrada al submenú Luxury.
// No hay ejecución directa aquí, ya que lleva a un submenú.
break;
// LUXURY_SUBMENU_INDEX (2) se maneja ingresando current_menu_level = 1, por lo que no hay ejecución directa aquí.
case 3: executeSecadoIndefinido(); break;
case 4: executeSecadoHarinas(); break;
case 5: executeSecadoFrutas(); break;
case 6: executeSecadoCustomizado(); break;
}
}
} else if (current_menu_level == 1) { // Elemento del submenú de lujo seleccionado
drawDryingModeScreen(item_selected); // Esto dibujará "Virginia", "Burley", "Habano"
// Ejecutar la lógica para el modo de secado de lujo del submenú seleccionado
switch (item_selected) {
case 0: executeSecadoTabacoVirginia(); break;
case 1: executeSecadoTabacoBurley(); break;
case 2: executeSecadoTabacoHabano(); break;
}
}
}
u8g2.sendBuffer(); // Transferir el búfer a la pantalla
}
// No hay delay() aquí, el loop puede seguir ejecutando otras tareas
}
// Function to draw the main menu - CORREGIDO para usar drawXBM en lugar de drawBitmapP
void drawMainMenu() {
// Calculate previous and next items for circular scrolling
int item_sel_previous = (item_selected - 1 + NUM_MAIN_ITEMS) % NUM_MAIN_ITEMS;
int item_sel_next = (item_selected + 1) % NUM_MAIN_ITEMS;
// Draw scrollbar background (8 pixels width, 64 pixels height)
u8g2.drawXBM(120, 0, 8, 64, bitmap_scrollbar_background);
// Draw scrollbar thumb
u8g2.drawBox(125, (64 / NUM_MAIN_ITEMS) * item_selected, 3, (64 / NUM_MAIN_ITEMS));
// Draw previous item
u8g2.setFont(u8g2_font_7x14_tf); // Regular font
u8g2.drawStr(25, 15, main_menu_items[item_sel_previous]);
u8g2.drawXBM(4, 2, 16, 16, bitmap_icons[item_sel_previous]);
// Draw selected item with outline background (128 pixels width, 21 pixels height)
u8g2.drawXBM(0, 22, 128, 21, bitmap_item_sel_outline); // Selected item background
u8g2.setFont(u8g2_font_7x14B_tf); // Bold font for selected item
u8g2.drawStr(25, 37, main_menu_items[item_selected]);
u8g2.drawXBM(4, 24, 16, 16, bitmap_icons[item_selected]);
// Draw next item
u8g2.setFont(u8g2_font_7x14_tf); // Regular font
u8g2.drawStr(25, 59, main_menu_items[item_sel_next]);
u8g2.drawXBM(4, 46, 16, 16, bitmap_icons[item_sel_next]);
}
// Function to draw the "Secado Luxury" submenu - CORREGIDO para usar drawXBM
void drawLuxurySubmenu() {
// Calculate previous and next items for circular scrolling
int item_sel_previous = (item_selected - 1 + NUM_LUXURY_ITEMS) % NUM_LUXURY_ITEMS;
int item_sel_next = (item_selected + 1) % NUM_LUXURY_ITEMS;
// Draw scrollbar background
u8g2.drawXBM(120, 0, 8, 64, bitmap_scrollbar_background);
// Draw scrollbar thumb
u8g2.drawBox(125, (64 / NUM_LUXURY_ITEMS) * item_selected, 3, (64 / NUM_LUXURY_ITEMS));
// Draw previous item
u8g2.setFont(u8g2_font_7x14_tf); // Regular font
u8g2.drawStr(25, 15, luxury_submenu_items[item_sel_previous]);
// Use the Secado Luxury icon for all submenu items for consistency
u8g2.drawXBM(4, 2, 16, 16, epd_bitmap_Secado_Luxury);
// Draw selected item with outline background
u8g2.drawXBM(0, 22, 128, 21, bitmap_item_sel_outline); // Selected item background
u8g2.setFont(u8g2_font_7x14B_tf); // Bold font for selected item
u8g2.drawStr(25, 37, luxury_submenu_items[item_selected]);
u8g2.drawXBM(4, 24, 16, 16, epd_bitmap_Secado_Luxury); // Using Luxury icon for selected submenu item
// Draw next item
u8g2.setFont(u8g2_font_7x14_tf); // Regular font
u8g2.drawStr(25, 59, luxury_submenu_items[item_sel_next]);
u8g2.drawXBM(4, 46, 16, 16, epd_bitmap_Secado_Luxury); // Using Luxury icon for next submenu item
}
// Function to draw the screen for a selected drying mode
void drawDryingModeScreen(int selected_index_in_current_menu) {
u8g2.setFont(u8g2_font_7x14B_tf); // A larger, more readable font
if (current_menu_level == 0) { // Main menu drying modes
u8g2.drawStr(0, 20, main_menu_items[selected_index_in_current_menu]);
u8g2.drawStr(0, 40, "Datos aqui...");
u8g2.drawStr(0, 60, "Presione '*' para volver");
} else if (current_menu_level == 1) { // Luxury submenu drying modes
u8g2.drawStr(0, 20, "Secado Tabaco");
u8g2.drawStr(0, 40, luxury_submenu_items[selected_index_in_current_menu]);
u8g2.drawStr(0, 60, "Presione '*' para volver");
}
}
// Function to draw the credits screens - CORREGIDO para usar drawXBM
void drawCreditsScreen(int screen_mode) {
u8g2.setFont(u8g2_font_7x14B_tf); // A larger, more readable font
if (screen_mode == 1) {
u8g2.drawXBM(0, 0, 128, 64, epd_bitmap_Secadora_menu__QR_); // QR (128x64)
u8g2.drawStr(0, 60, " "); // Small label
}
else if (screen_mode == 2) {
u8g2.drawXBM(0, 0, 128, 64, epd_bitmap_Gojo_icon); // Gojo (128x64)
u8g2.drawStr(0, 60, " "); // Small label
}
else if (screen_mode == 3) {
u8g2.drawXBM(0, 0, 128, 64, epd_bitmap_Neco_Arc__2_); // Neco Arc (128x64)
u8g2.drawStr(0, 60, " "); // Small label
}
}
/*
* --- DIAGRAMA DE CONEXIONES PARA ESP32 S3 Dev Kit ---
*
* NOTA IMPORTANTE: Los pines GPIO del ESP32 S3 son flexibles.
* Asegúrate de verificar el pinout específico de tu placa ESP32 S3.
* Los pines aquí sugeridos son comunes pero pueden variar.
*
* PANTALLA OLED SSD1306 (I2C):
* - SDA: GPIO 8
* - SCL: GPIO 9
* - VCC: 3.3V o 5V (dependiendo de tu módulo, verificar VIN/VCC)
* - GND: GND
*
* TECLADO MATRICIAL 4x4:
* - Filas (Rows):
* - Row 1: GPIO 39 (rowPins[0])
* - Row 2: GPIO 40 (rowPins[1])
* - Row 3: GPIO 41 (rowPins[2])
* - Row 4: GPIO 42 (rowPins[3])
* - Columnas (Cols):
* - Col 1: GPIO 4 (colPins[0])
* - Col 2: GPIO 5 (colPins[1])
* - Col 3: GPIO 6 (colPins[2])
* - Col 4: GPIO 7 (colPins[3])
*
* SENSORES DHT (DHT22 para cámara/salida, DHT11 para entrada):
* - DHT22 (Cámara - DHT22_C):
* - Data Pin: GPIO 47
* - VCC: 3.3V
* - GND: GND
* - DHT22 (Salida - DHT22_S):
* - Data Pin: GPIO 48
* - VCC: 3.3V
* - GND: GND
* - DHT11 (Entrada - DHT11):
* - Data Pin: GPIO 45
* - VCC: 3.3V
* - GND: GND
* (¡Importante! Conectar una resistencia pull-up de 4.7K - 10K Ohm entre el pin de datos y VCC para cada DHT).
*
* BUZZER PASIVO (BUZZER):
* - Pin de señal: GPIO 35 (conectar un pin del buzzer)
* - Otro pin del buzzer: GND (no requiere transistor si es pasivo y la corriente es baja para el ESP32 GPIO)
*
* VENTILADOR (FAN_PIN):
* - Pin de control (PWM): GPIO 36
* - VCC del ventilador: Fuente de alimentación externa (ej. 12V si es un ventilador de 12V)
* - GND del ventilador: Conectar a GND de la fuente externa y a GND del ESP32 (común).
* (¡Importante! Usar un transistor NPN/MOSFET para controlar el ventilador desde el ESP32,
* ya que los GPIOs no pueden manejar la corriente directamente).
*
* MÓDULO RTC DS3231 (I2C):
* - SDA: GPIO 8 (compartido con OLED)
* - SCL: GPIO 9 (compartido con OLED)
* - VCC: 3.3V o 5V
* - GND: GND
*
*/

