feat(ui): implementar pantalla triple de temperaturas con gesto swipe y transicion animada

This commit is contained in:
Sebastián Nieto 2026-09-21 23:03:58 -03:00
parent 36637eb2da
commit b932c9e298
4 changed files with 349 additions and 16 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1219,7 +1219,7 @@ void plotNoData(uint8_t cell) {
tft.setTextPadding(0); tft.setTextPadding(0);
tft.unloadFont(); tft.unloadFont();
} }
} else { } else if (currentScreenPage == 0) {
int x = cell4_x[cell]; int x = cell4_x[cell];
int y = cell4_y[cell]; int y = cell4_y[cell];
clearPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]); clearPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]);
@ -1257,6 +1257,8 @@ void initScreen() {
} }
if (fullScreenCell >= 0 && fullScreenCell < 4) { if (fullScreenCell >= 0 && fullScreenCell < 4) {
fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell]); fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell]);
} else if (currentScreenPage == 1) {
drawTriTempScreen(0);
} else { } else {
tft.fillScreen(TFT_BLACK); // clear screen tft.fillScreen(TFT_BLACK); // clear screen
for (uint8_t c = 0; c < 4; c++) { for (uint8_t c = 0; c < 4; c++) {
@ -1264,10 +1266,15 @@ void initScreen() {
arcMeter4(c, pid); arcMeter4(c, pid);
} }
drawQuadrantDividers(); 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; badgeEcoDrawn = false;
updateEcoBadge(true); updateEcoBadge(true);
} }
}
/*-------------------------------*/ /*-------------------------------*/
// get A B from response return in global A B variable; returns true only if valid OBD2 data bytes were parsed // get A B from response return in global A B variable; returns true only if valid OBD2 data bytes were parsed
@ -1486,6 +1493,10 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
bt_message = ""; bt_message = "";
return; return;
} }
tempEngineCoolant = data;
if (currentScreenPage == 1 && fullScreenCell == -1) {
updateTriTempCoolant(data);
}
break; // temp °C (ECT) break; // temp °C (ECT)
case 2: case 2:
data = A * 100.0 / 255; data = A * 100.0 / 255;
@ -1542,7 +1553,7 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
if (pidNo == fullScreenCell) { if (pidNo == fullScreenCell) {
plotFullScreen(fullScreenCell, data, warn, min, max, digit, unit); plotFullScreen(fullScreenCell, data, warn, min, max, digit, unit);
} }
} else { } else if (currentScreenPage == 0) {
plotArc4(pidNo, data, warn, min, max, digit, unit); plotArc4(pidNo, data, warn, min, max, digit, unit);
} }
bt_message = ""; // reset bt_message = ""; // reset
@ -1833,7 +1844,7 @@ void handleArcAlarmFlash() {
} }
} }
} }
} else { } else if (currentScreenPage == 0) {
// 4 quadrants mode // 4 quadrants mode
for (uint8_t cell = 0; cell < 4; cell++) { for (uint8_t cell = 0; cell < 4; cell++) {
if (last_valid_data[cell] != -999) { 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); handleAlarmSound(anyInRedAlarm, toggleTick);
} }

View file

@ -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 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 > // User configuration here to change display >
/*---------------------------*/ /*---------------------------*/
@ -343,6 +348,7 @@ String sendATCommand(const String &cmd, uint32_t timeoutMs = 2000) {
//---- Include Header File -------------------- //---- Include Header File --------------------
//#include "image.h" //#include "image.h"
#include "bluetooth.h" #include "bluetooth.h"
#include "tri_temp.h"
#include "meter.h" #include "meter.h"
#include "config.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)"); Terminal("[ OK ] Settings: Theme " + String(GAUGE_THEMES[themeIndex].name) + " (LED RGB)");
delay(40); 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 //start bluetooth
Terminal("[ INFO ] Initializing Bluetooth Controller..."); Terminal("[ INFO ] Initializing Bluetooth Controller...");
if (!BTSerial.begin("OBD2 Gauge", true)) { if (!BTSerial.begin("OBD2 Gauge", true)) {
@ -544,6 +559,13 @@ void loop() {
updateBacklightFade(); // non-blocking smooth backlight fade handler updateBacklightFade(); // non-blocking smooth backlight fade handler
handleArcAlarmFlash(); // non-blocking arc alarm flash handler (blinks when in TFT_RED) 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) { if (wokeFromSleep) {
wokeFromSleep = false; wokeFromSleep = false;
lastActivityTime = millis(); lastActivityTime = millis();
@ -564,6 +586,7 @@ void loop() {
static bool isTouching = false; static bool isTouching = false;
static uint32_t touchStartTime = 0; static uint32_t touchStartTime = 0;
static uint16_t touchStartX = 0, touchStartY = 0;
static uint16_t lastTouchX = 0, lastTouchY = 0; static uint16_t lastTouchX = 0, lastTouchY = 0;
static bool longPressHandled = false; static bool longPressHandled = false;
@ -580,16 +603,22 @@ void loop() {
if (!isTouching) { if (!isTouching) {
isTouching = true; isTouching = true;
touchStartTime = millis(); touchStartTime = millis();
touchStartX = touchX;
touchStartY = touchY;
lastTouchX = touchX; lastTouchX = touchX;
lastTouchY = touchY; lastTouchY = touchY;
longPressHandled = false; longPressHandled = false;
touchBeep(); // Crisp acoustic feedback on touch screen press! touchBeep(); // Crisp acoustic feedback on touch screen press!
} else { } else {
// Keep tracking coordinates while held to ensure stable quadrant detection // Keep tracking coordinates while held to ensure stable swipe/quadrant detection
lastTouchX = touchX; lastTouchX = touchX;
lastTouchY = touchY; 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! // Long press (> 1s): Open Config Menu immediately!
longPressHandled = true; longPressHandled = true;
touchBeep(); // Confirmation chirp touchBeep(); // Confirmation chirp
@ -605,7 +634,33 @@ void loop() {
if (isTouching) { if (isTouching) {
isTouching = false; isTouching = false;
if (!longPressHandled) { if (!longPressHandled) {
// Valid tap detected! 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 {
// Valid TAP detected!
if (currentScreenPage == 0) {
if (fullScreenCell == -1) { if (fullScreenCell == -1) {
// Switch to full screen for tapped quadrant // Switch to full screen for tapped quadrant
int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3); int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3);
@ -615,6 +670,11 @@ void loop() {
fullScreenCell = -1; fullScreenCell = -1;
} }
initScreen(); initScreen();
} else {
// Tap on Tri-Temp screen: immediate sensor refresh
updateTriTempSensors(true);
}
}
waitForTouchRelease(); // Clear mechanical bounce on release waitForTouchRelease(); // Clear mechanical bounce on release
} }
} }

View file

@ -0,0 +1,249 @@
#pragma once
#include <Arduino.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <TFT_eSPI.h>
#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);
}
}
}