diff --git a/dop.png b/dop.png new file mode 100644 index 0000000..3da17dc Binary files /dev/null and b/dop.png differ diff --git a/emulator/obd2_emulator.py b/emulator/obd2_emulator.py index 12bd5b1..512726b 100755 --- a/emulator/obd2_emulator.py +++ b/emulator/obd2_emulator.py @@ -469,8 +469,8 @@ def run_emulator(): sim_thread = threading.Thread(target=simulation_worker, daemon=True) sim_thread.start() - # Start GPS simulation thread if port exists or --gps requested - if not args.no_gps and (args.gps or os.path.exists(args.gps_port)): + # Start GPS simulation thread ONLY if --gps explicitly requested + if args.gps and not args.no_gps: gps_thread = threading.Thread( target=gps_simulation_worker, args=(args.gps_port, args.gps_baud), diff --git a/gauge_design.png b/gauge_design.png new file mode 100644 index 0000000..99048e1 Binary files /dev/null and b/gauge_design.png differ diff --git a/gps_icon.png b/gps_icon.png new file mode 100644 index 0000000..ae41669 Binary files /dev/null and b/gps_icon.png differ diff --git a/gps_icon.svg b/gps_icon.svg index 4cf5a89..ca95662 100644 --- a/gps_icon.svg +++ b/gps_icon.svg @@ -21,24 +21,24 @@ x="-25.60" y="-14.63" width="51.20" height="29.26" rx="7.31" ry="7.31" - transform="translate(100.00, 88.00) rotate(-34.0) translate(61.44, 0)"/> + transform="translate(100.00, 88.00) rotate(-39.5) translate(61.44, 0)"/> + transform="translate(100.00, 88.00) rotate(-39.5) translate(-61.44, 0)"/> diff --git a/gps_icon.xcf b/gps_icon.xcf new file mode 100644 index 0000000..11dc114 Binary files /dev/null and b/gps_icon.xcf differ diff --git a/sketch/obd2_gauge/bluetooth.h b/sketch/obd2_gauge/bluetooth.h index f784a7a..c267dcf 100644 --- a/sketch/obd2_gauge/bluetooth.h +++ b/sketch/obd2_gauge/bluetooth.h @@ -76,7 +76,8 @@ void scanBTdevice() {//scan bluetooth device bool isMatch = (dName == "OBDII") || (dName.indexOf("OBD") != -1) || (dName.indexOf("ELM") != -1) || - (dName == "DEBIAN") || + (dName.indexOf("DEBIAN") != -1) || + (dAddr == "6c:62:6d:14:08:fd") || (dAddr == "e8:48:b8:c8:20:00"); if (isMatch) {//match name. diff --git a/sketch/obd2_gauge/cluster_screen.h b/sketch/obd2_gauge/cluster_screen.h index 0051165..8e8aa64 100644 --- a/sketch/obd2_gauge/cluster_screen.h +++ b/sketch/obd2_gauge/cluster_screen.h @@ -6,6 +6,7 @@ #include "DSEG7_Modern_BoldItalic_34.h" #include "DSEG7_Modern_BoldItalic_44.h" #include "rainbow_text.h" +#include "kmh_aa_data.h" extern TFT_eSPI tft; extern uint16_t getThemePrimaryColor(); @@ -18,6 +19,7 @@ extern int8_t fullScreenCell; extern float currentRpm; extern float last_valid_data[]; extern float tempEngineCoolant; +extern bool isEcoOptimal; extern void drawPidIcon(int x, int y, const String &pidStr); extern void drawPidIconToSprite(TFT_eSprite &s, int ix, int iy, const String &pidStr, int y_slice, int slice_h, int global_y); extern void draw7SegDegreeC(TFT_eSPI &dst, int bx, int by, uint16_t color, uint16_t bgCol, bool rainbow); @@ -32,7 +34,7 @@ inline float getClusterRpmVal() { inline float getClusterSpeedVal() { if (isGpsSpeedValid()) return gpsSpeedKmph; if (last_valid_data[1] >= 0.0f) return last_valid_data[1]; - return 0.0f; + return -999.0f; } inline bool isClusterSpeedFromGps() { @@ -56,29 +58,34 @@ static int oldClusterSpd = -999; static int oldClusterCoolant = -999; static float oldClusterVolt = -999.0f; static bool oldClusterGpsLock = false; +static bool oldClusterEco = false; +static uint16_t oldClusterCoolCol = 0; +static uint16_t oldClusterVoltCol = 0; +static int oldClusterSegs = -1; static uint32_t lastClusterUpdate = 0; inline void syncClusterTrackers() { oldClusterRpm = (int)round(getClusterRpmVal()); - oldClusterSpd = (int)round(getClusterSpeedVal()); + float s = getClusterSpeedVal(); + oldClusterSpd = (s >= 0.0f) ? (int)round(s) : -999; float c = getClusterCoolantVal(); oldClusterCoolant = (c > -900.0f) ? (int)round(c) : -999; oldClusterVolt = getClusterVoltVal(); oldClusterGpsLock = isClusterSpeedFromGps(); + oldClusterEco = isEcoOptimal; lastClusterUpdate = millis(); } -// Helper: draw 5 top pagination dots (page 4 active) +// Helper: draw 4 top pagination dots (page 0 active) inline void drawClusterDots(int x_offset = 0) { - uint16_t actCol = isRainbowTheme() ? getRainbowScreenColor(188, 2) : (isYapaTheme() ? getYapaColor(4) : getThemePrimaryColor()); - tft.fillCircle(132 + x_offset, 2, 2, 0x5AEB); - tft.fillCircle(146 + x_offset, 2, 2, 0x5AEB); - tft.fillCircle(160 + x_offset, 2, 2, 0x5AEB); - tft.fillCircle(174 + x_offset, 2, 2, 0x5AEB); - tft.fillCircle(188 + x_offset, 2, 2, actCol); + uint16_t actCol = isRainbowTheme() ? getRainbowScreenColor(139, 2) : (isYapaTheme() ? getYapaColor(0) : getThemePrimaryColor()); + tft.fillCircle(139 + x_offset, 2, 2, actCol); + tft.fillCircle(153 + x_offset, 2, 2, 0x5AEB); + tft.fillCircle(167 + x_offset, 2, 2, 0x5AEB); + tft.fillCircle(181 + x_offset, 2, 2, 0x5AEB); } -// Draw static framing of all 4 cards +// Draw static framing of all 4 cards (units and titles drawn ONCE without flicker) inline void drawClusterBaseCards(int x_offset = 0) { uint16_t cardBg = 0x10A2; uint16_t border = 0x2945; @@ -90,54 +97,47 @@ inline void drawClusterBaseCards(int x_offset = 0) { uint16_t cCool = isRainbowTheme() ? getRainbowScreenColor(20, 150) : (isYapaTheme() ? getYapaColor(2) : priCol); uint16_t cVolt = isRainbowTheme() ? getRainbowScreenColor(180, 150) : (isYapaTheme() ? getYapaColor(3) : accCol); - // 1. TACHOMETER CARD (x: 8, y: 10, w: 304, h: 42) - tft.fillRoundRect(8 + x_offset, 10, 304, 42, 5, cardBg); - tft.drawRoundRect(8 + x_offset, 10, 304, 42, 5, border); - tft.fillRoundRect(12 + x_offset, 14, 3, 34, 1, cRpm); - drawPidIcon(18 + x_offset, 14, "010C"); - tft.setTextColor(0x8410, cardBg); - tft.drawString("RPM", 38 + x_offset, 16, 2); - // Tachometer background track (x: 76 to 216, w: 140, h: 12) - tft.fillRoundRect(76 + x_offset, 16, 140, 12, 3, 0x0841); - // RPM ticks - tft.drawString("1 2 3 4 5 6 7", 86 + x_offset, 31, 1); + // 1. TACHOMETER CARD (x: 8, y: 8, w: 304, h: 50) + tft.fillRoundRect(8 + x_offset, 8, 304, 50, 5, cardBg); + tft.drawRoundRect(8 + x_offset, 8, 304, 50, 5, border); + // Tachometer background rail on the LEFT (x: 18 to 90, w: 72, h: 14) + tft.fillRoundRect(18 + x_offset, 15, 72, 14, 3, 0x0841); + if (isYapaTheme()) { + int sx = 20 + x_offset; + char buf[2] = {0, 0}; + for (int i = 0; i < 7; i++) { + buf[0] = '1' + i; + tft.setTextColor(getYapaColor(i), cardBg); + tft.drawString(buf, sx, 32, 1); + sx += tft.textWidth(buf, 1) + tft.textWidth(" ", 1); + } + } else { + tft.setTextColor(cRpm, cardBg); + tft.drawString("1 2 3 4 5 6 7", 20 + x_offset, 32, 1); + } + // "RPM" unit on the far right in Chakra Petch Bold Italic (aligned with foot of digits at y = 55) + drawRpm_AA(tft, 242 + x_offset, 35, cRpm, cardBg, 240, isRainbowTheme()); - // 2. SPEEDOMETER CARD (x: 8, y: 56, w: 304, h: 76) - tft.fillRoundRect(8 + x_offset, 56, 304, 76, 5, cardBg); - tft.drawRoundRect(8 + x_offset, 56, 304, 76, 5, border); - tft.fillRoundRect(12 + x_offset, 60, 3, 68, 1, cSpd); - drawPidIcon(18 + x_offset, 60, "010D"); - tft.setTextColor(0x8410, cardBg); - tft.drawString("VELOCIDAD", 38 + x_offset, 62, 2); - // Background speed glow rail - tft.fillRoundRect(18 + x_offset, 124, 284, 3, 1, 0x0841); + // 2. SPEEDOMETER CARD (x: 8, y: 62, w: 304, h: 86) + tft.fillRoundRect(8 + x_offset, 62, 304, 86, 5, cardBg); + tft.drawRoundRect(8 + x_offset, 62, 304, 86, 5, border); + // Large anti-aliased "KM/H" (Chakra Petch Bold Italic) at far right (baseline at y = 145) + drawKmH_AA(tft, 198 + x_offset, 117, cSpd, cardBg, 240, isRainbowTheme()); - // 3. COOLANT TEMP CARD (x: 8, y: 136, w: 148, h: 98) - tft.fillRoundRect(8 + x_offset, 136, 148, 98, 5, cardBg); - tft.drawRoundRect(8 + x_offset, 136, 148, 98, 5, border); - tft.fillRoundRect(12 + x_offset, 140, 3, 90, 1, cCool); - drawPidIcon(18 + x_offset, 140, "0105"); - tft.setTextColor(0x8410, cardBg); - tft.drawString("REFRIG.", 38 + x_offset, 142, 2); - // Thermal bar rail (x: 18, y: 196, w: 128, h: 8) - tft.fillRoundRect(18 + x_offset, 196, 128, 8, 3, 0x0841); - tft.drawString("40`C", 18 + x_offset, 210, 1); - tft.drawString("120`C", 112 + x_offset, 210, 1); + // 3. COOLANT TEMP CARD (x: 8, y: 152, w: 148, h: 84) + tft.fillRoundRect(8 + x_offset, 152, 148, 84, 5, cardBg); + tft.drawRoundRect(8 + x_offset, 152, 148, 84, 5, border); + drawTitRef_AA(tft, 28 + x_offset, 158, cCool, cardBg, 240, isRainbowTheme()); + drawDegreeC_AA(tft, 117 + x_offset, 198, cCool, cardBg, 240, isRainbowTheme()); - // 4. BATTERY VOLTAGE CARD (x: 164, y: 136, w: 148, h: 98) - tft.fillRoundRect(164 + x_offset, 136, 148, 98, 5, cardBg); - tft.drawRoundRect(164 + x_offset, 136, 148, 98, 5, border); - tft.fillRoundRect(168 + x_offset, 140, 3, 90, 1, cVolt); - drawPidIcon(174 + x_offset, 140, "ATRV"); - tft.setTextColor(0x8410, cardBg); - tft.drawString("BATERIA", 194 + x_offset, 142, 2); - // Voltage bar rail (x: 174, y: 196, w: 128, h: 8) - tft.fillRoundRect(174 + x_offset, 196, 128, 8, 3, 0x0841); - tft.drawString("10V", 174 + x_offset, 210, 1); - tft.drawString("16V", 272 + x_offset, 210, 1); + // 4. BATTERY VOLTAGE CARD (x: 164, y: 152, w: 148, h: 84) + tft.fillRoundRect(164 + x_offset, 152, 148, 84, 5, cardBg); + tft.drawRoundRect(164 + x_offset, 152, 148, 84, 5, border); + drawTitBat_AA(tft, 208 + x_offset, 158, cVolt, cardBg, 240, isRainbowTheme()); + drawVolt_AA(tft, 286 + x_offset, 198, cVolt, cardBg, 240, isRainbowTheme()); } -// Initial direct draw of Page 4 +// Initial direct draw of Page 0 inline void drawClusterScreen(int x_offset = 0) { tft.fillScreen(TFT_BLACK); drawClusterDots(x_offset); @@ -149,14 +149,18 @@ inline void drawClusterScreen(int x_offset = 0) { oldClusterCoolant = -999; oldClusterVolt = -999.0f; oldClusterGpsLock = false; + oldClusterEco = !isEcoOptimal; + oldClusterCoolCol = 0; + oldClusterVoltCol = 0; + oldClusterSegs = -1; void updateClusterScreen(bool forceRedraw = false); updateClusterScreen(true); } -// Live zero-flicker non-blocking update of Page 4 +// Live zero-flicker non-blocking update of Page 0 (Cockpit Instrumental) inline void updateClusterScreen(bool forceRedraw) { - if (currentScreenPage != 4 || fullScreenCell != -1) return; + if (currentScreenPage != 0 || fullScreenCell != -1) return; uint16_t cardBg = 0x10A2; uint16_t priCol = getThemePrimaryColor(); @@ -175,33 +179,57 @@ inline void updateClusterScreen(bool forceRedraw) { if (forceRedraw || abs(curRpm - oldClusterRpm) >= 20 || (curRpm == 0 && oldClusterRpm != 0)) { oldClusterRpm = curRpm; - // Erase numeric text area - tft.fillRect(222, 14, 84, 26, cardBg); - tft.setTextPadding(0); - if (curRpm > 0) { - uint16_t rpmCol = (curRpm >= 6000) ? TFT_RED : ((curRpm >= 4500) ? 0xFD60 : cRpm); - tft.setTextColor(rpmCol, cardBg); - String rpmStr = String(curRpm); - tft.drawRightString(rpmStr, 302, 16, 4); - } else { - tft.setTextColor(0x632C, cardBg); - tft.drawRightString("---", 302, 16, 4); + // Segmented Tachometer Bar on the LEFT (10 segments between x: 20 and 88) + int activeSegs = map(constrain(curRpm, 0, 7000), 0, 7000, 0, 10); + if (activeSegs != oldClusterSegs || forceRedraw) { + oldClusterSegs = activeSegs; + for (int s = 0; s < 10; s++) { + int sx = 20 + s * 7; + uint16_t segCol; + if (s < activeSegs) { + if (s >= 8) segCol = TFT_RED; + else if (s >= 6) segCol = 0xFD60; + else segCol = isRainbowTheme() ? getRainbowScreenColor(sx, 20) : (isYapaTheme() ? getYapaColor(s) : cRpm); + } else { + segCol = 0x0841; // inactive dark track + } + tft.fillRect(sx, 17, 5, 10, segCol); + } } - // Segmented Tachometer Bar (12 segments between x: 78 and 214) - // 0 to 7000 RPM -> ~583 RPM per segment - int activeSegs = map(constrain(curRpm, 0, 7000), 0, 7000, 0, 12); - for (int s = 0; s < 12; s++) { - int sx = 78 + s * 11; - uint16_t segCol; - if (s < activeSegs) { - if (s >= 10) segCol = TFT_RED; - else if (s >= 8) segCol = 0xFD60; - else segCol = isRainbowTheme() ? getRainbowScreenColor(sx, 20) : cRpm; + // RPM Numeric Value (44pt) rendered with zero-flicker line buffer (x: 100..238, rightX: 238) + bool rainbowTxt = isRainbowTheme(); + if (curRpm > 0) { + uint16_t rpmCol = (curRpm >= 6000) ? TFT_RED : ((curRpm >= 4500) ? 0xFD60 : cRpm); + String rpmStr = String(curRpm); + drawDseg7RightFast44(tft, rpmStr.c_str(), 238, 11, rpmCol, cardBg, 138, rainbowTxt && (rpmCol != TFT_RED)); + } else { + drawDseg7RightFast44(tft, "---", 238, 11, cRpm, cardBg, 138, rainbowTxt); + } + } + + // ECO Indicator in Card 1 centered directly above RPM unit (x = 248, y = 14, w = 48, h = 17) + if (forceRedraw || isEcoOptimal != oldClusterEco) { + oldClusterEco = isEcoOptimal; + if (isEcoOptimal) { + tft.fillRoundRect(248, 14, 48, 17, 3, 0x0202); + if (isYapaTheme()) { + char buf[2] = {0, 0}; + const char *ecoStr = "ECO"; + int ex = 260; + for (int i = 0; i < 3; i++) { + buf[0] = ecoStr[i]; + tft.setTextColor(getYapaColor(i), 0x0202); + tft.drawString(buf, ex, 15, 2); + ex += tft.textWidth(buf, 2); + } } else { - segCol = 0x0841; // inactive dark track + tft.setTextColor(TFT_WHITE, 0x0202); + tft.drawString("ECO", 260, 15, 2); } - tft.fillRect(sx, 18, 9, 8, segCol); + tft.drawRoundRect(248, 14, 48, 17, 3, 0x07E0); + } else { + tft.fillRect(248, 14, 48, 17, cardBg); } } @@ -209,50 +237,31 @@ inline void updateClusterScreen(bool forceRedraw) { // 2. SPEEDOMETER (VELOCIDAD) // ------------------------------------------------------------- float curSpdF = getClusterSpeedVal(); - int curSpd = (int)round(curSpdF); + int curSpd = (curSpdF >= 0.0f) ? (int)round(curSpdF) : -999; bool isGps = isClusterSpeedFromGps(); - if (forceRedraw || curSpd != oldClusterSpd || isGps != oldClusterGpsLock) { - oldClusterSpd = curSpd; + // GPS Satellite icon in Card 2 centered directly above KM/H (where ECO was): x = 243, y = 82, w = 18, h = 18 + // Only shows satellite icon if speed comes from GPS; clears when speed is from OBD2 + if (forceRedraw || isGps != oldClusterGpsLock) { oldClusterGpsLock = isGps; - - // Source Pill (GPS / OBD) - tft.fillRect(116, 61, 46, 14, cardBg); if (isGps) { - tft.fillRoundRect(116, 61, 42, 14, 3, 0x0254); - tft.setTextColor(0x07FF, 0x0254); - tft.drawString("GPS", 125, 63, 1); + drawGpsSatelliteIcon(tft, 243, 82, 0x07FF, 0x07FF, cardBg, 2); } else { - tft.fillRoundRect(116, 61, 42, 14, 3, 0x18C3); - tft.setTextColor(0x8410, 0x18C3); - tft.drawString("OBD", 125, 63, 1); + tft.fillRect(238, 80, 28, 22, cardBg); } + } - // Giant Speed Display - tft.fillRect(166, 60, 138, 56, cardBg); - String spdStr = String(curSpd); + if (forceRedraw || curSpd != oldClusterSpd) { + oldClusterSpd = curSpd; - tft.loadFont(DSEG7_Modern_BoldItalic_44); - uint16_t spdCol = isRainbowTheme() ? getRainbowScreenColor(210, 80) : cSpd; - tft.setTextColor(spdCol, cardBg); - tft.drawRightString(spdStr, 248, 64, 1); - tft.unloadFont(); - - // Unit "km/h" - tft.setTextColor(0xBDD7, cardBg); - tft.drawString("km/h", 254, 88, 2); - - // Dynamic Bottom Speed Rail (w = 284) - int fillSpdW = map(constrain(curSpd, 0, 200), 0, 200, 0, 284); - tft.fillRect(18, 124, 284, 3, 0x0841); - if (fillSpdW > 0) { - if (isRainbowTheme()) { - for (int bx = 0; bx < fillSpdW; bx++) { - tft.drawFastVLine(18 + bx, 124, 3, getRainbowScreenColor(18 + bx, 124)); - } - } else { - tft.fillRect(18, 124, fillSpdW, 3, cSpd); - } + // Giant Speed Display using DSEG7 80pt rendered with zero-flicker line buffer (x: 14..194, rightX: 194) + if (curSpd >= 0) { + String spdStr = String(curSpd); + uint16_t spdCol = isRainbowTheme() ? getRainbowScreenColor(194, 105) : cSpd; + drawDseg7RightFast80(tft, spdStr.c_str(), 194, 65, spdCol, cardBg, 180, isRainbowTheme()); + } else { + uint16_t spdCol = isRainbowTheme() ? getRainbowScreenColor(194, 105) : cSpd; + drawDseg7RightFast80(tft, "---", 194, 65, spdCol, cardBg, 180, isRainbowTheme()); } } @@ -264,27 +273,18 @@ inline void updateClusterScreen(bool forceRedraw) { if (forceRedraw || curCool != oldClusterCoolant) { oldClusterCoolant = curCool; - tft.fillRect(20, 158, 130, 36, cardBg); if (curCool > -900) { uint16_t coolCol = (curCool >= 105) ? TFT_RED : ((curCool < 70) ? 0x05BF : cCool); - tft.setTextColor(coolCol, cardBg); String valStr = String(curCool); - tft.loadFont(DSEG7_Modern_BoldItalic_34); - tft.drawRightString(valStr, 102, 160, 1); - tft.unloadFont(); + bool rainbowTxt = isRainbowTheme(); + drawDseg7RightFast44(tft, valStr.c_str(), 112, 179, coolCol, cardBg, 102, rainbowTxt && (coolCol != TFT_RED)); - draw7SegDegreeC(tft, 106, 164, coolCol, cardBg, isRainbowTheme()); - - // Progress bar (40 to 120 °C) - int barW = map(constrain(curCool, 40, 120), 40, 120, 0, 128); - tft.fillRect(18, 196, 128, 8, 0x0841); - if (barW > 0) { - tft.fillRoundRect(18, 196, barW, 8, 2, coolCol); + if (coolCol != oldClusterCoolCol || forceRedraw) { + oldClusterCoolCol = coolCol; + drawDegreeC_AA(tft, 117, 198, coolCol, cardBg, 240, rainbowTxt); } } else { - tft.setTextColor(0x632C, cardBg); - tft.drawString("---", 60, 164, 4); - tft.fillRect(18, 196, 128, 8, 0x0841); + drawDseg7RightFast44(tft, "---", 112, 179, cCool, cardBg, 102, isRainbowTheme()); } } @@ -295,35 +295,24 @@ inline void updateClusterScreen(bool forceRedraw) { if (forceRedraw || abs(curVolt - oldClusterVolt) >= 0.08f) { oldClusterVolt = curVolt; - tft.fillRect(174, 158, 130, 36, cardBg); if (curVolt > 0.0f) { uint16_t voltCol = (curVolt < 11.8f || curVolt > 15.0f) ? TFT_RED : ((curVolt >= 13.5f) ? 0x07E0 : cVolt); String voltStr = String(curVolt, 1); + bool rainbowTxt = isRainbowTheme(); + drawDseg7RightFast44(tft, voltStr.c_str(), 282, 179, voltCol, cardBg, 112, rainbowTxt && (voltCol != TFT_RED)); - tft.loadFont(DSEG7_Modern_BoldItalic_34); - tft.setTextColor(voltCol, cardBg); - tft.drawRightString(voltStr, 268, 160, 1); - tft.unloadFont(); - - tft.setTextColor(0xBDD7, cardBg); - tft.drawString("V", 274, 172, 2); - - // Voltage bar (10.0 to 16.0 V) - int vBarW = map(constrain((int)(curVolt * 10), 100, 160), 100, 160, 0, 128); - tft.fillRect(174, 196, 128, 8, 0x0841); - if (vBarW > 0) { - tft.fillRoundRect(174, 196, vBarW, 8, 2, voltCol); + if (voltCol != oldClusterVoltCol || forceRedraw) { + oldClusterVoltCol = voltCol; + drawVolt_AA(tft, 286, 198, voltCol, cardBg, 240, rainbowTxt); } } else { - tft.setTextColor(0x632C, cardBg); - tft.drawString("---", 216, 164, 4); - tft.fillRect(174, 196, 128, 8, 0x0841); + drawDseg7RightFast44(tft, "---", 282, 179, cVolt, cardBg, 112, isRainbowTheme()); } } } -// Render Page 4 slice-by-slice into sprite (for double-buffered transitions) -inline void renderPage4ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) { +// Render Cluster Screen (Page 0) slice-by-slice into sprite (for double-buffered transitions) +inline void renderClusterScreenToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) { uint16_t cardBg = 0x10A2; uint16_t border = 0x2945; uint16_t priCol = getThemePrimaryColor(); @@ -334,157 +323,148 @@ inline void renderPage4ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s uint16_t cCool = isRainbowTheme() ? getRainbowScreenColor(20, 150) : (isYapaTheme() ? getYapaColor(2) : priCol); uint16_t cVolt = isRainbowTheme() ? getRainbowScreenColor(180, 150) : (isYapaTheme() ? getYapaColor(3) : accCol); - // Pagination dots on top slice (page 4 active) + // Pagination dots on top slice (page 0 active of 4 pages) if (y_slice == 0) { - uint16_t actCol = isRainbowTheme() ? getRainbowScreenColor(188, 2) : (isYapaTheme() ? getYapaColor(4) : priCol); - s.fillCircle(132, 2, 2, 0x5AEB); - s.fillCircle(146, 2, 2, 0x5AEB); - s.fillCircle(160, 2, 2, 0x5AEB); - s.fillCircle(174, 2, 2, 0x5AEB); - s.fillCircle(188, 2, 2, actCol); + uint16_t actCol = isRainbowTheme() ? getRainbowScreenColor(139, 2) : (isYapaTheme() ? getYapaColor(0) : priCol); + s.fillCircle(139, 2, 2, actCol); + s.fillCircle(153, 2, 2, 0x5AEB); + s.fillCircle(167, 2, 2, 0x5AEB); + s.fillCircle(181, 2, 2, 0x5AEB); } - // 1. TACHOMETER CARD (y: 10 to 52) - int c0Y = 10 - y_slice; - if (c0Y < slice_h && c0Y + 42 > 0) { - s.fillRoundRect(8, c0Y, 304, 42, 5, cardBg); - s.drawRoundRect(8, c0Y, 304, 42, 5, border); - s.fillRoundRect(12, c0Y + 4, 3, 34, 1, cRpm); - drawPidIconToSprite(s, 18, c0Y + 4, "010C", y_slice, slice_h, 10); - s.setTextColor(0x8410, cardBg); - s.drawString("RPM", 38, c0Y + 6, 2); - - // Tachometer background rail - s.fillRoundRect(76, c0Y + 6, 140, 12, 3, 0x0841); - s.drawString("1 2 3 4 5 6 7", 86, c0Y + 21, 1); + // 1. TACHOMETER CARD (y: 8 to 58) + int c0Y = 8 - y_slice; + if (c0Y < slice_h && c0Y + 50 > 0) { + s.fillRoundRect(8, c0Y, 304, 50, 5, cardBg); + s.drawRoundRect(8, c0Y, 304, 50, 5, border); + s.setTextColor(cRpm, cardBg); + // Tachometer background rail on the LEFT + s.fillRoundRect(18, c0Y + 7, 72, 14, 3, 0x0841); + if (isYapaTheme()) { + int sx = 20; + char buf[2] = {0, 0}; + for (int i = 0; i < 7; i++) { + buf[0] = '1' + i; + s.setTextColor(getYapaColor(i), cardBg); + s.drawString(buf, sx, c0Y + 24, 1); + sx += s.textWidth(buf, 1) + s.textWidth(" ", 1); + } + } else { + s.setTextColor(cRpm, cardBg); + s.drawString("1 2 3 4 5 6 7", 20, c0Y + 24, 1); + } float curRpmF = getClusterRpmVal(); int curRpm = (int)round(curRpmF); + bool rainbowTxt = isRainbowTheme(); + int digitY = c0Y + 3; if (curRpm > 0) { uint16_t rpmCol = (curRpm >= 6000) ? TFT_RED : ((curRpm >= 4500) ? 0xFD60 : cRpm); - s.setTextColor(rpmCol, cardBg); - s.drawRightString(String(curRpm), 302, c0Y + 6, 4); + String rpmStr = String(curRpm); + drawDseg7RightString44(s, rpmStr.c_str(), 238, digitY, rpmCol, cardBg, 0, slice_h, rainbowTxt && (rpmCol != TFT_RED)); - int activeSegs = map(constrain(curRpm, 0, 7000), 0, 7000, 0, 12); - for (int seg = 0; seg < 12; seg++) { - int sx = 78 + seg * 11; + int activeSegs = map(constrain(curRpm, 0, 7000), 0, 7000, 0, 10); + for (int seg = 0; seg < 10; seg++) { + int sx = 20 + seg * 7; uint16_t segCol = 0x0841; if (seg < activeSegs) { - if (seg >= 10) segCol = TFT_RED; - else if (seg >= 8) segCol = 0xFD60; - else segCol = isRainbowTheme() ? getRainbowScreenColor(sx, 20) : cRpm; + if (seg >= 8) segCol = TFT_RED; + else if (seg >= 6) segCol = 0xFD60; + else segCol = isRainbowTheme() ? getRainbowScreenColor(sx, 20) : (isYapaTheme() ? getYapaColor(seg) : cRpm); } - s.fillRect(sx, c0Y + 8, 9, 8, segCol); + s.fillRect(sx, c0Y + 9, 5, 10, segCol); } } else { - s.setTextColor(0x632C, cardBg); - s.drawRightString("---", 302, c0Y + 6, 4); + drawDseg7RightString44(s, "---", 238, digitY, cRpm, cardBg, 0, slice_h, rainbowTxt); + } + + drawRpm_AA(s, 242, c0Y + 27, cRpm, cardBg, slice_h, isRainbowTheme()); + + if (isEcoOptimal) { + s.fillRoundRect(248, c0Y + 6, 48, 17, 3, 0x0202); + if (isYapaTheme()) { + char buf[2] = {0, 0}; + const char *ecoStr = "ECO"; + int ex = 260; + for (int i = 0; i < 3; i++) { + buf[0] = ecoStr[i]; + s.setTextColor(getYapaColor(i), 0x0202); + s.drawString(buf, ex, c0Y + 7, 2); + ex += s.textWidth(buf, 2); + } + } else { + s.setTextColor(TFT_WHITE, 0x0202); + s.drawString("ECO", 260, c0Y + 7, 2); + } + s.drawRoundRect(248, c0Y + 6, 48, 17, 3, 0x07E0); } } - // 2. SPEEDOMETER CARD (y: 56 to 132) - int c1Y = 56 - y_slice; - if (c1Y < slice_h && c1Y + 76 > 0) { - s.fillRoundRect(8, c1Y, 304, 76, 5, cardBg); - s.drawRoundRect(8, c1Y, 304, 76, 5, border); - s.fillRoundRect(12, c1Y + 4, 3, 68, 1, cSpd); - drawPidIconToSprite(s, 18, c1Y + 4, "010D", y_slice, slice_h, 56); - s.setTextColor(0x8410, cardBg); - s.drawString("VELOCIDAD", 38, c1Y + 6, 2); + // 2. SPEEDOMETER CARD (y: 62 to 148) + int c1Y = 62 - y_slice; + if (c1Y < slice_h && c1Y + 86 > 0) { + s.fillRoundRect(8, c1Y, 304, 86, 5, cardBg); + s.drawRoundRect(8, c1Y, 304, 86, 5, border); bool isGps = isClusterSpeedFromGps(); if (isGps) { - s.fillRoundRect(116, c1Y + 5, 42, 14, 3, 0x0254); - s.setTextColor(0x07FF, 0x0254); - s.drawString("GPS", 125, c1Y + 7, 1); + drawGpsSatelliteIcon(s, 243, c1Y + 20, 0x07FF, 0x07FF, cardBg, 2); + } + + float curSpdF = getClusterSpeedVal(); + int curSpd = (curSpdF >= 0.0f) ? (int)round(curSpdF) : -999; + int digitY = c1Y + 3; + uint16_t spdCol = isRainbowTheme() ? getRainbowScreenColor(240, 105) : cSpd; + if (curSpd >= 0) { + String spdStr = String(curSpd); + drawDseg7RightString80(s, spdStr.c_str(), 194, digitY, spdCol, cardBg, 0, slice_h, isRainbowTheme()); } else { - s.fillRoundRect(116, c1Y + 5, 42, 14, 3, 0x18C3); - s.setTextColor(0x8410, 0x18C3); - s.drawString("OBD", 125, c1Y + 7, 1); + drawDseg7RightString80(s, "---", 194, digitY, spdCol, cardBg, 0, slice_h, isRainbowTheme()); } - int curSpd = (int)round(getClusterSpeedVal()); - String spdStr = String(curSpd); - s.loadFont(DSEG7_Modern_BoldItalic_44); - uint16_t spdCol = isRainbowTheme() ? getRainbowScreenColor(210, 80) : cSpd; - s.setTextColor(spdCol, cardBg); - s.drawRightString(spdStr, 248, c1Y + 8, 1); - s.unloadFont(); - - s.setTextColor(0xBDD7, cardBg); - s.drawString("km/h", 254, c1Y + 32, 2); - - int fillSpdW = map(constrain(curSpd, 0, 200), 0, 200, 0, 284); - s.fillRect(18, c1Y + 68, 284, 3, 0x0841); - if (fillSpdW > 0) { - s.fillRect(18, c1Y + 68, fillSpdW, 3, cSpd); - } + drawKmH_AA(s, 198, c1Y + 55, cSpd, cardBg, slice_h, isRainbowTheme()); } - // 3. COOLANT TEMP CARD (y: 136 to 234) - int c2Y = 136 - y_slice; - if (c2Y < slice_h && c2Y + 98 > 0) { - s.fillRoundRect(8, c2Y, 148, 98, 5, cardBg); - s.drawRoundRect(8, c2Y, 148, 98, 5, border); - s.fillRoundRect(12, c2Y + 4, 3, 90, 1, cCool); - drawPidIconToSprite(s, 18, c2Y + 4, "0105", y_slice, slice_h, 136); - s.setTextColor(0x8410, cardBg); - s.drawString("REFRIG.", 38, c2Y + 6, 2); - - s.fillRect(18, c2Y + 60, 128, 8, 0x0841); - s.drawString("40`C", 18, c2Y + 74, 1); - s.drawString("120`C", 112, c2Y + 74, 1); + // 3. COOLANT TEMP CARD (y: 152 to 236) + int c2Y = 152 - y_slice; + if (c2Y < slice_h && c2Y + 84 > 0) { + s.fillRoundRect(8, c2Y, 148, 84, 5, cardBg); + s.drawRoundRect(8, c2Y, 148, 84, 5, border); + drawTitRef_AA(s, 28, c2Y + 6, cCool, cardBg, slice_h, isRainbowTheme()); float curCoolF = getClusterCoolantVal(); int curCool = (curCoolF > -900.0f) ? (int)round(curCoolF) : -999; if (curCool > -900) { uint16_t coolCol = (curCool >= 105) ? TFT_RED : ((curCool < 70) ? 0x05BF : cCool); - s.loadFont(DSEG7_Modern_BoldItalic_34); - s.setTextColor(coolCol, cardBg); - s.drawRightString(String(curCool), 102, c2Y + 24, 1); - s.unloadFont(); + String valStr = String(curCool); + bool rainbowTxt = isRainbowTheme(); + int rightX = (valStr.length() >= 3) ? 112 : 106; + drawDseg7RightString44(s, valStr.c_str(), rightX, c2Y + 27, coolCol, cardBg, 0, slice_h, rainbowTxt && (coolCol != TFT_RED)); - int barW = map(constrain(curCool, 40, 120), 40, 120, 0, 128); - if (barW > 0) { - s.fillRoundRect(18, c2Y + 60, barW, 8, 2, coolCol); - } + drawDegreeC_AA(s, 117, c2Y + 46, coolCol, cardBg, slice_h, isRainbowTheme()); } else { - s.setTextColor(0x632C, cardBg); - s.drawString("---", 60, c2Y + 28, 4); + drawDseg7RightString44(s, "---", 112, c2Y + 27, cCool, cardBg, 0, slice_h, isRainbowTheme()); } } - // 4. BATTERY VOLTAGE CARD (y: 136 to 234) - int c3Y = 136 - y_slice; - if (c3Y < slice_h && c3Y + 98 > 0) { - s.fillRoundRect(164, c3Y, 148, 98, 5, cardBg); - s.drawRoundRect(164, c3Y, 148, 98, 5, border); - s.fillRoundRect(168, c3Y + 4, 3, 90, 1, cVolt); - drawPidIconToSprite(s, 174, c3Y + 4, "ATRV", y_slice, slice_h, 136); - s.setTextColor(0x8410, cardBg); - s.drawString("BATERIA", 194, c3Y + 6, 2); - - s.fillRect(174, c3Y + 60, 128, 8, 0x0841); - s.drawString("10V", 174, c3Y + 74, 1); - s.drawString("16V", 272, c3Y + 74, 1); + // 4. BATTERY VOLTAGE CARD (y: 152 to 236) + int c3Y = 152 - y_slice; + if (c3Y < slice_h && c3Y + 84 > 0) { + s.fillRoundRect(164, c3Y, 148, 84, 5, cardBg); + s.drawRoundRect(164, c3Y, 148, 84, 5, border); + drawTitBat_AA(s, 208, c3Y + 6, cVolt, cardBg, slice_h, isRainbowTheme()); float curVolt = getClusterVoltVal(); if (curVolt > 0.0f) { uint16_t voltCol = (curVolt < 11.8f || curVolt > 15.0f) ? TFT_RED : ((curVolt >= 13.5f) ? 0x07E0 : cVolt); - s.loadFont(DSEG7_Modern_BoldItalic_34); - s.setTextColor(voltCol, cardBg); - s.drawRightString(String(curVolt, 1), 268, c3Y + 24, 1); - s.unloadFont(); - s.setTextColor(0xBDD7, cardBg); - s.drawString("V", 274, c3Y + 36, 2); + String voltStr = String(curVolt, 1); + bool rainbowTxt = isRainbowTheme(); + drawDseg7RightString44(s, voltStr.c_str(), 282, c3Y + 27, voltCol, cardBg, 0, slice_h, rainbowTxt && (voltCol != TFT_RED)); - int vBarW = map(constrain((int)(curVolt * 10), 100, 160), 100, 160, 0, 128); - if (vBarW > 0) { - s.fillRoundRect(174, c3Y + 60, vBarW, 8, 2, voltCol); - } + drawVolt_AA(s, 286, c3Y + 46, voltCol, cardBg, slice_h, isRainbowTheme()); } else { - s.setTextColor(0x632C, cardBg); - s.drawString("---", 216, c3Y + 28, 4); + drawDseg7RightString44(s, "---", 282, c3Y + 27, cVolt, cardBg, 0, slice_h, isRainbowTheme()); } } } + diff --git a/sketch/obd2_gauge/coords_font.h b/sketch/obd2_gauge/coords_font.h index 8ef9c53..28d4db0 100644 --- a/sketch/obd2_gauge/coords_font.h +++ b/sketch/obd2_gauge/coords_font.h @@ -609,6 +609,45 @@ inline void drawGpsNavSubword(T &dst, const char *str, int x, int y, uint16_t co } } +// Calculate exact pixel width of anti-aliased Subword string +inline int getNavSubwordWidth(const char *str) { + int totalW = 0; + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + int gIdx = -1; + if (c >= 'A' && c <= 'Z') { + gIdx = c - 'A'; + } else if (c >= 'a' && c <= 'z') { + gIdx = c - 'a'; + } else if (c >= '0' && c <= '9') { + gIdx = 26 + (c - '0'); + } else if (c == ':') { + gIdx = 36; + } else if (c == '.') { + gIdx = 37; + } else if (c == '-') { + gIdx = 38; + } else if ((uint8_t)c == 0xC2 && (uint8_t)str[i + 1] == 0xB0) { + gIdx = 39; + i++; + } else if ((uint8_t)c == 0xB0 || c == '`') { + gIdx = 39; + } else if (c == '/') { + gIdx = 40; + } else if (c == ' ') { + gIdx = 41; + } + + if (gIdx >= 0) { + CoordsGlyphAA g; + memcpy_P(&g, &s_navSubGlyphs[gIdx], sizeof(CoordsGlyphAA)); + totalW += g.xAdv; + } + } + return totalW; +} + + // 3. Draw 100% anti-aliased Coordinate Value (e.g. "-34.60371°" or "--.-----°") template inline void drawCoordsAAValue(T &dst, const char *str, int x, int y, uint16_t color, uint16_t bgCol, int clip_h = 240) { diff --git a/sketch/obd2_gauge/gps_clock.h b/sketch/obd2_gauge/gps_clock.h index ad52a26..75e6d13 100644 --- a/sketch/obd2_gauge/gps_clock.h +++ b/sketch/obd2_gauge/gps_clock.h @@ -348,12 +348,11 @@ inline void drawGpsClockScreen(int x_offset = 0) { uint16_t cardBorder = 0x2945; // Subtle edge border bool synced = isGpsSynced(); - // Center: 5 Pagination Indicator Dots [○ ○ ● ○ ○] (Page 2 of 5 active) - tft.fillCircle(132 + x_offset, 2, 2, 0x5AEB); // Page 0 inactive - tft.fillCircle(146 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive - tft.fillCircle(160 + x_offset, 2, 2, priCol); // Page 2 active - tft.fillCircle(174 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive - tft.fillCircle(188 + x_offset, 2, 2, 0x5AEB); // Page 4 inactive + // Center: 4 Pagination Indicator Dots [○ ○ ● ○] (Page 2 of 4 active) + tft.fillCircle(139 + x_offset, 2, 2, 0x5AEB); // Page 0 inactive + tft.fillCircle(153 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive + tft.fillCircle(167 + x_offset, 2, 2, priCol); // Page 2 active + tft.fillCircle(181 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive // 2. MAIN CLOCK CARD (y = 19 to 121, height = 102, width = 312, x = 4) int clkX = 4 + x_offset; diff --git a/sketch/obd2_gauge/gps_icon_aa_data.h b/sketch/obd2_gauge/gps_icon_aa_data.h index c68f13b..5654864 100644 --- a/sketch/obd2_gauge/gps_icon_aa_data.h +++ b/sketch/obd2_gauge/gps_icon_aa_data.h @@ -1,160 +1,155 @@ -// Generated from accurate vector model matching gps_icon.jpg / gps_icon.svg +// Generated from accurate vector model matching gps_icon.png / gps_icon.svg // 8-bit Subpixel Antialiased Alpha Maps (0..255) #pragma once #include static const uint8_t GPS_BODY_AA_18[324] PROGMEM = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 201, 255, 99, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 48, 26, 54, 255, 255, 250, 106, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 171, 255, 238, 115, 216, 211, 55, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 77, 255, 250, 255, 223, 33, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 32, 96, 255, 251, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 106, 240, 183, 165, 255, 243, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 172, 255, 255, 213, 0, 45, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 160, 255, 147, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 28, 27, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 2, 2, 4, 0, 56, 231, 231, 22, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 64, 252, 255, 209, 24, 1, 1, 0, 0, 0, + 0, 0, 0, 2, 0, 45, 184, 182, 102, 233, 174, 10, 0, 1, 0, 0, 0, 0, + 0, 0, 1, 4, 7, 209, 255, 255, 212, 34, 1, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 233, 254, 252, 250, 13, 0, 2, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 76, 155, 128, 249, 255, 130, 1, 3, 0, 0, 0, 0, 0, 0, 0, + 2, 5, 107, 249, 255, 101, 36, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 23, 255, 255, 153, 17, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 91, 118, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - static const uint8_t GPS_WAVE1_AA_18[324] PROGMEM = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 18, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 185, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 255, 199, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 254, 137, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 255, 44, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 51, 193, 255, 151, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 29, 227, 225, 229, 255, 255, 152, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 23, 199, 234, 226, 167, 66, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 12, 201, 107, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 32, 255, 162, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 1, 6, 0, 77, 253, 111, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 207, 252, 33, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 12, 11, 61, 198, 255, 125, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 174, 248, 242, 255, 255, 135, 0, 3, 1, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 104, 191, 190, 141, 51, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 2, 2, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - static const uint8_t GPS_WAVE2_AA_18[324] PROGMEM = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 96, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 255, 84, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 239, 82, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 243, 53, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 229, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 245, 137, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 210, 232, 42, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 182, 248, 112, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 201, 255, 142, 0, 0, 0, 0, - 0, 0, 0, 0, 88, 124, 88, 96, 142, 198, 254, 242, 128, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 234, 255, 255, 255, 255, 244, 151, 57, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 48, 94, 111, 103, 73, 25, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 12, 15, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 187, 210, 2, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 219, 241, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 7, 238, 212, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 51, 255, 158, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 162, 255, 69, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 6, 0, 73, 255, 201, 3, 2, 0, + 0, 0, 0, 0, 2, 1, 3, 3, 1, 0, 0, 69, 242, 248, 48, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 142, 255, 253, 81, 0, 3, 0, 0, + 0, 0, 3, 0, 79, 205, 175, 176, 211, 255, 255, 210, 60, 0, 3, 0, 0, 0, + 0, 0, 3, 0, 87, 239, 255, 255, 239, 184, 93, 6, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 7, 25, 25, 9, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 3, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - static const uint8_t GPS_BODY_AA_28[784] PROGMEM = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 146, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 213, 255, 255, 66, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 139, 255, 255, 244, 255, 201, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 49, 29, 0, 124, 255, 249, 251, 255, 220, 70, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 120, 235, 255, 242, 135, 38, 241, 255, 255, 155, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 94, 255, 255, 254, 255, 255, 106, 87, 210, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 200, 253, 251, 255, 252, 252, 227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 218, 253, 253, 255, 254, 254, 238, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 115, 40, 149, 255, 247, 252, 248, 255, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 54, 201, 255, 213, 33, 216, 255, 255, 255, 237, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 126, 250, 255, 243, 255, 133, 16, 130, 169, 137, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 155, 255, 251, 249, 255, 229, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 110, 255, 255, 255, 169, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 181, 234, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 32, 58, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 62, 231, 255, 65, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 3, 0, 98, 249, 255, 254, 234, 8, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 126, 255, 251, 253, 255, 127, 3, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 40, 114, 112, 26, 132, 255, 255, 246, 91, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 84, 246, 255, 255, 234, 53, 171, 225, 55, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 15, 234, 255, 249, 249, 255, 208, 5, 13, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 3, 54, 255, 253, 255, 254, 253, 249, 28, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 18, 252, 252, 251, 250, 253, 232, 13, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 21, 173, 87, 117, 255, 255, 255, 255, 111, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 51, 219, 255, 248, 65, 97, 187, 180, 89, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 1, 84, 243, 255, 245, 255, 136, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 24, 255, 254, 251, 255, 143, 5, 5, 3, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 3, 145, 255, 251, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 134, 69, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - static const uint8_t GPS_WAVE1_AA_28[784] PROGMEM = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 166, 81, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 221, 255, 245, 20, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 212, 252, 244, 26, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 249, 213, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 249, 243, 158, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 236, 239, 106, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 108, 240, 230, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 250, 233, 231, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 79, 76, 51, 68, 116, 193, 249, 232, 255, 139, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 119, 255, 255, 248, 246, 255, 255, 242, 224, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 142, 255, 255, 255, 249, 255, 229, 149, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 123, 149, 157, 140, 103, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 176, 241, 83, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 22, 245, 255, 162, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 32, 245, 254, 144, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 83, 255, 255, 105, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 0, 192, 254, 251, 38, 0, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 4, 4, 0, 0, 117, 255, 255, 177, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 131, 255, 253, 247, 40, 1, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2, 0, 42, 117, 91, 95, 141, 219, 255, 255, 255, 86, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 2, 219, 255, 255, 255, 255, 255, 255, 230, 78, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2, 1, 165, 255, 255, 255, 252, 218, 135, 24, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 50, 75, 73, 46, 7, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - static const uint8_t GPS_WAVE2_AA_28[784] PROGMEM = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 199, 227, 71, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 255, 255, 179, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 248, 252, 171, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 225, 232, 133, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 228, 225, 106, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149, 224, 226, 68, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 193, 238, 179, 21, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 243, 227, 119, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 180, 228, 189, 49, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 159, 221, 236, 154, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 139, 243, 233, 192, 34, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132, 240, 243, 241, 88, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 167, 228, 232, 242, 134, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 74, 144, 225, 244, 229, 235, 121, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 84, 218, 199, 159, 175, 166, 177, 221, 238, 247, 243, 253, 183, 77, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 214, 255, 255, 255, 255, 255, 244, 237, 247, 248, 186, 126, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 107, 238, 242, 231, 255, 245, 224, 196, 175, 102, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 21, 43, 63, 80, 70, 47, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 30, 88, 13, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 203, 255, 157, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 11, 230, 252, 197, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 15, 230, 255, 185, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 36, 248, 255, 164, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 83, 255, 255, 121, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 164, 255, 255, 58, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 29, 243, 254, 218, 6, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 162, 255, 255, 117, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 89, 255, 253, 228, 14, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 72, 248, 250, 255, 80, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 3, 3, 4, 4, 0, 0, 0, 107, 250, 250, 255, 138, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 67, 186, 255, 253, 255, 153, 0, 3, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 57, 125, 90, 71, 73, 96, 143, 207, 255, 255, 255, 254, 124, 0, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 12, 240, 255, 255, 255, 255, 255, 255, 255, 255, 255, 196, 62, 0, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 6, 191, 255, 255, 255, 255, 255, 255, 232, 171, 77, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 8, 63, 97, 115, 113, 93, 59, 17, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 3, 4, 4, 4, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; diff --git a/sketch/obd2_gauge/gps_module.h b/sketch/obd2_gauge/gps_module.h index b1b0072..f1c3657 100644 --- a/sketch/obd2_gauge/gps_module.h +++ b/sketch/obd2_gauge/gps_module.h @@ -149,7 +149,7 @@ extern Preferences pref; static const uint32_t CANDIDATE_BAUDS[4] = {38400, 115200, 9600, 57600}; static const char *const CANDIDATE_LABELS[4] = { - "BUSCANDO: 38400", "BUSCANDO: 115K", "BUSCANDO: 9600", "BUSCANDO: 57.6K"}; + "BUSCANDO: 38.4 KBPS", "BUSCANDO: 115.2 KBPS", "BUSCANDO: 9.6 KBPS", "BUSCANDO: 57.6 KBPS"}; static uint32_t activeGpsBaud = 38400; static bool gpsUartLocked = false; @@ -305,7 +305,17 @@ inline void processGPS() { gpsUartLocked = true; activeGpsBaud = CANDIDATE_BAUDS[gpsBaudScanIdx]; pref.putUInt("gps_baud", activeGpsBaud); // Guardar velocidad exacta en NVRAM! - snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "FIJADO: %lu BPS", (unsigned long)activeGpsBaud); + if (activeGpsBaud == 115200) { + snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "FIJADO: 115.2 KBPS"); + } else if (activeGpsBaud == 57600) { + snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "FIJADO: 57.6 KBPS"); + } else if (activeGpsBaud == 38400) { + snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "FIJADO: 38.4 KBPS"); + } else if (activeGpsBaud == 9600) { + snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "FIJADO: 9.6 KBPS"); + } else { + snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "FIJADO: %lu BPS", (unsigned long)activeGpsBaud); + } configureGpsModule(); } } @@ -533,6 +543,26 @@ inline bool isGpsSerialReceiving() { } inline bool isGpsUartLocked() { return gpsUartLocked; } +// DOP Precision Rating matching standard DOP table (dop.png) +inline const char *getGpsDopRating() { + if (gpsHdop < 1.0f) return "IDEAL"; + if (gpsHdop < 2.0f) return "EXCELENTE"; + if (gpsHdop < 5.0f) return "BUENA"; + if (gpsHdop < 10.0f) return "MODERADA"; + if (gpsHdop <= 20.0f) return "REGULAR"; + return "POBRE"; +} + +inline uint16_t getGpsDopColor() { + if (!isGpsSynced()) return 0x9CD3; + if (gpsHdop < 1.0f) return 0x07FF; // Cyan (Ideal) + if (gpsHdop < 2.0f) return 0x07E0; // Verde (Excelente) + if (gpsHdop < 5.0f) return 0xFFE0; // Amarillo (Buena) + if (gpsHdop < 10.0f) return 0xFD20; // Naranja (Moderada) + if (gpsHdop <= 20.0f) return 0xFA60; // Naranja oscuro (Regular) + return TFT_RED; // Rojo (Pobre) +} + inline const char *getGpsDiagMessage() { if (gpsIsSleeping) return "MODO REPOSO"; @@ -543,7 +573,7 @@ inline const char *getGpsDiagMessage() { return "SIN DATOS RX"; } if (isGpsSynced()) { - return "PRECISION OPTIMA"; + return getGpsDopRating(); } if (gpsSats > 0) { return "SINTONIZANDO SATS"; diff --git a/sketch/obd2_gauge/gps_nav.h b/sketch/obd2_gauge/gps_nav.h index b07aa7e..8e6af6f 100644 --- a/sketch/obd2_gauge/gps_nav.h +++ b/sketch/obd2_gauge/gps_nav.h @@ -40,9 +40,22 @@ static uint8_t oldNavWaveState = 255; static bool oldNavSynced = false; static uint32_t lastNavPageEnterTime = 0; +static TFT_eSprite sprNavDiag(&tft); +static bool sprNavDiagAllocated = false; + +inline void initNavDiagSprite() { + if (!sprNavDiagAllocated) { + if (sprNavDiag.createSprite(138, 16) != nullptr) { + sprNavDiagAllocated = true; + } + } +} + // Synchronize tracking variables without touching TFT display (zero flicker) -inline void syncGpsNavTrackers() { - lastNavPageEnterTime = millis(); +inline void syncGpsNavTrackers(bool isPageEnter = false) { + if (isPageEnter) { + lastNavPageEnterTime = millis(); + } bool synced = isGpsSynced(); bool altValid = isGpsAltitudeValid(); lastNavAltRound = altValid ? (int)round(gpsAltitudeM) : -999; @@ -85,6 +98,32 @@ inline void drawLargeSatelliteIconSprite(TFT_eSprite &s, int x, int y, uint16_t drawLargeGpsSatelliteIcon(s, x, y, TFT_WHITE, color, bgColor, waveState); } +// Draw diagnostic message with static, right-justified "KBPS" unit for scanning/locked baud +template +inline void drawGpsNavDiag(T &dst, const char *msg, int c2X, int c2Y, uint16_t color, uint16_t bgCol, int clip_h = 240) { + if (strncmp(msg, "BUSCANDO", 8) == 0 || strncmp(msg, "FIJADO", 6) == 0) { + bool isBuscando = (strncmp(msg, "BUSCANDO", 8) == 0); + const char *prefix = isBuscando ? "BUSCANDO:" : "FIJADO:"; + drawGpsNavSubword(dst, prefix, c2X + 16, c2Y + 90, color, bgCol, clip_h); + drawGpsNavSubword(dst, "KBPS", c2X + 116, c2Y + 90, color, bgCol, clip_h); + + const char *p = msg + (isBuscando ? 8 : 6); + while (*p == ':' || *p == ' ') p++; + char valBuf[10]; + int vi = 0; + while (*p != '\0' && *p != ' ' && vi < (int)sizeof(valBuf) - 1) { + valBuf[vi++] = *p++; + } + valBuf[vi] = '\0'; + + int valW = getNavSubwordWidth(valBuf); + int valX = c2X + 113 - valW; + drawGpsNavSubword(dst, valBuf, valX, c2Y + 90, color, bgCol, clip_h); + } else { + drawGpsNavSubword(dst, msg, c2X + 16, c2Y + 90, color, bgCol, clip_h); + } +} + // Draw full Page 3 (GPS Navigation & Telemetry Screen: Altitud, Rumbo, Satélites, Coordenadas) inline void drawGpsNavScreen(int x_offset = 0) { if (x_offset == 0) { @@ -103,12 +142,11 @@ inline void drawGpsNavScreen(int x_offset = 0) { uint16_t cardBorder = 0x2945; // Subtle edge border bool synced = isGpsSynced(); - // Center: 5 Pagination Indicator Dots [○ ○ ○ ● ○] (Page 3 of 5 active) - tft.fillCircle(132 + x_offset, 2, 2, 0x5AEB); // Page 0 inactive - tft.fillCircle(146 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive - tft.fillCircle(160 + x_offset, 2, 2, 0x5AEB); // Page 2 inactive - tft.fillCircle(174 + x_offset, 2, 2, priCol); // Page 3 active - tft.fillCircle(188 + x_offset, 2, 2, 0x5AEB); // Page 4 inactive + // Center: 4 Pagination Indicator Dots [○ ○ ○ ●] (Page 3 of 4 active) + tft.fillCircle(139 + x_offset, 2, 2, 0x5AEB); // Page 0 inactive + tft.fillCircle(153 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive + tft.fillCircle(167 + x_offset, 2, 2, 0x5AEB); // Page 2 inactive + tft.fillCircle(181 + x_offset, 2, 2, priCol); // Page 3 active // 2. CARD 0 (Top Left): ALTITUD (x = 4, y = 19, w = 152, h = 102) int c0X = 4 + x_offset; @@ -124,7 +162,7 @@ inline void drawGpsNavScreen(int x_offset = 0) { bool altValid = isGpsAltitudeValid(); String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---"; - drawDseg7RightString34(tft, altStr.c_str(), c0X + 140, c0Y + 28, c0Col, cardBg, 0, 240, isRainbowTheme()); + drawDseg7RightString34(tft, altStr.c_str(), c0X + 140, c0Y + 28, c0Col, cardBg, 126, 240, isRainbowTheme()); // 3. CARD 1 (Top Right): RUMBO / BRÚJULA (x = 164, y = 19, w = 152, h = 102) int c1X = 164 + x_offset; @@ -137,7 +175,7 @@ inline void drawGpsNavScreen(int x_offset = 0) { bool hasCourse = synced && isGpsCourseValid(); String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---"; - drawDseg7RightString34(tft, crsStr.c_str(), c1X + 130, c1Y + 28, c1Col, cardBg, 0, 240, isRainbowTheme()); + drawDseg7RightString34(tft, crsStr.c_str(), c1X + 130, c1Y + 28, c1Col, cardBg, 95, 240, isRainbowTheme()); if (hasCourse) { tft.drawCircle(c1X + 137, c1Y + 33, 3, c1Col); tft.drawCircle(c1X + 137, c1Y + 33, 2, c1Col); @@ -164,13 +202,21 @@ inline void drawGpsNavScreen(int x_offset = 0) { // Large Satellites Count (DSEG7 34pt): Shows "---" while scanning/probing UART, "0" or count when locked String satStr = isGpsUartLocked() ? String(gpsSats) : "---"; - drawDseg7RightString34(tft, satStr.c_str(), c2X + 140, c2Y + 26, c2Col, cardBg, 0, 240, isRainbowTheme()); + drawDseg7RightString34(tft, satStr.c_str(), c2X + 140, c2Y + 26, c2Col, cardBg, 85, 240, isRainbowTheme()); // Telemetry details: HDOP precision & live diagnostic status String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---"); drawGpsNavSubword(tft, hdopStr.c_str(), c2X + 16, c2Y + 70, TFT_WHITE, cardBg, 240); - drawGpsNavSubword(tft, getGpsDiagMessage(), c2X + 16, c2Y + 90, synced ? 0x9CD3 : priCol, cardBg, 240); + uint16_t diagCol = synced ? getGpsDopColor() : priCol; + initNavDiagSprite(); + if (sprNavDiagAllocated) { + sprNavDiag.fillSprite(cardBg); + drawGpsNavDiag(sprNavDiag, getGpsDiagMessage(), -12, -88, diagCol, cardBg, 16); + sprNavDiag.pushSprite(c2X + 12, c2Y + 88); + } else { + drawGpsNavDiag(tft, getGpsDiagMessage(), c2X, c2Y, diagCol, cardBg, 240); + } // 5. CARD 3 (Bottom Right): COORDENADAS GPS (x = 164, y = 125, w = 152, h = 110) int c3X = 164 + x_offset; @@ -190,7 +236,7 @@ inline void drawGpsNavScreen(int x_offset = 0) { drawCoordsAAValue(tft, lonStr.c_str(), c3X + 16, c3Y + 76, c3Col, cardBg, 240); // Sync state trackers - syncGpsNavTrackers(); + syncGpsNavTrackers(true); } // 100% Flicker-Free Live Refresh for resting Page 3 (GPS Navigation & Telemetry) @@ -201,7 +247,7 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { // 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(); + syncGpsNavTrackers(false); return; } @@ -238,7 +284,7 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { oldNavCourse = gpsCourseDeg; lastNavHasCourse = hasCourse; String crsStr = hasCourse ? String(curCrsRound) : "---"; - drawDseg7RightString34(tft, crsStr.c_str(), c1X + 130, c1Y + 28, c1Col, cardBg, 85, 240, isRainbowTheme()); + drawDseg7RightString34(tft, crsStr.c_str(), c1X + 130, c1Y + 28, c1Col, cardBg, 95, 240, isRainbowTheme()); if (hasCourse) { tft.drawCircle(c1X + 137, c1Y + 33, 3, c1Col); @@ -287,8 +333,16 @@ inline void updateGpsNavScreen(bool forceRedraw = false) { strncpy(lastNavCardDiag, curDiag, sizeof(lastNavCardDiag) - 1); lastNavCardDiag[sizeof(lastNavCardDiag) - 1] = '\0'; lastNavDiagSynced = synced; - tft.fillRect(c2X + 16, c2Y + 88, 130, 16, cardBg); - drawGpsNavSubword(tft, curDiag, c2X + 16, c2Y + 90, synced ? 0x9CD3 : c2Col, cardBg, 240); + uint16_t diagCol = synced ? getGpsDopColor() : c2Col; + initNavDiagSprite(); + if (sprNavDiagAllocated) { + sprNavDiag.fillSprite(cardBg); + drawGpsNavDiag(sprNavDiag, curDiag, -12, -88, diagCol, cardBg, 16); + sprNavDiag.pushSprite(c2X + 12, c2Y + 88); + } else { + tft.fillRect(c2X + 12, c2Y + 88, 138, 16, cardBg); + drawGpsNavDiag(tft, curDiag, c2X, c2Y, diagCol, cardBg, 240); + } } // 3d. Large Satellite Icon: only redrawn if waveState or sync changes @@ -344,7 +398,7 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---"; int valY = c0Y + 28; if (valY < slice_h && valY + 38 > 0) { - drawDseg7RightString34(s, altStr.c_str(), c0X + 140, valY, c0Col, cardBg, 0, slice_h, isRainbowTheme()); + drawDseg7RightString34(s, altStr.c_str(), c0X + 140, valY, c0Col, cardBg, 126, slice_h, isRainbowTheme()); } } @@ -361,7 +415,7 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---"; int valY = c1Y + 28; if (valY < slice_h && valY + 38 > 0) { - drawDseg7RightString34(s, crsStr.c_str(), c1X + 130, valY, c1Col, cardBg, 0, slice_h, isRainbowTheme()); + drawDseg7RightString34(s, crsStr.c_str(), c1X + 130, valY, c1Col, cardBg, 95, slice_h, isRainbowTheme()); if (hasCourse) { int circY = c1Y + 33; if (circY + 4 > 0 && circY - 4 < slice_h) { @@ -392,11 +446,12 @@ inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s int satY = c2Y + 26; if (satY < slice_h && satY + 38 > 0) { String satStr = isGpsUartLocked() ? String(gpsSats) : "---"; - drawDseg7RightString34(s, satStr.c_str(), c2X + 140, satY, c2Col, cardBg, 0, slice_h, isRainbowTheme()); + drawDseg7RightString34(s, satStr.c_str(), c2X + 140, satY, c2Col, cardBg, 85, slice_h, isRainbowTheme()); } String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---"); drawGpsNavSubword(s, hdopStr.c_str(), c2X + 16, c2Y + 70, TFT_WHITE, cardBg, slice_h); - drawGpsNavSubword(s, getGpsDiagMessage(), c2X + 16, c2Y + 90, synced ? 0x9CD3 : c2Col, cardBg, slice_h); + uint16_t diagCol = synced ? getGpsDopColor() : c2Col; + drawGpsNavDiag(s, getGpsDiagMessage(), c2X, c2Y, diagCol, cardBg, slice_h); } // 5. CARD 3 (Bottom Right): COORDENADAS (y = 125 to 235) diff --git a/sketch/obd2_gauge/kmh_aa_data.h b/sketch/obd2_gauge/kmh_aa_data.h new file mode 100644 index 0000000..fc45d1a --- /dev/null +++ b/sketch/obd2_gauge/kmh_aa_data.h @@ -0,0 +1,234 @@ +#pragma once +#include + +#define KMH_AA_WIDTH 108 +#define KMH_AA_HEIGHT 28 + +#define RPM_AA_WIDTH 60 +#define RPM_AA_HEIGHT 20 + +#define DEGC_AA_WIDTH 33 +#define DEGC_AA_HEIGHT 25 + +#define VOLT_AA_WIDTH 22 +#define VOLT_AA_HEIGHT 25 + +#define TIT_REF_WIDTH 108 +#define TIT_REF_HEIGHT 11 + +#define TIT_BAT_WIDTH 60 +#define TIT_BAT_HEIGHT 11 + +// 8-bit Anti-Aliased 'kmh' (108x28) +static const uint8_t s_kmhAlpha[3024] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x10, 0x00, 0x00, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xB5, 0x01, 0x00, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x2F, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xFF, 0xFF, 0xFF, 0xE6, 0x15, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x37, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xFF, 0xFF, 0xFF, 0xFF, 0xCB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xEA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xFA, 0xFF, 0xFF, 0xFF, 0xFD, 0x44, 0x00, 0x00, 0x00, 0xA5, 0xFF, 0xFF, 0xFF, 0xFF, 0xCB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xEA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCB, 0x07, 0x00, 0x00, 0x00, 0x00, 0x05, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0xFF, 0xFF, 0xFF, 0xFF, 0xC8, 0x04, 0x00, 0x00, 0x00, 0x05, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x21, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x59, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x86, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xCB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0xFF, 0xFF, 0xFF, 0xFF, 0xD8, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x58, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xC8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0xFE, 0xFF, 0xFF, 0xFF, 0xF8, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA2, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0D, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x27, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xEA, 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA2, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x91, 0x6C, 0x6C, 0x6C, 0xD4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xAA, 0xFF, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xF2, 0xFF, 0xFF, 0xFF, 0xEA, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x91, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x75, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x67, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0xFF, 0xFF, 0xFF, 0xFF, 0xD9, 0x51, 0xFF, 0xFF, 0xFF, 0xFE, 0x18, 0x00, 0x00, 0x00, 0x00, 0xB2, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0xD6, 0xFF, 0xFF, 0xFF, 0xFF, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0xFF, 0xE5, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x48, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0xFF, 0xAC, 0x0A, 0xF6, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xAA, 0x07, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0xFB, 0xFF, 0xFF, 0xFF, 0xFD, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1B, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0xB2, 0xFF, 0xFF, 0xFF, 0xA9, 0x00, 0x00, 0x10, 0xE6, 0xFF, 0xFF, 0xFF, 0xEB, 0x15, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0x52, 0x00, 0x63, 0xFF, 0xFF, 0xFF, 0xEF, 0x03, 0x00, 0x9A, 0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0x00, 0x5D, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0xD3, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x25, 0x00, 0x15, 0xFD, 0xFF, 0xFF, 0xFF, 0x3C, 0x3D, 0xFE, 0xFF, 0xFF, 0xFF, 0xAC, 0x00, 0x00, 0x8A, 0xFF, 0xFF, 0xFF, 0xFF, 0xB3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0x94, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x62, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x02, 0x00, 0x00, 0xC4, 0xFF, 0xFF, 0xFF, 0x8C, 0xD7, 0xFF, 0xFF, 0xFF, 0xEC, 0x16, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xFF, 0xFF, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE8, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xCB, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x00, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0xFF, 0xFF, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xA2, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA2, 0xFF, 0xFF, 0xFF, 0xFF, 0x9E, 0x00, 0x00, 0x00, 0x24, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAE, 0x00, 0x00, 0x00, 0x11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0xFF, 0xFF, 0xFF, 0xFF, 0xD6, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA2, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0x71, 0x00, 0x00, 0x00, 0x00, 0xD5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x17, 0x00, 0x00, 0x00, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x44, 0x00, 0x00, 0x00, 0x00, 0x85, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x6B, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xB3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0x00, 0x00, 0x00, 0x00, 0x36, 0xFF, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0xFF, 0xFF, 0xFF, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x02, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0x86, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x56, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x17, 0x00, 0x00, 0x00, 0x00, 0x56, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE5, 0xFF, 0xFF, 0xED, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF, 0x79, 0x00, 0x00, 0x00, 0x00, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x59, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00, 0x00, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x14, 0x14, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0x4C, 0x00, 0x00, 0x00, 0x3B, 0xFC, 0xFF, 0xFF, 0xFF, 0xFD, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xB0, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3, 0xFF, 0xFF, 0xFF, 0xFF, 0xEB, 0x09, 0x00, 0x00, 0x00, 0xB0, 0xFF, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x10, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x00, 0x00, 0x00, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE9, 0xFF, 0xFF, 0xFF, 0xFF, 0xD8, 0x01, 0x00, 0x0B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6, 0x00, 0x00, 0x67, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCA, 0xFF, 0xFF, 0xFF, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +// 8-bit Anti-Aliased 'rpm' (60x20) +static const uint8_t s_rpmAlpha[1200] PROGMEM = { + 0x00, 0x00, 0x00, 0x09, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFD, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xFF, 0xFF, 0xFF, 0xB9, + 0x00, 0x00, 0x00, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x33, 0xFF, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xD8, 0xFF, 0xFF, 0xFF, 0x8D, + 0x00, 0x00, 0x00, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x3C, 0x00, 0x00, 0x00, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x3C, 0x00, 0x00, 0x5F, 0xFF, 0xFF, 0xFF, 0xFC, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x61, + 0x00, 0x00, 0x00, 0x8B, 0xFF, 0xFF, 0xFF, 0x4E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xA3, 0xFF, 0xFF, 0xFF, 0xB1, 0x00, 0x00, 0x00, 0x8B, 0xFF, 0xFF, 0xFF, 0x4E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xA6, 0xFF, 0xFF, 0xFF, 0xB1, 0x00, 0x00, 0x8B, 0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0x35, + 0x00, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xFF, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0xFF, 0xFF, 0xFF, 0x87, 0x00, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xFF, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xFF, 0xFF, 0xFF, 0x88, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xFF, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x0B, + 0x00, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0xE6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xFF, 0xFF, 0xFF, 0x5B, 0x00, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xFF, 0xFF, 0x5B, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0x00, + 0x00, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xBD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x11, 0xE9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0x00, + 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xFF, 0xFF, 0xFB, 0x06, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB9, 0xFF, 0xFF, 0xFB, 0x07, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0x74, 0x00, 0x00, 0x00, 0x00, 0x99, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0x85, 0x00, + 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xFC, 0xFF, 0xFF, 0xD5, 0x00, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0xD6, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0xA2, 0xFF, 0xFF, 0xBB, 0x00, 0x00, 0x00, 0x39, 0xFE, 0xFF, 0xFF, 0x9D, 0xFF, 0xFF, 0xFF, 0x59, 0x00, + 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x59, 0x00, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8E, 0xFF, 0xFF, 0xFF, 0xA9, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0x2B, 0xFB, 0xFF, 0xF8, 0x0A, 0x00, 0x04, 0xD2, 0xFF, 0xFF, 0xA8, 0x7A, 0xFF, 0xFF, 0xFF, 0x2D, 0x00, + 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x40, 0x00, 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0x45, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xED, 0x00, 0xC0, 0xFF, 0xFF, 0x4A, 0x00, 0x77, 0xFF, 0xFF, 0xEC, 0x15, 0xA7, 0xFF, 0xFF, 0xFB, 0x06, 0x00, + 0x00, 0x00, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x10, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x36, 0x00, 0x00, 0x00, 0xEC, 0xFF, 0xFF, 0xC0, 0x00, 0x72, 0xFF, 0xFF, 0x92, 0x20, 0xF5, 0xFF, 0xFF, 0x5B, 0x00, 0xD3, 0xFF, 0xFF, 0xD5, 0x00, 0x00, + 0x00, 0x19, 0xFF, 0xFF, 0xFF, 0xB0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xC2, 0xFF, 0xFF, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDA, 0x22, 0x00, 0x00, 0x00, 0x19, 0xFF, 0xFF, 0xFF, 0x94, 0x00, 0x23, 0xFF, 0xFF, 0xDA, 0xB5, 0xFF, 0xFF, 0xB4, 0x00, 0x05, 0xFA, 0xFF, 0xFF, 0xA9, 0x00, 0x00, + 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF4, 0xFF, 0xFF, 0xFF, 0x12, 0x00, 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0x8F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0xD6, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x1C, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x7D, 0x00, 0x00, + 0x00, 0x71, 0xFF, 0xFF, 0xFF, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0xFF, 0xFF, 0xE9, 0x00, 0x00, 0x00, 0x71, 0xFF, 0xFF, 0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0xFF, 0xFF, 0xFF, 0x3B, 0x00, 0x00, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0x66, 0x00, 0x00, 0x58, 0xFF, 0xFF, 0xFF, 0x51, 0x00, 0x00, + 0x00, 0x9E, 0xFF, 0xFF, 0xFF, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xFF, 0xFF, 0xFF, 0xBE, 0x00, 0x00, 0x00, 0x9E, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9E, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x39, 0xFF, 0xFF, 0xFF, 0xBE, 0x01, 0x00, 0x00, 0x85, 0xFF, 0xFF, 0xFF, 0x25, 0x00, 0x00, + 0x00, 0xCA, 0xFF, 0xFF, 0xFA, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0xFF, 0xFF, 0xFF, 0x93, 0x00, 0x00, 0x00, 0xCA, 0xFF, 0xFF, 0xFC, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCA, 0xFF, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x02, 0xE8, 0xFF, 0xF5, 0x23, 0x00, 0x00, 0x00, 0xB1, 0xFF, 0xFF, 0xF6, 0x02, 0x00, 0x00, + 0x01, 0xF4, 0xFF, 0xFF, 0xD5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x01, 0xF4, 0xFF, 0xFF, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF4, 0xFF, 0xFF, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x26, 0x34, 0x24, 0x00, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, + 0x22, 0xFF, 0xFF, 0xFF, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0xFF, 0xFF, 0xFF, 0x3C, 0x00, 0x00, 0x22, 0xFF, 0xFF, 0xFF, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xFF, 0xFF, 0xFF, 0x8A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFE, 0xFF, 0xFF, 0xA1, 0x00, 0x00, 0x00, + 0x4E, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0xFF, 0xFF, 0x11, 0x00, 0x00, 0x4E, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0xFF, 0xFF, 0xFF, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0xFF, 0xFF, 0xFF, 0x75, 0x00, 0x00, 0x00, +}; + +// 8-bit Anti-Aliased 'degc' (33x25) +static const uint8_t s_degcAlpha[825] PROGMEM = { + 0x00, 0x00, 0x1A, 0xCB, 0xFF, 0xFF, 0xFF, 0xFE, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x33, 0xE4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x3B, 0x00, 0x00, 0x00, + 0x2C, 0xF5, 0xFF, 0xFF, 0xF7, 0xE8, 0xF8, 0xFF, 0xFF, 0xE0, 0x14, 0x00, 0x00, 0x00, 0x01, 0x8A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0x21, 0x00, 0x00, + 0x6A, 0xFF, 0xFF, 0xDA, 0x24, 0x00, 0x3B, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x00, 0x08, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD5, 0x0F, 0x00, + 0x9A, 0xFF, 0xFF, 0x7D, 0x00, 0x00, 0x1A, 0xFF, 0xFF, 0xFA, 0x06, 0x00, 0x08, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0x04, + 0xC9, 0xFF, 0xFF, 0x9E, 0x00, 0x04, 0x95, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x35, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x22, + 0x9B, 0xFF, 0xFF, 0xFF, 0xE9, 0xEE, 0xFF, 0xFF, 0xFF, 0x93, 0x00, 0x00, 0x62, 0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC4, 0xFF, 0xFF, 0xFF, 0xF3, 0x01, + 0x06, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x92, 0x03, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFD, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xFF, 0xFF, 0xC5, 0x00, + 0x00, 0x14, 0xDE, 0xFF, 0xFF, 0xFF, 0xFA, 0x65, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0xFF, 0xFF, 0xDA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x68, 0x68, 0x68, 0x43, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8, 0xFF, 0xFF, 0xFF, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0xFF, 0xFF, 0xFF, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xFF, 0xFF, 0xFA, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0xFF, 0xFF, 0xFF, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF3, 0xFF, 0xFF, 0xFF, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xFF, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x6C, 0x6C, 0x6C, 0x6C, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B, 0xFF, 0xFF, 0xFF, 0xFF, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB9, 0xFF, 0xFF, 0xFF, 0xE7, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0xEB, 0xFF, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xD5, 0xFF, 0xFF, 0xFF, 0xFF, 0x88, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE9, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x3C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x37, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD4, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xC8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB8, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +// 8-bit Anti-Aliased 'volt' (22x25) +static const uint8_t s_voltAlpha[550] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xDA, 0xFF, 0xFF, 0xFF, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0x38, + 0xAC, 0xFF, 0xFF, 0xFF, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, + 0x7E, 0xFF, 0xFF, 0xFF, 0xF7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB9, 0xFF, 0xFF, 0xFF, 0xFD, 0x2C, 0x00, + 0x51, 0xFF, 0xFF, 0xFF, 0xFF, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xA2, 0x00, 0x00, + 0x23, 0xFF, 0xFF, 0xFF, 0xFF, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xFF, 0xFA, 0x21, 0x00, 0x00, + 0x01, 0xF3, 0xFF, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0xFF, 0xFF, 0xFF, 0xFF, 0x94, 0x00, 0x00, 0x00, + 0x00, 0xC8, 0xFF, 0xFF, 0xFF, 0x8D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0xFF, 0xFF, 0xF5, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x9A, 0xFF, 0xFF, 0xFF, 0xB3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0x86, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6C, 0xFF, 0xFF, 0xFF, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0xFF, 0xFF, 0xFF, 0xEF, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFA, 0x03, 0x00, 0x00, 0x00, 0x00, 0x31, 0xFF, 0xFF, 0xFF, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x11, 0xFF, 0xFF, 0xFF, 0xFF, 0x23, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xFF, 0xFF, 0xFF, 0xE7, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xE3, 0xFF, 0xFF, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x2F, 0xFE, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xB6, 0xFF, 0xFF, 0xFF, 0x6D, 0x00, 0x00, 0x00, 0xAD, 0xFF, 0xFF, 0xFF, 0xDE, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x88, 0xFF, 0xFF, 0xFF, 0x92, 0x00, 0x00, 0x2D, 0xFE, 0xFF, 0xFF, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5A, 0xFF, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0xAB, 0xFF, 0xFF, 0xFF, 0xD3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2D, 0xFF, 0xFF, 0xFF, 0xDD, 0x00, 0x2B, 0xFE, 0xFF, 0xFF, 0xFF, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0xF9, 0xFF, 0xFF, 0xFC, 0x06, 0xA8, 0xFF, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xD1, 0xFF, 0xFF, 0xFF, 0x51, 0xFD, 0xFF, 0xFF, 0xFF, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xA4, 0xFF, 0xFF, 0xFF, 0xE6, 0xFF, 0xFF, 0xFF, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x76, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +// 8-bit Anti-Aliased 'titRef' (108x11) +static const uint8_t s_titRefAlpha[1188] PROGMEM = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0x0F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0x0F, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x0A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x0B, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x41, 0xFF, 0xEE, 0x06, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFA, 0x31, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, + 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xDC, 0xFF, 0xC7, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xDC, 0xFF, 0xC7, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xBB, 0xFF, 0xEE, 0xC4, 0xC4, 0xC4, 0xEB, 0xFF, 0xBC, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xDC, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x9A, 0xFF, 0xFF, 0x4D, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xD5, 0x08, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xC4, 0xC4, 0xC4, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0A, 0xFD, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0A, 0xFD, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFE, 0x2D, 0x00, 0x00, 0x00, 0x26, 0xFF, 0xFC, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0A, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x05, 0xED, 0xF6, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x8E, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7E, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0x91, 0xE4, 0xF4, 0x0A, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xEF, 0xFE, 0x42, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x22, 0xFE, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x22, 0xFE, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x22, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0x40, 0x93, 0xFF, 0x58, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x5B, 0xFF, 0xE2, 0x0F, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0x0B, 0xF4, 0xEC, 0x03, 0x40, 0xFF, 0xB1, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0xA9, 0xFF, 0xA2, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xF1, 0xFF, 0x4E, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xF1, 0xFF, 0x4E, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x4F, 0xC4, 0xC4, 0xFF, 0xFC, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xF1, 0xFF, 0x4E, 0x00, 0x00, 0x5A, 0xFF, 0xA0, 0x00, 0x03, 0xEA, 0xF9, 0x11, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x12, 0xE5, 0xFF, 0x53, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xF4, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xF4, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xF4, 0x00, 0x00, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x47, 0xFE, 0xEC, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xFF, 0xFE, 0x28, 0x00, 0x00, 0x00, 0x22, 0xFF, 0xFC, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x12, 0xFA, 0xFE, 0xD0, 0xD0, 0xD0, 0xDD, 0xFF, 0xBC, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xBB, 0xFF, 0xEE, 0xC4, 0xC4, 0xC4, 0xE9, 0xFF, 0xB7, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x66, 0xFF, 0xBA, 0x00, 0x00, 0x00, 0x12, 0xF8, 0xFD, 0x18, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0A, 0xD8, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x0A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x09, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0xBF, 0xFF, 0x56, 0x00, 0x00, 0x00, 0x00, 0xA6, 0xFF, 0x6F, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x35, 0xFB, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, +}; + +// 8-bit Anti-Aliased 'titBat' (60x11) +static const uint8_t s_titBatAlpha[660] PROGMEM = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x00, 0x41, 0xFF, 0xEE, 0x06, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0x0F, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0xFF, 0xEE, 0x06, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xDA, 0xFF, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x9A, 0xFF, 0xFF, 0x4D, 0x00, 0x00, 0xC4, 0xC4, 0xC4, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xDC, 0xFF, 0xC7, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9A, 0xFF, 0xFF, 0x4D, 0x00, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x05, 0xED, 0xF6, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0A, 0xFD, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x05, 0xED, 0xF6, 0xFF, 0xA6, 0x00, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x08, 0xFD, 0xFF, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0x91, 0xE4, 0xF4, 0x0A, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0x91, 0xE4, 0xF4, 0x0A, 0x00, 0x00, + 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xDB, 0xFF, 0xDA, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0x40, 0x93, 0xFF, 0x58, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x22, 0xFE, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0x40, 0x93, 0xFF, 0x58, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0x4C, 0x00, 0x0B, 0xF4, 0xEC, 0x03, 0x40, 0xFF, 0xB1, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0B, 0xF4, 0xEC, 0x03, 0x40, 0xFF, 0xB1, 0x00, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x02, 0x6D, 0xFF, 0xF9, 0x00, 0x5A, 0xFF, 0xA0, 0x00, 0x03, 0xEA, 0xF9, 0x11, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xF1, 0xFF, 0x4E, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x5A, 0xFF, 0xA0, 0x00, 0x03, 0xEA, 0xF9, 0x11, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xF4, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x00, + 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x34, 0xFF, 0xFC, 0x12, 0xFA, 0xFE, 0xD0, 0xD0, 0xD0, 0xDD, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x12, 0xFA, 0xFE, 0xD0, 0xD0, 0xD0, 0xDD, 0xFF, 0xBC, 0x00, + 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC5, 0xFC, 0xFF, 0xBF, 0x66, 0xFF, 0xBA, 0x00, 0x00, 0x00, 0x12, 0xF8, 0xFD, 0x18, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0x62, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x66, 0xFF, 0xBA, 0x00, 0x00, 0x00, 0x12, 0xF8, 0xFD, 0x18, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0x0A, 0xBF, 0xFF, 0x56, 0x00, 0x00, 0x00, 0x00, 0xA6, 0xFF, 0x6F, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0xBF, 0xFF, 0x56, 0x00, 0x00, 0x00, 0x00, 0xA6, 0xFF, 0x6F, +}; + +// Draw anti-aliased bitmap to destination (TFT or Sprite) with alpha blending, rainbow and Yapa multi-color per letter +template +inline void drawKmhAaBitmap(T &dst, int bx, int by, int w, int h, const uint8_t *alpha, + uint16_t color, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { + dst.startWrite(); + for (int j = 0; j < h; j++) { + int py = by + j; + if (py < 0 || py >= clip_h) continue; + int rowOffset = j * w; + for (int i = 0; i < w; i++) { + int px = bx + i; + if (px < 0 || px >= 320) continue; + uint8_t a = pgm_read_byte(alpha + rowOffset + i); + if (a > 6) { + uint16_t curCol = color; + if (rainbow) { + curCol = getRainbowScreenColor(px, py); + } else if (isYapaTheme()) { + if (alpha == s_rpmAlpha) { + curCol = (i < 20) ? getYapaColor(0) : ((i < 39) ? getYapaColor(1) : getYapaColor(2)); + } else if (alpha == s_kmhAlpha) { + curCol = (i < 31) ? getYapaColor(0) : ((i < 63) ? getYapaColor(1) : ((i < 76) ? getYapaColor(2) : getYapaColor(3))); + } else if (alpha == s_degcAlpha) { + curCol = (i < 13) ? getYapaColor(0) : getYapaColor(1); + } else if (alpha == s_voltAlpha) { + curCol = getYapaColor(2); + } else if (alpha == s_titRefAlpha) { + curCol = getYapaColor((i * 12) / 108); + } else if (alpha == s_titBatAlpha) { + curCol = getYapaColor((i * 7) / 60); + } + } + uint16_t pixCol = (a >= 250) ? curCol : dst.alphaBlend(a, curCol, bgCol); + dst.drawPixel(px, py, pixCol); + } + } + } + dst.endWrite(); +} + +template +inline void drawKmH_AA(T &dst, int bx, int by, uint16_t color = 0xBDD7, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { + drawKmhAaBitmap(dst, bx, by, KMH_AA_WIDTH, KMH_AA_HEIGHT, s_kmhAlpha, color, bgCol, clip_h, rainbow); +} + +template +inline void drawRpm_AA(T &dst, int bx, int by, uint16_t color = 0xBDD7, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { + drawKmhAaBitmap(dst, bx, by, RPM_AA_WIDTH, RPM_AA_HEIGHT, s_rpmAlpha, color, bgCol, clip_h, rainbow); +} + +template +inline void drawDegreeC_AA(T &dst, int bx, int by, uint16_t color = 0xC618, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { + drawKmhAaBitmap(dst, bx, by, DEGC_AA_WIDTH, DEGC_AA_HEIGHT, s_degcAlpha, color, bgCol, clip_h, rainbow); +} + +template +inline void drawVolt_AA(T &dst, int bx, int by, uint16_t color = 0xC618, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { + drawKmhAaBitmap(dst, bx, by, VOLT_AA_WIDTH, VOLT_AA_HEIGHT, s_voltAlpha, color, bgCol, clip_h, rainbow); +} + +template +inline void drawTitRef_AA(T &dst, int bx, int by, uint16_t color = 0xBDD7, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { + drawKmhAaBitmap(dst, bx, by, TIT_REF_WIDTH, TIT_REF_HEIGHT, s_titRefAlpha, color, bgCol, clip_h, rainbow); +} + +template +inline void drawTitBat_AA(T &dst, int bx, int by, uint16_t color = 0xBDD7, uint16_t bgCol = 0x10A2, int clip_h = 240, bool rainbow = false) { + drawKmhAaBitmap(dst, bx, by, TIT_BAT_WIDTH, TIT_BAT_HEIGHT, s_titBatAlpha, color, bgCol, clip_h, rainbow); +} diff --git a/sketch/obd2_gauge/meter.h b/sketch/obd2_gauge/meter.h index 44288e9..d028f4b 100644 --- a/sketch/obd2_gauge/meter.h +++ b/sketch/obd2_gauge/meter.h @@ -256,76 +256,9 @@ void drawFullScreenTitle(const String &label, uint16_t color) { // Tactical ECO / OPT Badge (Ford Ka Endura 1.3 Sweet Spot: 1900 - 2550 RPM & // 45% - 82% Load) void updateEcoBadge(bool forceRedraw = false) { - // CRITICAL: ECO badge ONLY belongs on Page 0 (RPM quadrant or fullscreen - // RPM), NEVER on Page 1 (Tri-Temp)! - if (currentScreenPage != 0) { - badgeEcoDrawn = false; - return; - } - - // If in fullscreen on Page 0, only display if the fullscreen cell is RPM - bool isQuad = (fullScreenCell < 0 || fullScreenCell >= 4); - if (!isQuad && pidConfig[pidInCell[fullScreenCell]][3].toInt() != 3) { - badgeEcoDrawn = false; - return; - } - bool newEco = (ignitionOn && currentRpm >= 1900.0f && currentRpm <= 2550.0f && currentEngineLoad >= 45.0f && currentEngineLoad <= 82.0f); isEcoOptimal = newEco; - - if (forceRedraw) { - badgeEcoDrawn = !newEco; // Force redraw/re-erase - } - - if (newEco == badgeEcoDrawn) { - return; - } - - int bx = 0, by = 0, bw = 0, bh = 0; - - if (isQuad) { - // 4-Quadrant View: Positioned in top-right of RPM Quadrant (Cell 2) - int rpmCell = 2; - for (int c = 0; c < 4; c++) { - if (pidConfig[pidInCell[c]][3].toInt() == 3) { - rpmCell = c; - break; - } - } - bx = cell4_x[rpmCell] + 100; - by = cell4_y[rpmCell] + 4; - bw = 48; - bh = 18; - } else { - // Full-Screen View: Positioned in top-right header band - bx = 260; - by = 11; - bw = 50; - bh = 20; - } - - if (newEco) { - // Elegant tactical pill badge [ ECO ] - const uint16_t bgFill = 0x0202; // Deep dark forest emerald green - const uint16_t borderCol = 0x07E0; // Vibrant emerald neon green - tft.fillRoundRect(bx, by, bw, bh, 4, bgFill); - tft.drawRoundRect(bx, by, bw, bh, 4, borderCol); - tft.setTextColor(TFT_WHITE, bgFill); - if (isQuad) { - tft.drawCentreString("ECO", bx + (bw / 2), by + 1, 2); - } else { - tft.drawCentreString("ECO", bx + (bw / 2), by + 2, 2); - } - } else { - // Cleanly restore background gradient lines with zero visual trace - int ry = (isQuad && by >= 120) ? 121 : 0; - for (int y = by; y < by + bh; y++) { - uint16_t bgCol = getInstrumentBgColor(y, isQuad, ry); - tft.drawFastHLine(bx, y, bw, bgCol); - } - } - badgeEcoDrawn = newEco; } @@ -1052,13 +985,12 @@ inline int calcMeterBlocks(float val, int min, int max) { return b; } -// Restore natural vignette vertical gradient where GPS icon sits inline void clearPage0GpsIcon() { - uint16_t c1 = tft.alphaBlend((uint8_t)(255 * (82 - 3) / 82), - getThemeBgColor(), TFT_BLACK); - uint16_t c2 = tft.alphaBlend((uint8_t)(255 * (82 - 25) / 82), - getThemeBgColor(), TFT_BLACK); - tft.fillRectVGradient(294, 3, 24, 22, c1, c2); + // Page 0 uses the integrated GPS/OBD status pill inside the speedometer card +} + +inline void drawPage0GpsIcon(uint8_t waveState, bool synced) { + // Page 0 uses the integrated GPS/OBD status pill inside the speedometer card } inline void clearFullscreenGpsIcon() { @@ -1069,19 +1001,6 @@ inline void clearFullscreenGpsIcon() { tft.fillRectVGradient(294, 3, 24, 22, c1, c2); } -// Draw animated GPS satellite icon in top right of Quadrant 1 (Velocidad: cell -// 1) -inline void drawPage0GpsIcon(uint8_t waveState, bool synced) { - if (currentScreenPage != 0 || fullScreenCell != -1) - return; - clearPage0GpsIcon(); - uint16_t waveCol = getThemePrimaryColor(); - uint16_t bodyCol = TFT_WHITE; - uint16_t bgCol = tft.alphaBlend((uint8_t)(255 * (82 - 3) / 82), - getThemeBgColor(), TFT_BLACK); - drawGpsSatelliteIcon(tft, 296, 3, bodyCol, waveCol, bgCol, waveState); -} - // Draw animated GPS satellite icon in top right of Fullscreen Velocidad (cell // 1) inline void drawFullscreenGpsIcon(uint8_t waveState, bool synced) { @@ -1187,7 +1106,7 @@ void arcMeter4(uint8_t cell, uint8_t pid, bool redrawHeader = true) { int warn = warningValue[pid].toInt(); String unit = pidConfig[pid][1]; - if (last_valid_data[cell] != -999) { + if (last_valid_data[cell] > -900.0f) { old_data[cell] = -999; plotArc4(cell, last_valid_data[cell], warn, min, max, digit, unit); } else { @@ -1325,7 +1244,7 @@ void fullScreenMeter(uint8_t cell, uint8_t pid, bool redrawHeader = true) { bool digit = pidConfig[pid][7].toInt(); int warn = warningValue[pid].toInt(); - if (last_valid_data[cell] != -999) { + if (last_valid_data[cell] > -900.0f) { old_data[cell] = -999; plotFullScreen(cell, last_valid_data[cell], warn, min, max, digit, unit); } else { @@ -1440,6 +1359,12 @@ void plotNoData(uint8_t cell) { wasInRedAlarm[cell] = false; uint8_t pidIdx = pidInCell[cell]; + if (pidIdx == 0) { + tempEngineCoolant = -999.0f; + if (currentScreenPage == 1 && fullScreenCell == -1) { + updateTriTempCoolant(-999.0f); + } + } if (fullScreenCell >= 0 && fullScreenCell < 4) { if (cell == fullScreenCell) { @@ -1456,19 +1381,7 @@ void plotNoData(uint8_t cell) { tft.unloadFont(); } } else if (currentScreenPage == 0) { - int x = cell4_x[cell]; - int y = cell4_y[cell]; - clearPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]); - drawPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]); - for (uint8_t i = 0; i < NUM_BAR_BLOCKS; i++) { - drawQuadBarBlock(cell, i, METER_INACTIVE_COLOR); - } - tft.loadFont(DSEG7_Modern_BoldItalic_34); - tft.setTextColor(getThemeCellColor(cell), TFT_BLACK); - tft.setTextPadding(114); - tft.drawRightString("---", x + 152, y + 74, 1); - tft.setTextPadding(0); - tft.unloadFont(); + updateClusterScreen(false); } } @@ -1597,103 +1510,10 @@ inline void drawQuadBarBlockToSprite(TFT_eSprite &s, int16_t qx, int16_t qy, } } -// Render Page 0 (4 Quadrants) into double-buffered Sprite slice +// Render Page 0 (Cockpit Instrumental) into double-buffered Sprite slice inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) { - checkUpdateThemeGradients(); - uint16_t divCol = getThemeDivColor(); - uint16_t priCol = getThemePrimaryColor(); - - for (uint8_t c = 0; c < 4; c++) { - int qx = cell4_x[c] + x_offset; - int qy = cell4_y[c] - y_slice; - int rx = (c & 1) ? (161 + x_offset) : (0 + x_offset); - int global_ry = (c & 2) ? 121 : 0; - int ry = global_ry - y_slice; - - if (qx + 160 <= 0 || qx >= 320 || ry + 119 <= 0 || ry >= slice_h) - continue; - - // 1. Exact dark vignette gradient matching real arcMeter4 - s.fillRect(rx, ry, 159, 119, TFT_BLACK); - int gradStartY = (ry < 0) ? -ry : 0; - int gradEndY = (ry + 82 > slice_h) ? (slice_h - ry) : 82; - for (int j = gradStartY; j < gradEndY; j++) { - int curY = ry + j; - if (curY >= 0 && curY < slice_h) { - s.drawFastHLine(rx, curY, 159, themeGradQuad[j]); - } - } - - uint8_t pidIdx = pidInCell[c]; - - // 2. Real high-definition parameter icon (thermometer, speedometer, - // tachometer, battery) - drawPidIconToSprite(s, qx + 5, qy + 3, pidConfig[pidIdx][2], y_slice, - slice_h, global_ry); - - // 3. Exact 20 curved segmented gauge blocks along the arc (true - // mathematical polygons) - int warn = warningValue[pidIdx].toInt(); - int min = pidConfig[pidIdx][4].toInt(); - int max = pidConfig[pidIdx][5].toInt(); - bool digit = pidConfig[pidIdx][7].toInt(); - float val = last_valid_data[c]; - - int numActive = (val > -900.0f) ? calcMeterBlocks(val, min, max) : 0; - uint16_t activeCol = - (val > -900.0f) ? getMeterColor(pidIdx, val, warn) : priCol; - - for (uint8_t i = 0; i < NUM_BAR_BLOCKS; i++) { - uint16_t bCol = (i < numActive) ? activeCol : METER_INACTIVE_COLOR; - drawQuadBarBlockToSprite(s, qx, qy, i, bCol, slice_h); - } - - // 4. Digital value string using authentic DSEG7 34pt font with rainbow gradient - String numStr = (val <= -900.0f) ? "---" : String(val, digit); - uint16_t txtCol = (activeCol == TFT_RED) ? TFT_RED : getThemeCellColor(c); - bool rainbowTxt = isRainbowTheme() && (activeCol != TFT_RED); - int textY = qy + 74; - if (textY < slice_h && textY + 40 > 0) { - drawDseg7RightString34(s, numStr.c_str(), qx + 152, textY, txtCol, TFT_BLACK, 0, slice_h, rainbowTxt); - } - - // GPS Satellite icon in top right of Quadrant 1 (Velocidad: cell 1) - if (c == 1 && isGpsSpeedValid() && y_slice == 0) { - uint16_t waveCol = isRainbowTheme() ? (uint16_t)0x07E0 : priCol; - uint16_t bodyCol = TFT_WHITE; - uint16_t bgCol = themeGradQuad[3]; - drawGpsSatelliteIcon(s, qx + 136, 3, bodyCol, waveCol, bgCol, - getGpsWaveAnimState()); - } - } - - // 5. Eco Badge if active - if (isEcoOptimal) { - int rpmCell = 2; - for (int c = 0; c < 4; c++) { - if (pidConfig[pidInCell[c]][3].toInt() == 3) { - rpmCell = c; - break; - } - } - int bx = cell4_x[rpmCell] + 100 + x_offset; - int by = cell4_y[rpmCell] + 4 - y_slice; - int bw = 48, bh = 18; - if (by + bh > 0 && by < slice_h && bx + bw > 0 && bx < 320) { - s.fillRoundRect(bx, by, bw, bh, 4, 0x0202); - s.drawRoundRect(bx, by, bw, bh, 4, 0x07E0); - s.setTextColor(TFT_WHITE, 0x0202); - s.drawCentreString("ECO", bx + (bw / 2), by + 1, 2); - } - } - - // 6. Crosshair dividers - s.fillRect(159 + x_offset, 0, 2, slice_h, divCol); - int hLineY = 119 - y_slice; - if (hLineY >= 0 && hLineY < slice_h) { - s.fillRect(x_offset, hLineY, 320, 2, divCol); - } + renderClusterScreenToSprite(s, x_offset, y_slice, slice_h); } // Render Page 1 (3 Temperature Cards) into double-buffered Sprite slice @@ -1762,13 +1582,17 @@ inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, if (sprTriTitleReady && sprTriTitle[cardIdx].created()) { sprTriTitle[cardIdx].pushToSprite(&s, cardX + 16, cardY + 5); } else { - s.setTextColor(TFT_WHITE, cardBg); - s.drawString(titleStr, cardX + 16, cardY + 5, 4); + if (isYapaTheme()) { + drawYapaStringToSprite(s, titleStr, cardX + 16, cardY + 5, 4); + } else { + s.setTextColor(TFT_WHITE, cardBg); + s.drawString(titleStr, cardX + 16, cardY + 5, 4); + } } if (isYapaTheme()) { drawYapaStringToSprite(s, subStr, cardX + 16, cardY + 32, 1); } else { - s.setTextColor(0x8410, cardBg); + s.setTextColor(accentCol, cardBg); s.drawString(subStr, cardX + 16, cardY + 32, 1); } @@ -1782,24 +1606,26 @@ inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, ratio = 0.0f; if (ratio > 1.0f) ratio = 1.0f; - int fillW = (int)(barW * ratio); - if (fillW < 4) + int fillW = (val <= -900.0f) ? 0 : (int)(barW * ratio); + if (fillW < 4 && val > -900.0f) fillW = 4; - s.fillRoundRect(barX, barY, fillW, 8, 3, accentCol); + if (fillW > 0) + s.fillRoundRect(barX, barY, fillW, 8, 3, accentCol); // Value text using authentic DSEG7 34pt font with rainbow gradient String numStr = - (val <= -900.0f) ? "---" : (isInt ? String((int)val) : String(val, 1)); + (val <= -900.0f) ? "---" : String((int)round(val)); + uint16_t numCol = accentCol; bool rainbowTxt = isRainbowTheme() && (accentCol != TFT_RED); int numY = cardY + 20; if (numY < slice_h && numY + 40 > 0) { - drawDseg7RightString34(s, numStr.c_str(), cardX + 262, numY, accentCol, cardBg, 0, slice_h, rainbowTxt); + drawDseg7RightString34(s, numStr.c_str(), cardX + 262, numY, numCol, cardBg, 0, slice_h, rainbowTxt); } int unitY = cardY + 22; if (unitY < slice_h && unitY + 26 > 0) { bool rainbowUnit = isRainbowTheme() && (accentCol != TFT_RED); - uint16_t unitCol = rainbowUnit ? getRainbowScreenColor(cardX + 268, unitY) : (isYapaTheme() && accentCol != TFT_RED ? getYapaColor(numStr.length()) : (accentCol == TFT_RED ? TFT_RED : 0xC618)); + uint16_t unitCol = rainbowUnit ? getRainbowScreenColor(cardX + 268, unitY) : (isYapaTheme() && accentCol != TFT_RED ? getYapaColor(numStr.length()) : (accentCol == TFT_RED ? TFT_RED : accentCol)); draw7SegDegreeC(s, cardX + 268, unitY, unitCol, cardBg, rainbowUnit); } } @@ -1839,28 +1665,24 @@ inline void animateBlackTransition(int8_t toPage = -1) { sprTrans.fillSprite(TFT_BLACK); if (toPage == 0) - renderPage0ToSprite(sprTrans, 0, y_slice, slice_h); + renderClusterScreenToSprite(sprTrans, 0, y_slice, slice_h); else if (toPage == 1) renderPage1ToSprite(sprTrans, 0, y_slice, slice_h); else if (toPage == 2) renderPage2ToSprite(sprTrans, 0, y_slice, slice_h); - else if (toPage == 3) - renderPage3ToSprite(sprTrans, 0, y_slice, slice_h); else - renderPage4ToSprite(sprTrans, 0, y_slice, slice_h); + renderPage3ToSprite(sprTrans, 0, y_slice, slice_h); - // Pagination indicator dots on top slice (5 dots for 5 pages) + // Pagination indicator dots on top slice (4 dots for 4 pages) if (y_slice == 0) { - sprTrans.fillCircle(132, 2, 2, + sprTrans.fillCircle(139, 2, 2, (toPage == 0) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(146, 2, 2, + sprTrans.fillCircle(153, 2, 2, (toPage == 1) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(160, 2, 2, + sprTrans.fillCircle(167, 2, 2, (toPage == 2) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(174, 2, 2, + sprTrans.fillCircle(181, 2, 2, (toPage == 3) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(188, 2, 2, - (toPage == 4) ? getThemePrimaryColor() : 0x5AEB); } sprTrans.pushSprite(0, y_slice); @@ -1915,26 +1737,22 @@ inline void animateFadeTransition(int8_t fromPage = -1, int8_t toPage = -1) { sprFadeB.fillSprite(TFT_BLACK); if (fromPage == 0) - renderPage0ToSprite(sprTrans, 0, y_slice, slice_h); + renderClusterScreenToSprite(sprTrans, 0, y_slice, slice_h); else if (fromPage == 1) renderPage1ToSprite(sprTrans, 0, y_slice, slice_h); else if (fromPage == 2) renderPage2ToSprite(sprTrans, 0, y_slice, slice_h); - else if (fromPage == 3) - renderPage3ToSprite(sprTrans, 0, y_slice, slice_h); else - renderPage4ToSprite(sprTrans, 0, y_slice, slice_h); + renderPage3ToSprite(sprTrans, 0, y_slice, slice_h); if (toPage == 0) - renderPage0ToSprite(sprFadeB, 0, y_slice, slice_h); + renderClusterScreenToSprite(sprFadeB, 0, y_slice, slice_h); else if (toPage == 1) renderPage1ToSprite(sprFadeB, 0, y_slice, slice_h); else if (toPage == 2) renderPage2ToSprite(sprFadeB, 0, y_slice, slice_h); - else if (toPage == 3) - renderPage3ToSprite(sprFadeB, 0, y_slice, slice_h); else - renderPage4ToSprite(sprFadeB, 0, y_slice, slice_h); + renderPage3ToSprite(sprFadeB, 0, y_slice, slice_h); uint16_t *pA = (uint16_t *)sprTrans.getPointer(); uint16_t *pB = (uint16_t *)sprFadeB.getPointer(); @@ -1964,19 +1782,17 @@ inline void animateFadeTransition(int8_t fromPage = -1, int8_t toPage = -1) { } } - // Pagination indicator dots on top slice (5 dots for 5 pages) + // Pagination indicator dots on top slice (4 dots for 4 pages) if (y_slice == 0) { uint8_t activeDot = (alpha < 16) ? fromPage : toPage; - sprTrans.fillCircle(132, 2, 2, + sprTrans.fillCircle(139, 2, 2, (activeDot == 0) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(146, 2, 2, + sprTrans.fillCircle(153, 2, 2, (activeDot == 1) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(160, 2, 2, + sprTrans.fillCircle(167, 2, 2, (activeDot == 2) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(174, 2, 2, + sprTrans.fillCircle(181, 2, 2, (activeDot == 3) ? getThemePrimaryColor() : 0x5AEB); - sprTrans.fillCircle(188, 2, 2, - (activeDot == 4) ? getThemePrimaryColor() : 0x5AEB); } sprTrans.pushSprite(0, y_slice); @@ -1997,7 +1813,7 @@ inline void performScreenTransition(bool swipeLeft, int8_t fromPage = -1, if (fromPage < 0) fromPage = currentScreenPage; if (toPage < 0) - toPage = (fromPage + 1) % 5; + toPage = (fromPage + 1) % 4; if (transitionType == 1) { animateFadeTransition(fromPage, toPage); } else { @@ -2030,16 +1846,14 @@ void initScreen(bool redrawTFT) { // Screen is ALREADY 100% rendered with zero flicker by the double-buffered // transition engine! ONLY sync tracking variables so live updates continue // seamlessly without redrawing or blanking! - if (currentScreenPage == 1) { + if (currentScreenPage == 0) { + syncClusterTrackers(); + } else if (currentScreenPage == 1) { syncTriTempTrackers(); } else if (currentScreenPage == 2) { syncGpsClockTrackers(); } else if (currentScreenPage == 3) { - syncGpsNavTrackers(); - } else if (currentScreenPage == 4) { - syncClusterTrackers(); - } else if (currentScreenPage == 0 && fullScreenCell == -1) { - syncPage0Trackers(); + syncGpsNavTrackers(true); } #ifdef SERIAL_DEBUG Serial.printf(">>> [DB] initScreen (NO REDRAW) FINISHED OK (heap: %u)\n", @@ -2052,11 +1866,11 @@ void initScreen(bool redrawTFT) { old_data[i] = -999; } - if (fullScreenCell >= 0 && fullScreenCell < 4) { + if (currentScreenPage == 0) { #ifdef SERIAL_DEBUG - Serial.println(">>> [DB] initScreen: drawing fullScreenMeter"); + Serial.println(">>> [DB] initScreen: drawing drawClusterScreen(0)"); #endif - fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell]); + drawClusterScreen(0); } else if (currentScreenPage == 1) { #ifdef SERIAL_DEBUG Serial.println(">>> [DB] initScreen: drawing drawTriTempScreen(0)"); @@ -2072,27 +1886,6 @@ void initScreen(bool redrawTFT) { Serial.println(">>> [DB] initScreen: drawing drawGpsNavScreen(0)"); #endif drawGpsNavScreen(0); - } else if (currentScreenPage == 4) { -#ifdef SERIAL_DEBUG - Serial.println(">>> [DB] initScreen: drawing drawClusterScreen(0)"); -#endif - drawClusterScreen(0); - } else { -#ifdef SERIAL_DEBUG - Serial.println(">>> [DB] initScreen: drawing Page 0 quadrants"); -#endif - tft.fillScreen(TFT_BLACK); // clear screen - for (uint8_t c = 0; c < 4; c++) { - uint8_t pid = pidInCell[c]; - arcMeter4(c, pid); - } - drawQuadrantDividers(); - // Pagination indicator dots [● ○ ○ ○ ○] (Page 0 active) - tft.fillCircle(132, 2, 2, getThemePrimaryColor()); - tft.fillCircle(146, 2, 2, 0x5AEB); - tft.fillCircle(160, 2, 2, 0x5AEB); - tft.fillCircle(174, 2, 2, 0x5AEB); - tft.fillCircle(188, 2, 2, 0x5AEB); } if (currentScreenPage == 0 && fullScreenCell == -1) { badgeEcoDrawn = false; @@ -2232,10 +2025,8 @@ inline void updateGpsSpeedometer() { last_valid_data[1] = spd; int warn = warningValue[3].toInt(); - if (fullScreenCell == 1) { - plotFullScreen(1, spd, warn, 0, 160, false, "km/h"); - } else if (currentScreenPage == 0 && fullScreenCell == -1) { - plotArc4(1, spd, warn, 0, 160, false, "km/h"); + if (currentScreenPage == 0) { + updateClusterScreen(false); } } } @@ -2420,13 +2211,7 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen last_valid_data[pidNo] = data; - if (fullScreenCell >= 0 && fullScreenCell < 4) { - if (pidNo == fullScreenCell) { - plotFullScreen(fullScreenCell, data, warn, min, max, digit, unit); - } - } else if (currentScreenPage == 0) { - plotArc4(pidNo, data, warn, min, max, digit, unit); - } else if (currentScreenPage == 4) { + if (currentScreenPage == 0) { updateClusterScreen(false); } bt_message = ""; // reset @@ -2656,7 +2441,7 @@ void handleArcAlarmFlash() { if (fullScreenCell >= 0 && fullScreenCell < 4) { uint8_t cell = fullScreenCell; - if (last_valid_data[cell] != -999) { + if (last_valid_data[cell] > -900.0f) { uint8_t pidIdx = pidInCell[cell]; int warn = warningValue[pidIdx].toInt(); float data = last_valid_data[cell]; @@ -2720,9 +2505,9 @@ void handleArcAlarmFlash() { } } } else if (currentScreenPage == 0) { - // 4 quadrants mode + // Cockpit Instrumental: check alarms for buzzer without drawing old quadrants for (uint8_t cell = 0; cell < 4; cell++) { - if (last_valid_data[cell] != -999) { + if (last_valid_data[cell] > -900.0f) { uint8_t pidIdx = pidInCell[cell]; int warn = warningValue[pidIdx].toInt(); float data = last_valid_data[cell]; @@ -2731,65 +2516,13 @@ void handleArcAlarmFlash() { bool isAlarmActive = alarmEnabled[pidIdx]; if (inRed && isAlarmActive) anyInRedAlarm = true; - bool digit = pidConfig[pidIdx][7].toInt(); - String numStr = String(data, digit); - int x = cell4_x[cell]; - int y = cell4_y[cell]; - - if (wasInRedAlarm[cell] && !inRed) { - // Exited red alarm: restore steady arc, text, and icon - wasInRedAlarm[cell] = false; - drawPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]); - int min = pidConfig[pidIdx][4].toInt(); - int max = pidConfig[pidIdx][5].toInt(); - int newBlocks = calcMeterBlocks(data, min, max); - for (uint8_t i = 0; i < newBlocks; i++) { - drawQuadBarBlock(cell, i, col); - } - for (uint8_t i = newBlocks; i < NUM_BAR_BLOCKS; i++) { - drawQuadBarBlock(cell, i, METER_INACTIVE_COLOR); - } - tft.loadFont(DSEG7_Modern_BoldItalic_34); - tft.setTextColor(getThemeCellColor(cell), TFT_BLACK); - tft.setTextPadding(114); - tft.drawRightString(numStr.c_str(), x + 152, y + 74, 1); - tft.setTextPadding(0); - tft.unloadFont(); - } else if (inRed) { - wasInRedAlarm[cell] = true; - if (isAlarmActive) { - if (toggleTick) { - if (arcFlashVisible) { - drawPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]); - } else { - clearPidIcon(x + 5, y + 3, pidConfig[pidIdx][2]); - } - int min = pidConfig[pidIdx][4].toInt(); - int max = pidConfig[pidIdx][5].toInt(); - int newBlocks = calcMeterBlocks(data, min, max); - - if (arcFlashVisible) { - for (uint8_t i = 0; i < newBlocks; i++) { - drawQuadBarBlock(cell, i, TFT_RED); - } - for (uint8_t i = newBlocks; i < NUM_BAR_BLOCKS; i++) { - drawQuadBarBlock(cell, i, METER_INACTIVE_COLOR); - } - } else { - for (uint8_t i = 0; i < NUM_BAR_BLOCKS; i++) { - drawQuadBarBlock(cell, i, TFT_BLACK); - } - } - } - } - } } } } 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) { + if (last_valid_data[cell] > -900.0f) { uint8_t pidIdx = pidInCell[cell]; int warn = warningValue[pidIdx].toInt(); float data = last_valid_data[cell]; diff --git a/sketch/obd2_gauge/obd2_gauge.ino b/sketch/obd2_gauge/obd2_gauge.ino index 241a8ea..68ab684 100644 --- a/sketch/obd2_gauge/obd2_gauge.ino +++ b/sketch/obd2_gauge/obd2_gauge.ino @@ -301,16 +301,16 @@ int8_t currentScreenPage = inline void setScreenPage(int8_t page) { if (page < 0) page = 0; - if (page > 4) - page = 4; + if (page > 3) + page = 3; currentScreenPage = page; pref.begin("setting", false); pref.putChar("scr_page", page); pref.end(); } -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) +float tempEngineCoolant = -999.0f; // Temperatura de refrigerante de motor (OBD2) +float tempInterior = -999.0f; // Temperatura interior habitáculo (DS18B20 #1) +float tempExterior = -999.0f; // Temperatura exterior ambiente (DS18B20 #2) uint8_t dsSensorCount = 0; // Cantidad de sensores 1-Wire detectados // User configuration here to change display > @@ -344,12 +344,12 @@ uint8_t btDeviceCount = 0; // discovered bluetooth devices counter esp_bd_addr_t client_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // obdII mac addr esp_bd_addr_t recent_client_addr = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // keep last btaddr in RTC memory + 0x6C, 0x62, 0x6D, 0x14, 0x08, 0xFD}; // Default to PC emulator MAC const String client_name = "OBDII"; // adaptor name to search esp_spp_sec_t sec_mask = ESP_SPP_SEC_NONE; // or ESP_SPP_SEC_ENCRYPT|ESP_SPP_SEC_AUTHENTICATE to // request pincode confirmation -esp_spp_role_t role = ESP_SPP_ROLE_SLAVE; // or ESP_SPP_ROLE_MASTER +esp_spp_role_t role = ESP_SPP_ROLE_MASTER; // Initiator is SPP MASTER bool foundOBD2 = false; BluetoothSerial BTSerial; // bluetooth serial device @@ -548,8 +548,12 @@ volatile bool btConnectedTrigger = false; volatile bool systemSleeping = false; void btBackgroundTask(void *pvParameters) { + const esp_bd_addr_t EMU_ADDR = {0x6C, 0x62, 0x6D, 0x14, 0x08, 0xFD}; while (true) { if (!systemSleeping && !foundOBD2) { + bool connected = false; + + // 1. Intentar conectar a la direccion reciente guardada si es valida bool hasRecentAddr = false; for (uint8_t i = 0; i < 6; i++) { if (recent_client_addr[i] != 0) { @@ -563,29 +567,66 @@ void btBackgroundTask(void *pvParameters) { vTaskDelay(pdMS_TO_TICKS(100)); if (BTSerial.connect(recent_client_addr, 0, sec_mask, role)) { if (BTSerial.connected() && !systemSleeping) { - sendATCommand("ATE0", 800); - sendATCommand("ATL0", 800); - sendATCommand("ATH0", 800); - sendATCommand("ATAT1", 800); - sendATCommand("ATSP1", 800); - String r0100 = sendATCommand("0100", 2500); - ignitionOn = (r0100.indexOf("41 00") != -1); - foundOBD2 = true; - prompt = true; - bt_message = ""; - updateRearLedTheme(); - btConnectedTrigger = - true; // Signal Core 1 (UI thread) to switch screen! - } else { - BTSerial.disconnect(); - setRearLedOff(); - foundOBD2 = false; + connected = true; } - } else { - setRearLedOff(); - foundOBD2 = false; } } + + // 2. Si fallo o no habia direccion, intentar conectar a la MAC del emulador PC + if (!connected && !systemSleeping) { + bool isRecentSameAsEmu = true; + for (uint8_t i = 0; i < 6; i++) { + if (recent_client_addr[i] != EMU_ADDR[i]) { + isRecentSameAsEmu = false; + break; + } + } + if (!isRecentSameAsEmu) { + setRearLedColor(0, 0, 200); + BTSerial.disconnect(); + vTaskDelay(pdMS_TO_TICKS(100)); + if (BTSerial.connect(const_cast(EMU_ADDR), 0, sec_mask, role)) { + if (BTSerial.connected() && !systemSleeping) { + connected = true; + memcpy(recent_client_addr, EMU_ADDR, 6); + pref.putBytes("recent_client", recent_client_addr, 6); + } + } + } + } + + // 3. Si aun no conecto, buscar y conectar por nombre "OBDII" (inquiry scan) + if (!connected && !systemSleeping) { + setRearLedColor(0, 0, 200); + BTSerial.disconnect(); + vTaskDelay(pdMS_TO_TICKS(100)); + if (BTSerial.connect("OBDII")) { + if (BTSerial.connected() && !systemSleeping) { + connected = true; + } + } + } + + // Si logro conectar con cualquiera de los metodos: + if (connected && !systemSleeping) { + sendATCommand("ATE0", 800); + sendATCommand("ATL0", 800); + sendATCommand("ATH0", 800); + sendATCommand("ATAT1", 800); + sendATCommand("ATSP1", 800); + String r0100 = sendATCommand("0100", 2500); + ignitionOn = (r0100.indexOf("41 00") != -1); + foundOBD2 = true; + prompt = true; + bt_message = ""; + updateRearLedTheme(); + btConnectedTrigger = + true; // Signal Core 1 (UI thread) to switch screen! + } else { + BTSerial.disconnect(); + setRearLedOff(); + foundOBD2 = false; + } } ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(3500)); } @@ -711,7 +752,7 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6] if (transitionType > 1) transitionType = 0; currentScreenPage = pref.getChar("scr_page", 0); - if (currentScreenPage < 0 || currentScreenPage > 4) + if (currentScreenPage < 0 || currentScreenPage > 3) currentScreenPage = 0; if (brightnessIndex > 0) { targetBacklight = BRIGHTNESS_PWM_TABLE[brightnessIndex]; @@ -853,6 +894,11 @@ void loop() { updateGpsSpeedometer(); updateGpsIconAnim(); + // Non-blocking live refresh for resting Page 0 (Cockpit Instrumental Deportivo) + if (currentScreenPage == 0 && fullScreenCell == -1) { + updateClusterScreen(false); + } + // Non-blocking live refresh for resting Page 2 (GPS Clock & Date) if (currentScreenPage == 2 && fullScreenCell == -1) { updateGpsClockScreen(false); @@ -863,11 +909,6 @@ void loop() { updateGpsNavScreen(false); } - // Non-blocking live refresh for resting Page 4 (Cockpit Instrumental Deportivo) - if (currentScreenPage == 4 && fullScreenCell == -1) { - updateClusterScreen(false); - } - if (wokeFromSleep) { wokeFromSleep = false; lastActivityTime = millis(); @@ -927,10 +968,10 @@ void loop() { // (disabled in full-screen mode) if (!longPressHandled) { if (fullScreenCell == -1 && liveDeltaX < -28 && abs(liveDeltaY) < 85) { - // SWIPE LEFT: Transition to next page (0 -> 1 -> 2 -> 3 -> 4 -> 0) + // SWIPE LEFT: Transition to next page (0 -> 1 -> 2 -> 3 -> 0) longPressHandled = true; touchBeep(); - uint8_t nextPage = (currentScreenPage + 1) % 5; + uint8_t nextPage = (currentScreenPage + 1) % 4; performScreenTransition(true, currentScreenPage, nextPage); setScreenPage(nextPage); initScreen(false); // Seamless: keep transition frame without blanking @@ -940,11 +981,11 @@ void loop() { lastActivityTime = millis(); } else if (fullScreenCell == -1 && liveDeltaX > 28 && abs(liveDeltaY) < 85) { - // SWIPE RIGHT: Transition to previous page (0 -> 4 -> 3 -> 2 -> 1 -> 0) + // SWIPE RIGHT: Transition to previous page (0 -> 3 -> 2 -> 1 -> 0) longPressHandled = true; touchBeep(); uint8_t nextPage = - (currentScreenPage == 0) ? 4 : (currentScreenPage - 1); + (currentScreenPage == 0) ? 3 : (currentScreenPage - 1); performScreenTransition(false, currentScreenPage, nextPage); setScreenPage(nextPage); initScreen(false); // Seamless: keep transition frame without blanking @@ -984,7 +1025,7 @@ void loop() { // SWIPE LEFT longPressHandled = true; touchBeep(); - uint8_t nextPage = (currentScreenPage + 1) % 5; + uint8_t nextPage = (currentScreenPage + 1) % 4; performScreenTransition(true, currentScreenPage, nextPage); setScreenPage(nextPage); initScreen(false); // Seamless: keep transition frame without blanking @@ -997,7 +1038,7 @@ void loop() { longPressHandled = true; touchBeep(); uint8_t nextPage = - (currentScreenPage == 0) ? 4 : (currentScreenPage - 1); + (currentScreenPage == 0) ? 3 : (currentScreenPage - 1); performScreenTransition(false, currentScreenPage, nextPage); setScreenPage(nextPage); initScreen(false); // Seamless: keep transition frame without blanking @@ -1010,16 +1051,8 @@ void loop() { // Valid TAP detected! touchBeep(); 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(); + // Tap on Cockpit Instrumental: immediate cluster refresh + updateClusterScreen(true); } else if (currentScreenPage == 1) { // Tap on Tri-Temp screen: immediate sensor refresh updateTriTempSensors(true); @@ -1029,9 +1062,6 @@ void loop() { } else if (currentScreenPage == 3) { // Tap on GPS Navigation screen: immediate telemetry refresh updateGpsNavScreen(true); - } else if (currentScreenPage == 4) { - // Tap on Cockpit Instrumental: immediate cluster refresh - updateClusterScreen(true); } waitForTouchRelease(); lastActivityTime = millis(); @@ -1075,6 +1105,12 @@ void loop() { if (!isBtConnected && wasBtConnected) { wasBtConnected = false; currentRpm = 0.0f; + tempEngineCoolant = -999.0f; + for (uint8_t c = 0; c < 4; c++) { + if (c != 1 || !isGpsSpeedValid()) { + last_valid_data[c] = -999.0f; + } + } ignitionOn = false; foundOBD2 = false; uint8_t fallbackPage = isGpsSynced() ? 2 : 1; @@ -1089,14 +1125,16 @@ void loop() { if (!isBtConnected) { currentRpm = 0.0f; - ignitionOn = false; - if (currentScreenPage == 0 || fullScreenCell != -1) { - for (uint8_t c = 0; c < 4; c++) { - if (c == 1 && isGpsSpeedValid()) - continue; // Mantener la velocidad de GPS en el velocimetro - plotNoData(c); + tempEngineCoolant = -999.0f; + for (uint8_t c = 0; c < 4; c++) { + if (c != 1 || !isGpsSpeedValid()) { + last_valid_data[c] = -999.0f; } } + ignitionOn = false; + if (currentScreenPage == 0) { + updateClusterScreen(false); + } foundOBD2 = false; // Core 0 background task will handle reconnection smoothly! return; diff --git a/sketch/obd2_gauge/rainbow_text.h b/sketch/obd2_gauge/rainbow_text.h index 4b54142..8857d53 100644 --- a/sketch/obd2_gauge/rainbow_text.h +++ b/sketch/obd2_gauge/rainbow_text.h @@ -2,6 +2,7 @@ #include #include #include "DSEG7_Modern_BoldItalic_34.h" +#include "DSEG7_Modern_BoldItalic_44.h" #include "DSEG7_Modern_BoldItalic_80.h" extern bool isRainbowTheme(); @@ -132,9 +133,9 @@ inline void drawDseg7RightString34(T &dst, const char *str, int rightX, int y0, int totalW = getDseg7StringWidth34(str); int startX = rightX - totalW; - if (padding > totalW) { - int clearW = padding - totalW; - int clearX = rightX - padding; + if (padding > 0) { + int clearW = (padding > totalW) ? padding : totalW; + int clearX = rightX - clearW; if (clearX < 0) { clearW += clearX; clearX = 0; @@ -154,10 +155,93 @@ inline void drawDseg7RightString34(T &dst, const char *str, int rightX, int y0, } } +// Measure width in pixels of a string rendered in DSEG7 44pt (tight tracking adv=31) +inline int getDseg7StringWidth44(const char *str) { + int w = 0; + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + if (c == '.') w += 10; + else if (c == ' ') w += 31; + else w += 31; + } + return w + 5; +} + +// Draw a single DSEG7 44pt character directly to destination +template +inline void drawDseg7Digit44(T &dst, int x0, int y0, char c, uint16_t color, + uint16_t bgCol, int clip_h = 240, bool rainbow = false, int screen_offset_x = 0) { + uint32_t offset = 0; + uint8_t w = 36; + if (c >= '0' && c <= '9') { + offset = 388 + (c - '0') * 1584; + } else if (c == '.') { + offset = 16228; + w = 10; + } else if (c == '-') { + offset = 16668; + } else { + return; + } + + dst.startWrite(); + for (int dy = 0; dy < 44; dy++) { + int curY = y0 + dy; + if (curY < 0 || curY >= clip_h) + continue; + const uint8_t *row = &DSEG7_Modern_BoldItalic_44[offset + dy * w]; + for (int dx = 0; dx < w; dx++) { + int curX = x0 + dx; + if (curX < 0 || curX >= 320) + continue; + uint8_t a = pgm_read_byte(row + dx); + if (a == 0) + continue; + uint16_t fg = rainbow ? getRainbowScreenColor(curX + screen_offset_x, curY) : color; + if (a >= 240) { + dst.drawPixel(curX, curY, fg); + } else { + dst.drawPixel(curX, curY, dst.alphaBlend(a, fg, bgCol)); + } + } + } + dst.endWrite(); +} + +// Draw a DSEG7 44pt string right-aligned with automatic background padding and rainbow support +template +inline void drawDseg7RightString44(T &dst, const char *str, int rightX, int y0, + uint16_t color, uint16_t bgCol, int padding = 0, + int clip_h = 240, bool rainbow = false, int screen_offset_x = 0) { + int totalW = getDseg7StringWidth44(str); + int startX = rightX - totalW; + + if (padding > 0) { + int clearW = (padding > totalW) ? padding : totalW; + int clearX = rightX - clearW; + if (clearX < 0) { + clearW += clearX; + clearX = 0; + } + if (clearW > 0) { + dst.fillRect(clearX, y0, clearW, 44, bgCol); + } + } + + int curX = startX; + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + uint16_t charColor = isYapaTheme() ? getYapaColor(i) : color; + drawDseg7Digit44(dst, curX, y0, c, charColor, bgCol, clip_h, rainbow, screen_offset_x); + if (c == '.') curX += 10; + else curX += 31; + } +} + // Draw a single DSEG7 80pt digit directly to destination with rainbow gradient template inline void drawDseg7Digit80(T &dst, int x0, int y0, uint8_t digit, - uint16_t color, uint16_t bgCol, int clip_h = 240, bool rainbow = false) { + uint16_t color, uint16_t bgCol, int clip_h = 240, bool rainbow = false, int screen_offset_x = 0) { if (digit > 9) return; const uint8_t *bmp = &DSEG7_Modern_BoldItalic_80[388 + digit * 5200]; dst.startWrite(); @@ -173,7 +257,7 @@ inline void drawDseg7Digit80(T &dst, int x0, int y0, uint8_t digit, continue; uint8_t a = pgm_read_byte(row + dx); if (a == 0) continue; - uint16_t fg = rainbow ? getRainbowScreenColor(curX, curY) : color; + uint16_t fg = rainbow ? getRainbowScreenColor(curX + screen_offset_x, curY) : color; if (a >= 240) { dst.drawPixel(curX, curY, fg); } else { @@ -188,16 +272,17 @@ inline void drawDseg7Digit80(T &dst, int x0, int y0, uint8_t digit, template inline void drawDseg7RightString80(T &dst, const char *str, int rightX, int y0, uint16_t color, uint16_t bgCol, int padding = 0, - int clip_h = 240, bool rainbow = false) { - // Ancho aproximado por digito 80pt: advance de 65px - int len = 0; - for (int i = 0; str[i] != '\0'; i++) len++; - int totalW = len * 65; + int clip_h = 240, bool rainbow = false, int screen_offset_x = 0) { + int totalW = 0; + for (int i = 0; str[i] != '\0'; i++) { + if (str[i] == '-') totalW += 50; + else totalW += 65; + } int startX = rightX - totalW; - if (padding > totalW) { - int clearW = padding - totalW; - int clearX = rightX - padding; + if (padding > 0) { + int clearW = (padding > totalW) ? padding : totalW; + int clearX = rightX - clearW; if (clearX < 0) { clearW += clearX; clearX = 0; @@ -212,15 +297,159 @@ inline void drawDseg7RightString80(T &dst, const char *str, int rightX, int y0, char c = str[i]; uint16_t charColor = isYapaTheme() ? getYapaColor(i) : color; if (c >= '0' && c <= '9') { - drawDseg7Digit80(dst, curX, y0, c - '0', charColor, bgCol, clip_h, rainbow); + drawDseg7Digit80(dst, curX, y0, c - '0', charColor, bgCol, clip_h, rainbow, screen_offset_x); + curX += 65; } else if (c == '-') { - // Si es '-', dibujamos barra horizontal central de DSEG7 - dst.fillRect(curX + 10, y0 + 36, 45, 8, rainbow ? getRainbowScreenColor(curX + 32, y0 + 40) : charColor); + for (int dy = 0; dy < 6; dy++) { + int py = y0 + 37 + dy; + if (py >= 0 && py < clip_h) { + int startDx = 11 - dy / 2; + dst.fillRect(curX + startDx, py, 28, 1, rainbow ? getRainbowScreenColor(curX + screen_offset_x + startDx + 14, py) : charColor); + } + } + curX += 50; + } else { + curX += 65; } - curX += 65; } } +// Fast zero-flicker line-buffered DSEG7 44pt renderer (direct to TFT, NO heap allocation!) +template +inline void drawDseg7RightFast44(T &dst, const char *str, int rightX, int y0, + uint16_t color, uint16_t bgCol, int minTotalW = 0, + bool rainbow = false) { + int totalW = getDseg7StringWidth44(str); + int renderW = (minTotalW > totalW) ? minTotalW : totalW; + if (renderW > 160) renderW = 160; + int startX = rightX - renderW; + int textOffsetX = renderW - totalW; + + uint16_t lineBuf[160]; + + dst.startWrite(); + for (int dy = 0; dy < 44; dy++) { + int curY = y0 + dy; + if (curY < 0 || curY >= 240) continue; + + for (int i = 0; i < renderW; i++) { + lineBuf[i] = bgCol; + } + + int curX = textOffsetX; + for (int idx = 0; str[idx] != '\0'; idx++) { + char c = str[idx]; + uint32_t offset = 0; + uint8_t w = 36; + uint8_t adv = 31; + if (c >= '0' && c <= '9') { + offset = 388 + (c - '0') * 1584; + } else if (c == '.') { + offset = 16228; + w = 10; + adv = 10; + } else if (c == '-') { + offset = 16668; + } else { + curX += adv; + continue; + } + + const uint8_t *row = &DSEG7_Modern_BoldItalic_44[offset + dy * w]; + uint16_t charColor = isYapaTheme() ? getYapaColor(idx) : color; + + for (int dx = 0; dx < w; dx++) { + int bufX = curX + dx; + if (bufX < 0 || bufX >= renderW) continue; + uint8_t a = pgm_read_byte(row + dx); + if (a == 0) continue; + int screenX = startX + bufX; + uint16_t fg = rainbow ? getRainbowScreenColor(screenX, curY) : charColor; + if (a >= 240) { + lineBuf[bufX] = fg; + } else { + lineBuf[bufX] = dst.alphaBlend(a, fg, bgCol); + } + } + curX += adv; + } + + dst.pushImage(startX, curY, renderW, 1, lineBuf); + } + dst.endWrite(); +} + +// Fast zero-flicker line-buffered DSEG7 80pt renderer (direct to TFT, NO heap allocation!) +template +inline void drawDseg7RightFast80(T &dst, const char *str, int rightX, int y0, + uint16_t color, uint16_t bgCol, int minTotalW = 0, + bool rainbow = false) { + int totalW = 0; + for (int i = 0; str[i] != '\0'; i++) { + if (str[i] == '-') totalW += 50; + else totalW += 65; + } + int renderW = (minTotalW > totalW) ? minTotalW : totalW; + if (renderW > 184) renderW = 184; + int startX = rightX - renderW; + if (startX < 10) { + renderW -= (10 - startX); + startX = 10; + } + int textOffsetX = renderW - totalW; + + uint16_t lineBuf[200]; + + dst.startWrite(); + for (int dy = 0; dy < 80; dy++) { + int curY = y0 + dy; + if (curY < 0 || curY >= 240) continue; + + for (int i = 0; i < renderW; i++) { + lineBuf[i] = bgCol; + } + + int curX = textOffsetX; + for (int idx = 0; str[idx] != '\0'; idx++) { + char c = str[idx]; + uint16_t charColor = isYapaTheme() ? getYapaColor(idx) : color; + if (c >= '0' && c <= '9') { + const uint8_t *bmp = &DSEG7_Modern_BoldItalic_80[388 + (c - '0') * 5200 + dy * 65]; + for (int dx = 0; dx < 65; dx++) { + int bufX = curX + dx; + if (bufX < 0 || bufX >= renderW) continue; + uint8_t a = pgm_read_byte(bmp + dx); + if (a == 0) continue; + int screenX = startX + bufX; + uint16_t fg = rainbow ? getRainbowScreenColor(screenX, curY) : charColor; + if (a >= 240) { + lineBuf[bufX] = fg; + } else { + lineBuf[bufX] = dst.alphaBlend(a, fg, bgCol); + } + } + curX += 65; + } else if (c == '-') { + if (dy >= 37 && dy < 43) { + int startDx = 11 - (dy - 37) / 2; + for (int dx = startDx; dx < startDx + 28; dx++) { + int bufX = curX + dx; + if (bufX >= 0 && bufX < renderW) { + lineBuf[bufX] = rainbow ? getRainbowScreenColor(startX + bufX, curY) : charColor; + } + } + } + curX += 50; + } else { + curX += 65; + } + } + + dst.pushImage(startX, curY, renderW, 1, lineBuf); + } + dst.endWrite(); +} + // Apply spectral rainbow coloring to any pre-rendered text sprite with 100% anti-aliasing preservation inline void applyRainbowToSprite(TFT_eSprite &s, int screenX, int screenY, uint16_t bgCol = 0x10A2) { if (!s.created()) return; diff --git a/sketch/obd2_gauge/tri_temp.h b/sketch/obd2_gauge/tri_temp.h index 964c27a..41c3e0b 100644 --- a/sketch/obd2_gauge/tri_temp.h +++ b/sketch/obd2_gauge/tri_temp.h @@ -47,6 +47,11 @@ static TFT_eSprite sprTriTitle[3] = { TFT_eSprite(&tft), TFT_eSprite(&tft), TFT_ static bool sprTriTitleReady = false; inline void invalidateTriTempTitles() { + for (int i = 0; i < 3; i++) { + if (sprTriTitle[i].created()) { + sprTriTitle[i].deleteSprite(); + } + } sprTriTitleReady = false; } @@ -167,7 +172,12 @@ inline void draw7SegDegreeC(TFT_eSPI &dst, int bx, int by, uint16_t color = 0xC6 if (px < 0 || px >= dst.width()) continue; uint8_t a = pgm_read_byte(&s_dsegUnitAlpha[rowOffset + i]); if (a > 8) { - uint16_t curCol = rainbow ? getRainbowScreenColor(px, py) : color; + uint16_t curCol = color; + if (rainbow) { + curCol = getRainbowScreenColor(px, py); + } else if (isYapaTheme()) { + curCol = (i < 12) ? getYapaColor(0) : getYapaColor(1); + } uint16_t pixCol = (a >= 250) ? curCol : fastBlend(a, curCol, bgCol); dst.drawPixel(px, py, pixCol); } @@ -247,13 +257,13 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) { else accentCol = TFT_RED; } else if (cardIdx == 1) { titleStr = "INTERIOR"; - subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1 (GPIO 22)" : "DS18B20 SENSOR 1"; + subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1 (GPIO 22)" : "DS18B20 NO DETECTADO"; minRange = 10.0f; maxRange = 40.0f; accentCol = isRainbowTheme() ? (uint16_t)0x07E0 : (isYapaTheme() ? getYapaColor(1) : getThemeAccentColor()); } else { titleStr = "EXTERIOR"; - subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2 (GPIO 22)" : (dsSensorCount == 1 ? "ESTIMADA (-4.5`C)" : "DS18B20 SENSOR 2"); + subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2 (GPIO 22)" : "DS18B20 NO DETECTADO"; minRange = -10.0f; maxRange = 50.0f; accentCol = isRainbowTheme() ? (uint16_t)0xFD20 : (isYapaTheme() ? getYapaColor(2) : getThemePrimaryColor()); @@ -298,7 +308,7 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) { tft.unloadFont(); } - tft.setTextColor(0x8410, cardBg); // Soft gray + tft.setTextColor(accentCol, cardBg); tft.drawString(subStr, cardX + 16, cardY + 32, 1); } @@ -310,34 +320,35 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) { 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; + int fillW = (val <= -900.0f) ? 0 : (int)(barW * ratio); + if (fillW < 4 && val > -900.0f) fillW = 4; if (x_offset == 0) { - drawTriTempBar(barX, barY, fillW, accentCol); + drawTriTempBar(barX, barY, fillW, (val <= -900.0f) ? (uint16_t)0x18C3 : accentCol); oldTriFillW[cardIdx] = fillW; oldTriAccentCol[cardIdx] = accentCol; } else { tft.fillRoundRect(barX, barY, barW, 8, 3, 0x18C3); - tft.fillRoundRect(barX, barY, fillW, 8, 3, accentCol); + if (fillW > 0) { + tft.fillRoundRect(barX, barY, fillW, 8, 3, accentCol); + } } // 4. Digital Number using DSEG7 Modern Bold Italic 34pt String numStr; + uint16_t numCol = accentCol; if (val <= -900.0f) { numStr = "---"; - } else if (isInt) { - numStr = String((int)val); } else { - numStr = String(val, 1); + numStr = String((int)round(val)); } bool rainbowTxt = isRainbowTheme() && (accentCol != TFT_RED); - drawDseg7RightString34(tft, numStr.c_str(), cardX + 262, cardY + 20, accentCol, cardBg, 100, 240, rainbowTxt); + drawDseg7RightString34(tft, numStr.c_str(), cardX + 262, cardY + 20, numCol, cardBg, 100, 240, rainbowTxt); // Unit "°C" in authentic digital 7-segment italic typography matching numbers bool rainbowUnit = isRainbowTheme() && (accentCol != TFT_RED); - uint16_t unitCol = rainbowUnit ? getRainbowScreenColor(cardX + 268, cardY + 22) : (isYapaTheme() && accentCol != TFT_RED ? getYapaColor(numStr.length()) : (accentCol == TFT_RED ? TFT_RED : 0xC618)); + uint16_t unitCol = rainbowUnit ? getRainbowScreenColor(cardX + 268, cardY + 22) : (isYapaTheme() && accentCol != TFT_RED ? getYapaColor(numStr.length()) : (accentCol == TFT_RED ? TFT_RED : accentCol)); draw7SegDegreeC(tft, cardX + 268, cardY + 22, unitCol, cardBg, rainbowUnit); } @@ -388,7 +399,7 @@ inline void updateTriTempCardValue(uint8_t cardIdx, float val, float oldVal) { sX += cw; } } else { - tft.setTextColor(0x8410, cardBg); + tft.setTextColor(accentCol, cardBg); tft.drawString(subStr, cardX + 16, cardY + 32, 1); } } @@ -420,27 +431,26 @@ inline void updateTriTempCardValue(uint8_t cardIdx, float val, float oldVal) { 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; + int fillW = (val <= -900.0f) ? 0 : (int)(barW * ratio); + if (fillW < 4 && val > -900.0f) fillW = 4; if (fillW != oldTriFillW[cardIdx] || accentCol != oldTriAccentCol[cardIdx]) { - drawTriTempBar(barX, barY, fillW, accentCol); + drawTriTempBar(barX, barY, fillW, (val <= -900.0f) ? (uint16_t)0x18C3 : accentCol); oldTriFillW[cardIdx] = fillW; oldTriAccentCol[cardIdx] = accentCol; } // 3. Digital Number update using DSEG7 Modern Bold Italic 34pt (setTextPadding prevents flicker) String numStr; + uint16_t numCol = accentCol; if (val <= -900.0f) { numStr = "---"; - } else if (isInt) { - numStr = String((int)val); } else { - numStr = String(val, 1); + numStr = String((int)round(val)); } bool rainbowTxt = isRainbowTheme() && (accentCol != TFT_RED); - drawDseg7RightString34(tft, numStr.c_str(), cardX + 262, cardY + 20, accentCol, cardBg, 100, 240, rainbowTxt); + drawDseg7RightString34(tft, numStr.c_str(), cardX + 262, cardY + 20, numCol, cardBg, 100, 240, rainbowTxt); } // Synchronize tracking variables without touching TFT display (zero flicker) @@ -463,8 +473,8 @@ inline void syncTriTempTrackers() { float ratio = (vals[i] - minR[i]) / (maxR[i] - minR[i]); if (ratio < 0.0f) ratio = 0.0f; if (ratio > 1.0f) ratio = 1.0f; - int fillW = (int)(120 * ratio); - if (fillW < 4) fillW = 4; + int fillW = (vals[i] <= -900.0f) ? 0 : (int)(120 * ratio); + if (fillW < 4 && vals[i] > -900.0f) fillW = 4; oldTriFillW[i] = fillW; uint16_t acc = defAcc[i]; @@ -492,12 +502,11 @@ inline void drawTriTempScreen(int x_offset = 0) { syncTriTempTrackers(); - // Draw Page Indicator: [○ ● ○ ○ ○] (Page 2 of 5 active) - tft.fillCircle(132 + x_offset, 2, 2, 0x5AEB); // Page 0 inactive - tft.fillCircle(146 + x_offset, 2, 2, getThemePrimaryColor()); // Page 1 active - tft.fillCircle(160 + x_offset, 2, 2, 0x5AEB); // Page 2 inactive - tft.fillCircle(174 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive - tft.fillCircle(188 + x_offset, 2, 2, 0x5AEB); // Page 4 inactive + // Draw Page Indicator: [○ ● ○ ○] (Page 1 of 4 active) + tft.fillCircle(139 + x_offset, 2, 2, 0x5AEB); // Page 0 inactive + tft.fillCircle(153 + x_offset, 2, 2, getThemePrimaryColor()); // Page 1 active + tft.fillCircle(167 + x_offset, 2, 2, 0x5AEB); // Page 2 inactive + tft.fillCircle(181 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive } // Update coolant reading dynamically without redrawing entire screen @@ -529,23 +538,25 @@ inline void updateTriTempSensors(bool forceRedraw = false) { if (dsSensorCount > 0) { float t1 = dsSensors.getTempCByIndex(0); - if (t1 > -50.0f && t1 < 100.0f) { + if (t1 > -50.0f && t1 < 100.0f && t1 != 85.0f && t1 != -127.0f) { tempInterior = t1; + } else { + tempInterior = -999.0f; } if (dsSensorCount > 1) { float t2 = dsSensors.getTempCByIndex(1); - if (t2 > -50.0f && t2 < 100.0f) { + if (t2 > -50.0f && t2 < 100.0f && t2 != 85.0f && t2 != -127.0f) { tempExterior = t2; + } else { + tempExterior = -999.0f; } } else { - // Demo exterior offset if only 1 physical sensor connected - tempExterior = tempInterior - 4.5f; + tempExterior = -999.0f; } 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); + tempInterior = -999.0f; + tempExterior = -999.0f; } if (currentScreenPage == 1 && fullScreenCell == -1) { @@ -557,11 +568,15 @@ inline void updateTriTempSensors(bool forceRedraw = false) { oldTriInterior = tempInterior; oldTriExterior = tempExterior; } else { - if (forceRedraw || abs(tempInterior - oldTriInterior) >= 0.1f) { + int curI = (tempInterior > -900.0f) ? (int)round(tempInterior) : -999; + int oldI = (oldTriInterior > -900.0f) ? (int)round(oldTriInterior) : -999; + if (forceRedraw || curI != oldI) { updateTriTempCardValue(1, tempInterior, oldTriInterior); oldTriInterior = tempInterior; } - if (forceRedraw || abs(tempExterior - oldTriExterior) >= 0.1f) { + int curE = (tempExterior > -900.0f) ? (int)round(tempExterior) : -999; + int oldE = (oldTriExterior > -900.0f) ? (int)round(oldTriExterior) : -999; + if (forceRedraw || curE != oldE) { updateTriTempCardValue(2, tempExterior, oldTriExterior); oldTriExterior = tempExterior; }