OS ADJUNTO PDF CON LAS CONEXIONES REALES Y LOS DOS codigos el de la mega y el de esp32
lo siento pero no se como subir el código lo pegare a aqui
este es el código de la Arduino Mega
// ==========================================
// TRIVALENTE CONTROL SYSTEM - VERSION SIN PANTALLA LOCAL
// (Pantalla OLED SSD1309 eliminada; toda la UI delegada a ESP32)
// ==========================================
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <SPI.h>
#include <EEPROM.h>
#if defined(ARDUINO_ARCH_AVR)
#include <avr/wdt.h>
#endif
// ==========================================
// DEFINICION DE PINES
// ==========================================
#define PIN_IN_220V 14 // Modulo opto comercial 220V. LOGICA INVERTIDA: LOW = hay 220V
#define PIN_IN_12V 6 // Modulo opto comercial EL817_12V. LOGICA INVERTIDA: LOW = hay 12V
// SENSOR LLAMA (P810 + opto 4N35): LECTURA ANALOGICA en A0
#define PIN_IN_FLAME A0
const int FLAME_ADC_HI = 378;
const int FLAME_ADC_LOOSE = 256;
const int FLAME_ADC_LO = 225;
const int FLAME_ADC_SHORT = 50;
const int FLAME_ADC_SAMPLES = 64;
int lastFlameDecision = -1;
#define PIN_TEMP_SENSOR 7 // Sensores DS18B20
// Salidas Reles
#define PIN_RELAY_220V 2 // IN1
#define PIN_RELAY_12V 3 // IN2
#define PIN_RELAY_GAS 12 // IN3 (VALVULA GAS)
#define PIN_RELAY_FAN 4 // IN4 (VENTILADORES)
#define PIN_RELAY_COND 5 // IN5 (CONDENSACION)
#define RELAY_ON LOW
#define RELAY_OFF HIGH
#define PIN_LED_STATUS 8
#define PIN_BUZZER 13
// ==========================================
// CONSTANTES Y CONFIGURACION
// ==========================================
const float HYSTERESIS = 1.0;
const float TEMP_MIN = -14.0;
const float TEMP_MAX = 20.0;
float targetTemp = 4.0;
const unsigned long SWITCH_DELAY_MS = 3000;
const unsigned long GAS_BLOCK_DURATION_MS = 20UL * 60UL * 1000UL;
const unsigned long GAS_IGNITION_TIMEOUT_MS = 30000UL;
const unsigned long GAS_PURGE_DURATION_MS = 10000UL;
const unsigned long FLAME_LOSS_CONFIRM_MS = 5000UL;
const int GAS_MAX_RETRIES = 3;
const float V_REF = 5.0;
const unsigned long INPUT_DEBOUNCE_MS = 500;
const unsigned long DIAG_SERIAL_INTERVAL_MS = 350;
const bool GAS_PRUEBA_IGNORA_LLAMA = false;
const unsigned long GAS_FLAME_STABLE_MS = 5000;
const float FAN_REAR_ON_TEMP = 60.0;
const float FAN_REAR_OFF_TEMP = 50.0;
const unsigned long COND_AUTO_OFF_MS = 2UL * 60UL * 60UL * 1000UL;
const int ADDR_TARGET_TEMP = 0;
const int ADDR_TOTAL_RUNTIME = ADDR_TARGET_TEMP + sizeof(float);
const int ADDR_PARTIAL_RUNTIME = ADDR_TOTAL_RUNTIME + sizeof(unsigned long);
// Direcciones DS18B20 fijadas segun las pruebas realizadas.
// 0 = frigorifico interior, 1 = congelador, 2 = ventiladores exteriores.
DeviceAddress fridgeSensorAddress = { 0x28, 0x8C, 0x73, 0x22, 0x00, 0x00, 0x00, 0xDD };
DeviceAddress freezerSensorAddress = { 0x28, 0x51, 0x2C, 0x22, 0x00, 0x00, 0x00, 0xA9 };
DeviceAddress rearSensorAddress = { 0x28, 0xD9, 0xC6, 0x23, 0x00, 0x00, 0x00, 0x0A };
bool rawIn220VStable = false;
bool rawIn12VStable = false;
unsigned long debounce220VT = 0;
unsigned long debounce12VT = 0;
// ==========================================
// VARIABLES DE ESTADO
// ==========================================
enum EnergyMode {
MODE_NONE,
MODE_220V,
MODE_12V,
MODE_GAS,
MODE_TRANSITION
};
EnergyMode currentMode = MODE_NONE;
EnergyMode targetMode = MODE_NONE;
unsigned long switchTimer = 0;
unsigned long sensorTimer = 0;
unsigned long statusTimer = 0;
bool tempRequested = false;
bool prevHas12V = false;
unsigned long gasBlockTimer = 0;
bool gasBlocked = false;
unsigned long gasIgnitionTimer = 0;
bool gasFault = false;
int gasRetryCount = 0;
bool gasWaitingRetry = false;
unsigned long gasRetryTimer = 0;
unsigned long flameLossTimer = 0;
bool coolerActive = false;
float fridgeTemp = 0.0;
float freezerTemp = 0.0;
float rearTemp = 0.0;
bool fanActive = false;
bool condActive = false;
unsigned long condOnTimer = 0;
bool fanManualMode = false;
bool fanManualState = false;
int lastFlameAdcRaw = 0;
float lastFlamePinV = 0.0;
unsigned long tempSettingTimer = 0;
unsigned long bootTime = 0;
unsigned long runtimeTick = 0;
unsigned long runtimeSaveTimer = 0;
unsigned long fridgeRunTotalSeconds = 0;
unsigned long fridgeRunPartialSeconds = 0;
bool runtimeDirty = false;
// ==========================================
// COMUNICACION ESP32 (Serial1 - D18/D19)
// ==========================================
unsigned long esp32TxTimer = 0;
String esp32RxBuffer = "";
unsigned long esp32LastCommandMs = 0;
unsigned long esp32LastStatusTxMs = 0;
unsigned long esp32StatusPacketsSent = 0;
unsigned long esp32CommandsReceived = 0;
bool gasFlameStable = false;
bool flameReachedStableOnce = false;
bool oledDisplayPresent = false;
unsigned long oledRefreshTimer = 0;
int oledLastFridgeRounded = 32767;
int oledLastFreezerRounded = 32767;
bool fridgeSensorMissing = false;
bool freezerSensorMissing = false;
bool rearSensorMissing = false;
bool oledShowingAlert = false;
char oledLastAlertTop[10] = "";
char oledLastAlertBottom[10] = "";
const uint8_t OLED_PIN_CS = 49;
const uint8_t OLED_PIN_DC = 48;
const uint8_t OLED_PIN_RES = 47;
const uint8_t OLED_TEMP_AREA_START = 0;
const uint8_t OLED_TEMP_AREA_WIDTH = 74;
const uint8_t OLED_MODE_AREA_START = 78;
const uint8_t OLED_MODE_AREA_WIDTH = 50;
OneWire oneWire(PIN_TEMP_SENSOR);
DallasTemperature sensors(&oneWire);
const uint8_t OLED_CMD_MODE = 0x00;
const uint8_t OLED_DATA_MODE = 0x40;
const uint8_t OLED_PAGE_COUNT = 8;
const uint8_t OLED_WIDTH = 128;
const uint8_t GLYPH_SPACE[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
const uint8_t GLYPH_DASH[5] = { 0x08, 0x08, 0x08, 0x08, 0x08 };
const uint8_t GLYPH_DOT[5] = { 0x00, 0x60, 0x60, 0x00, 0x00 };
const uint8_t GLYPH_0[5] = { 0x3E, 0x51, 0x49, 0x45, 0x3E };
const uint8_t GLYPH_1[5] = { 0x00, 0x42, 0x7F, 0x40, 0x00 };
const uint8_t GLYPH_2[5] = { 0x42, 0x61, 0x51, 0x49, 0x46 };
const uint8_t GLYPH_3[5] = { 0x21, 0x41, 0x45, 0x4B, 0x31 };
const uint8_t GLYPH_4[5] = { 0x18, 0x14, 0x12, 0x7F, 0x10 };
const uint8_t GLYPH_5[5] = { 0x27, 0x45, 0x45, 0x45, 0x39 };
const uint8_t GLYPH_6[5] = { 0x3C, 0x4A, 0x49, 0x49, 0x30 };
const uint8_t GLYPH_7[5] = { 0x01, 0x71, 0x09, 0x05, 0x03 };
const uint8_t GLYPH_8[5] = { 0x36, 0x49, 0x49, 0x49, 0x36 };
const uint8_t GLYPH_9[5] = { 0x06, 0x49, 0x49, 0x29, 0x1E };
const uint8_t GLYPH_A[5] = { 0x7E, 0x11, 0x11, 0x11, 0x7E };
const uint8_t GLYPH_B[5] = { 0x7F, 0x49, 0x49, 0x49, 0x36 };
const uint8_t GLYPH_C[5] = { 0x3E, 0x41, 0x41, 0x41, 0x22 };
const uint8_t GLYPH_D[5] = { 0x7F, 0x41, 0x41, 0x22, 0x1C };
const uint8_t GLYPH_E[5] = { 0x7F, 0x49, 0x49, 0x49, 0x41 };
const uint8_t GLYPH_F[5] = { 0x7F, 0x09, 0x09, 0x09, 0x01 };
const uint8_t GLYPH_G[5] = { 0x3E, 0x41, 0x49, 0x49, 0x7A };
const uint8_t GLYPH_H[5] = { 0x7F, 0x08, 0x08, 0x08, 0x7F };
const uint8_t GLYPH_I[5] = { 0x00, 0x41, 0x7F, 0x41, 0x00 };
const uint8_t GLYPH_L[5] = { 0x7F, 0x40, 0x40, 0x40, 0x40 };
const uint8_t GLYPH_M[5] = { 0x7F, 0x02, 0x0C, 0x02, 0x7F };
const uint8_t GLYPH_N[5] = { 0x7F, 0x02, 0x0C, 0x10, 0x7F };
const uint8_t GLYPH_O[5] = { 0x3E, 0x41, 0x41, 0x41, 0x3E };
const uint8_t GLYPH_P[5] = { 0x7F, 0x09, 0x09, 0x09, 0x06 };
const uint8_t GLYPH_Q[5] = { 0x3E, 0x41, 0x51, 0x21, 0x5E };
const uint8_t GLYPH_R[5] = { 0x7F, 0x09, 0x19, 0x29, 0x46 };
const uint8_t GLYPH_S[5] = { 0x26, 0x49, 0x49, 0x49, 0x32 };
const uint8_t GLYPH_T[5] = { 0x01, 0x01, 0x7F, 0x01, 0x01 };
const uint8_t GLYPH_U[5] = { 0x3F, 0x40, 0x40, 0x40, 0x3F };
const uint8_t GLYPH_V[5] = { 0x1F, 0x20, 0x40, 0x20, 0x1F };
const uint8_t GLYPH_Y[5] = { 0x03, 0x04, 0x78, 0x04, 0x03 };
const uint8_t GLYPH_PLUS[5] = { 0x08, 0x08, 0x3E, 0x08, 0x08 };
// ==========================================
// UTILIDADES DE ENTRADA
// ==========================================
bool isSensorAddressConfigured(const DeviceAddress address) {
for (byte i = 0; i < 8; i++) {
if (address[i] != 0) return true;
}
return false;
}
void printSensorAddress(const DeviceAddress address) {
for (byte i = 0; i < 8; i++) {
if (address[i] < 16) Serial.print('0');
Serial.print(address[i], HEX);
if (i < 7) Serial.print(':');
}
}
void printDetectedSensorAddresses() {
byte sensorCount = sensors.getDeviceCount();
Serial.print(F("[DS18B20] Sondas detectadas: "));
Serial.println(sensorCount);
for (byte i = 0; i < sensorCount; i++) {
DeviceAddress detectedAddress;
if (sensors.getAddress(detectedAddress, i)) {
Serial.print(F("[DS18B20] Indice "));
Serial.print(i);
Serial.print(F(" -> "));
printSensorAddress(detectedAddress);
Serial.println();
}
}
}
void handleESP32Command(const String& command) {
if (command.length() == 0) return;
esp32LastCommandMs = millis();
esp32CommandsReceived++;
if (command.startsWith("TEMP,")) {
float newTemp = command.substring(5).toFloat();
if (newTemp >= TEMP_MIN && newTemp <= TEMP_MAX) {
targetTemp = newTemp;
tempSettingTimer = millis();
EEPROM.put(ADDR_TARGET_TEMP, targetTemp);
Serial.print(F("[ESP32] Temp recibida: "));
Serial.println(targetTemp, 1);
}
}
else if (command.startsWith("COND,")) {
int val = command.substring(5).toInt();
if (val == 1 && !condActive) {
condActive = true;
condOnTimer = millis();
Serial.println(F("[ESP32] COND ON"));
} else if (val == 0) {
condActive = false;
condOnTimer = 0;
Serial.println(F("[ESP32] COND OFF"));
}
}
else if (command.startsWith("FAN,")) {
int val = command.substring(4).toInt();
if (val == 1) {
fanManualMode = true;
fanManualState = true;
Serial.println(F("[ESP32] FAN manual ON"));
} else {
fanManualMode = true;
fanManualState = false;
Serial.println(F("[ESP32] FAN manual OFF -> auto"));
}
}
else if (command == "PARTIAL_RESET") {
resetRuntimeCounters(false, true);
Serial.println(F("[ESP32] Tiempo parcial reiniciado."));
}
else if (command == "TOTAL_RESET") {
resetRuntimeCounters(true, true);
Serial.println(F("[ESP32] Tiempo total y parcial reiniciados."));
}
else if (command == "RESET_MEGA") {
restartMegaFromCommand();
}
}
float readManagedTemperature(const DeviceAddress address, byte fallbackIndex) {
if (isSensorAddressConfigured(address)) {
return sensors.getTempC(address);
}
return sensors.getTempCByIndex(fallbackIndex);
}
bool readDigitalFiltered(byte pin, bool *stableState, unsigned long *debounceT, unsigned long debounceMs, bool usePullupLogic) {
bool rawNow = usePullupLogic ? (digitalRead(pin) == LOW) : (digitalRead(pin) == HIGH);
if (rawNow != *stableState) {
if (*debounceT == 0) *debounceT = millis();
else if (millis() - *debounceT >= debounceMs) {
*stableState = rawNow;
*debounceT = 0;
}
} else {
*debounceT = 0;
}
return *stableState;
}
bool readRaw220VPresent() {
return readDigitalFiltered(PIN_IN_220V, &rawIn220VStable, &debounce220VT, INPUT_DEBOUNCE_MS, true);
}
bool readRaw12VPresent() {
return readDigitalFiltered(PIN_IN_12V, &rawIn12VStable, &debounce12VT, INPUT_DEBOUNCE_MS, true);
}
bool readFlameOK() {
long sum = 0;
for (byte i = 0; i < FLAME_ADC_SAMPLES; i++) {
sum += analogRead(PIN_IN_FLAME);
delayMicroseconds(200);
}
int mean = (int)(sum / FLAME_ADC_SAMPLES);
lastFlameAdcRaw = mean;
lastFlamePinV = (mean * V_REF) / 1023.0;
int thresholdOn = flameReachedStableOnce ? FLAME_ADC_LOOSE : FLAME_ADC_LO;
int thresholdOff = flameReachedStableOnce ? (FLAME_ADC_LOOSE + ((FLAME_ADC_HI - FLAME_ADC_LOOSE) / 2))
: (FLAME_ADC_LO + ((FLAME_ADC_HI - FLAME_ADC_LO) / 2));
bool result;
if (mean < FLAME_ADC_SHORT) { result = false; lastFlameDecision = 0; }
else if (mean > FLAME_ADC_HI) { result = false; lastFlameDecision = 0; }
else if (mean < thresholdOn) { result = true; lastFlameDecision = 1; }
else if (mean > thresholdOff) { result = false; lastFlameDecision = 0; }
else {
if (lastFlameDecision < 0) lastFlameDecision = 0;
result = (lastFlameDecision == 1);
}
return result;
}
byte readFlameState() {
long sum = 0;
for (byte i = 0; i < 32; i++) {
sum += analogRead(PIN_IN_FLAME);
delayMicroseconds(150);
}
int mean = (int)(sum / 32);
lastFlameAdcRaw = mean;
lastFlamePinV = (mean * V_REF) / 1023.0;
int thresholdOn = flameReachedStableOnce ? FLAME_ADC_LOOSE : FLAME_ADC_LO;
int thresholdOff = FLAME_ADC_HI;
if (mean < FLAME_ADC_SHORT) return 0;
if (mean > thresholdOff) return 0;
if (mean < thresholdOn) return 1;
return 2;
}
int lastFlameADC = 0;
int readLastFlameADC() {
long sum = 0;
for (byte i = 0; i < FLAME_ADC_SAMPLES; i++) {
sum += analogRead(PIN_IN_FLAME);
delayMicroseconds(120);
}
lastFlameADC = (int)(sum / FLAME_ADC_SAMPLES);
return lastFlameADC;
}
bool isGasOpen() {
return currentMode == MODE_GAS && !gasFault && !gasWaitingRetry && digitalRead(PIN_RELAY_GAS) == RELAY_ON;
}
bool isGasOpenNoFlame() {
if (!isGasOpen()) return false;
if (GAS_PRUEBA_IGNORA_LLAMA) return false;
byte st = readFlameState();
if (st == 0) return true;
if (st == 2 && lastFlameDecision != 1) return true;
return false;
}
bool isGasFlameConfirmed() {
if (!isGasOpen()) return false;
if (GAS_PRUEBA_IGNORA_LLAMA) return gasFlameStable;
return gasFlameStable && readFlameOK();
}
bool isFlameLostAfterStable() {
if (gasFault) return true;
if (gasWaitingRetry) return true;
if (flameLossTimer > 0) return true;
if (isGasOpen() && (!readFlameOK() || isGasOpenNoFlame())) {
if (flameReachedStableOnce) return true;
if (gasIgnitionTimer > 0 && (millis() - gasIgnitionTimer) >= (GAS_IGNITION_TIMEOUT_MS / 2)) return true;
}
return false;
}
void printDiagSerial() {
static unsigned long lastT = 0;
if (millis() - lastT < DIAG_SERIAL_INTERVAL_MS) return;
lastT = millis();
int flameADC = readLastFlameADC();
bool f220 = readRaw220VPresent();
bool f12 = readRaw12VPresent();
bool flameOK = GAS_PRUEBA_IGNORA_LLAMA ? true : readFlameOK();
Serial.print(F("[DIAG] t=")); Serial.print(millis());
Serial.print(F(" M=")); Serial.print(currentMode);
Serial.print(F(" Fadc=")); Serial.print(flameADC);
Serial.print(F(" F=")); Serial.print(f220 ? '1' : '0'); Serial.print(f12 ? '1' : '0'); Serial.print(flameOK ? '1' : '0');
Serial.print(F(" Tf=")); Serial.print(fridgeTemp, 1);
Serial.print(F(" Tcong=")); Serial.print(freezerTemp, 1);
Serial.print(F(" Trear=")); Serial.print(rearTemp, 1);
Serial.print(F(" Fan=")); Serial.print(fanActive ? '1' : '0');
Serial.print(F(" Cond=")); Serial.print(condActive ? '1' : '0');
Serial.print(F(" Ts=")); Serial.print(targetTemp, 1);
Serial.print(F(" S1tx=")); Serial.print(esp32StatusPacketsSent);
Serial.print(F(" S1rx=")); Serial.print(esp32CommandsReceived);
Serial.print(F(" S1ageTx=")); Serial.print((esp32LastStatusTxMs == 0) ? 0 : (millis() - esp32LastStatusTxMs));
Serial.print(F(" S1ageRx=")); Serial.print((esp32LastCommandMs == 0) ? 0 : (millis() - esp32LastCommandMs));
Serial.println();
}
bool isFridgeRunningNow() {
if (digitalRead(PIN_RELAY_220V) == RELAY_ON) return true;
if (digitalRead(PIN_RELAY_12V) == RELAY_ON) return true;
if (currentMode == MODE_GAS) {
if (GAS_PRUEBA_IGNORA_LLAMA) return digitalRead(PIN_RELAY_GAS) == RELAY_ON;
return isGasFlameConfirmed();
}
return false;
}
void saveRuntimeCounters() {
EEPROM.put(ADDR_TOTAL_RUNTIME, fridgeRunTotalSeconds);
EEPROM.put(ADDR_PARTIAL_RUNTIME, fridgeRunPartialSeconds);
runtimeDirty = false;
runtimeSaveTimer = millis();
}
void updateRuntimeCounters() {
unsigned long now = millis();
if (runtimeTick == 0) runtimeTick = now;
while (now - runtimeTick >= 1000UL) {
runtimeTick += 1000UL;
if (isFridgeRunningNow()) {
fridgeRunTotalSeconds++;
fridgeRunPartialSeconds++;
runtimeDirty = true;
}
}
if (runtimeDirty && (now - runtimeSaveTimer >= 60000UL)) {
saveRuntimeCounters();
}
}
void resetRuntimeCounters(bool resetTotal, bool resetPartial) {
if (resetTotal) fridgeRunTotalSeconds = 0;
if (resetPartial) fridgeRunPartialSeconds = 0;
saveRuntimeCounters();
}
void restartMegaFromCommand() {
Serial.println(F("[ESP32] Reinicio software solicitado."));
saveRuntimeCounters();
#if defined(ARDUINO_ARCH_AVR)
delay(50);
wdt_enable(WDTO_15MS);
while (true) {}
#endif
}
// ==========================================
// OLED I2C AUXILIAR (solo temperaturas)
// ==========================================
const uint8_t* getOledGlyph(char c) {
switch (c) {
case '0': return GLYPH_0;
case '1': return GLYPH_1;
case '2': return GLYPH_2;
case '3': return GLYPH_3;
case '4': return GLYPH_4;
case '5': return GLYPH_5;
case '6': return GLYPH_6;
case '7': return GLYPH_7;
case '8': return GLYPH_8;
case '9': return GLYPH_9;
case 'A': return GLYPH_A;
case 'B': return GLYPH_B;
case 'C': return GLYPH_C;
case 'D': return GLYPH_D;
case 'E': return GLYPH_E;
case 'F': return GLYPH_F;
case 'G': return GLYPH_G;
case 'H': return GLYPH_H;
case 'I': return GLYPH_I;
case 'L': return GLYPH_L;
case 'M': return GLYPH_M;
case 'N': return GLYPH_N;
case 'O': return GLYPH_O;
case 'P': return GLYPH_P;
case 'Q': return GLYPH_Q;
case 'R': return GLYPH_R;
case 'S': return GLYPH_S;
case 'T': return GLYPH_T;
case 'U': return GLYPH_U;
case 'V': return GLYPH_V;
case 'Y': return GLYPH_Y;
case '+': return GLYPH_PLUS;
case '-': return GLYPH_DASH;
case '.': return GLYPH_DOT;
case ' ': return GLYPH_SPACE;
default: return GLYPH_SPACE;
}
}
void oledGetModeText(char* outBuffer, size_t outSize) {
if (currentMode == MODE_220V) snprintf(outBuffer, outSize, "220V");
else if (currentMode == MODE_12V) snprintf(outBuffer, outSize, "12V");
else if (currentMode == MODE_GAS) snprintf(outBuffer, outSize, "GAS");
else snprintf(outBuffer, outSize, "OFF");
}
void oledGetFanText(char* outBuffer, size_t outSize) {
snprintf(outBuffer, outSize, fanActive ? "FANON" : "FANOFF");
}
void oledSendCommand(uint8_t command) {
if (!oledDisplayPresent) return;
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
digitalWrite(OLED_PIN_CS, LOW);
digitalWrite(OLED_PIN_DC, LOW);
SPI.transfer(command);
digitalWrite(OLED_PIN_CS, HIGH);
SPI.endTransaction();
}
void oledWriteColumns(const uint8_t* columns, uint8_t count) {
if (!oledDisplayPresent) return;
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
digitalWrite(OLED_PIN_CS, LOW);
digitalWrite(OLED_PIN_DC, HIGH);
for (uint8_t i = 0; i < count; i++) SPI.transfer(columns[i]);
digitalWrite(OLED_PIN_CS, HIGH);
SPI.endTransaction();
}
void oledSetCursor(uint8_t page, uint8_t column) {
oledSendCommand(0xB0 | (page & 0x07));
oledSendCommand(0x00 | (column & 0x0F));
oledSendCommand(0x10 | ((column >> 4) & 0x0F));
}
void oledClear() {
if (!oledDisplayPresent) return;
uint8_t empty[16];
memset(empty, 0x00, sizeof(empty));
for (uint8_t page = 0; page < OLED_PAGE_COUNT; page++) {
oledSetCursor(page, 0);
for (uint8_t block = 0; block < (OLED_WIDTH / sizeof(empty)); block++) {
oledWriteColumns(empty, sizeof(empty));
}
}
}
void oledPrintText(uint8_t page, uint8_t column, const char* text) {
if (!oledDisplayPresent) return;
oledSetCursor(page, column);
while (*text) {
uint8_t buffer[6];
const uint8_t* glyph = getOledGlyph(*text++);
for (uint8_t i = 0; i < 5; i++) buffer[i] = glyph[i];
buffer[5] = 0x00;
oledWriteColumns(buffer, sizeof(buffer));
}
}
void oledPrintTextInverted(uint8_t page, uint8_t column, const char* text) {
if (!oledDisplayPresent) return;
oledSetCursor(page, column);
while (*text) {
uint8_t buffer[6];
const uint8_t* glyph = getOledGlyph(*text++);
for (uint8_t i = 0; i < 5; i++) buffer[i] = ~glyph[i];
buffer[5] = 0xFF;
oledWriteColumns(buffer, sizeof(buffer));
}
}
void oledClearPageRange(uint8_t pageStart, uint8_t pageCount) {
if (!oledDisplayPresent) return;
uint8_t empty[16];
memset(empty, 0x00, sizeof(empty));
for (uint8_t page = pageStart; page < (pageStart + pageCount) && page < OLED_PAGE_COUNT; page++) {
oledSetCursor(page, 0);
for (uint8_t block = 0; block < (OLED_WIDTH / sizeof(empty)); block++) {
oledWriteColumns(empty, sizeof(empty));
}
}
}
void oledClearArea(uint8_t pageStart, uint8_t pageCount, uint8_t columnStart, uint8_t columnCount) {
if (!oledDisplayPresent) return;
uint8_t empty[16];
memset(empty, 0x00, sizeof(empty));
for (uint8_t page = pageStart; page < (pageStart + pageCount) && page < OLED_PAGE_COUNT; page++) {
uint8_t remaining = columnCount;
uint8_t column = columnStart;
while (remaining > 0) {
uint8_t chunk = remaining > sizeof(empty) ? sizeof(empty) : remaining;
oledSetCursor(page, column);
oledWriteColumns(empty, chunk);
column += chunk;
remaining -= chunk;
}
}
}
void oledFillArea(uint8_t pageStart, uint8_t pageCount, uint8_t columnStart, uint8_t columnCount, uint8_t value) {
if (!oledDisplayPresent) return;
uint8_t block[16];
memset(block, value, sizeof(block));
for (uint8_t page = pageStart; page < (pageStart + pageCount) && page < OLED_PAGE_COUNT; page++) {
uint8_t remaining = columnCount;
uint8_t column = columnStart;
while (remaining > 0) {
uint8_t chunk = remaining > sizeof(block) ? sizeof(block) : remaining;
oledSetCursor(page, column);
oledWriteColumns(block, chunk);
column += chunk;
remaining -= chunk;
}
}
}
void oledPrintText3x(uint8_t pageTop, uint8_t column, const char* text) {
if (!oledDisplayPresent) return;
while (*text) {
const uint8_t* glyph = getOledGlyph(*text++);
uint8_t pageBuffers[2][18];
memset(pageBuffers, 0x00, sizeof(pageBuffers));
for (uint8_t i = 0; i < 5; i++) {
uint8_t src = glyph[i];
uint16_t expanded = 0;
for (uint8_t bit = 0; bit < 8; bit++) {
if (src & (1 << bit)) {
uint8_t expandedBit = bit * 2;
expanded |= (uint16_t(0x03) << expandedBit);
}
}
for (uint8_t page = 0; page < 2; page++) {
uint8_t pageByte = (expanded >> (page * 8)) & 0xFF;
uint8_t offset = i * 3;
pageBuffers[page][offset] = pageByte;
pageBuffers[page][offset + 1] = pageByte;
pageBuffers[page][offset + 2] = pageByte;
}
}
for (uint8_t page = 0; page < 2 && (pageTop + page) < OLED_PAGE_COUNT; page++) {
oledSetCursor(pageTop + page, column);
oledWriteColumns(pageBuffers[page], sizeof(pageBuffers[page]));
}
column += sizeof(pageBuffers[0]);
}
}
void oledDrawDivider(uint8_t page, uint8_t pattern) {
if (!oledDisplayPresent || page >= OLED_PAGE_COUNT) return;
oledSetCursor(page, 0);
uint8_t line[16];
memset(line, pattern, sizeof(line));
for (uint8_t block = 0; block < (OLED_WIDTH / sizeof(line)); block++) {
oledWriteColumns(line, sizeof(line));
}
}
void oledDrawVerticalDivider(uint8_t column, uint8_t pattern) {
if (!oledDisplayPresent || column >= OLED_WIDTH) return;
for (uint8_t page = 0; page < OLED_PAGE_COUNT; page++) {
oledSetCursor(page, column);
oledWriteColumns(&pattern, 1);
}
}
void oledFormatTemperature(float value, char* outBuffer, size_t outSize) {
int scaledValue = (int)(value >= 0.0 ? (value * 10.0f + 0.5f) : (value * 10.0f - 0.5f));
int absScaled = scaledValue >= 0 ? scaledValue : -scaledValue;
int wholePart = absScaled / 10;
int decimalPart = absScaled % 10;
if (scaledValue < 0) {
snprintf(outBuffer, outSize, "-%d.%dC", wholePart, decimalPart);
} else {
snprintf(outBuffer, outSize, "%d.%dC", wholePart, decimalPart);
}
}
uint8_t oledCenteredColumn3x(const char* text) {
size_t len = strlen(text);
int width = (int)len * 18;
int column = (OLED_WIDTH - width) / 2;
if (column < 0) column = 0;
return (uint8_t)column;
}
uint8_t oledCenteredColumn2x(const char* text) {
size_t len = strlen(text);
int width = (int)len * 12;
int column = (OLED_WIDTH - width) / 2;
if (column < 0) column = 0;
return (uint8_t)column;
}
uint8_t oledCenteredColumn1xArea(const char* text, uint8_t startColumn, uint8_t areaWidth) {
int width = (int)strlen(text) * 6;
int column = startColumn + ((int)areaWidth - width) / 2;
if (column < startColumn) column = startColumn;
return (uint8_t)column;
}
void oledPrintText2x(uint8_t pageTop, uint8_t column, const char* text) {
if (!oledDisplayPresent) return;
while (*text) {
const uint8_t* glyph = getOledGlyph(*text++);
uint8_t pageBuffers[2][12];
memset(pageBuffers, 0x00, sizeof(pageBuffers));
for (uint8_t i = 0; i < 5; i++) {
uint8_t src = glyph[i];
uint16_t expanded = 0;
for (uint8_t bit = 0; bit < 8; bit++) {
if (src & (1 << bit)) {
uint8_t expandedBit = bit * 2;
expanded |= (uint16_t(0x03) << expandedBit);
}
}
for (uint8_t page = 0; page < 2; page++) {
uint8_t pageByte = (expanded >> (page * 8)) & 0xFF;
uint8_t offset = i * 2;
pageBuffers[page][offset] = pageByte;
pageBuffers[page][offset + 1] = pageByte;
}
}
for (uint8_t page = 0; page < 2 && (pageTop + page) < OLED_PAGE_COUNT; page++) {
oledSetCursor(pageTop + page, column);
oledWriteColumns(pageBuffers[page], sizeof(pageBuffers[page]));
}
column += sizeof(pageBuffers[0]);
}
}
void oledDrawFanIcon(uint8_t page, uint8_t column, bool active) {
if (!oledDisplayPresent || page >= OLED_PAGE_COUNT) return;
static const uint8_t fanOn[8] = { 0x24, 0x66, 0x3C, 0x18, 0x18, 0x3C, 0x66, 0x24 };
static const uint8_t fanOff[8] = { 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00 };
oledSetCursor(page, column);
oledWriteColumns(active ? fanOn : fanOff, 8);
}
bool oledGetAlertText(char* topText, size_t topSize, char* bottomText, size_t bottomSize) {
bool has220V = readRaw220VPresent();
bool has12V = readRaw12VPresent();
if (has220V && has12V) {
snprintf(topText, topSize, "220V");
snprintf(bottomText, bottomSize, "+12V");
return true;
}
if (isFlameLostAfterStable() || isGasOpenNoFlame()) {
snprintf(topText, topSize, "SIN");
snprintf(bottomText, bottomSize, "LLAMA");
return true;
}
if (gasFault) {
snprintf(topText, topSize, "FALLO");
snprintf(bottomText, bottomSize, "GAS");
return true;
}
if (fridgeSensorMissing) {
snprintf(topText, topSize, "FRIGO");
snprintf(bottomText, bottomSize, "ERR");
return true;
}
if (freezerSensorMissing) {
snprintf(topText, topSize, "CONG");
snprintf(bottomText, bottomSize, "ERR");
return true;
}
if (rearSensorMissing) {
snprintf(topText, topSize, "TRAS");
snprintf(bottomText, bottomSize, "ERR");
return true;
}
return false;
}
unsigned long getGasBlockRemainingSeconds() {
if (!gasBlocked) return 0;
unsigned long elapsed = millis() - gasBlockTimer;
if (elapsed >= GAS_BLOCK_DURATION_MS) return 0;
return (GAS_BLOCK_DURATION_MS - elapsed) / 1000UL;
}
void formatCountdownMmSs(unsigned long totalSeconds, char* outBuffer, size_t outSize) {
unsigned long minutes = totalSeconds / 60UL;
unsigned long seconds = totalSeconds % 60UL;
snprintf(outBuffer, outSize, "%02lu:%02lu", minutes, seconds);
}
bool oledStateLineIsCritical(const char* line) {
return strncmp(line, "GAS BLOQ", 8) == 0 ||
strncmp(line, "FALLO GAS", 9) == 0 ||
strncmp(line, "SIN LLAMA", 9) == 0;
}
void oledDrawNormalLayout() {
oledClear();
oledLastFridgeRounded = 32767;
oledLastFreezerRounded = 32767;
oledPrintText(0, oledCenteredColumn1xArea("TEMPERATURAS", OLED_TEMP_AREA_START, OLED_TEMP_AREA_WIDTH), "TEMPERATURAS");
oledPrintText(0, oledCenteredColumn1xArea("ENERGIA", OLED_MODE_AREA_START, OLED_MODE_AREA_WIDTH), "ENERGIA");
for (uint8_t page = 0; page < 4; page++) {
oledSetCursor(page, 74);
uint8_t solid = 0xFF;
oledWriteColumns(&solid, 1);
}
oledPrintText(4, oledCenteredColumn1xArea("ESTADOS", 0, OLED_WIDTH), "ESTADOS");
}
void initAuxOledDisplay() {
pinMode(OLED_PIN_CS, OUTPUT);
pinMode(OLED_PIN_DC, OUTPUT);
pinMode(OLED_PIN_RES, OUTPUT);
digitalWrite(OLED_PIN_CS, HIGH);
digitalWrite(OLED_PIN_DC, HIGH);
digitalWrite(OLED_PIN_RES, HIGH);
SPI.begin();
digitalWrite(OLED_PIN_RES, LOW);
delay(10);
digitalWrite(OLED_PIN_RES, HIGH);
delay(10);
oledDisplayPresent = true;
oledSendCommand(0xAE);
oledSendCommand(0xD5); oledSendCommand(0x80);
oledSendCommand(0xA8); oledSendCommand(0x3F);
oledSendCommand(0xD3); oledSendCommand(0x00);
oledSendCommand(0x40);
oledSendCommand(0x8D); oledSendCommand(0x14);
oledSendCommand(0x20); oledSendCommand(0x02);
oledSendCommand(0xA1);
oledSendCommand(0xC8);
oledSendCommand(0xDA); oledSendCommand(0x12);
oledSendCommand(0x81); oledSendCommand(0xCF);
oledSendCommand(0xD9); oledSendCommand(0xF1);
oledSendCommand(0xDB); oledSendCommand(0x40);
oledSendCommand(0xA4);
oledSendCommand(0xA6);
oledSendCommand(0xAF);
oledDrawNormalLayout();
oledShowingAlert = false;
oledLastAlertTop[0] = '\0';
oledLastAlertBottom[0] = '\0';
Serial.println(F("[OLED SPI] Inicializada."));
}
void updateAuxOledDisplay() {
if (!oledDisplayPresent) return;
if (millis() - oledRefreshTimer < 500) return;
oledRefreshTimer = millis();
char fridgeText[10];
char freezerText[10];
char rearText[10];
char targetText[10];
char fridgeLine[18];
char freezerLine[18];
char rearLine[18];
char stateLine1[22];
char stateLine2[22];
char stateLine3[22];
int roundedFridge = (int)(fridgeTemp >= 0.0 ? (fridgeTemp + 0.5) : (fridgeTemp - 0.5));
int roundedFreezer = (int)(freezerTemp >= 0.0 ? (freezerTemp + 0.5) : (freezerTemp - 0.5));
bool showAdjust = (millis() - tempSettingTimer) <= 3000UL;
uint8_t modeBoxStart = OLED_MODE_AREA_START + 4;
uint8_t modeBoxWidth = OLED_MODE_AREA_WIDTH - 8;
unsigned long gasBlockRemainingSeconds = getGasBlockRemainingSeconds();
char countdownText[8];
oledFormatTemperature(fridgeTemp, fridgeText, sizeof(fridgeText));
oledFormatTemperature(freezerTemp, freezerText, sizeof(freezerText));
oledFormatTemperature(rearTemp, rearText, sizeof(rearText));
oledFormatTemperature(targetTemp, targetText, sizeof(targetText));
formatCountdownMmSs(gasBlockRemainingSeconds, countdownText, sizeof(countdownText));
snprintf(fridgeLine, sizeof(fridgeLine), "FRIGO ->%s", fridgeText);
snprintf(freezerLine, sizeof(freezerLine), "CONG ->%s", freezerText);
snprintf(rearLine, sizeof(rearLine), "T.COND->%s", rearText);
stateLine1[0] = '\0';
stateLine2[0] = '\0';
stateLine3[0] = '\0';
uint8_t stateIndex = 0;
if (gasBlocked && stateIndex < 3) {
snprintf(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), 22, "GAS BLOQ %s", countdownText);
stateIndex++;
}
if (readRaw220VPresent() && readRaw12VPresent() && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "220V + 12V", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if ((isFlameLostAfterStable() || isGasOpenNoFlame()) && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "SIN LLAMA", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if (gasFault && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "FALLO GAS", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if (fridgeSensorMissing && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "FRIGO ERR", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if (freezerSensorMissing && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "CONG ERR", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if (rearSensorMissing && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "T.COND ERR", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if (condActive && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "CONDENSACION ON", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if (fanActive && stateIndex < 3) {
strncpy(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), "VENTILADOR ON", 21);
if (stateIndex == 0) stateLine1[21] = '\0';
else if (stateIndex == 1) stateLine2[21] = '\0';
else stateLine3[21] = '\0';
stateIndex++;
}
if (showAdjust && stateIndex < 3) {
snprintf(stateIndex == 0 ? stateLine1 : (stateIndex == 1 ? stateLine2 : stateLine3), 22, "AJUSTE %s", targetText);
stateIndex++;
}
oledClearArea(1, 3, OLED_TEMP_AREA_START, OLED_TEMP_AREA_WIDTH);
oledPrintText(1, oledCenteredColumn1xArea(fridgeLine, OLED_TEMP_AREA_START, OLED_TEMP_AREA_WIDTH), fridgeLine);
oledPrintText(2, oledCenteredColumn1xArea(freezerLine, OLED_TEMP_AREA_START, OLED_TEMP_AREA_WIDTH), freezerLine);
oledPrintText(3, oledCenteredColumn1xArea(rearLine, OLED_TEMP_AREA_START, OLED_TEMP_AREA_WIDTH), rearLine);
oledClearArea(1, 3, OLED_MODE_AREA_START, OLED_MODE_AREA_WIDTH);
if (currentMode == MODE_220V) oledFillArea(1, 1, modeBoxStart, modeBoxWidth, 0xFF);
if (currentMode == MODE_12V) oledFillArea(2, 1, modeBoxStart, modeBoxWidth, 0xFF);
if (currentMode == MODE_GAS) oledFillArea(3, 1, modeBoxStart, modeBoxWidth, 0xFF);
if (currentMode == MODE_220V) oledPrintTextInverted(1, oledCenteredColumn1xArea("220V", modeBoxStart, modeBoxWidth), "220V");
else oledPrintText(1, oledCenteredColumn1xArea("220V", modeBoxStart, modeBoxWidth), "220V");
if (currentMode == MODE_12V) oledPrintTextInverted(2, oledCenteredColumn1xArea("12V", modeBoxStart, modeBoxWidth), "12V");
else oledPrintText(2, oledCenteredColumn1xArea("12V", modeBoxStart, modeBoxWidth), "12V");
if (currentMode == MODE_GAS) oledPrintTextInverted(3, oledCenteredColumn1xArea("GAS", modeBoxStart, modeBoxWidth), "GAS");
else oledPrintText(3, oledCenteredColumn1xArea("GAS", modeBoxStart, modeBoxWidth), "GAS");
oledClearArea(5, 3, 0, OLED_WIDTH);
if (stateLine1[0] != '\0') {
if (oledStateLineIsCritical(stateLine1)) {
oledFillArea(5, 1, 0, OLED_WIDTH, 0xFF);
oledPrintTextInverted(5, oledCenteredColumn1xArea(stateLine1, 0, OLED_WIDTH), stateLine1);
} else {
oledPrintText(5, oledCenteredColumn1xArea(stateLine1, 0, OLED_WIDTH), stateLine1);
}
}
if (stateLine2[0] != '\0') {
if (oledStateLineIsCritical(stateLine2)) {
oledFillArea(6, 1, 0, OLED_WIDTH, 0xFF);
oledPrintTextInverted(6, oledCenteredColumn1xArea(stateLine2, 0, OLED_WIDTH), stateLine2);
} else {
oledPrintText(6, oledCenteredColumn1xArea(stateLine2, 0, OLED_WIDTH), stateLine2);
}
}
if (stateLine3[0] != '\0') {
if (oledStateLineIsCritical(stateLine3)) {
oledFillArea(7, 1, 0, OLED_WIDTH, 0xFF);
oledPrintTextInverted(7, oledCenteredColumn1xArea(stateLine3, 0, OLED_WIDTH), stateLine3);
} else {
oledPrintText(7, oledCenteredColumn1xArea(stateLine3, 0, OLED_WIDTH), stateLine3);
}
}
oledLastFridgeRounded = roundedFridge;
oledLastFreezerRounded = roundedFreezer;
}
// ==========================================
// SETUP
// ==========================================
void setup() {
Serial.begin(9600);
Serial1.begin(9600);
Wire.begin();
Wire.setClock(100000);
#if defined(ARDUINO_ARCH_AVR)
Wire.setWireTimeout(3000, true);
#endif
Serial.println(F("--- INICIO SISTEMA TRIVALENTE (SIN PANTALLA LOCAL) ---"));
pinMode(PIN_IN_220V, INPUT_PULLUP);
pinMode(PIN_IN_12V, INPUT_PULLUP);
prevHas12V = (digitalRead(PIN_IN_12V) == LOW);
pinMode(PIN_RELAY_220V, OUTPUT);
pinMode(PIN_RELAY_12V, OUTPUT);
pinMode(PIN_RELAY_GAS, OUTPUT);
pinMode(PIN_RELAY_FAN, OUTPUT);
pinMode(PIN_RELAY_COND, OUTPUT);
digitalWrite(PIN_RELAY_220V, RELAY_OFF);
digitalWrite(PIN_RELAY_12V, RELAY_OFF);
digitalWrite(PIN_RELAY_GAS, RELAY_OFF);
digitalWrite(PIN_RELAY_FAN, RELAY_OFF);
digitalWrite(PIN_RELAY_COND, RELAY_OFF);
pinMode(PIN_LED_STATUS, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
digitalWrite(PIN_LED_STATUS, HIGH);
digitalWrite(PIN_BUZZER, HIGH); delay(100); digitalWrite(PIN_BUZZER, LOW);
delay(50);
digitalWrite(PIN_BUZZER, HIGH); delay(100); digitalWrite(PIN_BUZZER, LOW);
float storedTemp = 0.0;
EEPROM.get(ADDR_TARGET_TEMP, storedTemp);
if (storedTemp >= TEMP_MIN && storedTemp <= TEMP_MAX) targetTemp = storedTemp;
EEPROM.get(ADDR_TOTAL_RUNTIME, fridgeRunTotalSeconds);
EEPROM.get(ADDR_PARTIAL_RUNTIME, fridgeRunPartialSeconds);
if (fridgeRunTotalSeconds > 4294967000UL) fridgeRunTotalSeconds = 0;
if (fridgeRunPartialSeconds > 4294967000UL) fridgeRunPartialSeconds = 0;
sensors.begin();
sensors.setWaitForConversion(false);
printDetectedSensorAddresses();
initAuxOledDisplay();
Serial.println(F("[ESP32-LINK] Serial1 activo a 9600 bps por D18(TX1) y D19(RX1)."));
Serial.println(F("Iniciado con exito."));
statusTimer = millis();
bootTime = millis();
runtimeTick = millis();
runtimeSaveTimer = millis();
}
void loop() {
receiveFromESP32();
readSensors();
determinePriority();
handleModeSwitching();
controlThermostat();
controlExteriorFans();
controlCondResistance();
handleAudioAlarms();
updateRuntimeCounters();
updateIndicators();
updateAuxOledDisplay();
if (millis() - esp32TxTimer >= 500) {
sendStatusToESP32();
esp32TxTimer = millis();
}
printDiagSerial();
delay(50);
}
// ==========================================
// FUNCIONES DE LECTURA Y CONTROL
// ==========================================
void readSensors() {
if (!tempRequested) {
sensors.requestTemperatures();
tempRequested = true;
sensorTimer = millis();
}
if (tempRequested && (millis() - sensorTimer >= 800)) {
fridgeTemp = readManagedTemperature(fridgeSensorAddress, 0);
freezerTemp = readManagedTemperature(freezerSensorAddress, 1);
rearTemp = readManagedTemperature(rearSensorAddress, 2);
fridgeSensorMissing = (fridgeTemp == DEVICE_DISCONNECTED_C);
freezerSensorMissing = (freezerTemp == DEVICE_DISCONNECTED_C);
rearSensorMissing = (rearTemp == DEVICE_DISCONNECTED_C);
if (fridgeTemp == DEVICE_DISCONNECTED_C) fridgeTemp = targetTemp + HYSTERESIS + 0.5f;
if (freezerTemp == DEVICE_DISCONNECTED_C) freezerTemp = 0.0;
if (rearTemp == DEVICE_DISCONNECTED_C) rearTemp = 0.0;
tempRequested = false;
}
}
void determinePriority() {
bool has220V = readRaw220VPresent();
bool has12V = readRaw12VPresent();
if (has220V || has12V) {
if (gasBlocked || gasBlockTimer != 0) {
gasBlocked = false;
gasBlockTimer = 0;
Serial.println(F("Bloqueo GAS cancelado por retorno de 12V/220V."));
}
}
if (prevHas12V && !has12V && !has220V) {
gasBlocked = true;
gasBlockTimer = millis();
Serial.println(F("Motor OFF. Bloqueo 20m."));
}
prevHas12V = has12V;
if (gasBlocked && (millis() - gasBlockTimer >= GAS_BLOCK_DURATION_MS)) {
gasBlocked = false;
gasBlockTimer = 0;
}
EnergyMode requestedMode = MODE_NONE;
if (has220V) requestedMode = MODE_220V;
else if (has12V) requestedMode = MODE_12V;
else requestedMode = gasBlocked ? MODE_NONE : MODE_GAS;
if (requestedMode != targetMode) {
targetMode = requestedMode;
if (currentMode != targetMode) {
currentMode = MODE_TRANSITION;
gasFault = false;
gasRetryCount = 0;
flameLossTimer = 0;
flameReachedStableOnce = false;
turnOffAllRelays();
switchTimer = millis();
}
}
}
void handleModeSwitching() {
if (currentMode == MODE_TRANSITION && (millis() - switchTimer >= SWITCH_DELAY_MS)) {
currentMode = targetMode;
}
}
void controlThermostat() {
if (fridgeTemp >= (targetTemp + HYSTERESIS)) coolerActive = true;
else if (fridgeTemp <= (targetTemp - HYSTERESIS)) coolerActive = false;
if (currentMode == MODE_TRANSITION) {
turnOffAllRelays();
gasIgnitionTimer = 0;
flameLossTimer = 0;
} else if (coolerActive) {
switch (currentMode) {
case MODE_220V:
digitalWrite(PIN_RELAY_220V, RELAY_ON); digitalWrite(PIN_RELAY_12V, RELAY_OFF); digitalWrite(PIN_RELAY_GAS, RELAY_OFF);
break;
case MODE_12V:
digitalWrite(PIN_RELAY_220V, RELAY_OFF); digitalWrite(PIN_RELAY_12V, RELAY_ON); digitalWrite(PIN_RELAY_GAS, RELAY_OFF);
break;
case MODE_GAS:
digitalWrite(PIN_RELAY_220V, RELAY_OFF); digitalWrite(PIN_RELAY_12V, RELAY_OFF);
if (gasFault) {
digitalWrite(PIN_RELAY_GAS, RELAY_OFF);
gasFlameStable = false;
flameReachedStableOnce = false;
} else if (gasWaitingRetry) {
digitalWrite(PIN_RELAY_GAS, RELAY_OFF);
flameLossTimer = 0;
gasFlameStable = false;
if (millis() - gasRetryTimer >= GAS_PURGE_DURATION_MS) {
gasWaitingRetry = false;
flameReachedStableOnce = false;
}
} else {
digitalWrite(PIN_RELAY_GAS, RELAY_ON);
if (gasIgnitionTimer == 0) gasIgnitionTimer = millis();
unsigned long elapsedIg = millis() - gasIgnitionTimer;
if (GAS_PRUEBA_IGNORA_LLAMA) {
if (elapsedIg >= GAS_FLAME_STABLE_MS) {
gasFlameStable = true;
flameReachedStableOnce = true;
}
flameLossTimer = 0;
} else {
bool flameNow = readFlameOK();
if (!flameNow) {
gasFlameStable = false;
if (elapsedIg >= GAS_IGNITION_TIMEOUT_MS || flameReachedStableOnce) {
if (flameLossTimer == 0) flameLossTimer = millis();
if (millis() - flameLossTimer >= FLAME_LOSS_CONFIRM_MS) {
digitalWrite(PIN_RELAY_GAS, RELAY_OFF);
gasRetryCount++;
flameLossTimer = 0;
if (gasRetryCount >= GAS_MAX_RETRIES) {
gasFault = true;
flameReachedStableOnce = false;
Serial.println(F("Fallo GAS"));
}
else { gasWaitingRetry = true; gasRetryTimer = millis(); gasIgnitionTimer = 0; }
}
} else {
flameLossTimer = 0;
}
} else {
flameLossTimer = 0;
if (elapsedIg >= GAS_FLAME_STABLE_MS) {
gasRetryCount = 0;
gasFlameStable = true;
flameReachedStableOnce = true;
}
}
}
}
break;
default:
turnOffAllRelays();
break;
}
} else {
turnOffAllRelays();
gasIgnitionTimer = 0;
flameLossTimer = 0;
}
}
void controlExteriorFans() {
if (fanManualMode) {
fanActive = fanManualState;
if (!fanManualState && rearTemp <= FAN_REAR_OFF_TEMP) {
fanManualMode = false;
}
} else {
if (rearTemp >= FAN_REAR_ON_TEMP) fanActive = true;
if (rearTemp <= FAN_REAR_OFF_TEMP) fanActive = false;
}
digitalWrite(PIN_RELAY_FAN, fanActive ? RELAY_ON : RELAY_OFF);
}
void controlCondResistance() {
if (condActive) {
if (condOnTimer > 0 && (millis() - condOnTimer >= COND_AUTO_OFF_MS)) {
condActive = false;
condOnTimer = 0;
Serial.println(F("[COND] Auto-OFF: 2 horas cumplidas de seguridad."));
}
}
digitalWrite(PIN_RELAY_COND, condActive ? RELAY_ON : RELAY_OFF);
}
void handleAudioAlarms() {
static unsigned long buzzerTimer = 0;
static bool buzzerState = false;
bool has220V = readRaw220VPresent();
bool has12V = readRaw12VPresent();
if (has220V && has12V) {
if (millis() - buzzerTimer >= 100) { buzzerState = !buzzerState; digitalWrite(PIN_BUZZER, buzzerState); buzzerTimer = millis(); }
return;
}
bool noFlameImmediate = isGasOpenNoFlame();
bool flameLost = isFlameLostAfterStable();
if (flameLost) {
if (millis() - buzzerTimer >= 300) { buzzerState = !buzzerState; digitalWrite(PIN_BUZZER, buzzerState); buzzerTimer = millis(); }
} else if (gasFault) {
if (millis() - buzzerTimer >= 500) { buzzerState = !buzzerState; digitalWrite(PIN_BUZZER, buzzerState); buzzerTimer = millis(); }
} else if (noFlameImmediate) {
if (millis() - buzzerTimer >= 400) { buzzerState = !buzzerState; digitalWrite(PIN_BUZZER, buzzerState); buzzerTimer = millis(); }
} else {
digitalWrite(PIN_BUZZER, LOW);
}
}
void updateIndicators() {
digitalWrite(PIN_LED_STATUS, HIGH);
}
void turnOffAllRelays() {
digitalWrite(PIN_RELAY_220V, RELAY_OFF);
digitalWrite(PIN_RELAY_12V, RELAY_OFF);
digitalWrite(PIN_RELAY_GAS, RELAY_OFF);
digitalWrite(PIN_RELAY_FAN, RELAY_OFF);
}
// ==========================================
// COMUNICACION CON ESP32 (Serial1)
// ==========================================
void sendStatusToESP32() {
bool has220V = readRaw220VPresent();
bool has12V = readRaw12VPresent();
bool flameOK = GAS_PRUEBA_IGNORA_LLAMA ? true : readFlameOK();
bool fridgeRunning = isFridgeRunningNow();
unsigned long gasBlockTimeLeftSeconds = getGasBlockRemainingSeconds();
unsigned long condTimeLeftSeconds = 0;
if (condActive && condOnTimer > 0) {
unsigned long elapsed = millis() - condOnTimer;
unsigned long condTimeLeftMs = (elapsed < COND_AUTO_OFF_MS) ? (COND_AUTO_OFF_MS - elapsed) : 0;
condTimeLeftSeconds = condTimeLeftMs / 1000UL;
}
const char* modeStr = "NONE";
if (currentMode == MODE_220V) modeStr = "220V";
else if (currentMode == MODE_12V) modeStr = "12V";
else if (currentMode == MODE_GAS) modeStr = "GAS";
else if (currentMode == MODE_TRANSITION) modeStr = "TRANS";
Serial1.print(F("STAT,"));
Serial1.print(modeStr); Serial1.print(',');
Serial1.print(fridgeTemp, 1); Serial1.print(',');
Serial1.print(targetTemp, 1); Serial1.print(',');
Serial1.print(freezerTemp, 1); Serial1.print(',');
Serial1.print(rearTemp, 1); Serial1.print(',');
Serial1.print(gasFault ? 1 : 0); Serial1.print(',');
Serial1.print(gasBlocked ? 1 : 0); Serial1.print(',');
Serial1.print(gasBlockTimeLeftSeconds); Serial1.print(',');
Serial1.print(gasRetryCount); Serial1.print(',');
Serial1.print(fanActive ? 1 : 0); Serial1.print(',');
Serial1.print(fanManualMode ? 1 : 0); Serial1.print(',');
Serial1.print(condActive ? 1 : 0); Serial1.print(',');
Serial1.print(condTimeLeftSeconds); Serial1.print(',');
Serial1.print(has220V ? 1 : 0); Serial1.print(',');
Serial1.print(has12V ? 1 : 0); Serial1.print(',');
Serial1.print(flameOK ? 1 : 0); Serial1.print(',');
Serial1.print(fridgeRunning ? 1 : 0); Serial1.print(',');
Serial1.print(fridgeRunTotalSeconds); Serial1.print(',');
Serial1.println(fridgeRunPartialSeconds);
esp32LastStatusTxMs = millis();
esp32StatusPacketsSent++;
}
void receiveFromESP32() {
while (Serial1.available()) {
char c = (char)Serial1.read();
if (c == '\n') {
esp32RxBuffer.trim();
handleESP32Command(esp32RxBuffer);
esp32RxBuffer = "";
} else if (c != '\r') {
if (esp32RxBuffer.length() < 32) esp32RxBuffer += c;
}
}
}
Y Este es el de la ESP32
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33
#define Tft_Backlight 21
#define MODO_DEMO 0
const char* FW_ESP32_VERSION = "SERIE-U0";
#define MEGA_SERIAL Serial
SPIClass mySPI(VSPI);
XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);
TFT_eSPI tft = TFT_eSPI();
#define COLOR_FONDO TFT_BLACK
#define COLOR_PANEL 0x18C3
#define COLOR_PANEL_INNER 0x0861
#define COLOR_PANEL_HEADER 0x2126
#define COLOR_BOTON_OPCIONES 0x4210
#define COLOR_BOTON_OPCIONES_TEXTO 0x07FF
#define COLOR_BORDE_LUZ TFT_WHITE
#define COLOR_BORDE_SOMBRA 0x39C7
#define COLOR_SOMBRA 0x0000
#define COLOR_TEXTO_SECUNDARIO TFT_LIGHTGREY
int modoActivo = 0;
float tempInterior = 0.0;
float tempObjetivo = 4.0;
float tempCongelador = 0.0;
float tempTrasera = 0.0;
bool gasFault = false;
bool gasBlocked = false;
unsigned long gasBlockTimeLeft = 0;
int gasRetryCount = 0;
bool fanActive = false;
bool fanManualMode = false;
bool condActive = false;
unsigned long condTimeLeft = 0;
bool has220V = false;
bool has12V = false;
bool flameOK = false;
bool fridgeRunning = false;
unsigned long fridgeRunTotalSeconds = 0;
unsigned long fridgeRunPartialSeconds = 0;
int estadoPantalla = 0; // 0 = Principal, 1 = Menú Opciones
unsigned long ultimaInteraccion = 0;
const unsigned long TIEMPO_APAGADO = 60000;
bool pantallaApagada = false;
unsigned long ultimaDemoActualizacion = 0;
unsigned long ultimoTickDemo = 0;
int demoEscena = 0;
bool avisoCable220VActivo = false;
unsigned long ultimoPaqueteMegaMs = 0;
bool comunicacionMegaActiva = false;
const unsigned long TIMEOUT_COM_MEGA_MS = 2500;
unsigned long megaStatPacketsRx = 0;
unsigned long megaRawBytesRx = 0;
char megaLastByteRx = '-';
String bufferSerial = "";
unsigned long megaParseErrors = 0;
unsigned long megaLinesRx = 0;
int megaLastLineLen = 0;
void disenarInterfazEstatica();
void disenarMenuOpciones();
void actualizarDatosPantalla();
void dibujarBarraYTriangulo(float temp);
void dibujarIconosFuentes();
void dibujarAspasVentilador(int x, int y, uint16_t color);
void encenderPantalla();
void apagarPantalla();
void dibujarPanelRelieve(int x, int y, int w, int h, const char* titulo, uint16_t colorTitulo);
void dibujarBotonRelieve(int x, int y, int w, int h, uint16_t fondo, uint16_t texto, const char* etiqueta, int tx, int ty, int textSize);
void aplicarEstadoDemo();
void actualizarModoDemo();
void dibujarAvisoCable220V();
void actualizarEstadoComunicacionMega();
void procesarSerialMega();
void enviarComandoMega(const String& comando);
bool procesarLineaMega(const String& linea);
void setup() {
pinMode(Tft_Backlight, OUTPUT);
digitalWrite(Tft_Backlight, HIGH);
tft.init();
tft.setRotation(1);
tft.invertDisplay(false);
tft.fillScreen(COLOR_FONDO);
mySPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
ts.begin(mySPI);
ts.setRotation(1);
#if MODO_DEMO
aplicarEstadoDemo();
#endif
MEGA_SERIAL.begin(9600);
disenarInterfazEstatica();
actualizarDatosPantalla();
ultimaInteraccion = millis();
}
void loop() {
// Procesar datos entrantes desde la Mega
#if MODO_DEMO
actualizarModoDemo();
#else
procesarSerialMega();
actualizarEstadoComunicacionMega();
#endif
// Lectura de panel táctil
if (ts.touched()) {
TS_Point p = ts.getPoint();
if (pantallaApagada) {
encenderPantalla();
ultimaInteraccion = millis();
delay(300);
return;
}
ultimaInteraccion = millis();
int x = map(p.x, 200, 3800, 0, 320);
int y = map(p.y, 200, 3800, 0, 240);
if (estadoPantalla == 0) {
if (x > 168 && x < 213 && y > 126 && y < 171) {
if (tempObjetivo > -14.0) {
tempObjetivo -= 0.5;
#if !MODO_DEMO
enviarComandoMega(String("TEMP,") + String(tempObjetivo, 1));
#endif
actualizarDatosPantalla();
}
tft.drawRoundRect(168, 126, 45, 45, 4, TFT_WHITE);
delay(150);
tft.drawRoundRect(168, 126, 45, 45, 4, TFT_DARKGREY);
}
if (x > 262 && x < 307 && y > 126 && y < 171) {
if (tempObjetivo < 20.0) {
tempObjetivo += 0.5;
#if !MODO_DEMO
enviarComandoMega(String("TEMP,") + String(tempObjetivo, 1));
#endif
actualizarDatosPantalla();
}
tft.drawRoundRect(262, 126, 45, 45, 4, TFT_WHITE);
delay(150);
tft.drawRoundRect(262, 126, 45, 45, 4, TFT_DARKGREY);
}
if (x > 162 && x < 310 && y > 186 && y < 234) {
estadoPantalla = 1;
tft.fillScreen(COLOR_FONDO);
disenarMenuOpciones();
delay(300);
}
} else if (estadoPantalla == 1) {
// Volver
if (x > 20 && x < 300 && y > 190 && y < 230) {
estadoPantalla = 0;
tft.fillScreen(COLOR_FONDO);
disenarInterfazEstatica();
actualizarDatosPantalla();
delay(300);
}
// Reset Arduino Mega
if (x > 220 && x < 305 && y > 43 && y < 75) {
tft.fillScreen(COLOR_FONDO);
tft.setTextSize(2);
tft.setTextColor(TFT_RED, COLOR_FONDO);
#if MODO_DEMO
tft.drawString("MODO DEMO", 80, 85);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, COLOR_FONDO);
tft.drawString("Reset Mega desactivado", 72, 120);
#else
tft.drawString("REINICIANDO MEGA...", 30, 100);
enviarComandoMega("RESET_MEGA");
#endif
delay(600);
disenarMenuOpciones();
}
// Reset Total
if (x > 255 && x < 295 && y > 80 && y < 106) {
fridgeRunTotalSeconds = 0;
fridgeRunPartialSeconds = 0;
#if !MODO_DEMO
enviarComandoMega("TOTAL_RESET");
#endif
disenarMenuOpciones();
delay(200);
}
// Reset Parcial
if (x > 255 && x < 295 && y > 115 && y < 141) {
fridgeRunPartialSeconds = 0;
#if !MODO_DEMO
enviarComandoMega("PARTIAL_RESET");
#endif
disenarMenuOpciones();
delay(200);
}
// Toggle Resistencia IN5
if (x > 200 && x < 295 && y > 146 && y < 178) {
bool newCondState = !condActive;
condActive = newCondState;
#if !MODO_DEMO
enviarComandoMega(String("COND,") + String(newCondState ? 1 : 0));
#endif
disenarMenuOpciones();
delay(200);
}
}
}
if (!pantallaApagada && (millis() - ultimaInteraccion >= TIEMPO_APAGADO)) {
apagarPantalla();
}
}
void aplicarEstadoDemo() {
switch (demoEscena) {
case 0:
modoActivo = 0;
has220V = false;
has12V = false;
gasFault = false;
gasBlocked = false;
flameOK = false;
fanActive = false;
fridgeRunning = false;
tempInterior = 7.5;
tempCongelador = -10.0;
tempTrasera = 19.0;
break;
case 1:
modoActivo = 1;
has220V = true;
has12V = false;
gasFault = false;
gasBlocked = false;
flameOK = false;
fanActive = false;
fridgeRunning = true;
tempInterior = 5.0;
tempCongelador = -14.5;
tempTrasera = 28.0;
break;
case 2:
modoActivo = 2;
has220V = false;
has12V = true;
gasFault = false;
gasBlocked = false;
flameOK = false;
fanActive = true;
fridgeRunning = true;
tempInterior = 4.2;
tempCongelador = -16.0;
tempTrasera = 32.0;
break;
case 3:
modoActivo = 3;
has220V = false;
has12V = false;
gasFault = false;
gasBlocked = false;
flameOK = true;
fanActive = true;
fridgeRunning = true;
tempInterior = 3.0;
tempCongelador = -18.0;
tempTrasera = 34.0;
break;
default:
modoActivo = 3;
has220V = false;
has12V = false;
gasFault = true;
gasBlocked = true;
flameOK = false;
fanActive = false;
fridgeRunning = false;
tempInterior = 8.8;
tempCongelador = -8.0;
tempTrasera = 24.0;
break;
}
}
void actualizarModoDemo() {
unsigned long ahora = millis();
if (ahora - ultimoTickDemo >= 1000) {
ultimoTickDemo = ahora;
fridgeRunTotalSeconds++;
if (fridgeRunning) fridgeRunPartialSeconds++;
}
if (ahora - ultimaDemoActualizacion >= 4000) {
ultimaDemoActualizacion = ahora;
demoEscena = (demoEscena + 1) % 5;
aplicarEstadoDemo();
if (!pantallaApagada) {
if (estadoPantalla == 0) actualizarDatosPantalla();
else disenarMenuOpciones();
}
}
}
bool procesarLineaMega(const String& linea) {
if (!linea.startsWith("STAT,")) return false;
String campos[19];
int inicio = 5;
int indiceCampo = 0;
while (indiceCampo < 19) {
int coma = linea.indexOf(',', inicio);
if (coma == -1) {
campos[indiceCampo++] = linea.substring(inicio);
break;
}
campos[indiceCampo++] = linea.substring(inicio, coma);
inicio = coma + 1;
}
if (indiceCampo != 18 && indiceCampo != 19) return false;
int idxGasRetry = 7;
int idxFanActive = 8;
int idxFanManual = 9;
int idxCondActive = 10;
int idxCondTimeLeft = 11;
int idxHas220 = 12;
int idxHas12 = 13;
int idxFlameOK = 14;
int idxFridgeRunning = 15;
int idxTotalRuntime = 16;
int idxPartialRuntime = 17;
String modo = campos[0];
if (modo == "220V") modoActivo = 1;
else if (modo == "12V") modoActivo = 2;
else if (modo == "GAS") modoActivo = 3;
else modoActivo = 0;
tempInterior = campos[1].toFloat();
tempObjetivo = campos[2].toFloat();
tempCongelador = campos[3].toFloat();
tempTrasera = campos[4].toFloat();
gasFault = campos[5].toInt() != 0;
gasBlocked = campos[6].toInt() != 0;
if (indiceCampo == 19) {
gasBlockTimeLeft = campos[7].toInt() * 1000UL;
idxGasRetry = 8;
idxFanActive = 9;
idxFanManual = 10;
idxCondActive = 11;
idxCondTimeLeft = 12;
idxHas220 = 13;
idxHas12 = 14;
idxFlameOK = 15;
idxFridgeRunning = 16;
idxTotalRuntime = 17;
idxPartialRuntime = 18;
} else {
gasBlockTimeLeft = 0;
}
gasRetryCount = campos[idxGasRetry].toInt();
fanActive = campos[idxFanActive].toInt() != 0;
fanManualMode = campos[idxFanManual].toInt() != 0;
condActive = campos[idxCondActive].toInt() != 0;
condTimeLeft = campos[idxCondTimeLeft].toInt() * 1000UL;
has220V = campos[idxHas220].toInt() != 0;
has12V = campos[idxHas12].toInt() != 0;
flameOK = campos[idxFlameOK].toInt() != 0;
fridgeRunning = campos[idxFridgeRunning].toInt() != 0;
fridgeRunTotalSeconds = campos[idxTotalRuntime].toInt();
fridgeRunPartialSeconds = campos[idxPartialRuntime].toInt();
ultimoPaqueteMegaMs = millis();
megaStatPacketsRx++;
megaLastByteRx = 'S';
return true;
}
void procesarSerialMega() {
while (MEGA_SERIAL.available() > 0) {
char c = (char)MEGA_SERIAL.read();
megaRawBytesRx++;
megaLastByteRx = c;
if (c == '\n') {
megaLastLineLen = bufferSerial.length();
megaLinesRx++;
bufferSerial.trim();
bool estadoValido = procesarLineaMega(bufferSerial);
if (!estadoValido && bufferSerial.length() > 0) {
megaParseErrors++;
}
bufferSerial = "";
if (estadoValido && !pantallaApagada) {
if (estadoPantalla == 0) actualizarDatosPantalla();
else disenarMenuOpciones();
}
} else if (c != '\r') {
if (bufferSerial.length() < 180) bufferSerial += c;
}
}
}
void enviarComandoMega(const String& comando) {
String comandoNormalizado = comando;
comandoNormalizado.trim();
if (comandoNormalizado.length() == 0) return;
MEGA_SERIAL.println(comandoNormalizado);
}
void apagarPantalla() {
digitalWrite(Tft_Backlight, LOW);
pantallaApagada = true;
}
void encenderPantalla() {
digitalWrite(Tft_Backlight, HIGH);
pantallaApagada = false;
if (estadoPantalla == 0) {
tft.fillScreen(COLOR_FONDO);
disenarInterfazEstatica();
actualizarDatosPantalla();
} else {
disenarMenuOpciones();
}
}
void dibujarPanelRelieve(int x, int y, int w, int h, const char* titulo, uint16_t colorTitulo) {
tft.fillRoundRect(x + 2, y + 2, w, h, 6, COLOR_SOMBRA);
tft.fillRoundRect(x, y, w, h, 6, COLOR_PANEL);
tft.drawRoundRect(x, y, w, h, 6, COLOR_BORDE_LUZ);
tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 6, COLOR_BORDE_SOMBRA);
tft.fillRoundRect(x + 4, y + 4, w - 8, h - 8, 5, COLOR_PANEL_INNER);
if (titulo != nullptr && titulo[0] != '\0') {
tft.fillRoundRect(x + 4, y + 4, w - 8, 17, 5, colorTitulo);
tft.fillRect(x + 4, y + 13, w - 8, 8, colorTitulo);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, colorTitulo);
tft.drawString(titulo, x + 8, y + 8);
}
}
void dibujarBotonRelieve(int x, int y, int w, int h, uint16_t fondo, uint16_t texto, const char* etiqueta, int tx, int ty, int textSize) {
tft.fillRoundRect(x + 2, y + 2, w, h, 5, COLOR_SOMBRA);
tft.fillRoundRect(x, y, w, h, 5, fondo);
tft.drawRoundRect(x, y, w, h, 5, COLOR_BORDE_LUZ);
tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 5, COLOR_BORDE_SOMBRA);
tft.drawFastHLine(x + 5, y + 4, w - 10, TFT_WHITE);
tft.drawFastVLine(x + 4, y + 5, h - 10, TFT_WHITE);
tft.setTextSize(textSize);
tft.setTextColor(texto, fondo);
tft.drawString(etiqueta, tx, ty);
}
void dibujarAvisoCable220V() {
const int x = 34;
const int y = 86;
const int w = 252;
const int h = 70;
const uint16_t fondoAviso = 0x1000;
const uint16_t sombraTexto = 0x3000;
tft.fillRoundRect(x, y, w, h, 8, fondoAviso);
tft.drawRoundRect(x, y, w, h, 8, TFT_RED);
tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 8, TFT_WHITE);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(2);
tft.setTextColor(sombraTexto, fondoAviso);
tft.drawString("CABLE DE 220V", 161, 109);
tft.drawString("CONECTADO", 161, 133);
tft.setTextColor(TFT_RED, fondoAviso);
tft.drawString("CABLE DE 220V", 160, 108);
tft.drawString("CONECTADO", 160, 132);
tft.setTextDatum(TL_DATUM);
}
void actualizarEstadoComunicacionMega() {
bool enlaceActual = (ultimoPaqueteMegaMs != 0) && ((millis() - ultimoPaqueteMegaMs) <= TIMEOUT_COM_MEGA_MS);
if (enlaceActual == comunicacionMegaActiva) return;
comunicacionMegaActiva = enlaceActual;
if (!pantallaApagada) {
tft.fillScreen(COLOR_FONDO);
if (estadoPantalla == 0) {
disenarInterfazEstatica();
actualizarDatosPantalla();
} else {
disenarMenuOpciones();
}
}
}
void actualizarDatosPantalla() {
if (pantallaApagada || estadoPantalla != 0) return;
bool cable220VConectado = has220V && has12V;
if (avisoCable220VActivo && !cable220VConectado) {
disenarInterfazEstatica();
}
uint16_t gasColor = COLOR_PANEL_HEADER;
String gasTexto = "GAS INACT.";
String gasTiempo = "";
tft.setTextSize(1);
if (modoActivo == 3) {
gasColor = gasFault ? TFT_RED : TFT_ORANGE;
gasTexto = gasFault ? "GAS BLOQ." : "GAS ACTIVO";
} else if (gasBlocked) {
gasColor = TFT_RED;
gasTexto = "GAS BLOQ.";
if (gasBlockTimeLeft > 0) {
unsigned long totalSeconds = gasBlockTimeLeft / 1000UL;
unsigned long minRest = totalSeconds / 60UL;
unsigned long secRest = totalSeconds % 60UL;
char tiempoBuffer[8];
snprintf(tiempoBuffer, sizeof(tiempoBuffer), "%02lu:%02lu", minRest, secRest);
gasTiempo = tiempoBuffer;
}
}
tft.fillRoundRect(14, 9, 87, 30, 5, gasColor);
uint16_t gasTextColor = ((modoActivo == 3) && !gasFault) ? TFT_RED : TFT_WHITE;
tft.setTextColor(gasTextColor, gasColor);
if (gasTiempo.length() > 0) {
tft.drawString(gasTexto, 28, 13);
tft.drawString(gasTiempo, 40, 25);
} else {
tft.drawString(gasTexto, 34, 20);
}
uint16_t c220 = has220V ? 0x07FF : COLOR_PANEL_HEADER;
tft.fillRoundRect(116, 9, 87, 30, 5, c220);
if (has220V) {
tft.setTextColor(TFT_BLACK, c220);
tft.drawString("220V ACT.", 136, 20);
} else {
tft.setTextColor(TFT_LIGHTGREY, c220);
tft.drawString("220V INAC.", 134, 20);
}
uint16_t c12 = has12V ? 0xF3BF : COLOR_PANEL_HEADER;
tft.fillRoundRect(219, 9, 87, 30, 5, c12);
if (has12V) {
tft.setTextColor(TFT_BLACK, c12);
tft.drawString("12V ACTIVO", 240, 20);
} else {
tft.setTextColor(TFT_LIGHTGREY, c12);
tft.drawString("12V INACT.", 238, 20);
}
// Los iconos van al final para que no queden tapados por los paneles superiores.
dibujarIconosFuentes();
tft.fillRect(18, 72, 130, 42, COLOR_PANEL_INNER);
uint16_t colorTempInt = (tempInterior > 8.0) ? TFT_RED : TFT_BLUE;
tft.setTextColor(colorTempInt, COLOR_FONDO);
tft.setTextSize(4);
tft.drawString(String(tempInterior, 1), 32, 74);
tft.setTextSize(2);
tft.setTextColor(TFT_SKYBLUE, COLOR_FONDO);
tft.drawString("C", 124, 74);
dibujarBarraYTriangulo(tempObjetivo);
tft.fillRoundRect(217, 126, 41, 45, 4, COLOR_PANEL_HEADER);
tft.drawRoundRect(217, 126, 41, 45, 4, COLOR_BORDE_LUZ);
tft.drawRoundRect(218, 127, 39, 43, 4, COLOR_BORDE_SOMBRA);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, COLOR_PANEL_HEADER);
tft.drawString("SET", 228, 132);
tft.setTextColor(TFT_YELLOW, COLOR_PANEL_HEADER);
tft.drawString(String(tempObjetivo, 1), 222, 150);
tft.fillRect(34, 154, 94, 22, COLOR_PANEL_INNER);
tft.setTextSize(2);
tft.setTextColor(TFT_SKYBLUE, COLOR_FONDO);
tft.drawString(String(tempCongelador, 1) + " C", 36, 154);
uint16_t colorVent = fanActive ? 0x07FF : COLOR_PANEL_HEADER;
uint16_t textoVent = fanActive ? TFT_BLACK : TFT_LIGHTGREY;
tft.fillRoundRect(16, 204, 132, 24, 5, colorVent);
dibujarAspasVentilador(32, 216, fanActive ? TFT_BLACK : TFT_RED);
tft.setTextSize(2);
tft.setTextColor(textoVent, colorVent);
tft.drawString(fanActive ? "ON" : "OFF", 52, 208);
if (cable220VConectado) {
dibujarAvisoCable220V();
}
avisoCable220VActivo = cable220VConectado;
}
void dibujarIconosFuentes() {
tft.setTextColor(TFT_WHITE, COLOR_FONDO);
tft.fillRect(14, 11, 18, 16, COLOR_FONDO);
uint16_t colorLlamaExterior = (modoActivo == 3) ? TFT_RED : TFT_DARKGREY;
uint16_t colorLlamaInterior = (modoActivo == 3) ? TFT_YELLOW : TFT_LIGHTGREY;
tft.drawFastVLine(16, 21, 4, colorLlamaExterior);
tft.drawFastVLine(17, 18, 8, colorLlamaExterior);
tft.drawFastVLine(18, 16, 10, colorLlamaExterior);
tft.drawFastVLine(19, 15, 11, colorLlamaExterior);
tft.drawFastVLine(20, 17, 9, colorLlamaExterior);
tft.drawFastVLine(21, 14, 12, colorLlamaExterior);
tft.drawFastVLine(22, 13, 13, colorLlamaExterior);
tft.drawFastVLine(23, 15, 11, colorLlamaExterior);
tft.drawFastVLine(24, 16, 10, colorLlamaExterior);
tft.drawFastVLine(25, 17, 9, colorLlamaExterior);
tft.drawFastVLine(26, 19, 7, colorLlamaExterior);
tft.drawFastVLine(27, 22, 3, colorLlamaExterior);
tft.drawFastVLine(20, 22, 3, colorLlamaInterior);
tft.drawFastVLine(21, 20, 5, colorLlamaInterior);
tft.drawFastVLine(22, 19, 6, colorLlamaInterior);
tft.drawFastVLine(23, 21, 4, colorLlamaInterior);
tft.fillRect(118, 11, 16, 16, COLOR_FONDO);
if (has220V) {
uint16_t colorEnchufe = 0x07FF;
tft.drawRoundRect(119, 17, 10, 8, 2, colorEnchufe);
tft.fillRect(121, 19, 6, 4, colorEnchufe);
tft.drawFastVLine(121, 13, 4, colorEnchufe);
tft.drawFastVLine(126, 13, 4, colorEnchufe);
tft.drawFastVLine(124, 25, 3, colorEnchufe);
tft.drawFastHLine(123, 28, 3, colorEnchufe);
}
tft.fillRect(220, 12, 18, 16, COLOR_FONDO);
if (has12V) {
uint16_t colorBateria = TFT_WHITE;
tft.drawRoundRect(221, 15, 14, 10, 2, colorBateria);
tft.fillRect(235, 18, 2, 4, colorBateria);
tft.drawFastHLine(224, 20, 2, colorBateria);
tft.drawFastHLine(228, 20, 3, colorBateria);
tft.drawFastVLine(229, 19, 3, colorBateria);
}
}
void dibujarAspasVentilador(int x, int y, uint16_t color) {
tft.fillCircle(x, y, 8, COLOR_FONDO);
tft.drawLine(x - 7, y - 7, x + 7, y + 7, color);
tft.drawLine(x - 7, y + 7, x + 7, y - 7, color);
tft.drawLine(x - 7, y, x + 7, y, color);
tft.drawLine(x, y - 7, x, y + 7, color);
tft.fillCircle(x, y, 3, color);
}
void disenarInterfazEstatica() {
tft.fillScreen(COLOR_FONDO);
dibujarPanelRelieve(10, 5, 95, 38, "", COLOR_PANEL_HEADER);
dibujarPanelRelieve(112, 5, 95, 38, "", COLOR_PANEL_HEADER);
dibujarPanelRelieve(215, 5, 95, 38, "", COLOR_PANEL_HEADER);
dibujarPanelRelieve(10, 48, 145, 132, "FRIGO INTERIOR", 0x03EF);
dibujarPanelRelieve(162, 48, 148, 132, "", 0x2989);
dibujarPanelRelieve(10, 186, 145, 48, "VENTILADOR", 0x03EF);
dibujarPanelRelieve(162, 186, 148, 48, "", COLOR_PANEL_HEADER);
tft.setTextSize(1);
tft.setTextColor(COLOR_TEXTO_SECUNDARIO, COLOR_FONDO);
tft.drawString("ACTUAL:", 20, 118);
tft.drawString("CONGELADOR", 36, 138);
tft.setTextSize(2);
tft.setTextColor(TFT_SKYBLUE, COLOR_FONDO);
tft.drawString("-18.0 C", 36, 154);
tft.setTextSize(1);
tft.setTextColor(COLOR_TEXTO_SECUNDARIO, COLOR_FONDO);
tft.drawString("-14", 170, 94);
tft.drawString("+20", 278, 94);
dibujarBotonRelieve(168, 126, 45, 45, TFT_YELLOW, TFT_BLACK, "-", 186, 140, 2);
dibujarBotonRelieve(262, 126, 45, 45, TFT_CYAN, TFT_BLACK, "+", 278, 140, 2);
dibujarBotonRelieve(162, 186, 148, 48, COLOR_BOTON_OPCIONES, COLOR_BOTON_OPCIONES_TEXTO, "OPCIONES", 184, 200, 2);
}
void disenarMenuOpciones() {
tft.fillScreen(COLOR_FONDO);
tft.setTextSize(2);
tft.setTextColor(TFT_WHITE, COLOR_FONDO);
tft.drawString("MENU DE OPCIONES", 60, 10);
dibujarPanelRelieve(10, 38, 300, 148, "CONFIGURACION", 0x2989);
// 1. Reset Placa
tft.setTextSize(1);
tft.setTextColor(COLOR_TEXTO_SECUNDARIO, COLOR_FONDO);
tft.drawString("RESETO GENERAL DE PLACA", 18, 46);
dibujarBotonRelieve(220, 43, 84, 32, TFT_RED, TFT_WHITE, "RESET", 231, 49, 2);
// 2. Tiempo Total
unsigned long hTotal = fridgeRunTotalSeconds / 3600;
unsigned long mTotal = (fridgeRunTotalSeconds % 3600) / 60;
tft.setTextSize(1);
tft.setTextColor(COLOR_TEXTO_SECUNDARIO, COLOR_FONDO);
tft.drawString("TIEMPO TOTAL:", 18, 80);
tft.setTextSize(2);
tft.setTextColor(TFT_CYAN, COLOR_FONDO);
tft.drawString(String(hTotal) + "h " + String(mTotal) + "m", 18, 93);
dibujarBotonRelieve(255, 80, 40, 26, TFT_RED, TFT_WHITE, "R", 269, 83, 2);
// 3. Tiempo Parcial
unsigned long hParcial = fridgeRunPartialSeconds / 3600;
unsigned long mParcial = (fridgeRunPartialSeconds % 3600) / 60;
tft.setTextSize(1);
tft.setTextColor(COLOR_TEXTO_SECUNDARIO, COLOR_FONDO);
tft.drawString("TIEMPO PARCIAL:", 18, 115);
tft.setTextSize(2);
tft.setTextColor(TFT_YELLOW, COLOR_FONDO);
tft.drawString(String(hParcial) + "h " + String(mParcial) + "m", 18, 128);
dibujarBotonRelieve(255, 115, 40, 26, TFT_YELLOW, TFT_BLACK, "R", 269, 118, 2);
// 4. Resistencia Condensación (IN5)
tft.setTextSize(1);
tft.setTextColor(COLOR_TEXTO_SECUNDARIO, COLOR_FONDO);
tft.drawString("RES. CONDENSACION (IN5):", 18, 150);
unsigned long minRestantes = condTimeLeft / 60000;
uint16_t colorBotonIN5 = condActive ? TFT_GREEN : TFT_DARKGREY;
uint16_t colorTextoIN5 = condActive ? TFT_BLACK : TFT_WHITE;
tft.fillRoundRect(200, 146, 95, 32, 5, colorBotonIN5);
tft.setTextSize(1);
tft.setTextColor(colorTextoIN5, colorBotonIN5);
if (condActive) {
tft.drawString("ENCENDIDO", 212, 152);
tft.drawString("Rest: " + String(minRestantes) + "m", 215, 164);
} else {
tft.drawString("APAGADO", 222, 158);
}
// Volver
dibujarBotonRelieve(20, 192, 280, 32, COLOR_PANEL_HEADER, TFT_WHITE, "< VOLVER", 100, 198, 2);
}
void dibujarBarraYTriangulo(float temp) {
if (estadoPantalla != 0) return;
tft.fillRect(166, 54, 140, 38, COLOR_PANEL_INNER);
for(int i = 0; i < 120; i++){
uint16_t colorBarra = tft.color565(i * 2, 100, 255 - (i * 2));
tft.drawFastVLine(176 + i, 80, 8, colorBarra);
}
if (temp < -14.0) temp = -14.0;
if (temp > 20.0) temp = 20.0;
int xTriangulo = map(temp * 10, -140, 200, 176, 296);
tft.drawTriangle(xTriangulo - 4, 76, xTriangulo + 4, 76, xTriangulo, 79, TFT_WHITE);
tft.fillTriangle(xTriangulo - 4, 76, xTriangulo + 4, 76, xTriangulo, 79, TFT_WHITE);
tft.setTextSize(1);
tft.setTextColor(TFT_YELLOW, COLOR_FONDO);
String textoTemp = String(temp, 1);
int xTexto = xTriangulo - 10;
if (xTexto < 215) xTexto = 215;
if (xTexto > 240) xTexto = 240;
tft.drawString(textoTemp, xTexto, 62);
}