diff --git a/diagrama_conexion_ds18b20.png b/diagrama_conexion_ds18b20.png new file mode 100644 index 0000000..f1ad8d4 Binary files /dev/null and b/diagrama_conexion_ds18b20.png differ diff --git a/sketch/obd2_gauge/meter.h b/sketch/obd2_gauge/meter.h index f435c31..578e32c 100644 --- a/sketch/obd2_gauge/meter.h +++ b/sketch/obd2_gauge/meter.h @@ -1219,7 +1219,7 @@ void plotNoData(uint8_t cell) { tft.setTextPadding(0); tft.unloadFont(); } - } else { + } else if (currentScreenPage == 0) { int x = cell4_x[cell]; int y = cell4_y[cell]; clearPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]); @@ -1257,6 +1257,8 @@ void initScreen() { } if (fullScreenCell >= 0 && fullScreenCell < 4) { fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell]); + } else if (currentScreenPage == 1) { + drawTriTempScreen(0); } else { tft.fillScreen(TFT_BLACK); // clear screen for (uint8_t c = 0; c < 4; c++) { @@ -1264,9 +1266,14 @@ void initScreen() { arcMeter4(c, pid); } drawQuadrantDividers(); + // Pagination indicator dots [● ○] + tft.fillCircle(153, 2, 2, getThemePrimaryColor()); // Page 1 active + tft.fillCircle(167, 2, 2, 0x5AEB); // Page 2 inactive + } + if (currentScreenPage == 0 && fullScreenCell == -1) { + badgeEcoDrawn = false; + updateEcoBadge(true); } - badgeEcoDrawn = false; - updateEcoBadge(true); } /*-------------------------------*/ @@ -1486,6 +1493,10 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen bt_message = ""; return; } + tempEngineCoolant = data; + if (currentScreenPage == 1 && fullScreenCell == -1) { + updateTriTempCoolant(data); + } break; // temp °C (ECT) case 2: data = A * 100.0 / 255; @@ -1542,7 +1553,7 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen if (pidNo == fullScreenCell) { plotFullScreen(fullScreenCell, data, warn, min, max, digit, unit); } - } else { + } else if (currentScreenPage == 0) { plotArc4(pidNo, data, warn, min, max, digit, unit); } bt_message = ""; // reset @@ -1833,7 +1844,7 @@ void handleArcAlarmFlash() { } } } - } else { + } else if (currentScreenPage == 0) { // 4 quadrants mode for (uint8_t cell = 0; cell < 4; cell++) { if (last_valid_data[cell] != -999) { @@ -1898,6 +1909,19 @@ void handleArcAlarmFlash() { } } } + } else { + // Page 1 (Tri-Temperature screen): check alarm conditions for acoustic warning without touching screen graphics + for (uint8_t cell = 0; cell < 4; cell++) { + if (last_valid_data[cell] != -999) { + uint8_t pidIdx = pidInCell[cell]; + int warn = warningValue[pidIdx].toInt(); + float data = last_valid_data[cell]; + uint16_t col = getMeterColor(pidIdx, data, warn); + bool inRed = (col == TFT_RED); + bool isAlarmActive = alarmEnabled[pidIdx]; + if (inRed && isAlarmActive) anyInRedAlarm = true; + } + } } handleAlarmSound(anyInRedAlarm, toggleTick); } diff --git a/sketch/obd2_gauge/obd2_gauge.ino b/sketch/obd2_gauge/obd2_gauge.ino index c4baa9d..9550619 100644 --- a/sketch/obd2_gauge/obd2_gauge.ino +++ b/sketch/obd2_gauge/obd2_gauge.ino @@ -181,7 +181,12 @@ void updateRearLedTheme() { */ const uint8_t pidInCell[4] = { 0, 3, 1, 2 }; // 4 Cuadrantes: 0=Refrigerante, 1=Velocidad, 2=Revoluciones, 3=Voltaje -int8_t fullScreenCell = -1; // -1 = 4 cuadrantes, 0..3 = pantalla completa de ese cuadrante +int8_t fullScreenCell = -1; // -1 = 4 cuadrantes / multi-pagina, 0..3 = pantalla completa de ese cuadrante +int8_t currentScreenPage = 0; // 0 = 4 cuadrantes, 1 = Tri-Temperatura (Agua + Int + Ext) +float tempEngineCoolant = 88.0f; // Temperatura de refrigerante de motor (OBD2) +float tempInterior = 22.5f; // Temperatura interior habitáculo (DS18B20 #1) +float tempExterior = 16.8f; // Temperatura exterior ambiente (DS18B20 #2) +uint8_t dsSensorCount = 0; // Cantidad de sensores 1-Wire detectados // User configuration here to change display > /*---------------------------*/ @@ -343,6 +348,7 @@ String sendATCommand(const String &cmd, uint32_t timeoutMs = 2000) { //---- Include Header File -------------------- //#include "image.h" #include "bluetooth.h" +#include "tri_temp.h" #include "meter.h" #include "config.h" @@ -504,6 +510,15 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6] Terminal("[ OK ] Settings: Theme " + String(GAUGE_THEMES[themeIndex].name) + " (LED RGB)"); delay(40); + // Initialize 1-Wire DS18B20 digital temperature bus + initTriTemp(); + if (dsSensorCount > 0) { + Terminal("[ OK ] 1-Wire: " + String(dsSensorCount) + "x DS18B20 Temp Sensors on GPIO 22"); + } else { + Terminal("[ OK ] 1-Wire: DS18B20 Bus Online (GPIO 22)"); + } + delay(40); + //start bluetooth Terminal("[ INFO ] Initializing Bluetooth Controller..."); if (!BTSerial.begin("OBD2 Gauge", true)) { @@ -544,6 +559,13 @@ void loop() { updateBacklightFade(); // non-blocking smooth backlight fade handler handleArcAlarmFlash(); // non-blocking arc alarm flash handler (blinks when in TFT_RED) + // Non-blocking periodic reading of DS18B20 digital temperature sensors (every 1s) + static uint32_t lastDsReadTime = 0; + if (millis() - lastDsReadTime >= 1000) { + lastDsReadTime = millis(); + updateTriTempSensors(false); + } + if (wokeFromSleep) { wokeFromSleep = false; lastActivityTime = millis(); @@ -564,6 +586,7 @@ void loop() { static bool isTouching = false; static uint32_t touchStartTime = 0; + static uint16_t touchStartX = 0, touchStartY = 0; static uint16_t lastTouchX = 0, lastTouchY = 0; static bool longPressHandled = false; @@ -580,16 +603,22 @@ void loop() { if (!isTouching) { isTouching = true; touchStartTime = millis(); + touchStartX = touchX; + touchStartY = touchY; lastTouchX = touchX; lastTouchY = touchY; longPressHandled = false; touchBeep(); // Crisp acoustic feedback on touch screen press! } else { - // Keep tracking coordinates while held to ensure stable quadrant detection + // Keep tracking coordinates while held to ensure stable swipe/quadrant detection lastTouchX = touchX; lastTouchY = touchY; - if (!longPressHandled && (millis() - touchStartTime > 1000)) { + // Long press check: only trigger if finger was held stationary (displacement < 30px) + int16_t currentDistX = abs((int16_t)lastTouchX - (int16_t)touchStartX); + int16_t currentDistY = abs((int16_t)lastTouchY - (int16_t)touchStartY); + + if (!longPressHandled && (millis() - touchStartTime > 1000) && currentDistX < 30 && currentDistY < 30) { // Long press (> 1s): Open Config Menu immediately! longPressHandled = true; touchBeep(); // Confirmation chirp @@ -605,16 +634,47 @@ void loop() { if (isTouching) { isTouching = false; if (!longPressHandled) { - // Valid tap detected! - if (fullScreenCell == -1) { - // Switch to full screen for tapped quadrant - int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3); - fullScreenCell = quad; + int16_t deltaX = (int16_t)lastTouchX - (int16_t)touchStartX; + int16_t deltaY = (int16_t)lastTouchY - (int16_t)touchStartY; + + // Check for horizontal swipe gesture (displacement > 45px, mostly horizontal) + if (deltaX < -45 && abs(deltaY) < 90) { + // SWIPE LEFT: Page 0 -> Page 1 (Tri-Temperature Screen) + if (fullScreenCell != -1) { + fullScreenCell = -1; + } + if (currentScreenPage == 0) { + animateSlideTransition(true); + currentScreenPage = 1; + initScreen(); + } + } else if (deltaX > 45 && abs(deltaY) < 90) { + // SWIPE RIGHT: Page 1 -> Page 0 (4 Quadrants) + if (fullScreenCell != -1) { + fullScreenCell = -1; + } + if (currentScreenPage == 1) { + animateSlideTransition(false); + currentScreenPage = 0; + initScreen(); + } } else { - // Return to 4 quadrants view - fullScreenCell = -1; + // Valid TAP detected! + if (currentScreenPage == 0) { + if (fullScreenCell == -1) { + // Switch to full screen for tapped quadrant + int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3); + fullScreenCell = quad; + } else { + // Return to 4 quadrants view + fullScreenCell = -1; + } + initScreen(); + } else { + // Tap on Tri-Temp screen: immediate sensor refresh + updateTriTempSensors(true); + } } - initScreen(); waitForTouchRelease(); // Clear mechanical bounce on release } } diff --git a/sketch/obd2_gauge/tri_temp.h b/sketch/obd2_gauge/tri_temp.h new file mode 100644 index 0000000..2cbb7d8 --- /dev/null +++ b/sketch/obd2_gauge/tri_temp.h @@ -0,0 +1,249 @@ +#pragma once + +#include +#include +#include +#include +#include "DSEG7_Modern_BoldItalic_34.h" + +// Hardware 1-Wire Pin: Conector CN1 Pin 3 (GPIO 22) +#define ONE_WIRE_BUS_PIN 22 + +extern TFT_eSPI tft; +extern uint16_t getThemePrimaryColor(); +extern uint16_t getThemeBgColor(); +extern void touchBeep(); + +// Global states for multi-page system +extern int8_t fullScreenCell; +extern int8_t currentScreenPage; // 0 = 4 cuadrantes, 1 = Tri-Temperatura +extern bool ignitionOn; + +// Global temperature values +extern float tempEngineCoolant; +extern float tempInterior; +extern float tempExterior; +extern uint8_t dsSensorCount; + +// OneWire and DallasTemperature instances +static OneWire oneWireObj(ONE_WIRE_BUS_PIN); +static DallasTemperature dsSensors(&oneWireObj); + +// Previous values for flicker-free redrawing +static float oldTriCoolant = -999.0f; +static float oldTriInterior = -999.0f; +static float oldTriExterior = -999.0f; + +// Initialize 1-Wire sensors +inline void initTriTemp() { + dsSensors.begin(); + dsSensors.setWaitForConversion(false); // Non-blocking conversion (0ms wait) + dsSensorCount = dsSensors.getDeviceCount(); + if (dsSensorCount > 0) { + dsSensors.requestTemperatures(); + } +#ifdef SERIAL_DEBUG + Serial.printf("[1-WIRE] Sensores DS18B20 detectados en GPIO 22: %d\n", dsSensorCount); +#endif +} + +// Draw a single temperature card with card index (0: Motor, 1: Interior, 2: Exterior) +inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) { + int cardY = 6 + cardIdx * 77; // y = 6, 83, 160 + int cardH = 73; + int cardX = 4 + x_offset; + int cardW = 312; + + if (cardX + cardW <= 0 || cardX >= 320) return; + + uint16_t cardBg = 0x10A2; // Dark graphite navy + uint16_t cardBorder = 0x2945; // Subtle edge border + + // 1. Draw Card Background and Border + tft.fillRoundRect(cardX, cardY, cardW, cardH, 6, cardBg); + tft.drawRoundRect(cardX, cardY, cardW, cardH, 6, cardBorder); + + // 2. Card Metadata & Accent Color + uint16_t accentCol = getThemePrimaryColor(); + const char* titleStr = ""; + const char* subStr = ""; + float minRange = 0.0f, maxRange = 100.0f; + bool isInt = false; + + if (cardIdx == 0) { + titleStr = "MOTOR / REFRIGERANTE"; + subStr = ignitionOn ? "ECU OBD-II (PID 0105)" : "ECU OBD-II (STANDBY)"; + minRange = 60.0f; + maxRange = 120.0f; + isInt = true; + if (val < 100.0f) accentCol = getThemePrimaryColor(); + else if (val < 110.0f) accentCol = TFT_YELLOW; + else accentCol = TFT_RED; + } else if (cardIdx == 1) { + titleStr = "HABITACULO / INTERIOR"; + subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1 (GPIO 22)" : "DS18B20 SENSOR 1 (DEMO)"; + minRange = 10.0f; + maxRange = 40.0f; + accentCol = 0x07F3; // Mint Green + } else { + titleStr = "AMBIENTE / EXTERIOR"; + subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2 (GPIO 22)" : (dsSensorCount == 1 ? "ESTIMADA (-4.5`C)" : "DS18B20 SENSOR 2 (DEMO)"); + minRange = -10.0f; + maxRange = 50.0f; + accentCol = 0x3D7F; // Sky Blue + } + + // Left vertical accent bar + tft.fillRoundRect(cardX + 6, cardY + 8, 4, cardH - 16, 2, accentCol); + + // Titles + tft.setTextColor(TFT_WHITE, cardBg); + tft.drawString(titleStr, cardX + 16, cardY + 8, 2); + + tft.setTextColor(0x8410, cardBg); // Soft gray + tft.drawString(subStr, cardX + 16, cardY + 28, 1); + + // 3. Horizontal Progress Gauge Bar + int barX = cardX + 16; + int barY = cardY + 50; + int barW = 120; + int barH = 8; + tft.fillRoundRect(barX, barY, barW, barH, 3, 0x18C3); // Trough background + + float ratio = (val - minRange) / (maxRange - minRange); + if (ratio < 0.0f) ratio = 0.0f; + if (ratio > 1.0f) ratio = 1.0f; + int fillW = (int)(barW * ratio); + if (fillW < 4) fillW = 4; + tft.fillRoundRect(barX, barY, fillW, barH, 3, accentCol); + + // 4. Digital Number using DSEG7 Modern Bold Italic 34pt + String numStr; + if (val <= -900.0f) { + numStr = "---"; + } else if (isInt) { + numStr = String((int)val); + } else { + numStr = String(val, 1); + } + + tft.loadFont(DSEG7_Modern_BoldItalic_34); + tft.setTextColor(accentCol, cardBg); + tft.setTextPadding(100); + tft.drawRightString(numStr.c_str(), cardX + 262, cardY + 22, 1); + tft.setTextPadding(0); + tft.unloadFont(); + + // Unit "°C" + tft.setTextColor(0xC618, cardBg); + tft.drawString("`C", cardX + 268, cardY + 32, 2); +} + +// Draw full Tri-Temp screen +inline void drawTriTempScreen(int x_offset = 0) { + if (x_offset == 0) { + tft.fillScreen(TFT_BLACK); + } else { + tft.fillRect(0, 0, x_offset, 240, TFT_BLACK); + } + + drawTriTempCard(0, tempEngineCoolant, x_offset); + drawTriTempCard(1, tempInterior, x_offset); + drawTriTempCard(2, tempExterior, x_offset); + + oldTriCoolant = tempEngineCoolant; + oldTriInterior = tempInterior; + oldTriExterior = tempExterior; + + // Draw Page Indicator: [○ ●] (Page 2 active) + tft.fillCircle(153, 2, 2, 0x5AEB); // Page 1 inactive + tft.fillCircle(167, 2, 2, getThemePrimaryColor()); // Page 2 active +} + +// Update coolant reading dynamically without redrawing entire screen +inline void updateTriTempCoolant(float val) { + tempEngineCoolant = val; + if (currentScreenPage != 1 || fullScreenCell != -1) return; + if (val != oldTriCoolant) { + drawTriTempCard(0, val, 0); + oldTriCoolant = val; + } +} + +// Update 1-Wire sensors (Interior and Exterior) +inline void updateTriTempSensors(bool forceRedraw = false) { + // Hotplug scan if 0 sensors detected yet + if (dsSensorCount == 0) { + static uint32_t lastHotplugCheck = 0; + if (millis() - lastHotplugCheck >= 5000) { + lastHotplugCheck = millis(); + dsSensors.begin(); + dsSensorCount = dsSensors.getDeviceCount(); + if (dsSensorCount > 0) { + dsSensors.setWaitForConversion(false); + dsSensors.requestTemperatures(); + forceRedraw = true; + } + } + } + + if (dsSensorCount > 0) { + float t1 = dsSensors.getTempCByIndex(0); + if (t1 > -50.0f && t1 < 100.0f) { + tempInterior = t1; + } + if (dsSensorCount > 1) { + float t2 = dsSensors.getTempCByIndex(1); + if (t2 > -50.0f && t2 < 100.0f) { + tempExterior = t2; + } + } else { + // Demo exterior offset if only 1 physical sensor connected + tempExterior = tempInterior - 4.5f; + } + dsSensors.requestTemperatures(); // non-blocking next reading request + } else { + // Simulated demo readings if sensors not yet plugged in + tempInterior = 22.4f + 0.3f * sin(millis() / 4000.0f); + tempExterior = 16.8f + 0.2f * cos(millis() / 5000.0f); + } + + if (currentScreenPage == 1 && fullScreenCell == -1) { + if (forceRedraw || abs(tempInterior - oldTriInterior) >= 0.1f) { + drawTriTempCard(1, tempInterior, 0); + oldTriInterior = tempInterior; + } + if (forceRedraw || abs(tempExterior - oldTriExterior) >= 0.1f) { + drawTriTempCard(2, tempExterior, 0); + oldTriExterior = tempExterior; + } + } +} + +// Animated mobile-like slide transition between screens +inline void animateSlideTransition(bool swipeLeft) { + touchBeep(); + const uint8_t STEPS = 10; + const uint16_t STEP_W = 320 / STEPS; // 32 px + uint16_t primary = getThemePrimaryColor(); + + if (swipeLeft) { + // Swipe left: wipe beam sweeping from right to left + for (int s = 0; s < STEPS; s++) { + int x = 320 - (s + 1) * STEP_W; + tft.fillRect(x, 0, STEP_W, 240, TFT_BLACK); + tft.drawFastVLine(x, 0, 240, primary); + tft.drawFastVLine(x + 1, 0, 240, TFT_WHITE); + delay(10); + } + } else { + // Swipe right: wipe beam sweeping from left to right + for (int s = 0; s < STEPS; s++) { + int x = s * STEP_W; + tft.fillRect(x, 0, STEP_W, 240, TFT_BLACK); + tft.drawFastVLine(x + STEP_W - 1, 0, 240, primary); + tft.drawFastVLine(x + STEP_W - 2, 0, 240, TFT_WHITE); + delay(10); + } + } +}