feat(ui): implementar pantalla triple de temperaturas con gesto swipe y transicion animada
This commit is contained in:
parent
36637eb2da
commit
b932c9e298
4 changed files with 349 additions and 16 deletions
BIN
diagrama_conexion_ds18b20.png
Normal file
BIN
diagrama_conexion_ds18b20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -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,10 +1266,15 @@ 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);
|
||||
}
|
||||
}
|
||||
/*-------------------------------*/
|
||||
|
||||
// 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 = "";
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +634,33 @@ void loop() {
|
|||
if (isTouching) {
|
||||
isTouching = false;
|
||||
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) {
|
||||
// Switch to full screen for tapped quadrant
|
||||
int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3);
|
||||
|
|
@ -615,6 +670,11 @@ void loop() {
|
|||
fullScreenCell = -1;
|
||||
}
|
||||
initScreen();
|
||||
} else {
|
||||
// Tap on Tri-Temp screen: immediate sensor refresh
|
||||
updateTriTempSensors(true);
|
||||
}
|
||||
}
|
||||
waitForTouchRelease(); // Clear mechanical bounce on release
|
||||
}
|
||||
}
|
||||
|
|
|
|||
249
sketch/obd2_gauge/tri_temp.h
Normal file
249
sketch/obd2_gauge/tri_temp.h
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue