obd2_gauge/sketch/obd2_gauge/gps_nav.h
Sebastian Nieto d124d013ab feat(ui): transiciones fluidas de 4 pantallas, integracion GPS y buffer de transicion persistente sin fragmentacion de memoria
- Buffer de transicion persistente (sprTrans) de 25KB asignado una sola vez en setup() para evitar fallos de memoria heap contigua
- Eliminacion de carga y descarga dinamica de fuentes en renderPage2ToSprite durante las transiciones para evitar fugas de RAM
- Timeout de seguridad por hardware de 250ms en waitForTouchRelease() para prevenir bloqueos por ruido en la lamina resistiva
- Pantallas 2 y 3: Reloj atomico GPS con DSEG7 de 80pt y Telemetria satelital completa (rumbo, satelites, altitud, coordenadas)
- Documentacion de conexion y pines para modulo GPS N32+/U-blox
2026-10-11 18:33:03 -03:00

502 lines
18 KiB
C

#pragma once
#include <Arduino.h>
#include <TFT_eSPI.h>
#include "gps_module.h"
#include "DSEG7_Modern_BoldItalic_34.h"
extern TFT_eSPI tft;
extern uint16_t getThemePrimaryColor();
extern uint16_t getThemeBgColor();
extern int8_t currentScreenPage;
extern int8_t fullScreenCell;
// Previous values for flicker-free redrawing of Page 3
static float oldNavAlt = -999.0f;
static float oldNavCourse = -999.0f;
static uint8_t oldNavSats = 255;
static float oldNavHdop = -999.0f;
static double oldNavLat = 0.0;
static double oldNavLon = 0.0;
static bool oldNavFix = false;
static int lastNavAltRound = -999;
static bool lastNavAltValid = false;
static int lastNavCrsRound = -999;
static bool lastNavHasCourse = false;
static const char* lastNavCardDir = "";
static uint8_t lastNavCardSats = 255;
static bool lastNavSatsSync = false;
static float lastNavCardHdop = -999.0f;
static char lastNavCardDiag[32] = "";
static bool lastNavDiagSynced = false;
static uint8_t oldNavWaveState = 255;
static bool oldNavSynced = false;
static uint32_t lastNavPageEnterTime = 0;
// Synchronize tracking variables without touching TFT display (zero flicker)
inline void syncGpsNavTrackers() {
lastNavPageEnterTime = millis();
bool synced = isGpsSynced();
bool altValid = isGpsAltitudeValid();
lastNavAltRound = altValid ? (int)round(gpsAltitudeM) : -999;
lastNavAltValid = altValid;
oldNavAlt = gpsAltitudeM;
bool hasCourse = synced && isGpsCourseValid();
lastNavCrsRound = hasCourse ? (int)round(gpsCourseDeg) : -999;
lastNavHasCourse = hasCourse;
oldNavCourse = gpsCourseDeg;
lastNavCardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
lastNavCardSats = gpsSats;
lastNavSatsSync = synced;
oldNavSats = gpsSats;
lastNavCardHdop = gpsHdop;
oldNavHdop = gpsHdop;
strncpy(lastNavCardDiag, getGpsDiagMessage(), sizeof(lastNavCardDiag) - 1);
lastNavCardDiag[sizeof(lastNavCardDiag) - 1] = '\0';
lastNavDiagSynced = synced;
oldNavWaveState = getGpsWaveAnimState();
oldNavSynced = synced;
oldNavLat = gpsLatitude;
oldNavLon = gpsLongitude;
oldNavFix = synced;
}
// Large animated satellite icon faithful to gps_icon.jpg for the Navigation Dashboard
inline void drawLargeSatelliteIcon(int x, int y, uint16_t color, uint8_t waveState = 2, uint16_t bgColor = 0x10A2) {
drawLargeGpsSatelliteIcon(tft, x, y, TFT_WHITE, color, bgColor, waveState);
}
// Large animated satellite icon for double-buffered sprite slice
inline void drawLargeSatelliteIconSprite(TFT_eSprite &s, int x, int y, uint16_t color, uint8_t waveState = 2, uint16_t bgColor = 0x10A2) {
drawLargeGpsSatelliteIcon(s, x, y, TFT_WHITE, color, bgColor, waveState);
}
// Draw full Page 3 (GPS Navigation & Telemetry Screen: Altitud, Rumbo, Satélites, Coordenadas)
inline void drawGpsNavScreen(int x_offset = 0) {
if (x_offset == 0) {
tft.fillScreen(TFT_BLACK);
} else {
tft.fillRect(0, 0, x_offset, 240, TFT_BLACK);
}
uint16_t priCol = getThemePrimaryColor();
uint16_t cardBg = 0x10A2; // Dark graphite navy
uint16_t cardBorder = 0x2945; // Subtle edge border
bool synced = isGpsSynced();
// 1. TOP BAR (y = 0 to 18)
// Left: Screen Title
tft.setTextColor(0x8410, TFT_BLACK);
tft.drawString("TELEMETRIA GPS", 8 + x_offset, 5, 1);
// Center: 4 Pagination Indicator Dots [○ ○ ○ ●] (Page 4 active)
tft.fillCircle(139 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive
tft.fillCircle(153 + x_offset, 2, 2, 0x5AEB); // Page 2 inactive
tft.fillCircle(167 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive
tft.fillCircle(181 + x_offset, 2, 2, priCol); // Page 4 active
// 2. CARD 0 (Top Left): ALTITUD (x = 4, y = 19, w = 152, h = 102)
int c0X = 4 + x_offset;
int c0Y = 19;
int cW = 152;
int cH1 = 102;
tft.fillRoundRect(c0X, c0Y, cW, cH1, 6, cardBg);
tft.drawRoundRect(c0X, c0Y, cW, cH1, 6, cardBorder);
tft.fillRoundRect(c0X + 6, c0Y + 8, 4, cH1 - 16, 2, priCol);
tft.setTextColor(0x8410, cardBg);
tft.drawString("ALTITUD", c0X + 16, c0Y + 8, 2);
tft.setTextColor(0x9CD3, cardBg);
tft.drawString("MSNM", c0X + 96, c0Y + 8, 2);
bool altValid = isGpsAltitudeValid();
String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---";
tft.loadFont(DSEG7_Modern_BoldItalic_34);
tft.setTextColor(priCol, cardBg);
tft.setTextPadding(0);
tft.drawRightString(altStr.c_str(), c0X + 140, c0Y + 28, 1);
tft.unloadFont();
// 3. CARD 1 (Top Right): RUMBO / BRÚJULA (x = 164, y = 19, w = 152, h = 102)
int c1X = 164 + x_offset;
int c1Y = 19;
tft.fillRoundRect(c1X, c1Y, cW, cH1, 6, cardBg);
tft.drawRoundRect(c1X, c1Y, cW, cH1, 6, cardBorder);
tft.fillRoundRect(c1X + 6, c1Y + 8, 4, cH1 - 16, 2, priCol);
tft.setTextColor(0x8410, cardBg);
tft.drawString("RUMBO / BRUJULA", c1X + 16, c1Y + 8, 2);
bool hasCourse = synced && isGpsCourseValid();
String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---";
tft.loadFont(DSEG7_Modern_BoldItalic_34);
tft.setTextColor(priCol, cardBg);
tft.setTextPadding(0);
tft.drawRightString(crsStr.c_str(), c1X + 130, c1Y + 28, 1);
tft.unloadFont();
if (hasCourse) {
tft.drawCircle(c1X + 137, c1Y + 33, 3, priCol);
tft.drawCircle(c1X + 137, c1Y + 33, 2, priCol);
} else {
tft.fillCircle(c1X + 137, c1Y + 33, 4, cardBg);
}
const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
tft.setTextColor(TFT_WHITE, cardBg);
tft.drawString(cardDir, c1X + 16, c1Y + 74, 2);
// 4. CARD 2 (Bottom Left): SATÉLITES GPS EN GRANDE (x = 4, y = 125, w = 152, h = 110)
int c2X = 4 + x_offset;
int c2Y = 125;
int cH2 = 110;
tft.fillRoundRect(c2X, c2Y, cW, cH2, 6, cardBg);
tft.drawRoundRect(c2X, c2Y, cW, cH2, 6, cardBorder);
tft.fillRoundRect(c2X + 6, c2Y + 8, 4, cH2 - 16, 2, priCol);
tft.setTextColor(0x8410, cardBg);
tft.drawString("SATELITES GPS", c2X + 16, c2Y + 8, 2);
// Large Satellite Icon (animated body + waves)
uint8_t waveState = getGpsWaveAnimState();
drawLargeSatelliteIcon(c2X + 16, c2Y + 32, priCol, waveState, cardBg);
// Large Satellites Count (DSEG7 34pt)
String satStr = String(gpsSats);
tft.loadFont(DSEG7_Modern_BoldItalic_34);
tft.setTextColor(priCol, cardBg);
tft.setTextPadding(0);
tft.drawRightString(satStr.c_str(), c2X + 140, c2Y + 26, 1);
tft.unloadFont();
// Telemetry details: HDOP precision & live diagnostic status
String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---");
tft.setTextColor(TFT_WHITE, cardBg);
tft.drawString(hdopStr.c_str(), c2X + 16, c2Y + 68, 2);
tft.setTextColor(synced ? 0x9CD3 : priCol, cardBg);
tft.drawString(getGpsDiagMessage(), c2X + 16, c2Y + 90, 1);
// 5. CARD 3 (Bottom Right): COORDENADAS GPS (x = 164, y = 125, w = 152, h = 110)
int c3X = 164 + x_offset;
int c3Y = 125;
tft.fillRoundRect(c3X, c3Y, cW, cH2, 6, cardBg);
tft.drawRoundRect(c3X, c3Y, cW, cH2, 6, cardBorder);
tft.fillRoundRect(c3X + 6, c3Y + 8, 4, cH2 - 16, 2, priCol);
tft.setTextColor(0x8410, cardBg);
tft.drawString("COORDENADAS", c3X + 16, c3Y + 8, 2);
tft.setTextColor(0x8410, cardBg);
tft.drawString("LAT", c3X + 16, c3Y + 26, 1);
String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°";
tft.setTextColor(priCol, cardBg);
tft.drawString(latStr.c_str(), c3X + 16, c3Y + 38, 2);
tft.setTextColor(0x8410, cardBg);
tft.drawString("LON", c3X + 16, c3Y + 64, 1);
String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°";
tft.setTextColor(priCol, cardBg);
tft.drawString(lonStr.c_str(), c3X + 16, c3Y + 76, 2);
// Sync state trackers
syncGpsNavTrackers();
}
// 100% Flicker-Free Live Refresh for resting Page 3 (GPS Navigation & Telemetry)
inline void updateGpsNavScreen(bool forceRedraw = false) {
if (currentScreenPage != 3 || fullScreenCell != -1) return;
// Post-transition stabilization: during the first 400ms after landing on Page 3,
// do not wipe or repaint cards over SPI. Any incoming serial bytes from GPS
// are safely absorbed into tracking variables so there is ZERO blackout/blink!
if (!forceRedraw && (millis() - lastNavPageEnterTime < 400)) {
syncGpsNavTrackers();
return;
}
uint16_t priCol = getThemePrimaryColor();
uint16_t cardBg = 0x10A2;
bool synced = isGpsSynced();
// 1. Altitude changed: update Card 0 digits only
bool altValid = isGpsAltitudeValid();
int curAltRound = altValid ? (int)round(gpsAltitudeM) : -999;
if (forceRedraw || curAltRound != lastNavAltRound || altValid != lastNavAltValid) {
lastNavAltRound = curAltRound;
lastNavAltValid = altValid;
oldNavAlt = gpsAltitudeM;
int c0X = 4;
int c0Y = 19;
String altStr = altValid ? String(curAltRound) : "---";
tft.loadFont(DSEG7_Modern_BoldItalic_34);
tft.setTextColor(priCol, cardBg);
tft.setTextPadding(126);
tft.drawRightString(altStr.c_str(), c0X + 140, c0Y + 28, 1);
tft.setTextPadding(0);
tft.unloadFont();
}
// 2. Course / Heading changed: update Card 1
bool hasCourse = synced && isGpsCourseValid();
int curCrsRound = hasCourse ? (int)round(gpsCourseDeg) : -999;
int c1X = 164;
int c1Y = 19;
if (forceRedraw || curCrsRound != lastNavCrsRound || hasCourse != lastNavHasCourse) {
lastNavCrsRound = curCrsRound;
oldNavCourse = gpsCourseDeg;
lastNavHasCourse = hasCourse;
String crsStr = hasCourse ? String(curCrsRound) : "---";
tft.loadFont(DSEG7_Modern_BoldItalic_34);
tft.setTextColor(priCol, cardBg);
tft.setTextPadding(85);
tft.drawRightString(crsStr.c_str(), c1X + 130, c1Y + 28, 1);
tft.setTextPadding(0);
tft.unloadFont();
if (hasCourse) {
tft.drawCircle(c1X + 137, c1Y + 33, 3, priCol);
tft.drawCircle(c1X + 137, c1Y + 33, 2, priCol);
} else {
tft.fillCircle(c1X + 137, c1Y + 33, 4, cardBg);
}
}
// Direction name changed (e.g. NORTE, SUR)
const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
if (forceRedraw || strcmp(cardDir, lastNavCardDir) != 0) {
lastNavCardDir = cardDir;
tft.setTextPadding(128);
tft.setTextColor(TFT_WHITE, cardBg);
tft.drawString(cardDir, c1X + 16, c1Y + 74, 2);
tft.setTextPadding(0);
}
// 3. Card 2: Satellites, HDOP and Diagnostic status
int c2X = 4;
int c2Y = 125;
// 3a. Satellites Count (DSEG7 34pt)
if (forceRedraw || gpsSats != lastNavCardSats || synced != lastNavSatsSync) {
lastNavCardSats = gpsSats;
lastNavSatsSync = synced;
oldNavSats = gpsSats;
String satStr = String(gpsSats);
tft.loadFont(DSEG7_Modern_BoldItalic_34);
tft.setTextColor(priCol, cardBg);
tft.setTextPadding(85);
tft.drawRightString(satStr.c_str(), c2X + 140, c2Y + 26, 1);
tft.setTextPadding(0);
tft.unloadFont();
}
// 3b. HDOP precision
if (forceRedraw || abs(gpsHdop - lastNavCardHdop) >= 0.2f) {
lastNavCardHdop = gpsHdop;
oldNavHdop = gpsHdop;
String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---");
tft.setTextPadding(130);
tft.setTextColor(TFT_WHITE, cardBg);
tft.drawString(hdopStr.c_str(), c2X + 16, c2Y + 68, 2);
tft.setTextPadding(0);
}
// 3c. Diagnostic status message ("PRECISION OPTIMA", "SIN SEÑAL GPS", etc.)
// Decoupled: only redrawn when message text actually changes, with setTextPadding (zero flicker!)
const char* curDiag = getGpsDiagMessage();
if (forceRedraw || strcmp(curDiag, lastNavCardDiag) != 0 || synced != lastNavDiagSynced) {
strncpy(lastNavCardDiag, curDiag, sizeof(lastNavCardDiag) - 1);
lastNavCardDiag[sizeof(lastNavCardDiag) - 1] = '\0';
lastNavDiagSynced = synced;
tft.setTextPadding(130);
tft.setTextColor(synced ? 0x9CD3 : priCol, cardBg);
tft.drawString(curDiag, c2X + 16, c2Y + 90, 1);
tft.setTextPadding(0);
}
// 3d. Large Satellite Icon: only redrawn if waveState or sync changes
uint8_t curWaveState = getGpsWaveAnimState();
if (forceRedraw || curWaveState != oldNavWaveState || synced != oldNavSynced) {
oldNavWaveState = curWaveState;
oldNavSynced = synced;
drawLargeSatelliteIcon(c2X + 16, c2Y + 32, priCol, curWaveState, cardBg);
}
// 4. Coordinates changed: update Card 3 (setTextPadding avoids fillRect flicker)
if (forceRedraw || abs(gpsLatitude - oldNavLat) >= 0.00005 || abs(gpsLongitude - oldNavLon) >= 0.00005) {
oldNavLat = gpsLatitude;
oldNavLon = gpsLongitude;
int c3X = 164;
int c3Y = 125;
String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°";
tft.setTextPadding(130);
tft.setTextColor(priCol, cardBg);
tft.drawString(latStr.c_str(), c3X + 16, c3Y + 38, 2);
String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°";
tft.drawString(lonStr.c_str(), c3X + 16, c3Y + 76, 2);
tft.setTextPadding(0);
}
}
// Render Page 3 into double-buffered sprite slice for slide / fade transitions
inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) {
uint16_t priCol = getThemePrimaryColor();
uint16_t cardBg = 0x10A2;
uint16_t cardBorder = 0x2945;
bool synced = isGpsSynced();
// 1. TOP BAR in top slice
if (y_slice == 0) {
s.setTextColor(0x8410, TFT_BLACK);
s.drawString("TELEMETRIA GPS", 8 + x_offset, 5, 1);
}
// 2. CARD 0 (Top Left): ALTITUD (y = 19 to 121)
int c0X = 4 + x_offset;
int c0Y = 19 - y_slice;
int cW = 152;
int cH1 = 102;
if (c0X + cW > 0 && c0X < 320 && c0Y + cH1 > 0 && c0Y < slice_h) {
s.fillRoundRect(c0X, c0Y, cW, cH1, 6, cardBg);
s.drawRoundRect(c0X, c0Y, cW, cH1, 6, cardBorder);
s.fillRoundRect(c0X + 6, c0Y + 8, 4, cH1 - 16, 2, priCol);
if (c0Y + 24 > 0 && c0Y + 8 < slice_h) {
s.setTextColor(0x8410, cardBg);
s.drawString("ALTITUD", c0X + 16, c0Y + 8, 2);
s.setTextColor(0x9CD3, cardBg);
s.drawString("MSNM", c0X + 96, c0Y + 8, 2);
}
bool altValid = isGpsAltitudeValid();
String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---";
int valY = c0Y + 28;
if (valY < slice_h && valY + 38 > 0) {
s.fontLoaded = true;
s.setTextColor(priCol, cardBg);
s.setTextPadding(0);
s.drawRightString(altStr.c_str(), c0X + 140, valY, 1);
s.fontLoaded = false;
}
}
// 3. CARD 1 (Top Right): RUMBO (y = 19 to 121)
int c1X = 164 + x_offset;
int c1Y = 19 - y_slice;
if (c1X + cW > 0 && c1X < 320 && c1Y + cH1 > 0 && c1Y < slice_h) {
s.fillRoundRect(c1X, c1Y, cW, cH1, 6, cardBg);
s.drawRoundRect(c1X, c1Y, cW, cH1, 6, cardBorder);
s.fillRoundRect(c1X + 6, c1Y + 8, 4, cH1 - 16, 2, priCol);
if (c1Y + 24 > 0 && c1Y + 8 < slice_h) {
s.setTextColor(0x8410, cardBg);
s.drawString("RUMBO / BRUJULA", c1X + 16, c1Y + 8, 2);
}
bool hasCourse = synced && isGpsCourseValid();
String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---";
int valY = c1Y + 28;
if (valY < slice_h && valY + 38 > 0) {
s.fontLoaded = true;
s.setTextColor(priCol, cardBg);
s.setTextPadding(0);
s.drawRightString(crsStr.c_str(), c1X + 130, valY, 1);
s.fontLoaded = false;
if (hasCourse) {
int circY = c1Y + 33;
if (circY + 4 > 0 && circY - 4 < slice_h) {
s.drawCircle(c1X + 137, circY, 3, priCol);
s.drawCircle(c1X + 137, circY, 2, priCol);
}
}
}
int dirY = c1Y + 74;
if (dirY + 16 > 0 && dirY < slice_h) {
const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
s.setTextColor(TFT_WHITE, cardBg);
s.drawString(cardDir, c1X + 16, dirY, 2);
}
}
// 4. CARD 2 (Bottom Left): SATÉLITES GPS (y = 125 to 235)
int c2X = 4 + x_offset;
int c2Y = 125 - y_slice;
int cH2 = 110;
if (c2X + cW > 0 && c2X < 320 && c2Y + cH2 > 0 && c2Y < slice_h) {
s.fillRoundRect(c2X, c2Y, cW, cH2, 6, cardBg);
s.drawRoundRect(c2X, c2Y, cW, cH2, 6, cardBorder);
s.fillRoundRect(c2X + 6, c2Y + 8, 4, cH2 - 16, 2, priCol);
if (c2Y + 24 > 0 && c2Y + 8 < slice_h) {
s.setTextColor(0x8410, cardBg);
s.drawString("SATELITES GPS", c2X + 16, c2Y + 8, 2);
}
uint8_t waveState = getGpsWaveAnimState();
int icnY = c2Y + 32;
if (icnY + 28 > 0 && icnY < slice_h) {
drawLargeSatelliteIconSprite(s, c2X + 16, icnY, priCol, waveState, cardBg);
}
int satY = c2Y + 26;
if (satY < slice_h && satY + 38 > 0) {
String satStr = String(gpsSats);
s.fontLoaded = true;
s.setTextColor(priCol, cardBg);
s.setTextPadding(0);
s.drawRightString(satStr.c_str(), c2X + 140, satY, 1);
s.fontLoaded = false;
}
int hdopY = c2Y + 68;
if (hdopY < slice_h && hdopY + 16 > 0) {
String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---");
s.setTextColor(TFT_WHITE, cardBg);
s.drawString(hdopStr.c_str(), c2X + 16, hdopY, 2);
}
int qualY = c2Y + 90;
if (qualY + 10 > 0 && qualY < slice_h) {
s.setTextColor(synced ? 0x9CD3 : priCol, cardBg);
s.drawString(getGpsDiagMessage(), c2X + 16, qualY, 1);
}
}
// 5. CARD 3 (Bottom Right): COORDENADAS (y = 125 to 235)
int c3X = 164 + x_offset;
int c3Y = 125 - y_slice;
if (c3X + cW > 0 && c3X < 320 && c3Y + cH2 > 0 && c3Y < slice_h) {
s.fillRoundRect(c3X, c3Y, cW, cH2, 6, cardBg);
s.drawRoundRect(c3X, c3Y, cW, cH2, 6, cardBorder);
s.fillRoundRect(c3X + 6, c3Y + 8, 4, cH2 - 16, 2, priCol);
if (c3Y + 24 > 0 && c3Y + 8 < slice_h) {
s.setTextColor(0x8410, cardBg);
s.drawString("COORDENADAS", c3X + 16, c3Y + 8, 2);
}
int latHdrY = c3Y + 26;
if (latHdrY < slice_h && latHdrY + 10 > 0) {
s.setTextColor(0x8410, cardBg);
s.drawString("LAT", c3X + 16, latHdrY, 1);
}
int latValY = c3Y + 38;
if (latValY < slice_h && latValY + 18 > 0) {
String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°";
s.setTextColor(priCol, cardBg);
s.drawString(latStr.c_str(), c3X + 16, latValY, 2);
}
int lonHdrY = c3Y + 64;
if (lonHdrY < slice_h && lonHdrY + 10 > 0) {
s.setTextColor(0x8410, cardBg);
s.drawString("LON", c3X + 16, lonHdrY, 1);
}
int lonValY = c3Y + 76;
if (lonValY < slice_h && lonValY + 18 > 0) {
String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°";
s.setTextColor(priCol, cardBg);
s.drawString(lonStr.c_str(), c3X + 16, lonValY, 2);
}
}
}