diff --git a/README.md b/README.md
index 2892f23..87d9e2d 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ El sistema fue diseñado y calibrado a medida para el motor **Ford Ka Endura-E 1
### 1. Cuadrante de 4 Instrumentos Simultáneos
* **Temperatura de Refrigerante (PID 0105):** Rango 60°C a 120°C. Color dinámico (verde en rango normal, amarillo de advertencia y rojo a partir de 110°C con alarma sonora y visual).
-* **Velocidad del Vehículo (PID 010D):** Rango 0 a 160 km/h.
+* **Velocidad del Vehículo (Cálculo GPS Satelital + Fallback OBD-II):** Velocidad real de avance medida por satélite con precisión milimétrica (`gps.speed.kmph()`). Muestra el distintivo `[ GPS ]` en el cuadrante. Si se pierde señal (túnel o estacionamiento subterráneo), conmuta de forma transparente al sensor del vehículo (PID 010D).
* **Revoluciones por Minuto (PID 010C):** Rango 0 a 6.000 RPM.
* **Voltaje de Batería (Comando ATRV):** Rango 10.0V a 16.0V en tiempo real directo del borne del ELM327.
@@ -27,7 +27,32 @@ El sistema fue diseñado y calibrado a medida para el motor **Ford Ka Endura-E 1
* **Diseño táctico:** Pastilla rectangular con bordes redondeados en verde esmeralda brillante (`[ ECO ]`) que se ilumina en el cuadrante de RPM y en el encabezado de pantalla completa.
* Al salir del rango óptimo, se apaga de inmediato restaurando de forma invisible el degradado del fondo.
-### 4. Audio y Melodías de Alarma
+### 4. Pantalla Tri-Temperatura (Página 2)
+* Tres tarjetas tácticas con barra de progreso y lectura numérica digital:
+ * **Refrigerante:** Lectura en vivo de ECU OBD-II.
+ * **Habitáculo (Interior):** Sensor digital Dallas DS18B20 #1 en bus 1-Wire (GPIO 22).
+ * **Ambiente (Exterior):** Sensor digital Dallas DS18B20 #2 en bus 1-Wire (GPIO 22).
+
+### 5. Pantalla Reloj Digital GPS & Fecha (Página 3)
+* **Hora Satelital Atómica:** Dígitos gigantes en tipografía **DSEG7 Modern 80pt** perfectamente centrados en la tarjeta superior, sincronizados con satélites GPS en huso horario UTC-3 (Argentina).
+* **Dos Puntos Parpadeantes:** Pulso rítmico continuo entre horas y minutos.
+* **Badge Calendario y Fecha:** Estructurado en dos bloques:
+ * **Badge Calendario (Izquierda):** Cabecera en color de tema con el mes abreviado a 3 letras (`SEP`, `OCT`, `NOV`...) y número del día en 34pt 7 segmentos.
+ * **Información Textual (Derecha):** Día de la semana completo en mayúsculas (`VIERNES`), línea divisoria bitono y año (`2026`).
+
+### 6. Pantalla de Telemetría GPS y Navegación (Página 4)
+* **Altitud (msnm):** Elevación respecto al nivel del mar en metros con lectura digital en 34pt.
+* **Rumbo y Brújula:** Dirección de avance en grados (0° a 359°) y punto cardinal completo en tiempo real (`NORTE`, `NORESTE`, `ESTE`, `SURESTE`, `SUR`, `SUROESTE`, `OESTE`, `NOROESTE`).
+* **Satélites GPS en Grande:** Icono satelital vectorizado a gran escala, número de satélites en seguimiento en 34pt, índice de precisión HDOP y diagnóstico de señal.
+* **Coordenadas Geográficas:** Latitud y Longitud con precisión de 5 decimales.
+* **Transiciones Fluidas:** Navegación táctil deslizante (*Swipe*) o desvanecida (*Fade*) entre las 4 pantallas mediante indicador de páginas `[● ○ ○ ○]`, `[○ ● ○ ○]`, `[○ ○ ● ○]`, `[○ ○ ○ ●]`.
+
+### 7. Gestión Inteligente de Energía para GPS (Comandos UBX)
+* Integración total con el sistema de **Auto-Sleep**:
+ * Al suspender el sistema por inactividad o parada de motor, transmite la trama binaria `UBX-RXM-PMREQ` para apagar la etapa de radiofrecuencia del módulo GPS (consumo inferior a 0.5 mA).
+ * Al reactivarse por toque en pantalla o botón, envía una ráfaga serie *Wake-on-UART* para reanudar el seguimiento satelital de inmediato.
+
+### 7. Audio y Melodías de Alarma
* Control de volumen independiente: Silencio, Ultra Bajo, Bajo (Casio suave), Medio y Alto.
* Selector de sonidos y tonos de alarma:
* Chime BMW (doble tono sincronizado).
@@ -35,7 +60,7 @@ El sistema fue diseñado y calibrado a medida para el motor **Ford Ka Endura-E 1
* Tono bitonal clásico.
* Alarma continua urgente.
-### 5. Menú de Configuración Táctil
+### 8. Menú de Configuración Táctil
* **Gestión de Alarmas:** Conmutadores táctiles independientes ON/OFF para cada parámetro con guardado persistente en NVS (Preferences).
* **Temas Visuales:** 6 paletas de color con degradados ambientales de fondo (*Cyan, Emerald, Amber, Violet, Ice Blue, Graphite*).
* **Brillo Automático:** Regulación inteligente mediante sensor de luz ambiente (LDR) integrado en la placa, o selección manual (5% a 100%).
@@ -50,11 +75,18 @@ El sistema fue diseñado y calibrado a medida para el motor **Ford Ka Endura-E 1
* Pantalla táctil resistiva con controlador XPT2046.
* Sensor de luz LDR en pin analógico.
* LED RGB integrado.
-2. **Escáner OBD-II Bluetooth:**
+2. **Módulo GPS Satelital N32+ / U-blox NEO-7M / NEO-6M:**
+ * Conexión: Ficha de 4 pines al conector rotulado **UART** (o P1).
+ * Pin 1 (Vin 5V), Pin 2 (GPIO 1 TX0 hacia RX GPS), Pin 3 (GPIO 3 RX0 desde TX GPS), Pin 4 (GND).
+ * Comunicación: UART0 Hardware Serial nativo a 9600 bps (8N1).
+ * Nota para programación: Al flashear por cable USB, desconectar la ficha de 4 pines del GPS para evitar colisión con el grabador USB.
+3. **Escáner OBD-II Bluetooth:**
* Adaptador ELM327 v1.5 (muy recomendado con chip PIC18F25K80, evitar clones v2.1 de baja calidad).
-3. **Buzzer / Parlante:**
+4. **Sensores de Temperatura Digitales DS18B20:**
+ * Hasta 2 sensores en bus 1-Wire conectado a Pin 3 del conector CN1 (`GPIO 22`).
+5. **Buzzer / Parlante:**
* Parlantito o buzzer pasivo de 8 ohms conectado al pin de audio del CYD (controlado por canal PWM con modulación de volumen).
-4. **Alimentación:**
+6. **Alimentación:**
* Módulo convertidor 12V a 5V USB (conectado a positivo bajo llave o directo si se utiliza auto-sleep).
---
diff --git a/diagrama_conexion_ds18b20.png b/diagrama_conexion_ds18b20.png
index f1ad8d4..383e374 100644
Binary files a/diagrama_conexion_ds18b20.png and b/diagrama_conexion_ds18b20.png differ
diff --git a/emulator/README.md b/emulator/README.md
index eb99f27..d99779b 100644
--- a/emulator/README.md
+++ b/emulator/README.md
@@ -37,3 +37,29 @@ Script en Python que emula un adaptador OBD-II Bluetooth ELM327 v1.5 completo y
- `010B`: Presión absoluta del múltiple (MAP)
- `0104`: Carga del motor (Engine Load)
- `221E1C` / `015C`: Temperatura de aceite y fluido de transmisión Ford
+
+---
+
+## Simulación de GPS NMEA por USB Serie (/dev/ttyUSB0)
+
+La pantalla CYD recibe datos GPS a través de su puerto UART0 (`Serial` a 9600 bps), el cual está conectado internamente al conector USB (chip CH340). Esto permite simular un módulo GPS real directamente desde la PC **sin tocar ni una sola línea del firmware del ESP32**.
+
+### Opción A: Simulador GPS Independiente (Solo GPS sin Bluetooth)
+Ideal para probar las pantallas de telemetría GPS, reloj satelital, brújula y velocímetro sin necesidad de Bluetooth:
+
+```bash
+# Modo dinámico (aceleración, velocidad crucero y frenado automático):
+python3 emulator/gps_emulator.py
+
+# O con velocidad fija en km/h:
+python3 emulator/gps_emulator.py --speed 75.0
+```
+
+### Opción B: Integrado con el Emulador OBD-II
+Si ejecutas `emulator/obd2_emulator.py` con la CYD conectada por USB, detecta automáticamente `/dev/ttyUSB0` y transmite las tramas NMEA ($GPRMC, $GPGGA, $GPVTG) **sincronizadas exactamente con la velocidad del motor/ECU**:
+
+```bash
+python3 emulator/obd2_emulator.py
+```
+*Si deseas desactivar la simulación GPS al correr el emulador OBD-II, puedes usar `python3 emulator/obd2_emulator.py --no-gps`.*
+
diff --git a/emulator/gps_emulator.py b/emulator/gps_emulator.py
new file mode 100755
index 0000000..1e96ae1
--- /dev/null
+++ b/emulator/gps_emulator.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+"""
+Simulador GPS NMEA para ESP32 CYD (Cluster Ford Ka)
+Transmite sentencias NMEA 0183 ($GPRMC, $GPGGA, $GPVTG) a 9600 bps
+a través del puerto serie USB (/dev/ttyUSB0).
+
+Permite probar y calibrar la telemetría GPS de la pantalla CYD
+(Velocímetro GPS, Reloj Satelital, Brújula, Altitud, Coordenadas, Satélites)
+sin requerir un módulo GPS físico ni modificar el firmware del ESP32.
+"""
+
+import sys
+import time
+import math
+import serial
+import argparse
+from datetime import datetime, timezone
+
+def nmea_checksum(sentence: str) -> str:
+ """Calcula el checksum XOR estándar NMEA 0183"""
+ c = 0
+ for ch in sentence:
+ c ^= ord(ch)
+ return f"{c:02X}"
+
+def build_nmea(body: str) -> bytes:
+ """Envuelve la trama con '$' y '*CHECKSUM\\r\\n'"""
+ chk = nmea_checksum(body)
+ return f"${body}*{chk}\r\n".encode("ascii")
+
+def run_gps_emulator(port="/dev/ttyUSB0", baud=9600, rate_hz=2.0, speed_sim=True, fixed_speed=70.0):
+ print("=" * 60)
+ print(" SIMULADOR GPS NMEA PARA ESP32 CYD")
+ print("=" * 60)
+ print(f"Puerto: {port}")
+ print(f"Baudrate: {baud} bps (8N1)")
+ print(f"Tasa: {rate_hz} Hz ({1.0/rate_hz:.2f}s por trama)")
+ print(f"Modo: {'Ciclo dinámico de velocidad' if speed_sim else f'Velocidad fija {fixed_speed} km/h'}")
+ print("-" * 60)
+
+ try:
+ ser = serial.Serial()
+ ser.port = port
+ ser.baudrate = baud
+ ser.timeout = 1
+ ser.dtr = False
+ ser.rts = False
+ ser.open()
+ print(f"[OK] Conectado exitosamente a {port}")
+ except Exception as e:
+ print(f"[ERROR] No se pudo abrir {port}: {e}")
+ print("Verifica que la placa CYD esté conectada por USB y que tengas permisos.")
+ sys.exit(1)
+
+ # Coordenadas iniciales simuladas (Buenos Aires, Argentina)
+ # -34.6037 S, -58.3816 W
+ base_lat = -34.6037
+ base_lon = -58.3816
+ altitude = 35.0 # metros
+ heading = 45.0 # grados (Noreste)
+ satellites = 9
+
+ t_start = time.time()
+ interval = 1.0 / rate_hz
+
+ try:
+ while True:
+ t = time.time()
+ dt = t - t_start
+
+ # 1. Simulación de velocidad
+ if speed_sim:
+ # Ciclo de 30 segundos: aceleración, crucero, desaceleración
+ phase = dt % 30.0
+ if phase < 5.0:
+ current_speed = 0.0
+ elif phase < 15.0:
+ # Acelera de 0 a 85 km/h
+ current_speed = (phase - 5.0) / 10.0 * 85.0
+ elif phase < 22.0:
+ # Crucero a 85 km/h con pequeña variación
+ current_speed = 85.0 + math.sin(phase) * 3.0
+ else:
+ # Frena de 85 a 0 km/h
+ current_speed = max(0.0, 85.0 * (1.0 - (phase - 22.0) / 8.0))
+ else:
+ current_speed = fixed_speed
+
+ # Variación suave del rumbo según movimiento
+ if current_speed > 1.0:
+ heading = (heading + 1.5 * interval) % 360.0
+
+ # Velocidad en nudos para NMEA (1 km/h = 0.539957 nudos)
+ speed_knots = current_speed * 0.539957
+
+ # 2. Hora y fecha UTC actual
+ now = datetime.now(timezone.utc)
+ time_str = now.strftime("%H%M%S.00")
+ date_str = now.strftime("%d%m%y")
+
+ # Formato de coordenadas NMEA: DDMM.MMMM
+ lat_deg = int(abs(base_lat))
+ lat_min = (abs(base_lat) - lat_deg) * 60.0
+ lat_dir = "S" if base_lat < 0 else "N"
+ lat_nmea = f"{lat_deg:02d}{lat_min:07.4f}"
+
+ lon_deg = int(abs(base_lon))
+ lon_min = (abs(base_lon) - lon_deg) * 60.0
+ lon_dir = "W" if base_lon < 0 else "E"
+ lon_nmea = f"{lon_deg:03d}{lon_min:07.4f}"
+
+ # 3. Construcción de sentencias NMEA
+ # A. $GPRMC: Sentencia recomendada mínima (Hora, Estado A, Lat, Lon, Nudos, Rumbo, Fecha)
+ rmc = f"GPRMC,{time_str},A,{lat_nmea},{lat_dir},{lon_nmea},{lon_dir},{speed_knots:.1f},{heading:.1f},{date_str},,,A"
+
+ # B. $GPGGA: Datos de fijación GPS (Hora, Lat, Lon, Calidad 1, Satélites, HDOP, Altura)
+ gga = f"GPGGA,{time_str},{lat_nmea},{lat_dir},{lon_nmea},{lon_dir},1,{satellites:02d},0.9,{altitude:.1f},M,16.0,M,,"
+
+ # C. $GPVTG: Rumbo y velocidad respecto al fondo (Rumbo, T, Nudos, N, Km/h, K)
+ vtg = f"GPVTG,{heading:.1f},T,,M,{speed_knots:.1f},N,{current_speed:.1f},K,A"
+
+ # Enviar ráfaga NMEA al ESP32 CYD
+ ser.write(build_nmea(rmc))
+ ser.write(build_nmea(gga))
+ ser.write(build_nmea(vtg))
+ ser.flush()
+
+ # Feedback en consola en una sola línea interactiva
+ print(f"\r[GPS Sim] Satélites: {satellites} | Vel: {current_speed:5.1f} km/h | Rumbo: {heading:5.1f}° | Alt: {altitude:4.1f}m | Hora UTC: {now.strftime('%H:%M:%S')}", end="", flush=True)
+
+ time.sleep(interval)
+
+ except KeyboardInterrupt:
+ print("\n\n[INFO] Deteniendo simulador GPS...")
+ finally:
+ ser.close()
+ print("[OK] Puerto serie cerrado.")
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Simulador GPS NMEA para ESP32 CYD")
+ parser.add_argument("--port", default="/dev/ttyUSB0", help="Puerto serie de la placa CYD (default: /dev/ttyUSB0)")
+ parser.add_argument("--baud", type=int, default=9600, help="Baudrate serie (default: 9600)")
+ parser.add_argument("--rate", type=float, default=2.0, help="Frecuencia en Hz (default: 2.0 Hz)")
+ parser.add_argument("--speed", type=float, default=None, help="Velocidad fija en km/h (desactiva ciclo)")
+ args = parser.parse_args()
+
+ sim_speed = (args.speed is None)
+ fix_spd = args.speed if args.speed is not None else 70.0
+ run_gps_emulator(port=args.port, baud=args.baud, rate_hz=args.rate, speed_sim=sim_speed, fixed_speed=fix_spd)
diff --git a/emulator/obd2_emulator.py b/emulator/obd2_emulator.py
index bf93851..12bd5b1 100755
--- a/emulator/obd2_emulator.py
+++ b/emulator/obd2_emulator.py
@@ -359,6 +359,79 @@ def handle_client_stream(sock):
print("\n[OBD2] Sesión cerrada.")
+def nmea_checksum(sentence: str) -> str:
+ """Calcula el checksum XOR estándar NMEA 0183"""
+ c = 0
+ for ch in sentence:
+ c ^= ord(ch)
+ return f"{c:02X}"
+
+
+def gps_simulation_worker(port="/dev/ttyUSB0", baud=9600):
+ """Hilo secundario para transmitir sentencias NMEA al ESP32 CYD vía USB serie"""
+ try:
+ import serial
+ from datetime import datetime, timezone
+ ser = serial.Serial()
+ ser.port = port
+ ser.baudrate = baud
+ ser.timeout = 1
+ ser.dtr = False
+ ser.rts = False
+ ser.open()
+ print(f"[GPS Sim] Puerto serie {port} abierto a {baud} bps. Transmitiendo NMEA sincronizado con ECU.")
+ except Exception as e:
+ print(f"[GPS Sim] Aviso: No se pudo abrir {port} para GPS ({e}).")
+ return
+
+ base_lat = -34.6037
+ base_lon = -58.3816
+ altitude = 35.0
+ heading = 45.0
+ satellites = 9
+
+ try:
+ while True:
+ with state.lock:
+ current_speed = state.speed
+
+ if current_speed > 1.0:
+ heading = (heading + 0.8) % 360.0
+
+ speed_knots = current_speed * 0.539957
+ now = datetime.now(timezone.utc)
+ time_str = now.strftime("%H%M%S.00")
+ date_str = now.strftime("%d%m%y")
+
+ lat_deg = int(abs(base_lat))
+ lat_min = (abs(base_lat) - lat_deg) * 60.0
+ lat_dir = "S" if base_lat < 0 else "N"
+ lat_nmea = f"{lat_deg:02d}{lat_min:07.4f}"
+
+ lon_deg = int(abs(base_lon))
+ lon_min = (abs(base_lon) - lon_deg) * 60.0
+ lon_dir = "W" if base_lon < 0 else "E"
+ lon_nmea = f"{lon_deg:03d}{lon_min:07.4f}"
+
+ rmc = f"GPRMC,{time_str},A,{lat_nmea},{lat_dir},{lon_nmea},{lon_dir},{speed_knots:.1f},{heading:.1f},{date_str},,,A"
+ gga = f"GPGGA,{time_str},{lat_nmea},{lat_dir},{lon_nmea},{lon_dir},1,{satellites:02d},0.9,{altitude:.1f},M,16.0,M,,"
+ vtg = f"GPVTG,{heading:.1f},T,,M,{speed_knots:.1f},N,{current_speed:.1f},K,A"
+
+ for msg in [rmc, gga, vtg]:
+ raw = f"${msg}*{nmea_checksum(msg)}\r\n".encode("ascii")
+ ser.write(raw)
+ ser.flush()
+
+ time.sleep(0.5) # 2 Hz update rate
+ except Exception as e:
+ pass
+ finally:
+ try:
+ ser.close()
+ except Exception:
+ pass
+
+
def simulation_worker():
"""Background thread to continuously update simulated values"""
while True:
@@ -370,6 +443,10 @@ def run_emulator():
parser = argparse.ArgumentParser(description="OBD-II Bluetooth Emulator con soporte ECO Badge (Ford Ka Endura 1.3)")
parser.add_argument("--mode", "-m", choices=["cycle", "eco"], default="cycle", help="Modo inicial: 'cycle' (demostración) o 'eco' (fijo)")
parser.add_argument("--eco", "-e", action="store_true", help="Iniciar directamente en modo ECO fijo (RPM=2250, Carga=58%%)")
+ parser.add_argument("--gps", action="store_true", help="Forzar habilitación de simulación GPS por cable USB")
+ parser.add_argument("--no-gps", action="store_true", help="Desactivar simulación GPS por cable USB")
+ parser.add_argument("--gps-port", default="/dev/ttyUSB0", help="Puerto serie para simulación GPS (default: /dev/ttyUSB0)")
+ parser.add_argument("--gps-baud", type=int, default=9600, help="Baudrate para simulación GPS (default: 9600)")
args = parser.parse_args()
if args.eco or args.mode == "eco":
@@ -392,6 +469,15 @@ 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)):
+ gps_thread = threading.Thread(
+ target=gps_simulation_worker,
+ args=(args.gps_port, args.gps_baud),
+ daemon=True
+ )
+ gps_thread.start()
+
# Set up D-Bus loop for BlueZ SPP registration
DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
diff --git a/gps_icon.jpg b/gps_icon.jpg
new file mode 100644
index 0000000..2af4bd3
Binary files /dev/null and b/gps_icon.jpg differ
diff --git a/gps_icon.svg b/gps_icon.svg
new file mode 100644
index 0000000..4cf5a89
--- /dev/null
+++ b/gps_icon.svg
@@ -0,0 +1,44 @@
+
+
diff --git a/gps_pinout.jpg b/gps_pinout.jpg
new file mode 100644
index 0000000..3fac66c
Binary files /dev/null and b/gps_pinout.jpg differ
diff --git a/hardware/GPS_N32_INTEGRACION.md b/hardware/GPS_N32_INTEGRACION.md
new file mode 100644
index 0000000..df3cad1
--- /dev/null
+++ b/hardware/GPS_N32_INTEGRACION.md
@@ -0,0 +1,180 @@
+# Integración Módulo GPS N32+ con Modo Reposo por Comandos UBX
+## Placa ESP32-2432S028 (CYD)
+
+Este documento detalla la conexión física, el protocolo binario para poner a dormir y despertar el módulo GPS N32+ (U-blox) mediante comandos serie, y el esquema de integración con el firmware del instrumental OBD-II.
+
+---
+
+## 1. Especificaciones y Pinout del Módulo N32+ GPS
+
+
+
+* **Chipset**: U-blox 7 / NEO-7M (o compatible).
+* **Protocolo de salida**: NMEA 0183 (mensajes $GPRMC, $GPGGA, $GPVTG).
+* **Protocolo de configuración**: Binario U-blox (UBX).
+* **Velocidad predeterminada (Baudrate)**: 9600 bps (8N1).
+* **Voltaje de alimentación**: 5V (conectado a `Vin` del conector P1).
+* **Nivel lógico UART**: 3.3V (compatible directo con ESP32 sin conversores).
+* **Consumo activo**: ~45 a 60 mA.
+* **Consumo en modo reposo (Software Standby / Backup)**: ~0.3 a 0.5 mA.
+
+---
+
+## 2. Esquema de Conexión Física (Bidireccional)
+
+
+
+Para recibir datos de navegación y al mismo tiempo poder enviar comandos de suspensión y despertar, se utiliza la ficha de 4 pines del conector rotulado como **UART** (o **P1**) en la parte trasera de la placa CYD:
+
+| Pin del GPS N32+ | Pin en Conector UART (P1) | Función | Notas |
+| :--- | :--- | :--- | :--- |
+| **VCC / V+EN** (Naranja) | **Pin 1** (`Vin` 5V) | Alimentación | 5V directo del regulador/USB |
+| **RX** (Blanco / Amarillo) | **Pin 2** (`GPIO 1` / TX0) | TX del ESP32 | Salida serial para enviar comandos UBX de reposo |
+| **TX** (Azul / Verde) | **Pin 3** (`GPIO 3` / RX0) | RX del ESP32 | Entrada serial UART0 (sentencias NMEA a 9600 bps) |
+| **GND** (Verde / Negro) | **Pin 4** (`GND`) | Masa común | Retorno de corriente |
+
+> **Programación por cable USB**:
+> Dado que el conector UART comparte internamente las líneas GPIO 1 y GPIO 3 con el chip conversor USB-Serie (CH340), **desconectar la ficha de 4 pines del GPS al flashear nuevo firmware por cable USB** para evitar interferencias en la carga. Una vez completada la carga, reconectar la ficha para el funcionamiento normal.
+
+---
+
+## 3. Comandos Binarios UBX de Reposo y Despertar
+
+El chip U-blox cuenta con el mensaje binario **`UBX-RXM-PMREQ`** (Class `0x02`, ID `0x41`), que ordena la desconexión inmediata de la etapa de radiofrecuencia y procesado satelital.
+
+### A. Trama para poner a dormir al GPS (Sleep / Backup Mode)
+Estructura de 16 bytes con sincronismo, encabezado, longitud, payload y checksum Fletcher:
+
+```text
+Header: 0xB5 0x62 (Sync UBX)
+Class: 0x02 (RXM)
+ID: 0x41 (PMREQ)
+Length: 0x08 0x00 (8 bytes de payload)
+Payload: 0x00 0x00 0x00 0x00 (Duración = 0: suspensión indefinida hasta despertar)
+ 0x02 0x00 0x00 0x00 (Flags: modo Backup / Software Standby)
+Checksum: 0x4D 0x3B (CK_A, CK_B)
+```
+
+**Array en C++ listo para enviar**:
+```cpp
+const uint8_t UBX_SLEEP_CMD[] = {
+ 0xB5, 0x62, 0x02, 0x41, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00,
+ 0x4D, 0x3B
+};
+```
+
+### B. Secuencia para despertar al GPS (Wake-up)
+U-blox admite el despertar por actividad en la línea serie (**Wake on UART**). Basta con transmitir una ráfaga corta de bytes (por ejemplo `0xFF`) para que el receptor reactive sus osciladores y vuelva al modo activo.
+
+```cpp
+void gpsWake() {
+ // Envío de ráfaga de pulsos para despertar el receptor U-blox
+ uint8_t wakeBuffer[16];
+ memset(wakeBuffer, 0xFF, sizeof(wakeBuffer));
+ gpsSerial.write(wakeBuffer, sizeof(wakeBuffer));
+ gpsSerial.flush();
+}
+```
+
+---
+
+## 4. Código Modelo para Integrar en el Firmware
+
+```cpp
+#include
+#include
+
+#define GPS_RX_PIN 3 // Conector UART Pin 3 (RX0 ESP32, entrada desde TX del GPS)
+#define GPS_TX_PIN 1 // Conector UART Pin 2 (TX0 ESP32, salida hacia RX del GPS)
+#define GPS_BAUDRATE 9600
+
+#define gpsSerial Serial // Usamos UART0 por hardware nativo
+TinyGPSPlus gps;
+
+bool gpsIsSleeping = false;
+
+// Inicialización del puerto serie para GPS
+void setupGPS() {
+ pinMode(GPS_RX_PIN, INPUT_PULLUP);
+ gpsSerial.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
+}
+
+// Poner el GPS en modo reposo de ultra bajo consumo
+void gpsSleep() {
+ if (gpsIsSleeping) return;
+
+ // Trama UBX-RXM-PMREQ para modo Backup permanente
+ const uint8_t ubxSleep[] = {
+ 0xB5, 0x62, 0x02, 0x41, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00,
+ 0x4D, 0x3B
+ };
+
+ gpsSerial.write(ubxSleep, sizeof(ubxSleep));
+ gpsSerial.flush();
+ gpsIsSleeping = true;
+ Serial.println(">>> [GPS] Modulo GPS puesto en modo reposo (Sleep).");
+}
+
+// Despertar el GPS
+void gpsWake() {
+ if (!gpsIsSleeping) return;
+
+ // Enviar ráfaga de sincronismo en línea serie para disparar el evento Wake-on-UART
+ for (uint8_t i = 0; i < 32; i++) {
+ gpsSerial.write(0xFF);
+ }
+ gpsSerial.flush();
+ gpsIsSleeping = false;
+ Serial.println(">>> [GPS] Modulo GPS despertado.");
+}
+
+// Lectura continua en el bucle principal
+void processGPS() {
+ if (gpsIsSleeping) return;
+
+ while (gpsSerial.available() > 0) {
+ gps.encode(gpsSerial.read());
+ }
+}
+```
+
+---
+
+## 5. Integración con el Sistema de Reposo Existente
+
+En el archivo `sketch/obd2_gauge/config.h`:
+
+1. **Al entrar en suspensión (`enterLightSleep`)**:
+ ```cpp
+ gpsSleep(); // Apaga la etapa de radio y antena del GPS
+ ```
+
+2. **Al salir de la suspensión (`wokeFromSleep`)**:
+ ```cpp
+ gpsWake(); // Reactiva el receptor U-blox
+ ```
+
+---
+
+## 6. Funcionalidades a Implementar Posteriormente
+
+1. **Velocidad Real vs Velocidad OBD-II**:
+ - Lectura: `gps.speed.kmph()`.
+ - Comparación directa con la velocidad reportada por la ECU (PID `010D`).
+
+2. **Cronómetro 0 a 100 km/h (Performance Meter)**:
+ - Detección automática de detención (`speed == 0`).
+ - Disparo del cronómetro al detectar movimiento (`speed > 1.0 km/h`).
+ - Registro del tiempo exacto en milisegundos al cruzar los `100.0 km/h`.
+
+3. **Hora Satelital Atómica y Fecha**:
+ - Lectura de `gps.time.hour()`, `gps.time.minute()`, `gps.time.second()`.
+ - Ajuste de huso horario (`hora - 3` para huso horario UTC-3).
+
+4. **Altitud y Rumbo**:
+ - `gps.altitude.meters()` para altitud msnm.
+ - `gps.course.deg()` para orientación y brújula digital.
diff --git a/hardware/create_annotated_photo.py b/hardware/create_annotated_photo.py
new file mode 100644
index 0000000..7d4a979
--- /dev/null
+++ b/hardware/create_annotated_photo.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+"""
+Toma la foto real enviada por el usuario (gps_pinout.jpg) y genera
+una imagen anotada con el pinout exacto, colores de cables soldados
+y su conexion a los conectores P1 y P3 de la placa ESP32 CYD.
+"""
+
+from PIL import Image, ImageDraw, ImageFont
+import os
+
+# 1. Cargar y recortar la foto original
+src = Image.open("hardware/gps_pinout.jpg")
+sw, sh = src.size
+
+# Queremos enfocar el módulo y los cables
+# El módulo está entre x = 0.2 y 0.85
+crop_box = (int(sw * 0.15), 0, int(sw * 0.85), sh)
+cropped = src.crop(crop_box)
+
+# Redimensionar la foto recortada a un tamaño adecuado para el panel izquierdo (ancho 600)
+target_h = 680
+target_w = int(cropped.width * (target_h / cropped.height))
+cropped_resized = cropped.resize((target_w, target_h), Image.Resampling.LANCZOS)
+
+# Dimensiones finales del canvas
+W = target_w + 580
+H = 760
+
+im = Image.new("RGB", (W, H), (11, 15, 25))
+draw = ImageDraw.Draw(im)
+
+# Pegar la foto en el lateral izquierdo
+photo_x = 30
+photo_y = 65
+im.paste(cropped_resized, (photo_x, photo_y))
+
+# Borde decorativo alrededor de la foto
+draw.rectangle([photo_x - 1, photo_y - 1, photo_x + target_w, photo_y + target_h], outline=(50, 70, 110), width=2)
+
+# Cargar fuentes
+try:
+ f_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
+ f_sub = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 13)
+ f_sec = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 15)
+ f_bold = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 13)
+ f_reg = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
+ f_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
+except Exception:
+ f_title = f_sub = f_sec = f_bold = f_reg = f_small = ImageFont.load_default()
+
+# -------------------------------------------------------------
+# ENCABEZADO
+# -------------------------------------------------------------
+draw.text((30, 15), "PINOUT IDENTIFICADO DE TU MODULO GPS (FOTO REAL)", fill=(255, 255, 255), font=f_title)
+draw.text((30, 40), "Correspondencia de tus cables soldados con la placa ESP32 CYD (ESP32-2432S028)", fill=(0, 210, 255), font=f_sub)
+
+# -------------------------------------------------------------
+# PANEL DERECHO: DETALLE DE PINES Y CABLES
+# -------------------------------------------------------------
+rx = photo_x + target_w + 25
+rw = W - rx - 30
+
+def draw_card(box, fill, outline, radius=6):
+ draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=1)
+
+# Tarjeta 1: Cable Naranja (VCC y EN)
+c1_box = [rx, photo_y, rx + rw, photo_y + 115]
+draw_card(c1_box, (24, 30, 46), (220, 90, 30))
+draw.rectangle([rx, photo_y, rx + 10, photo_y + 115], fill=(255, 120, 20))
+draw.text((rx + 22, photo_y + 10), "1. CABLE NARANJA --> PADS 'V' y 'EN'", fill=(255, 140, 40), font=f_sec)
+draw.text((rx + 22, photo_y + 32), "• Señal: VCC (Alimentación 5V) + Enable activado.", fill=(220, 225, 235), font=f_reg)
+draw.text((rx + 22, photo_y + 52), " (Tu foto muestra el pad 'EN' puenteado con estaño a 'V').", fill=(160, 175, 195), font=f_small)
+draw.text((rx + 22, photo_y + 75), "• CONECTAR A:", fill=(255, 215, 60), font=f_bold)
+draw.text((rx + 145, photo_y + 75), "PIN 1 del Conector P1 (Vin 5V)", fill=(255, 255, 255), font=f_bold)
+draw.text((rx + 22, photo_y + 93), " Entrega 5V directo desde el puerto USB.", fill=(160, 175, 195), font=f_small)
+
+# Tarjeta 2: Cable Azul (TX)
+c2_box = [rx, photo_y + 128, rx + rw, photo_y + 243]
+draw_card(c2_box, (24, 30, 46), (40, 120, 220))
+draw.rectangle([rx, photo_y + 128, rx + 10, photo_y + 243], fill=(35, 130, 245))
+draw.text((rx + 22, photo_y + 138), "2. CABLE AZUL --> PAD 'T' (TX)", fill=(60, 160, 255), font=f_sec)
+draw.text((rx + 22, photo_y + 160), "• Señal: TX (Salida de datos NMEA del GPS al ESP32).", fill=(220, 225, 235), font=f_reg)
+draw.text((rx + 22, photo_y + 180), " Transmite velocidad, satélites, hora, rumbo y coordenadas.", fill=(160, 175, 195), font=f_small)
+draw.text((rx + 22, photo_y + 203), "• CONECTAR A:", fill=(255, 215, 60), font=f_bold)
+draw.text((rx + 145, photo_y + 203), "PIN 3 del Conector P3 (GPIO 35)", fill=(255, 255, 255), font=f_bold)
+draw.text((rx + 22, photo_y + 221), " Entrada RX1 por hardware de la placa CYD.", fill=(160, 175, 195), font=f_small)
+
+# Tarjeta 3: Cable Blanco (RX)
+c3_box = [rx, photo_y + 256, rx + rw, photo_y + 371]
+draw_card(c3_box, (24, 30, 46), (180, 185, 200))
+draw.rectangle([rx, photo_y + 256, rx + 10, photo_y + 371], fill=(230, 235, 245))
+draw.text((rx + 22, photo_y + 266), "3. CABLE BLANCO --> PAD 'R' (RX)", fill=(240, 245, 255), font=f_sec)
+draw.text((rx + 22, photo_y + 288), "• Señal: RX (Entrada de comandos al GPS desde el ESP32).", fill=(220, 225, 235), font=f_reg)
+draw.text((rx + 22, photo_y + 308), " Permite poner el GPS en modo Sleep y reactivarlo.", fill=(160, 175, 195), font=f_small)
+draw.text((rx + 22, photo_y + 331), "• CONECTAR A:", fill=(255, 215, 60), font=f_bold)
+draw.text((rx + 145, photo_y + 331), "PIN 2 del Conector P1 (GPIO 1 / TX0)", fill=(255, 255, 255), font=f_bold)
+draw.text((rx + 22, photo_y + 349), " Salida de comandos de reposo del instrumental.", fill=(160, 175, 195), font=f_small)
+
+# Tarjeta 4: Cable Verde (GND)
+c4_box = [rx, photo_y + 384, rx + rw, photo_y + 499]
+draw_card(c4_box, (24, 30, 46), (30, 170, 90))
+draw.rectangle([rx, photo_y + 384, rx + 10, photo_y + 499], fill=(40, 200, 110))
+draw.text((rx + 22, photo_y + 394), "4. CABLE VERDE --> PAD 'G' (GND)", fill=(50, 215, 120), font=f_sec)
+draw.text((rx + 22, photo_y + 416), "• Señal: GND (Masa / Retorno común de corriente).", fill=(220, 225, 235), font=f_reg)
+draw.text((rx + 22, photo_y + 436), " Referencia de masa para alimentación y bus serie.", fill=(160, 175, 195), font=f_small)
+draw.text((rx + 22, photo_y + 459), "• CONECTAR A:", fill=(255, 215, 60), font=f_bold)
+draw.text((rx + 145, photo_y + 459), "PIN 4 del Conector P1 (GND)", fill=(255, 255, 255), font=f_bold)
+draw.text((rx + 22, photo_y + 477), " (O bien al Pin 2 del conector P3).", fill=(160, 175, 195), font=f_small)
+
+# Tarjeta 5: Pad VB (Sin conectar)
+c5_box = [rx, photo_y + 512, rx + rw, photo_y + 610]
+draw_card(c5_box, (18, 22, 34), (60, 75, 100))
+draw.rectangle([rx, photo_y + 512, rx + 10, photo_y + 610], fill=(120, 130, 150))
+draw.text((rx + 22, photo_y + 522), "5. PAD 'VB' (V_BACKUP) --> SIN CONECTAR", fill=(170, 185, 205), font=f_bold)
+draw.text((rx + 22, photo_y + 544), "• Este pad dorado está abajo de todo, junto a la pila botón.", fill=(140, 155, 175), font=f_small)
+draw.text((rx + 22, photo_y + 564), "• No requiere conexión: Tu módulo ya tiene la pila recargable", fill=(140, 155, 175), font=f_small)
+draw.text((rx + 22, photo_y + 584), " soldada en la placa para retener la memoria satelital.", fill=(140, 155, 175), font=f_small)
+
+# Tarjeta inferior de resumen
+c6_box = [rx, photo_y + 623, rx + rw, photo_y + 680]
+draw_card(c6_box, (28, 24, 16), (220, 170, 40))
+draw.text((rx + 18, photo_y + 632), "CONSEJO:", fill=(255, 200, 50), font=f_bold)
+draw.text((rx + 95, photo_y + 632), "Al flashear por USB el CYD, si el cable blanco (RX)", fill=(240, 240, 240), font=f_small)
+draw.text((rx + 18, photo_y + 652), "está enchufado a GPIO 1, desconéctalo temporalmente para evitar conflicto.", fill=(240, 240, 240), font=f_small)
+
+# Guardar
+out1 = "hardware/pinout_gps_foto_anotada.png"
+out2 = "pinout_gps_foto_anotada.png"
+im.save(out1, "PNG")
+im.save(out2, "PNG")
+print("Imagen generada con éxito:", out1)
diff --git a/hardware/diagrama_conexion_ds18b20.png b/hardware/diagrama_conexion_ds18b20.png
index f1ad8d4..383e374 100644
Binary files a/hardware/diagrama_conexion_ds18b20.png and b/hardware/diagrama_conexion_ds18b20.png differ
diff --git a/hardware/diagrama_conexion_hardware.png b/hardware/diagrama_conexion_hardware.png
new file mode 100644
index 0000000..383e374
Binary files /dev/null and b/hardware/diagrama_conexion_hardware.png differ
diff --git a/hardware/generate_diagram.py b/hardware/generate_diagram.py
new file mode 100644
index 0000000..5f650c8
--- /dev/null
+++ b/hardware/generate_diagram.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python3
+"""
+Genera el diagrama de conexión completo para la placa ESP32 CYD:
+- 2x Sensores de temperatura DS18B20 en conector CN1 (1-Wire GPIO 22)
+- 1x Módulo GPS N32+ / U-blox en conectores P1 y P3 (5V, GND, GPIO 35 RX, GPIO 1 TX)
+"""
+
+from PIL import Image, ImageDraw, ImageFont
+import os
+
+W, H = 1060, 750
+im = Image.new("RGB", (W, H), (11, 15, 25)) # Sleek dark navy
+draw = ImageDraw.Draw(im)
+
+# Load fonts
+try:
+ font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18)
+ font_subtitle = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
+ font_sec = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 13)
+ font_bold = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 11)
+ font_reg = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
+ font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 10)
+ font_tiny = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 9)
+except Exception:
+ font_title = ImageFont.load_default()
+ font_subtitle = font_sec = font_bold = font_reg = font_small = font_tiny = font_title
+
+# Helper for drawing rounded boxes
+def draw_card(box, fill, outline, radius=6):
+ draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=1)
+
+# Color palette
+C_BG = (11, 15, 25)
+C_CARD = (20, 26, 42)
+C_CARD_BORDER = (45, 58, 88)
+C_CARD_HEADER = (28, 36, 58)
+C_TEXT_WHITE = (240, 243, 248)
+C_TEXT_MUTED = (140, 153, 175)
+C_TEXT_CYAN = (0, 210, 255)
+
+# Wire Colors
+COLOR_VCC_3V3 = (255, 75, 75) # Red (3.3V Sensores)
+COLOR_GND_DS = (40, 130, 255) # Blue (GND Sensores)
+COLOR_DATA_DS = (245, 195, 35) # Gold / Yellow (Datos Sensores)
+
+# GPS Wire Colors (Matching User's physical soldered cable)
+COLOR_GPS_VCC = (255, 120, 20) # Cable Naranja (Pad V y EN puenteados)
+COLOR_GPS_TX = (35, 130, 245) # Cable Azul (Pad T / TX del GPS)
+COLOR_GPS_RX = (235, 240, 250) # Cable Blanco (Pad R / RX del GPS)
+COLOR_GPS_GND = (40, 200, 110) # Cable Verde (Pad G / GND del GPS)
+
+# -------------------------------------------------------------
+# 1. HEADER
+# -------------------------------------------------------------
+draw.text((35, 20), "DIAGRAMA DE CONEXION: SENSORES DS18B20 Y MODULO GPS A PLACA CYD", fill=C_TEXT_WHITE, font=font_title)
+draw.text((35, 45), "ESP32-2432S028 (CYD 2.8\" TFT) - Conectores Traseros CN1, P1 (Alimentación / TX) y P3 (RX)", fill=C_TEXT_CYAN, font=font_subtitle)
+draw.line([(35, 68), (W - 35, 68)], fill=(35, 46, 72), width=1)
+
+# -------------------------------------------------------------
+# 2. SECCIÓN IZQUIERDA ARRIBA: SENSORES DS18B20
+# -------------------------------------------------------------
+ds_panel_box = [35, 80, 520, 315]
+draw_card(ds_panel_box, (15, 20, 34), C_CARD_BORDER, radius=8)
+draw.text((50, 92), "1. BUS 1-WIRE: TEMPERATURA INTERIOR Y EXTERIOR", fill=(100, 215, 255), font=font_sec)
+
+# Sensor 1 (Interior)
+s1_box = [50, 120, 175, 195]
+draw_card(s1_box, (24, 32, 50), (60, 75, 105), radius=5)
+draw.text((70, 126), "SENSOR 1", fill=(100, 215, 255), font=font_bold)
+draw.text((72, 142), "DS18B20", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((68, 158), "Habitaculo", fill=C_TEXT_MUTED, font=font_small)
+draw.text((68, 172), "(Cara plana)", fill=(100, 115, 135), font=font_tiny)
+
+# Sensor 2 (Exterior)
+s2_box = [205, 120, 330, 195]
+draw_card(s2_box, (24, 32, 50), (60, 75, 105), radius=5)
+draw.text((225, 126), "SENSOR 2", fill=(100, 215, 255), font=font_bold)
+draw.text((227, 142), "DS18B20", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((229, 158), "Exterior", fill=C_TEXT_MUTED, font=font_small)
+draw.text((223, 172), "(Cara plana)", fill=(100, 115, 135), font=font_tiny)
+
+# Pines Sensor 1
+p1_s1 = (72, 195)
+p2_s1 = (112, 195)
+p3_s1 = (152, 195)
+draw.text((65, 200), "P1:GND", fill=COLOR_GND_DS, font=font_tiny)
+draw.text((106, 200), "P2:DQ", fill=COLOR_DATA_DS, font=font_tiny)
+draw.text((144, 200), "P3:VCC", fill=COLOR_VCC_3V3, font=font_tiny)
+
+# Pines Sensor 2
+p1_s2 = (227, 195)
+p2_s2 = (267, 195)
+p3_s2 = (307, 195)
+draw.text((220, 200), "P1:GND", fill=COLOR_GND_DS, font=font_tiny)
+draw.text((261, 200), "P2:DQ", fill=COLOR_DATA_DS, font=font_tiny)
+draw.text((299, 200), "P3:VCC", fill=COLOR_VCC_3V3, font=font_tiny)
+
+# Resistencia 4.7k
+res_box = [370, 235, 415, 265]
+draw_card(res_box, (220, 200, 160), (140, 120, 80), radius=3)
+# Bandas de color de 4.7k (Amarillo, Violeta, Rojo, Dorado)
+draw.line([(380, 235), (380, 265)], fill=(240, 210, 20), width=3) # Amarillo (4)
+draw.line([(388, 235), (388, 265)], fill=(150, 40, 180), width=3) # Violeta (7)
+draw.line([(396, 235), (396, 265)], fill=(220, 30, 30), width=3) # Rojo (x100)
+draw.line([(406, 235), (406, 265)], fill=(210, 175, 55), width=2) # Dorado (+-5%)
+draw.text((352, 218), "Resistencia 4.7k", fill=C_TEXT_WHITE, font=font_bold)
+draw.text((362, 272), "(Pull-up)", fill=C_TEXT_MUTED, font=font_small)
+
+# Wires DS18B20:
+# GND (Azul) - Pata 1 de ambos
+draw.line([(p1_s1[0], 215), (p1_s1[0], 285)], fill=COLOR_GND_DS, width=2)
+draw.line([(p1_s2[0], 215), (p1_s2[0], 285)], fill=COLOR_GND_DS, width=2)
+draw.line([(p1_s1[0], 285), (540, 285)], fill=COLOR_GND_DS, width=2)
+draw.ellipse([p1_s1[0]-3, 285-3, p1_s1[0]+3, 285+3], fill=COLOR_GND_DS)
+draw.ellipse([p1_s2[0]-3, 285-3, p1_s2[0]+3, 285+3], fill=COLOR_GND_DS)
+
+# DQ Datos (Amarillo) - Pata 2 de ambos
+draw.line([(p2_s1[0], 215), (p2_s1[0], 255)], fill=COLOR_DATA_DS, width=2)
+draw.line([(p2_s2[0], 215), (p2_s2[0], 255)], fill=COLOR_DATA_DS, width=2)
+draw.line([(p2_s1[0], 255), (370, 255)], fill=COLOR_DATA_DS, width=2)
+draw.line([(415, 255), (540, 255)], fill=COLOR_DATA_DS, width=2)
+draw.ellipse([p2_s1[0]-3, 255-3, p2_s1[0]+3, 255+3], fill=COLOR_DATA_DS)
+draw.ellipse([p2_s2[0]-3, 255-3, p2_s2[0]+3, 255+3], fill=COLOR_DATA_DS)
+
+# VCC 3.3V (Rojo) - Pata 3 de ambos y conectado al otro extremo de resistencia 4.7k
+draw.line([(p3_s1[0], 215), (p3_s1[0], 230)], fill=COLOR_VCC_3V3, width=2)
+draw.line([(p3_s2[0], 215), (p3_s2[0], 230)], fill=COLOR_VCC_3V3, width=2)
+draw.line([(p3_s1[0], 230), (540, 230)], fill=COLOR_VCC_3V3, width=2)
+draw.ellipse([p3_s1[0]-3, 230-3, p3_s1[0]+3, 230+3], fill=COLOR_VCC_3V3)
+draw.ellipse([p3_s2[0]-3, 230-3, p3_s2[0]+3, 230+3], fill=COLOR_VCC_3V3)
+draw.line([(392, 235), (392, 230)], fill=COLOR_VCC_3V3, width=2)
+draw.ellipse([392-3, 230-3, 392+3, 230+3], fill=COLOR_VCC_3V3)
+
+
+# -------------------------------------------------------------
+# 3. SECCIÓN IZQUIERDA ABAJO: MODULO GPS (FOTO REAL / PADS V, EN, T, R, G, VB)
+# -------------------------------------------------------------
+gps_panel_box = [35, 335, 520, 560]
+draw_card(gps_panel_box, (15, 20, 34), C_CARD_BORDER, radius=8)
+draw.text((50, 347), "2. MODULO GPS N32+ / U-BLOX (TUS CABLES SOLDADOS)", fill=(100, 255, 160), font=font_sec)
+
+# Modulo GPS Box
+gps_body_box = [50, 375, 255, 545]
+draw_card(gps_body_box, (20, 38, 70), (40, 80, 140), radius=6)
+draw.text((65, 382), "MODULO GPS", fill=C_TEXT_WHITE, font=font_bold)
+draw.text((65, 396), "N32+ (Foto Real)", fill=C_TEXT_CYAN, font=font_tiny)
+
+# Antena Cerámica cuadrada en el módulo
+ant_box = [62, 415, 140, 495]
+draw_card(ant_box, (180, 145, 60), (120, 95, 35), radius=4)
+draw.ellipse([94, 448, 106, 460], fill=(230, 230, 230), outline=(100, 100, 100))
+draw.text((65, 500), "Antena GPS", fill=C_TEXT_MUTED, font=font_tiny)
+
+# Pila de botón abajo a la derecha
+draw.ellipse([80, 516, 102, 538], fill=(195, 202, 212), outline=(130, 135, 145))
+draw.text((108, 522), "VB: Pila Backup", fill=(140, 155, 175), font=font_tiny)
+
+# Pines del GPS (en el lateral derecho del módulo x=255)
+gps_p_vcc = (255, 395)
+gps_p_tx = (255, 435)
+gps_p_rx = (255, 470)
+gps_p_gnd = (255, 505)
+
+draw.text((150, 387), "V+EN (5V)", fill=COLOR_GPS_VCC, font=font_bold)
+draw.text((150, 399), "[Naranja]", fill=COLOR_GPS_VCC, font=font_tiny)
+
+draw.text((150, 427), "T: TX", fill=COLOR_GPS_TX, font=font_bold)
+draw.text((150, 439), "[Azul]", fill=COLOR_GPS_TX, font=font_tiny)
+
+draw.text((150, 462), "R: RX", fill=COLOR_GPS_RX, font=font_bold)
+draw.text((150, 474), "[Blanco]", fill=(200, 205, 215), font=font_tiny)
+
+draw.text((150, 497), "G: GND", fill=COLOR_GPS_GND, font=font_bold)
+draw.text((150, 509), "[Verde]", fill=COLOR_GPS_GND, font=font_tiny)
+
+# Puntos de conexión en el borde
+draw.ellipse([gps_p_vcc[0]-4, gps_p_vcc[1]-4, gps_p_vcc[0]+4, gps_p_vcc[1]+4], fill=COLOR_GPS_VCC)
+draw.ellipse([gps_p_tx[0]-4, gps_p_tx[1]-4, gps_p_tx[0]+4, gps_p_tx[1]+4], fill=COLOR_GPS_TX)
+draw.ellipse([gps_p_rx[0]-4, gps_p_rx[1]-4, gps_p_rx[0]+4, gps_p_rx[1]+4], fill=COLOR_GPS_RX)
+draw.ellipse([gps_p_gnd[0]-4, gps_p_gnd[1]-4, gps_p_gnd[0]+4, gps_p_gnd[1]+4], fill=COLOR_GPS_GND)
+
+# Trazado de cables GPS hacia la derecha (todos al conector UART / P1)
+# Cable Naranja (VCC 5V) -> va a Pin 1 de P1 (x=600, y=278)
+draw.line([gps_p_vcc, (340, 395), (340, 278), (605, 278)], fill=COLOR_GPS_VCC, width=2)
+
+# Cable Blanco (RX GPS) -> va a Pin 2 de P1 (x=600, y=298)
+draw.line([gps_p_rx, (280, 470), (280, 298), (605, 298)], fill=COLOR_GPS_RX, width=2)
+
+# Cable Azul (TX GPS) -> va a Pin 3 de P1 (x=600, y=318)
+draw.line([gps_p_tx, (300, 435), (300, 318), (605, 318)], fill=COLOR_GPS_TX, width=2)
+
+# Cable Verde (GND GPS) -> va a Pin 4 de P1 (x=600, y=338)
+draw.line([gps_p_gnd, (320, 505), (320, 338), (605, 338)], fill=COLOR_GPS_GND, width=2)
+
+
+# -------------------------------------------------------------
+# 4. SECCIÓN DERECHA: PLACA ESP32 CYD (ESP32-2432S028)
+# -------------------------------------------------------------
+cyd_box = [570, 80, 1025, 560]
+draw_card(cyd_box, (18, 24, 38), (240, 190, 40), radius=10)
+draw.text((590, 92), "PLACA ESP32 CYD (ESP32-2432S028)", fill=(255, 215, 60), font=font_sec)
+draw.text((590, 110), "Vista de Conectores Traseros JST 1.25mm", fill=C_TEXT_MUTED, font=font_small)
+
+# --- CONECTOR CN1 (4 pines) ---
+cn1_box = [590, 135, 1005, 235]
+draw_card(cn1_box, (25, 34, 52), (50, 68, 100), radius=6)
+draw.text((605, 142), "CONECTOR CN1 (Sensores Temperatura 1-Wire)", fill=(100, 215, 255), font=font_bold)
+
+draw.rectangle([605, 160, 620, 172], fill=COLOR_VCC_3V3)
+draw.text((630, 160), "Pin 1: 3.3V (VCC)", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((760, 160), "<-- Pata 3 de ambos DS18B20", fill=COLOR_VCC_3V3, font=font_small)
+
+draw.rectangle([605, 177, 620, 189], fill=(70, 75, 85))
+draw.text((630, 177), "Pin 2: (No usar / Libre)", fill=(130, 135, 145), font=font_small)
+
+draw.rectangle([605, 194, 620, 206], fill=COLOR_DATA_DS)
+draw.text((630, 194), "Pin 3: GPIO 22 (Datos)", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((785, 194), "<-- Pata 2 de ambos DS18B20 + 4.7k", fill=COLOR_DATA_DS, font=font_small)
+
+draw.rectangle([605, 211, 620, 223], fill=COLOR_GND_DS)
+draw.text((630, 211), "Pin 4: GND (Masa)", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((765, 211), "<-- Pata 1 de ambos DS18B20", fill=COLOR_GND_DS, font=font_small)
+
+# Conexion fisica cables CN1
+draw.line([(540, 230), (575, 230), (575, 166), (605, 166)], fill=COLOR_VCC_3V3, width=2)
+draw.line([(540, 255), (570, 255), (570, 200), (605, 200)], fill=COLOR_DATA_DS, width=2)
+draw.line([(540, 285), (565, 285), (565, 217), (605, 217)], fill=COLOR_GND_DS, width=2)
+
+
+# --- CONECTOR P1 / UART (4 pines: Vin, TX0, RX0, GND) ---
+p1_box = [590, 245, 1005, 370]
+draw_card(p1_box, (25, 34, 52), (50, 68, 100), radius=6)
+draw.text((605, 252), "CONECTOR UART / P1 (Conexion GPS Completa)", fill=(255, 180, 50), font=font_bold)
+
+draw.rectangle([605, 272, 620, 284], fill=COLOR_GPS_VCC)
+draw.text((630, 272), "Pin 1: Vin (5V USB)", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((760, 272), "<-- Cable Naranja (Pad V+EN GPS)", fill=COLOR_GPS_VCC, font=font_bold)
+
+draw.rectangle([605, 292, 620, 304], fill=COLOR_GPS_RX)
+draw.text((630, 292), "Pin 2: GPIO 1 (TX0)", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((760, 292), "--> Cable Blanco (Pad R/RX GPS)", fill=(235, 240, 250), font=font_bold)
+
+draw.rectangle([605, 312, 620, 324], fill=COLOR_GPS_TX)
+draw.text((630, 312), "Pin 3: GPIO 3 (RX0)", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((760, 312), "<-- Cable Azul (Pad T/TX GPS)", fill=COLOR_GPS_TX, font=font_bold)
+
+draw.rectangle([605, 332, 620, 344], fill=COLOR_GPS_GND)
+draw.text((630, 332), "Pin 4: GND (Masa)", fill=C_TEXT_WHITE, font=font_reg)
+draw.text((760, 332), "<-- Cable Verde (Pad G/GND GPS)", fill=COLOR_GPS_GND, font=font_bold)
+
+
+# --- CONECTOR P3 (4 pines: 3.3V, GND, GPIO 35, GPIO 21) ---
+p3_box = [590, 380, 1005, 545]
+draw_card(p3_box, (25, 34, 52), (50, 68, 100), radius=6)
+draw.text((605, 387), "CONECTOR P3 (Libre / GPIOs Auxiliares)", fill=(100, 255, 160), font=font_bold)
+
+draw.rectangle([605, 407, 620, 419], fill=(70, 75, 85))
+draw.text((630, 407), "Pin 1: 3.3V (Libre)", fill=(130, 135, 145), font=font_small)
+
+draw.rectangle([605, 427, 620, 439], fill=(70, 75, 85))
+draw.text((630, 427), "Pin 2: GND (Masa auxiliar)", fill=(130, 135, 145), font=font_small)
+
+draw.rectangle([605, 447, 620, 459], fill=(70, 75, 85))
+draw.text((630, 447), "Pin 3: GPIO 35 (Libre)", fill=(130, 135, 145), font=font_small)
+
+draw.rectangle([605, 467, 620, 479], fill=(70, 75, 85))
+draw.text((630, 467), "Pin 4: GND / IO21 (Libre)", fill=(130, 135, 145), font=font_small)
+
+
+# -------------------------------------------------------------
+# 5. RESUMEN DE CONEXION RAPIDA (BLOQUE INFERIOR)
+# -------------------------------------------------------------
+summary_box = [35, 575, 1025, 730]
+draw_card(summary_box, (15, 20, 32), (50, 65, 95), radius=8)
+draw.text((50, 585), "TABLA RESUMEN DE CONEXIONES EXACTAS PARA TUS CABLES:", fill=C_TEXT_WHITE, font=font_bold)
+
+# Columna 1: DS18B20
+draw.rectangle([50, 608, 62, 620], fill=COLOR_GND_DS)
+draw.text((70, 608), "Pata 1 Sensores (Masa) --> Pin 4 de CN1 (GND)", fill=C_TEXT_WHITE, font=font_small)
+
+draw.rectangle([50, 628, 62, 640], fill=COLOR_DATA_DS)
+draw.text((70, 628), "Pata 2 Sensores (Datos) --> Pin 3 de CN1 (GPIO 22) [+ Resistencia 4.7k a 3.3V]", fill=C_TEXT_WHITE, font=font_small)
+
+draw.rectangle([50, 648, 62, 660], fill=COLOR_VCC_3V3)
+draw.text((70, 648), "Pata 3 Sensores (VCC) --> Pin 1 de CN1 (3.3V Corriente)", fill=C_TEXT_WHITE, font=font_small)
+
+# Columna 2: GPS (Todo al conector UART / P1)
+col2_x = 540
+draw.rectangle([col2_x, 608, col2_x + 12, 620], fill=COLOR_GPS_VCC)
+draw.text((col2_x + 20, 608), "Cable NARANJA (Pads V + EN) --> Pin 1 de UART (Vin 5V USB Alimentación)", fill=C_TEXT_WHITE, font=font_small)
+
+draw.rectangle([col2_x, 628, col2_x + 12, 640], fill=COLOR_GPS_RX)
+draw.text((col2_x + 20, 628), "Cable BLANCO (Pad R / RX) --> Pin 2 de UART (GPIO 1 / TX0 Comandos Sleep)", fill=C_TEXT_WHITE, font=font_small)
+
+draw.rectangle([col2_x, 648, col2_x + 12, 660], fill=COLOR_GPS_TX)
+draw.text((col2_x + 20, 648), "Cable AZUL (Pad T / TX) --> Pin 3 de UART (GPIO 3 / RX0 Datos NMEA)", fill=C_TEXT_WHITE, font=font_small)
+
+draw.rectangle([col2_x, 668, col2_x + 12, 680], fill=COLOR_GPS_GND)
+draw.text((col2_x + 20, 668), "Cable VERDE (Pad G / GND) --> Pin 4 de UART (GND / Masa Común)", fill=C_TEXT_WHITE, font=font_small)
+
+# Guardar en ambas ubicaciones
+im.save("hardware/diagrama_conexion_ds18b20.png", "PNG")
+im.save("diagrama_conexion_ds18b20.png", "PNG")
+im.save("hardware/diagrama_conexion_hardware.png", "PNG")
+print("Imagenes generadas exitosamente!")
diff --git a/hardware/generate_gps_pinout.py b/hardware/generate_gps_pinout.py
new file mode 100644
index 0000000..c9828d5
--- /dev/null
+++ b/hardware/generate_gps_pinout.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python3
+"""
+Genera una ilustración técnica de alta resolución del pinout del módulo GPS
+(U-blox NEO-6M / NEO-7M / N32+) y su correspondencia con la placa ESP32 CYD.
+"""
+
+from PIL import Image, ImageDraw, ImageFont
+import os
+
+W, H = 1000, 680
+im = Image.new("RGB", (W, H), (11, 15, 25)) # Sleek dark navy
+draw = ImageDraw.Draw(im)
+
+# Load fonts
+try:
+ font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18)
+ font_subtitle = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
+ font_sec = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
+ font_bold = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 12)
+ font_reg = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
+ font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 10)
+ font_tiny = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 9)
+ font_chip = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 10)
+except Exception:
+ font_title = font_subtitle = font_sec = font_bold = font_reg = font_small = font_tiny = font_chip = ImageFont.load_default()
+
+def draw_card(box, fill, outline, radius=6, width=1):
+ draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)
+
+# Palette
+C_CARD = (18, 24, 38)
+C_CARD_BORDER = (45, 58, 88)
+C_TEXT_WHITE = (245, 248, 252)
+C_TEXT_MUTED = (145, 158, 180)
+C_TEXT_CYAN = (0, 210, 255)
+
+COLOR_VCC = (255, 60, 60)
+COLOR_RX = (255, 145, 0)
+COLOR_TX = (40, 215, 125)
+COLOR_GND = (45, 135, 255)
+COLOR_PPS = (190, 100, 255)
+
+# -------------------------------------------------------------
+# 1. HEADER
+# -------------------------------------------------------------
+draw.text((40, 20), "PINOUT Y GUIA DE CONEXION: MODULO GPS U-BLOX / N32+", fill=C_TEXT_WHITE, font=font_title)
+draw.text((40, 46), "Chipsets NEO-6M / NEO-7M / NEO-8M / N32+ con Antena Cerámica y Puerto Serial UART", fill=C_TEXT_CYAN, font=font_subtitle)
+draw.line([(40, 70), (W - 40, 70)], fill=(35, 46, 72), width=1)
+
+# -------------------------------------------------------------
+# 2. CUADRO PRINCIPAL DEL MODULO GPS (ILUSTRACION DE PLACA)
+# -------------------------------------------------------------
+board_panel = [40, 85, 480, 480]
+draw_card(board_panel, (14, 18, 30), C_CARD_BORDER, radius=8)
+draw.text((55, 95), "VISTA SUPERIOR DEL MODULO GPS", fill=C_TEXT_CYAN, font=font_sec)
+
+# PCB del GPS (Azul industrial estilo U-blox)
+pcb_box = [80, 130, 440, 440]
+draw_card(pcb_box, (15, 50, 105), (35, 80, 155), radius=10, width=2)
+
+# Orificios de montaje en las 4 esquinas (con arandela dorada)
+for hx, hy in [(95, 145), (425, 145), (95, 425), (425, 425)]:
+ draw.ellipse([hx-7, hy-7, hx+7, hy+7], fill=(190, 160, 60), outline=(140, 110, 30))
+ draw.ellipse([hx-4, hy-4, hx+4, hy+4], fill=(11, 15, 25))
+
+# Antena Cerámica Cuadrada (Parche cerámico color marrón dorado)
+ant_box = [115, 165, 260, 310]
+draw_card(ant_box, (185, 145, 65), (130, 95, 35), radius=6, width=2)
+# Superficie metalizada superior de la antena
+draw_card([125, 175, 250, 300], (210, 175, 95), (150, 115, 50), radius=4, width=1)
+# Punto central de soldadura / alimentación RF
+draw.ellipse([182, 232, 194, 244], fill=(245, 245, 245), outline=(120, 120, 120), width=1)
+draw.text((135, 315), "ANTENA CERAMICA GPS", fill=(210, 185, 130), font=font_small)
+
+# Blindaje Metálico U-blox (Shield Can)
+shield_box = [280, 165, 415, 285]
+draw_card(shield_box, (190, 198, 210), (140, 148, 160), radius=4, width=2)
+draw.rectangle([282, 167, 413, 172], fill=(220, 228, 238)) # Reflejo de luz
+draw.text((295, 178), "u-blox", fill=(190, 30, 30), font=font_sec)
+draw.text((295, 202), "NEO-7M-0-001", fill=(30, 35, 45), font=font_chip)
+draw.text((295, 218), "GPS / GLONASS", fill=(60, 70, 85), font=font_tiny)
+# Mini QR / DataMatrix decorativo
+draw_card([370, 235, 405, 270], (240, 240, 240), (80, 80, 80), radius=2)
+draw.rectangle([374, 239, 380, 245], fill=(30, 30, 30))
+draw.rectangle([395, 239, 401, 245], fill=(30, 30, 30))
+draw.rectangle([374, 260, 380, 266], fill=(30, 30, 30))
+draw.rectangle([386, 248, 392, 254], fill=(30, 30, 30))
+
+# Pila de botón de respaldo (RTC / Ephemeris Backup Battery)
+batt_x, batt_y = 305, 325
+draw.ellipse([batt_x-16, batt_y-16, batt_x+16, batt_y+16], fill=(195, 202, 212), outline=(130, 135, 145), width=2)
+draw.text((batt_x-10, batt_y-5), "3V", fill=(50, 55, 65), font=font_tiny)
+draw.text((280, 345), "Pila RTC Backup", fill=C_TEXT_MUTED, font=font_tiny)
+
+# LED de Estado PPS (Fix Indicator)
+led_x, led_y = 380, 325
+draw.ellipse([led_x-7, led_y-7, led_x+7, led_y+7], fill=(50, 220, 110), outline=(20, 140, 60), width=1)
+draw.ellipse([led_x-3, led_y-3, led_x+3, led_y+3], fill=(200, 255, 220)) # brillo central
+draw.text((358, 345), "LED PPS (Fix)", fill=(80, 235, 130), font=font_tiny)
+
+# Silkscreen del modelo
+draw.text((120, 350), "N32+ GPS MODULE", fill=(220, 230, 245), font=font_bold)
+draw.text((120, 368), "UART: 9600 bps 8N1", fill=(160, 190, 230), font=font_small)
+
+# Tira de pines (Header) en la parte inferior del PCB
+# 5 pines: VCC, RX, TX, GND, PPS
+header_box = [130, 395, 390, 430]
+draw_card(header_box, (20, 20, 20), (50, 50, 50), radius=3)
+
+pin_defs = [
+ ("VCC", COLOR_VCC, 160),
+ ("RX", COLOR_RX, 215),
+ ("TX", COLOR_TX, 270),
+ ("GND", COLOR_GND, 325),
+ ("PPS", COLOR_PPS, 370)
+]
+
+for name, col, px in pin_defs:
+ # Pin cuadrado dorado con orificio
+ draw.rectangle([px-8, 400, px+8, 420], fill=(220, 185, 55), outline=(150, 120, 30))
+ draw.ellipse([px-4, 406, px+4, 414], fill=(20, 20, 20))
+ # Texto de serigrafia en PCB
+ draw.text((px-9, 380), name, fill=col, font=font_chip)
+
+
+# -------------------------------------------------------------
+# 3. DETALLE DE CADA PIN Y CONEXION A LA CYD (COLUMNA DERECHA)
+# -------------------------------------------------------------
+pins_panel = [500, 85, 960, 480]
+draw_card(pins_panel, (14, 18, 30), C_CARD_BORDER, radius=8)
+draw.text((515, 95), "DESCRIPCION DE PINES Y DESTINO EN PLACA CYD", fill=C_TEXT_WHITE, font=font_sec)
+
+# Tarjeta Pin 1: VCC
+p1_card = [515, 125, 945, 185]
+draw_card(p1_card, (26, 32, 48), (200, 50, 50), radius=6)
+draw.rectangle([515, 125, 523, 185], fill=COLOR_VCC)
+draw.text((535, 132), "PIN 1: VCC (Alimentacion 3.3V a 5.0V DC)", fill=COLOR_VCC, font=font_bold)
+draw.text((535, 149), "Entrada de corriente con regulador LDO integrado en la placa GPS.", fill=C_TEXT_MUTED, font=font_small)
+draw.text((535, 165), "--> CONECTAR A: Pin 1 del conector P1 de la CYD (Vin 5V USB)", fill=C_TEXT_WHITE, font=font_reg)
+
+# Tarjeta Pin 2: RX
+p2_card = [515, 195, 945, 255]
+draw_card(p2_card, (26, 32, 48), (200, 120, 20), radius=6)
+draw.rectangle([515, 195, 523, 255], fill=COLOR_RX)
+draw.text((535, 202), "PIN 2: RX (Entrada Serial UART del GPS - Nivel 3.3V)", fill=COLOR_RX, font=font_bold)
+draw.text((535, 219), "Recibe comandos UBX de reposo (Sleep) y despertar (Wake) desde el ESP32.", fill=C_TEXT_MUTED, font=font_small)
+draw.text((535, 235), "--> CONECTAR A: Pin 2 del conector P1 de la CYD (GPIO 1 / TX0)", fill=C_TEXT_WHITE, font=font_reg)
+
+# Tarjeta Pin 3: TX
+p3_card = [515, 265, 945, 325]
+draw_card(p3_card, (26, 32, 48), (30, 160, 90), radius=6)
+draw.rectangle([515, 265, 523, 325], fill=COLOR_TX)
+draw.text((535, 272), "PIN 3: TX (Salida Serial UART del GPS - Nivel 3.3V)", fill=COLOR_TX, font=font_bold)
+draw.text((535, 289), "Transmite sentencias NMEA ($GPRMC, $GPGGA, velocidad, hora satelital).", fill=C_TEXT_MUTED, font=font_small)
+draw.text((535, 305), "--> CONECTAR A: Pin 3 del conector P3 de la CYD (GPIO 35 / RX1)", fill=C_TEXT_WHITE, font=font_reg)
+
+# Tarjeta Pin 4: GND
+p4_card = [515, 335, 945, 395]
+draw_card(p4_card, (26, 32, 48), (30, 90, 180), radius=6)
+draw.rectangle([515, 335, 523, 395], fill=COLOR_GND)
+draw.text((535, 342), "PIN 4: GND (Masa / Retorno comun)", fill=COLOR_GND, font=font_bold)
+draw.text((535, 359), "Retorno de corriente y referencia de nivel logico serial.", fill=C_TEXT_MUTED, font=font_small)
+draw.text((535, 375), "--> CONECTAR A: Pin 4 del conector P1 de la CYD (GND)", fill=C_TEXT_WHITE, font=font_reg)
+
+# Tarjeta Pin 5: PPS (Opcional)
+p5_card = [515, 405, 945, 465]
+draw_card(p5_card, (22, 28, 42), (120, 60, 180), radius=6)
+draw.rectangle([515, 405, 523, 465], fill=COLOR_PPS)
+draw.text((535, 412), "PIN 5: PPS (Pulse Per Second - Pulso 1 Hz)", fill=COLOR_PPS, font=font_bold)
+draw.text((535, 429), "Pulso de sincronismo atómico cada 1 segundo exacto cuando hay fix 3D.", fill=C_TEXT_MUTED, font=font_small)
+draw.text((535, 445), "--> NO REQUERIDO: Queda libre / no conectar (sincronismo por NMEA)", fill=(160, 170, 185), font=font_small)
+
+
+# -------------------------------------------------------------
+# 4. ESPECIFICACIONES TECNICAS Y TIPS DE INSTALACION (INFERIOR)
+# -------------------------------------------------------------
+info_box = [40, 500, 960, 655]
+draw_card(info_box, (15, 20, 32), (45, 58, 88), radius=8)
+draw.text((55, 510), "ESPECIFICACIONES TECNICAS Y RECOMENDACIONES DE INSTALACION:", fill=C_TEXT_WHITE, font=font_bold)
+
+col1_x = 55
+val1_x = 180
+draw.text((col1_x, 532), "• Chipset:", fill=C_TEXT_CYAN, font=font_bold)
+draw.text((val1_x, 532), "U-blox 7 / NEO-7M (o NEO-6M / N32+) 56 canales.", fill=C_TEXT_WHITE, font=font_small)
+
+draw.text((col1_x, 552), "• Protocolo:", fill=C_TEXT_CYAN, font=font_bold)
+draw.text((val1_x, 552), "NMEA 0183 a 9600 bps (8N1 - 8 bits, no paridad, 1 stop).", fill=C_TEXT_WHITE, font=font_small)
+
+draw.text((col1_x, 572), "• Consumo:", fill=C_TEXT_CYAN, font=font_bold)
+draw.text((val1_x, 572), "Activo: ~45 mA | Modo Sleep (UBX-RXM-PMREQ): < 0.5 mA.", fill=C_TEXT_WHITE, font=font_small)
+
+draw.text((col1_x, 592), "• Nivel Logico:", fill=C_TEXT_CYAN, font=font_bold)
+draw.text((val1_x, 592), "3.3V TTL (compatible directo con pines GPIO del ESP32).", fill=C_TEXT_WHITE, font=font_small)
+
+col2_x = 525
+val2_x = 705
+draw.text((col2_x, 532), "• Posición de Antena:", fill=(255, 215, 60), font=font_bold)
+draw.text((val2_x, 532), "Cara cerámica orientada al cielo.", fill=C_TEXT_WHITE, font=font_small)
+
+draw.text((col2_x, 552), " (En el tablero del vehículo, libre de chapas metálicas o tintes metalizados).", fill=C_TEXT_MUTED, font=font_small)
+
+draw.text((col2_x, 575), "• LED Verde (Fix/PPS):", fill=(80, 235, 130), font=font_bold)
+draw.text((val2_x, 575), "Apagado: buscando satélites.", fill=C_TEXT_WHITE, font=font_small)
+
+draw.text((col2_x, 595), " (Destella 1 vez por segundo cuando la señal satelital 3D está fijada).", fill=C_TEXT_MUTED, font=font_small)
+
+draw.text((col1_x, 625), "NOTA: Al programar el ESP32 por USB, si el pin RX del GPS esta conectado a GPIO 1 (P1), desconectar temporalmente el cable amarillo para evitar colisiones.", fill=(255, 180, 80), font=font_small)
+
+# Guardar imagen en hardware/ y en raíz
+out_path_1 = "hardware/pinout_gps.png"
+out_path_2 = "hardware/gps_pinout.png"
+out_path_3 = "pinout_gps.png"
+im.save(out_path_1, "PNG")
+im.save(out_path_2, "PNG")
+im.save(out_path_3, "PNG")
+print("Pinout GPS generado exitosamente en:", out_path_1, out_path_2, out_path_3)
diff --git a/hardware/gps_pinout.jpg b/hardware/gps_pinout.jpg
new file mode 100644
index 0000000..3fac66c
Binary files /dev/null and b/hardware/gps_pinout.jpg differ
diff --git a/hardware/gps_pinout.png b/hardware/gps_pinout.png
new file mode 100644
index 0000000..fff8c20
Binary files /dev/null and b/hardware/gps_pinout.png differ
diff --git a/hardware/pinout_gps.png b/hardware/pinout_gps.png
new file mode 100644
index 0000000..fff8c20
Binary files /dev/null and b/hardware/pinout_gps.png differ
diff --git a/hardware/pinout_gps_foto_anotada.png b/hardware/pinout_gps_foto_anotada.png
new file mode 100644
index 0000000..f32898c
Binary files /dev/null and b/hardware/pinout_gps_foto_anotada.png differ
diff --git a/pinout_gps.png b/pinout_gps.png
new file mode 100644
index 0000000..fff8c20
Binary files /dev/null and b/pinout_gps.png differ
diff --git a/pinout_gps_foto_anotada.png b/pinout_gps_foto_anotada.png
new file mode 100644
index 0000000..f32898c
Binary files /dev/null and b/pinout_gps_foto_anotada.png differ
diff --git a/sketch/obd2_gauge/DSEG7_Modern_BoldItalic_80.h b/sketch/obd2_gauge/DSEG7_Modern_BoldItalic_80.h
index bf92410..43bd934 100644
--- a/sketch/obd2_gauge/DSEG7_Modern_BoldItalic_80.h
+++ b/sketch/obd2_gauge/DSEG7_Modern_BoldItalic_80.h
@@ -1,3 +1,4 @@
+#pragma once
const uint8_t DSEG7_Modern_BoldItalic_80[] PROGMEM = {
0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x50,
diff --git a/sketch/obd2_gauge/Latin_Sans_Bold_26.h b/sketch/obd2_gauge/Latin_Sans_Bold_26.h
new file mode 100644
index 0000000..b595105
--- /dev/null
+++ b/sketch/obd2_gauge/Latin_Sans_Bold_26.h
@@ -0,0 +1,1293 @@
+#pragma once
+#include
+
+// Antialiased Latin Sans Bold 26pt (VLW font for TFT_eSPI)
+const uint8_t Latin_Sans_Bold_26[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F,
+ 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0D,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51,
+ 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11,
+ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19,
+ 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59,
+ 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x12,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0D,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67,
+ 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x13, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B,
+ 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x15,
+ 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x11,
+ 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0E,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x0E,
+ 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0F,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D,
+ 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC1,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC9, 0x00, 0x00, 0x00, 0x18,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xD1, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x00, 0x00, 0x18,
+ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xA0, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0F,
+ 0x3C, 0xFF, 0xFF, 0xFF, 0x70, 0x3C, 0xFF, 0xFF, 0xFF, 0x70, 0x3C, 0xFF, 0xFF, 0xFF, 0x70, 0x3C,
+ 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x24, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x53, 0xFF,
+ 0xFF, 0xB2, 0x00, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0x83, 0x00, 0x00, 0x00, 0xB2, 0xFF, 0xFF, 0x55,
+ 0x00, 0x00, 0x00, 0xE2, 0xFF, 0xFF, 0x26, 0x00, 0x00, 0x11, 0xFF, 0xFF, 0xF5, 0x02, 0x00, 0x00,
+ 0x40, 0xFF, 0xFF, 0xCA, 0x00, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0x9B, 0x00, 0x00, 0x00, 0x9F, 0xFF,
+ 0xFF, 0x6D, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0x3E, 0x00, 0x00, 0x04, 0xF9, 0xFF, 0xFF, 0x10,
+ 0x00, 0x00, 0x2D, 0xFF, 0xFF, 0xE2, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xFF, 0xB3, 0x00, 0x00, 0x00,
+ 0x8C, 0xFF, 0xFF, 0x85, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0xFF, 0x56, 0x00, 0x00, 0x00, 0xEB, 0xFF,
+ 0xFF, 0x28, 0x00, 0x00, 0x1A, 0xFF, 0xFF, 0xF7, 0x03, 0x00, 0x00, 0x4A, 0xFF, 0xFF, 0xCB, 0x00,
+ 0x00, 0x00, 0x79, 0xFF, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0xA8, 0xFF, 0xFF, 0x6F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x08, 0x67, 0xC5, 0xE7, 0xF8, 0xE0, 0x9C, 0x27, 0x00, 0x00, 0x00, 0x00, 0x11, 0xD0,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x4A, 0x00, 0x00, 0x00, 0xA8, 0xFF, 0xFF, 0xFF, 0xEC,
+ 0xD9, 0xFC, 0xFF, 0xFF, 0xF2, 0x16, 0x00, 0x25, 0xFE, 0xFF, 0xFF, 0x99, 0x03, 0x00, 0x37, 0xF9,
+ 0xFF, 0xFF, 0x87, 0x00, 0x78, 0xFF, 0xFF, 0xFC, 0x10, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xDA,
+ 0x00, 0xB1, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x00, 0x69, 0xFF, 0xFF, 0xFF, 0x16, 0xD5, 0xFF,
+ 0xFF, 0xAF, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x3C, 0xEB, 0xFF, 0xFF, 0x9F, 0x00,
+ 0x00, 0x00, 0x00, 0x38, 0xFF, 0xFF, 0xFF, 0x52, 0xF5, 0xFF, 0xFF, 0x99, 0x00, 0x00, 0x00, 0x00,
+ 0x32, 0xFF, 0xFF, 0xFF, 0x5E, 0xF3, 0xFF, 0xFF, 0x99, 0x00, 0x00, 0x00, 0x00, 0x34, 0xFF, 0xFF,
+ 0xFF, 0x5D, 0xE7, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, 0x52, 0xD0,
+ 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xFF, 0xFF, 0xFF, 0x39, 0xA7, 0xFF, 0xFF, 0xD1,
+ 0x00, 0x00, 0x00, 0x00, 0x6E, 0xFF, 0xFF, 0xFF, 0x10, 0x6A, 0xFF, 0xFF, 0xFD, 0x14, 0x00, 0x00,
+ 0x00, 0xAF, 0xFF, 0xFF, 0xCE, 0x00, 0x19, 0xF9, 0xFF, 0xFF, 0x9D, 0x04, 0x00, 0x46, 0xFD, 0xFF,
+ 0xFF, 0x75, 0x00, 0x00, 0x8E, 0xFF, 0xFF, 0xFF, 0xED, 0xDE, 0xFE, 0xFF, 0xFF, 0xE3, 0x0A, 0x00,
+ 0x00, 0x06, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED, 0x32, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x67, 0xC5, 0xEF, 0xF7, 0xDB, 0x8F, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x74, 0xF9,
+ 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xA4,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00,
+ 0x00, 0x31, 0xFF, 0xFF, 0xF6, 0x6C, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x38, 0xFF,
+ 0xCA, 0x27, 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x33, 0x81, 0x04, 0x00, 0x00,
+ 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF,
+ 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF,
+ 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xA8, 0xA8,
+ 0xA8, 0xA8, 0xFA, 0xFF, 0xFF, 0xE0, 0xA8, 0xA8, 0xA8, 0x7B, 0x5C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0x5C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x28, 0x96, 0xD8, 0xF5, 0xF9, 0xE6, 0xB4, 0x58, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x5F, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB6, 0x07, 0x00,
+ 0x00, 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xEF, 0xE4, 0xFE, 0xFF, 0xFF, 0xFF, 0x89, 0x00, 0x00, 0xA1,
+ 0xFF, 0xFF, 0xFF, 0x6B, 0x01, 0x00, 0x2E, 0xEF, 0xFF, 0xFF, 0xEC, 0x02, 0x00, 0xBA, 0xE4, 0xF1,
+ 0xC7, 0x00, 0x00, 0x00, 0x00, 0x95, 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x09, 0xE1, 0xFF, 0xFF, 0xE3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xA0, 0xFF,
+ 0xFF, 0xFF, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x9E, 0xFF, 0xFF, 0xFF, 0xCD,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xBB, 0xFF, 0xFF, 0xFF, 0xD1, 0x18, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0xD6, 0xFF, 0xFF, 0xFF, 0xC1, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x26, 0xE6, 0xFF, 0xFF, 0xFF, 0x92, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE4,
+ 0xFF, 0xFF, 0xFB, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xCB, 0xFF, 0xFF, 0xF9,
+ 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xF5, 0xFF, 0xFF, 0xFF, 0xF2, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0xF0, 0xF0, 0xF0, 0x65, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x6C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6C,
+ 0x00, 0x00, 0x00, 0x2B, 0x97, 0xD8, 0xF5, 0xF9, 0xE6, 0xB5, 0x5D, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x6E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x0E, 0x00, 0x00, 0x48, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xEB, 0xE8, 0xFF, 0xFF, 0xFF, 0xFF, 0x97, 0x00, 0x00, 0xC6, 0xFF, 0xFF, 0xF8, 0x4C,
+ 0x00, 0x00, 0x3A, 0xF3, 0xFF, 0xFF, 0xF1, 0x00, 0x05, 0xC8, 0xDD, 0xEF, 0x99, 0x00, 0x00, 0x00,
+ 0x00, 0x9F, 0xFF, 0xFF, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2,
+ 0xFF, 0xFF, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x20, 0x82, 0xFF, 0xFF, 0xFF,
+ 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xEA, 0x63, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xFF, 0xFF, 0xFF, 0xCE, 0x53, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xB6, 0xE4, 0xFB, 0xFF, 0xFF, 0xFF, 0xD5, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x4E, 0xE9, 0xFF, 0xFF, 0xE6, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x5B, 0xFF, 0xFF, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0x7D, 0x3D, 0xC6, 0xDB, 0xF0, 0x66, 0x00, 0x00, 0x00, 0x00, 0x56,
+ 0xFF, 0xFF, 0xFF, 0x6F, 0x1C, 0xFC, 0xFF, 0xFF, 0xEA, 0x41, 0x00, 0x00, 0x31, 0xDD, 0xFF, 0xFF,
+ 0xFF, 0x38, 0x00, 0x9D, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x00,
+ 0x00, 0x0A, 0xB2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD1, 0x19, 0x00, 0x00, 0x00,
+ 0x00, 0x4B, 0xA8, 0xDE, 0xF7, 0xFA, 0xE9, 0xBA, 0x66, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x13, 0xEB, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xA1, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xFF,
+ 0xFF, 0xDE, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xDB, 0xFF, 0xE9,
+ 0x7C, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xFF, 0xFF, 0x61, 0x75,
+ 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFA, 0xFF, 0xC0, 0x00, 0x78, 0xFF,
+ 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xC7, 0xFF, 0xF8, 0x27, 0x00, 0x78, 0xFF, 0xFF,
+ 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0xFF, 0xFF, 0x7E, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xF0,
+ 0x00, 0x00, 0x00, 0x00, 0x1B, 0xF1, 0xFF, 0xD7, 0x06, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xF0, 0x00,
+ 0x00, 0x00, 0x00, 0xAF, 0xFF, 0xFE, 0x3E, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
+ 0x00, 0x4F, 0xFF, 0xFF, 0x9C, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
+ 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x9C,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x66, 0xA8,
+ 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xD1, 0xFF, 0xFF, 0xFA, 0xA8, 0xA8, 0x37, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB4, 0x00, 0x00, 0x28, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xB4, 0x00, 0x00, 0x37, 0xFF, 0xFF, 0xFF, 0xB7, 0xA8, 0xA8, 0xA8, 0xA8,
+ 0xA8, 0xA8, 0x76, 0x00, 0x00, 0x47, 0xFF, 0xFF, 0xFF, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x56, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x66, 0xFF, 0xFF, 0xFD, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75,
+ 0xFF, 0xFF, 0xEE, 0x47, 0xBD, 0xF2, 0xF7, 0xD7, 0x8A, 0x15, 0x00, 0x00, 0x00, 0x85, 0xFF, 0xFF,
+ 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE5, 0x2B, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF5, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xDA, 0x05, 0x00, 0xA4, 0xFF, 0xFF, 0xFC, 0x6B, 0x05, 0x00,
+ 0x2F, 0xD9, 0xFF, 0xFF, 0xFF, 0x59, 0x00, 0x05, 0x08, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x3F,
+ 0xFF, 0xFF, 0xFF, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xFB, 0xFF,
+ 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFD, 0xFF, 0xFF, 0xAD,
+ 0x15, 0xC2, 0xD7, 0xED, 0x90, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x87, 0x00, 0xDD,
+ 0xFF, 0xFF, 0xF8, 0x51, 0x00, 0x00, 0x2B, 0xD9, 0xFF, 0xFF, 0xFF, 0x35, 0x00, 0x5E, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xEA, 0xE4, 0xFF, 0xFF, 0xFF, 0xFF, 0xA5, 0x00, 0x00, 0x00, 0x84, 0xFE, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAE, 0x09, 0x00, 0x00, 0x00, 0x00, 0x36, 0x9F, 0xDB, 0xF7, 0xF9,
+ 0xE3, 0xAC, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0xA7, 0xE4, 0xFA, 0xEE, 0xBC,
+ 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8D,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0xFF, 0xFF, 0xFF, 0xF7, 0xD9, 0xFA, 0xFF, 0xFF, 0xFF, 0x45, 0x00,
+ 0x00, 0x0B, 0xE9, 0xFF, 0xFF, 0xBD, 0x12, 0x00, 0x25, 0xEC, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0x59,
+ 0xFF, 0xFF, 0xF6, 0x17, 0x00, 0x00, 0x00, 0x7C, 0xE0, 0xB8, 0x8B, 0x00, 0x00, 0xA4, 0xFF, 0xFF,
+ 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xFF, 0x86, 0x28,
+ 0xA6, 0xE9, 0xF9, 0xDE, 0x8F, 0x15, 0x00, 0x00, 0x00, 0xF3, 0xFF, 0xFF, 0xAA, 0xF6, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xE1, 0x1F, 0x00, 0x01, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0xAE, 0xE6, 0xFF,
+ 0xFF, 0xFF, 0xBD, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0x6B, 0x00, 0x00, 0x0E, 0xC8, 0xFF, 0xFF,
+ 0xFF, 0x2C, 0x00, 0xFC, 0xFF, 0xFF, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x66,
+ 0x00, 0xEA, 0xFF, 0xFF, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, 0x7D, 0x00, 0xBE,
+ 0xFF, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x23, 0xFF, 0xFF, 0xFF, 0x75, 0x00, 0x7F, 0xFF, 0xFF,
+ 0xFD, 0x24, 0x00, 0x00, 0x00, 0x5A, 0xFF, 0xFF, 0xFF, 0x53, 0x00, 0x24, 0xFC, 0xFF, 0xFF, 0xBA,
+ 0x0C, 0x00, 0x1F, 0xDB, 0xFF, 0xFF, 0xF7, 0x12, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0xF0, 0xD3,
+ 0xFA, 0xFF, 0xFF, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x05, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xAC, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xB6, 0xE9, 0xFB, 0xEC, 0xB9, 0x57, 0x01,
+ 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x50, 0xE0,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x50, 0xD2, 0xF0, 0xF0, 0xF0,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x61, 0xFF, 0xFF, 0xDD, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xF0, 0xFF,
+ 0xFF, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0xFF, 0xAD, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xFF, 0xFF, 0xF6, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0E, 0xE6, 0xFF, 0xFF, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8A,
+ 0xFF, 0xFF, 0xF2, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xF7, 0xFF, 0xFF, 0x82,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xFF, 0xFF, 0xF7, 0x11, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0xED, 0xFF, 0xFF, 0xA5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x50, 0xFF, 0xFF, 0xFF, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xFF,
+ 0xFF, 0xF8, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xCA, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xFF, 0xFF, 0xFF, 0x9E, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x40, 0xFF, 0xFF, 0xFF, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37,
+ 0x9F, 0xDA, 0xF5, 0xFA, 0xEA, 0xBF, 0x70, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFE, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC, 0x20, 0x00, 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0xF1, 0x9B, 0x8B,
+ 0xCA, 0xFF, 0xFF, 0xFF, 0xBF, 0x00, 0x00, 0xA1, 0xFF, 0xFF, 0xFF, 0x34, 0x00, 0x00, 0x01, 0xC2,
+ 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0xB6, 0xFF, 0xFF, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
+ 0xFF, 0x2F, 0x00, 0x8E, 0xFF, 0xFF, 0xFB, 0x0A, 0x00, 0x00, 0x00, 0x93, 0xFF, 0xFF, 0xFB, 0x0C,
+ 0x00, 0x1F, 0xF5, 0xFF, 0xFF, 0xA2, 0x1B, 0x0A, 0x49, 0xF2, 0xFF, 0xFF, 0x8E, 0x00, 0x00, 0x00,
+ 0x39, 0xDC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x8C, 0x03, 0x00, 0x00, 0x00, 0x08, 0x64,
+ 0xE6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA1, 0x34, 0x00, 0x00, 0x00, 0x14, 0xC9, 0xFF, 0xFF, 0xEF,
+ 0x9C, 0x8D, 0xC7, 0xFF, 0xFF, 0xFC, 0x6B, 0x00, 0x00, 0x9B, 0xFF, 0xFF, 0xF3, 0x20, 0x00, 0x00,
+ 0x00, 0x96, 0xFF, 0xFF, 0xFA, 0x26, 0x03, 0xF9, 0xFF, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x27,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x22, 0xFF, 0xFF, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFF, 0xFF,
+ 0xFF, 0x9E, 0x19, 0xFF, 0xFF, 0xFF, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x93,
+ 0x00, 0xE0, 0xFF, 0xFF, 0xF4, 0x20, 0x00, 0x00, 0x00, 0x97, 0xFF, 0xFF, 0xFF, 0x5A, 0x00, 0x6B,
+ 0xFF, 0xFF, 0xFF, 0xF1, 0xA1, 0x8F, 0xC5, 0xFF, 0xFF, 0xFF, 0xDC, 0x07, 0x00, 0x00, 0x8F, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0x28, 0x00, 0x00, 0x00, 0x00, 0x39, 0x9E, 0xD9,
+ 0xF5, 0xFB, 0xEB, 0xBF, 0x70, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x91, 0xD5, 0xF3, 0xF7,
+ 0xDC, 0x97, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xF6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF7, 0x4D, 0x00, 0x00, 0x00, 0x2F, 0xF9, 0xFF, 0xFF, 0xFF, 0xE8, 0xE6, 0xFF, 0xFF, 0xFF, 0xF6,
+ 0x20, 0x00, 0x00, 0xA9, 0xFF, 0xFF, 0xFF, 0x6D, 0x00, 0x00, 0x5F, 0xFE, 0xFF, 0xFF, 0x9D, 0x00,
+ 0x00, 0xEF, 0xFF, 0xFF, 0xCA, 0x00, 0x00, 0x00, 0x00, 0xAE, 0xFF, 0xFF, 0xF0, 0x06, 0x0E, 0xFF,
+ 0xFF, 0xFF, 0x95, 0x00, 0x00, 0x00, 0x00, 0x66, 0xFF, 0xFF, 0xFF, 0x33, 0x10, 0xFF, 0xFF, 0xFF,
+ 0x92, 0x00, 0x00, 0x00, 0x00, 0x51, 0xFF, 0xFF, 0xFF, 0x5C, 0x01, 0xF4, 0xFF, 0xFF, 0xC2, 0x00,
+ 0x00, 0x00, 0x00, 0x7B, 0xFF, 0xFF, 0xFF, 0x6F, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x51, 0x00, 0x00,
+ 0x29, 0xED, 0xFF, 0xFF, 0xFF, 0x7B, 0x00, 0x49, 0xFF, 0xFF, 0xFF, 0xFD, 0xC5, 0xBC, 0xF7, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x75, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF,
+ 0xFF, 0x68, 0x00, 0x00, 0x00, 0x5A, 0xC5, 0xF4, 0xF3, 0xC7, 0x60, 0x22, 0xFF, 0xFF, 0xFF, 0x49,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xFF, 0xFF, 0xFF, 0x1C, 0x00, 0x65,
+ 0xB0, 0xD6, 0xB8, 0x00, 0x00, 0x00, 0x00, 0xAE, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x78, 0xFF, 0xFF,
+ 0xFF, 0x5A, 0x00, 0x01, 0x6C, 0xFF, 0xFF, 0xFF, 0x6D, 0x00, 0x00, 0x14, 0xF0, 0xFF, 0xFF, 0xFF,
+ 0xE0, 0xE8, 0xFF, 0xFF, 0xFF, 0xD2, 0x05, 0x00, 0x00, 0x00, 0x50, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xD7, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xA4, 0xE3, 0xFA, 0xEF, 0xC9, 0x73,
+ 0x07, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x28, 0x80, 0xFF, 0xFF, 0xFF, 0x28, 0x80, 0xFF,
+ 0xFF, 0xFF, 0x28, 0x80, 0xFF, 0xFF, 0xFF, 0x28, 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, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x28, 0x80, 0xFF, 0xFF, 0xFF, 0x28,
+ 0x80, 0xFF, 0xFF, 0xFF, 0x28, 0x80, 0xFF, 0xFF, 0xFF, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0C, 0xF5, 0xFF, 0xFF, 0xFF, 0xC5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFD, 0xFF, 0xFF, 0xA7, 0xFF, 0xFF, 0xDA,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xFF, 0xFF, 0xE8, 0x28,
+ 0xFF, 0xFF, 0xFF, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3, 0xFF,
+ 0xFF, 0x99, 0x00, 0xD8, 0xFF, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0xFF, 0xFF, 0xFF, 0x47, 0x00, 0x86, 0xFF, 0xFF, 0xEB, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x8D, 0xFF, 0xFF, 0xEE, 0x05, 0x00, 0x32, 0xFF, 0xFF, 0xFF, 0x4D, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xE6, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xFF,
+ 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0xFF, 0xFF, 0xFF, 0x4C, 0x00, 0x00, 0x00,
+ 0x8A, 0xFF, 0xFF, 0xF7, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0xFF, 0xFF, 0xF2, 0x07,
+ 0x00, 0x00, 0x00, 0x36, 0xFF, 0xFF, 0xFF, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xF4, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x5D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x1D, 0x00,
+ 0x00, 0x00, 0x00, 0xBA, 0xFF, 0xFF, 0xFD, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD8, 0xFF, 0xFF,
+ 0xFF, 0x77, 0x00, 0x00, 0x00, 0x1A, 0xFD, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0xEB, 0xFF, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xFF, 0xFF, 0x57, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0xD1, 0xFF, 0xFF, 0xEE, 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0xFF, 0xFF, 0xFF, 0x8D, 0x00, 0x2D, 0xFF, 0xFF,
+ 0xFF, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xFF, 0xFF, 0xE6, 0x03,
+ 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xE9, 0xC3, 0x81, 0x1C, 0x00, 0x00,
+ 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x59,
+ 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xE3, 0xC8, 0xC8, 0xC8, 0xC8, 0xDA, 0xFA, 0xFF, 0xFF, 0xFF,
+ 0xF8, 0x22, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0xBD, 0xFF,
+ 0xFF, 0xFF, 0x73, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48,
+ 0xFF, 0xFF, 0xFF, 0x86, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x55, 0xFF, 0xFF, 0xFF, 0x60, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x11,
+ 0x47, 0xDD, 0xFF, 0xFF, 0xE3, 0x0A, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xC5, 0x22, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0x6E, 0x12, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xE1, 0xC4, 0xC4,
+ 0xC4, 0xC4, 0xCC, 0xE7, 0xFF, 0xFF, 0xFF, 0xEE, 0x43, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xE6, 0xFF, 0xFF, 0xF1, 0x16, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xFF, 0x6E, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0xFF, 0xFF, 0xFF, 0x91, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0xFF, 0xFF, 0xFF, 0x83, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0xE5, 0xFF, 0xFF, 0xFF, 0x48, 0x44,
+ 0xFF, 0xFF, 0xFF, 0xE3, 0xC8, 0xC8, 0xC8, 0xC8, 0xCD, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x02,
+ 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC4, 0x15,
+ 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0xDC, 0xA8, 0x51, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x83, 0xC2, 0xE5, 0xF8, 0xF7, 0xE4, 0xB8, 0x6F, 0x11,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x98, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xEF, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x06, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFD,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0x91, 0xFF, 0xFF, 0xFF, 0xF5, 0x7E, 0x24,
+ 0x01, 0x07, 0x2F, 0x9A, 0xFE, 0xFF, 0xFF, 0xF9, 0x26, 0x00, 0x1B, 0xFA, 0xFF, 0xFF, 0xF8, 0x38,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0x75, 0xFF, 0xFF, 0xFF,
+ 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xBB, 0x74, 0x2A, 0x00, 0xB3, 0xFF,
+ 0xFF, 0xFF, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xD7, 0xFF, 0xFF, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xE8, 0xFF, 0xFF, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3, 0xFF, 0xFF, 0xF5, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA9, 0xFF, 0xFF, 0xFF, 0x2F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0xFF, 0xFF, 0xFF, 0x93, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xBF, 0x65, 0x11, 0x00, 0x0E, 0xF0, 0xFF, 0xFF,
+ 0xFC, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xF6, 0xFF, 0xFF, 0xD7, 0x01, 0x00, 0x73,
+ 0xFF, 0xFF, 0xFF, 0xF9, 0x88, 0x29, 0x02, 0x03, 0x23, 0x82, 0xF6, 0xFF, 0xFF, 0xFF, 0x53, 0x00,
+ 0x00, 0x00, 0xA4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8C,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x7A, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5,
+ 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x74, 0xB7, 0xE2, 0xF5, 0xFA, 0xE8, 0xBD,
+ 0x75, 0x15, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xE9, 0xCF,
+ 0x97, 0x4B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xD8, 0x3B, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xEB,
+ 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x5E, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00,
+ 0x00, 0x08, 0x44, 0xBB, 0xFF, 0xFF, 0xFF, 0xFA, 0x38, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x8E, 0xFF, 0xFF, 0xFF, 0xC3, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD1, 0xFF, 0xFF, 0xFF, 0x2C, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xFF, 0xFF, 0xFF, 0x6F, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xFF, 0xFF, 0xFF, 0x94, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFF, 0xFF, 0xFF, 0xA8, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xA7,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0xFF, 0xFF, 0xFF,
+ 0x91, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xDE, 0xFF,
+ 0xFF, 0xFD, 0x1C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x99, 0xFF,
+ 0xFF, 0xFF, 0xAF, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x01, 0x35, 0xB4, 0xFF,
+ 0xFF, 0xFF, 0xEE, 0x1D, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE5, 0xF6, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF7, 0x4E, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xDC, 0x32, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9,
+ 0xE1, 0xAF, 0x5E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xB9, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0x07, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0x4A, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xF0, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0xD5, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA4, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA4, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0xE4, 0xE4, 0x92, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x75, 0xB4, 0xE1, 0xF3, 0xFB, 0xED, 0xC9,
+ 0x8E, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xA8, 0x0F, 0x00, 0x00, 0x00, 0x02, 0xB1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFE, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCA, 0x0A, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0xFF, 0xFA,
+ 0x8C, 0x2D, 0x04, 0x02, 0x21, 0x7F, 0xF4, 0xFF, 0xFF, 0xFF, 0x8E, 0x00, 0x17, 0xF6, 0xFF, 0xFF,
+ 0xFC, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0xF3, 0xFF, 0xFF, 0xF8, 0x12, 0x71, 0xFF,
+ 0xFF, 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x75, 0x24, 0x00,
+ 0xB1, 0xFF, 0xFF, 0xFF, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xD6, 0xFF, 0xFF, 0xF3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xE8, 0xFF, 0xFF, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xD3, 0xFF, 0xFF, 0xF6, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xAA, 0xFF, 0xFF, 0xFF, 0x35, 0x00,
+ 0x00, 0x00, 0x00, 0x24, 0xC4, 0xC4, 0xC4, 0xC4, 0xF4, 0xFF, 0xFF, 0x9C, 0x69, 0xFF, 0xFF, 0xFF,
+ 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xFF, 0xFF, 0x9C, 0x10, 0xF2,
+ 0xFF, 0xFF, 0xFE, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xD6, 0xFF, 0xFF, 0x9C,
+ 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0xFD, 0x94, 0x2E, 0x02, 0x00, 0x05, 0x2D, 0x6C, 0xCF, 0xFF, 0xFF,
+ 0xFF, 0x9C, 0x00, 0x01, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xDE, 0x34, 0x00, 0x00, 0x02, 0x7E, 0xF6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xEE, 0x87, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x76, 0xB7, 0xE2, 0xF5, 0xF8,
+ 0xE5, 0xCC, 0x95, 0x55, 0x0A, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08,
+ 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x88, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xBE, 0xFF,
+ 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8,
+ 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x92, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0C, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF,
+ 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xB0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF,
+ 0xFF, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xB0,
+ 0x43, 0x98, 0xBF, 0xE7, 0x39, 0x00, 0x00, 0x00, 0x15, 0xFF, 0xFF, 0xFF, 0xA8, 0x65, 0xFF, 0xFF,
+ 0xFF, 0x88, 0x00, 0x00, 0x00, 0x42, 0xFF, 0xFF, 0xFF, 0x8D, 0x17, 0xFB, 0xFF, 0xFF, 0xF5, 0x4C,
+ 0x02, 0x20, 0xCC, 0xFF, 0xFF, 0xFF, 0x4B, 0x00, 0x97, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xCE, 0x02, 0x00, 0x0B, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDB, 0x21,
+ 0x00, 0x00, 0x00, 0x03, 0x65, 0xBD, 0xEC, 0xFC, 0xF1, 0xC9, 0x77, 0x0D, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xD9, 0xFF, 0xFF, 0xFE, 0x68, 0x00,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xD7, 0xFF, 0xFF, 0xFE, 0x67,
+ 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x16, 0xD6, 0xFF, 0xFF, 0xFE,
+ 0x66, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x15, 0xD4, 0xFF, 0xFF,
+ 0xFE, 0x65, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x14, 0xD2, 0xFF,
+ 0xFF, 0xFE, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x13, 0xD0,
+ 0xFF, 0xFF, 0xFE, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x12,
+ 0xCE, 0xFF, 0xFF, 0xFE, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x8C, 0xCD, 0xFF, 0xFF, 0xFF, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xE2, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED, 0xFF, 0xFF, 0xFF, 0xC0, 0x04, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xA6, 0x0D, 0xB5, 0xFF, 0xFF, 0xFF, 0x90, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0x00, 0x11, 0xDE, 0xFF, 0xFF, 0xFF,
+ 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x31, 0xF7,
+ 0xFF, 0xFF, 0xF6, 0x31, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x61, 0xFF, 0xFF, 0xFF, 0xDF, 0x13, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x9A, 0xFF, 0xFF, 0xFF, 0xBB, 0x03, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xCB, 0xFF, 0xFF, 0xFF, 0x8A, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xEC, 0xFF, 0xFF, 0xFF, 0x56, 0x00,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xFD, 0xFF, 0xFF,
+ 0xF4, 0x2D, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4,
+ 0xE4, 0x11, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x14, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14,
+ 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xE6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8B, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xD8, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xF4, 0xF7, 0xFF, 0xDC,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xFF, 0xFF, 0xEC, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xF8,
+ 0xB8, 0xFF, 0xFF, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xB2, 0xFF, 0xFF, 0xEC, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x73, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xC8, 0xFF, 0xFF, 0x78, 0xFF,
+ 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x19, 0xFE, 0xFF,
+ 0xD0, 0x67, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0xDE, 0xFF, 0xFF, 0x22, 0x00, 0x00,
+ 0x68, 0xFF, 0xFF, 0x87, 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x90, 0xFF, 0xFF,
+ 0x74, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0x37, 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10,
+ 0x3E, 0xFF, 0xFF, 0xC5, 0x00, 0x0E, 0xF9, 0xFF, 0xE4, 0x01, 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x10, 0x02, 0xE8, 0xFF, 0xFE, 0x16, 0x56, 0xFF, 0xFF, 0x92, 0x00, 0x68, 0xFF, 0xFF,
+ 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x99, 0xFF, 0xFF, 0x59, 0x9E, 0xFF, 0xFF, 0x40, 0x00,
+ 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x46, 0xFF, 0xFF, 0x9A, 0xE4, 0xFF,
+ 0xEA, 0x03, 0x00, 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x05, 0xEE, 0xFF,
+ 0xF2, 0xFF, 0xFF, 0x9B, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00,
+ 0x00, 0xA1, 0xFF, 0xFF, 0xFF, 0xFF, 0x48, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x10, 0x00, 0x00, 0x4F, 0xFF, 0xFF, 0xFF, 0xF0, 0x05, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xEC,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x08, 0xF3, 0xFF, 0xFF, 0xA3, 0x00, 0x00, 0x00, 0x68,
+ 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0xA9, 0xFF, 0xFF, 0x51, 0x00,
+ 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xEC, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0x4B, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xD7, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED,
+ 0x11, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xEB, 0xE8, 0xFF,
+ 0xFF, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xF6, 0x65,
+ 0xFF, 0xFF, 0xFA, 0x26, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x09, 0xD2, 0xFF, 0xFF, 0xB1, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x0F, 0x45, 0xFF, 0xFF, 0xFF, 0x42, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x10, 0x00, 0xB6, 0xFF, 0xFF, 0xD0, 0x02, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x2A, 0xFC, 0xFF, 0xFF, 0x64, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x96, 0xFF, 0xFF, 0xE7, 0x0C, 0x4A, 0xFF, 0xFF, 0xFF,
+ 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x15, 0xF1, 0xFF, 0xFF, 0x85, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x77, 0xFF, 0xFF, 0xF7, 0x55, 0xFF,
+ 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x07, 0xDF, 0xFF, 0xFF, 0xC6,
+ 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x57, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x39, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xA8, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00, 0x00, 0x00, 0x13, 0x72, 0xB5, 0xE1,
+ 0xF4, 0xF7, 0xE4, 0xBE, 0x7E, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x77, 0xF4,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x8F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xA4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0x07, 0x00,
+ 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF, 0xFA, 0x8D, 0x2B, 0x03, 0x01, 0x21, 0x7A, 0xF3, 0xFF, 0xFF,
+ 0xFF, 0x9B, 0x00, 0x00, 0x11, 0xF1, 0xFF, 0xFF, 0xFD, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x33, 0xF4, 0xFF, 0xFF, 0xFE, 0x2E, 0x00, 0x6C, 0xFF, 0xFF, 0xFF, 0x95, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF, 0xFF, 0x98, 0x00, 0xAD, 0xFF, 0xFF, 0xFF, 0x2E, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xFA, 0xFF, 0xFF, 0xDA, 0x00, 0xD5, 0xFF, 0xFF,
+ 0xF3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFE, 0x04,
+ 0xE8, 0xFF, 0xFF, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0xFF,
+ 0xFF, 0xFF, 0x17, 0xE5, 0xFF, 0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xBA, 0xFF, 0xFF, 0xFF, 0x17, 0xD2, 0xFF, 0xFF, 0xF7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xFF, 0xFC, 0x06, 0xA5, 0xFF, 0xFF, 0xFF, 0x39, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFC, 0xFF, 0xFF, 0xD4, 0x00, 0x61, 0xFF, 0xFF, 0xFF,
+ 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xFF, 0xFF, 0x89, 0x00, 0x0A,
+ 0xEA, 0xFF, 0xFF, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xF4, 0xFF, 0xFF, 0xFA,
+ 0x21, 0x00, 0x00, 0x63, 0xFF, 0xFF, 0xFF, 0xFD, 0x97, 0x2E, 0x03, 0x01, 0x20, 0x7A, 0xF3, 0xFF,
+ 0xFF, 0xFF, 0x74, 0x00, 0x00, 0x00, 0x00, 0x91, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFB, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xA8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0xF0, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+ 0x6F, 0xB5, 0xE2, 0xF5, 0xFA, 0xE7, 0xBD, 0x77, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xD9, 0x9F, 0x3C, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9A, 0x02, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xF3, 0xE8, 0xE8, 0xE8, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x7A, 0xFE, 0xFF, 0xFF, 0xF7, 0x10, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0xFF, 0xFF, 0xFF, 0x4F, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xFF, 0xFF, 0xFF, 0x6B, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0xFF, 0xFF, 0xFF, 0x60, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xFF, 0xFF, 0xFF, 0x3B, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x7D, 0xFF, 0xFF, 0xFF, 0xDF, 0x01, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xEF, 0xE0, 0xE0, 0xE0, 0xE5, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x52, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x6E, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0xD6, 0x90, 0x29, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x12, 0x71, 0xB4, 0xE1, 0xF4, 0xF7, 0xE4, 0xBE, 0x7D, 0x1C, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x74, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0x8B,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFB, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xBB, 0x05, 0x00, 0x00, 0x00, 0x71, 0xFF, 0xFF, 0xFF, 0xFB, 0x8E, 0x2C, 0x03,
+ 0x01, 0x21, 0x7B, 0xF4, 0xFF, 0xFF, 0xFF, 0x92, 0x00, 0x00, 0x0F, 0xF0, 0xFF, 0xFF, 0xFD, 0x4D,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0xF5, 0xFF, 0xFF, 0xFD, 0x26, 0x00, 0x69, 0xFF, 0xFF,
+ 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xFF, 0xFF, 0xFF, 0x91, 0x00,
+ 0xAB, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFB, 0xFF,
+ 0xFF, 0xD4, 0x00, 0xD4, 0xFF, 0xFF, 0xF4, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xD1, 0xFF, 0xFF, 0xFE, 0x03, 0xE7, 0xFF, 0xFF, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x14, 0xE7, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xFF, 0x18, 0xD5, 0xFF, 0xFF, 0xF5,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xFF, 0xFF, 0xFF, 0x07, 0xAB,
+ 0xFF, 0xFF, 0xFF, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xFA, 0xFF, 0xFF,
+ 0xDC, 0x00, 0x6B, 0xFF, 0xFF, 0xFF, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66,
+ 0xFF, 0xFF, 0xFF, 0x9C, 0x00, 0x11, 0xF5, 0xFF, 0xFF, 0xFD, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x1F, 0xEB, 0xFF, 0xFF, 0xFF, 0x38, 0x00, 0x00, 0x79, 0xFF, 0xFF, 0xFF, 0xF6, 0x74, 0x0F,
+ 0x00, 0x00, 0x07, 0x57, 0xE5, 0xFF, 0xFF, 0xFF, 0xAC, 0x00, 0x00, 0x00, 0x03, 0xAF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFB, 0xDC, 0xD8, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xD9, 0x13, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x8B, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x19, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x87, 0xCC, 0xF5, 0xFF, 0xFF, 0xFF, 0xF6, 0xB6, 0x52, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xFF, 0xFF, 0xFF, 0xBD,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE,
+ 0xFF, 0xFF, 0xFF, 0x86, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x39, 0xFD, 0xFF, 0xFF, 0xFF, 0xF5, 0xD6, 0xD8, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xB7, 0xEF, 0xFB, 0xED, 0xCA,
+ 0x3E, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xD9, 0xA2,
+ 0x44, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xAA, 0x07, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF3, 0xE8, 0xE8, 0xE8, 0xE8,
+ 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0E, 0x72, 0xFD, 0xFF, 0xFF, 0xFC, 0x14, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0xFF, 0xFF, 0xFF, 0x49, 0x00, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0x55, 0x00, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xFF, 0xFF, 0xFF, 0x35,
+ 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x7B, 0xFF, 0xFF, 0xFF,
+ 0xDC, 0x02, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xF3, 0xE8, 0xE8, 0xE8, 0xE8, 0xED, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xF7, 0x42, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xD6, 0x3C, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00,
+ 0x00, 0x03, 0xD1, 0xFF, 0xFF, 0xFB, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x00, 0x00, 0x00, 0x00, 0x3D, 0xFF, 0xFF, 0xFF, 0xC9, 0x02, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xFF, 0xFF, 0xFF, 0x6C, 0x00, 0x00, 0x00, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xF2, 0xFF, 0xFF, 0xF1, 0x1B, 0x00,
+ 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF,
+ 0xAE, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xD7,
+ 0xFF, 0xFF, 0xFF, 0x4E, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xE2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x9F, 0xD2, 0xEF, 0xFB,
+ 0xF6, 0xE5, 0xBE, 0x7A, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x4A, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xFF, 0xFF, 0xFF, 0xF6,
+ 0xC1, 0xAE, 0xC0, 0xF2, 0xFF, 0xFF, 0xFF, 0xF4, 0x20, 0x00, 0x00, 0x31, 0xFF, 0xFF, 0xFF, 0xBB,
+ 0x0D, 0x00, 0x00, 0x00, 0x0E, 0xA9, 0xFF, 0xFF, 0xFF, 0x8F, 0x00, 0x00, 0x5D, 0xFF, 0xFF, 0xFF,
+ 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xE6, 0xD3, 0xB0, 0x72, 0x00, 0x00, 0x51, 0xFF, 0xFF,
+ 0xFF, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xFE,
+ 0xFF, 0xFF, 0xFB, 0x8C, 0x2E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEB, 0xB2, 0x7A, 0x3C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x05, 0x98, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x90, 0x16, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x24, 0x84, 0xC9, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x2B,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x3F, 0x78, 0xBD, 0xFD, 0xFF, 0xFF, 0xFF,
+ 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0xDF, 0xFF,
+ 0xFF, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65,
+ 0xFF, 0xFF, 0xFF, 0x4D, 0x15, 0x90, 0xB7, 0xDD, 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x75, 0xFF, 0xFF, 0xFF, 0x3E, 0x03, 0xE2, 0xFF, 0xFF, 0xFF, 0x92, 0x1E, 0x00, 0x00, 0x00, 0x06,
+ 0x55, 0xF0, 0xFF, 0xFF, 0xF5, 0x0B, 0x00, 0x4E, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0xD6, 0xDE,
+ 0xF6, 0xFF, 0xFF, 0xFF, 0xFF, 0x7B, 0x00, 0x00, 0x00, 0x56, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x6A, 0xB0, 0xDB, 0xF4,
+ 0xFD, 0xF6, 0xE5, 0xBD, 0x7E, 0x22, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xB4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0xA0, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xFE,
+ 0xFF, 0xFF, 0xFA, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0x8A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF,
+ 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF,
+ 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF,
+ 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70,
+ 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30,
+ 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF,
+ 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF,
+ 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF,
+ 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91,
+ 0xFF, 0xFF, 0xFF, 0x2F, 0x68, 0xFF, 0xFF, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xA1, 0xFF, 0xFF, 0xFF, 0x22, 0x51, 0xFF, 0xFF, 0xFF, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xD8, 0xFF, 0xFF, 0xFC, 0x0A, 0x19, 0xFF, 0xFF, 0xFF, 0xE9, 0x12, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x57, 0xFF, 0xFF, 0xFF, 0xC5, 0x00, 0x00, 0xB6, 0xFF, 0xFF, 0xFF, 0xD0, 0x42, 0x04, 0x00,
+ 0x15, 0x75, 0xF8, 0xFF, 0xFF, 0xFF, 0x55, 0x00, 0x00, 0x28, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
+ 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x36, 0xDE, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x60, 0xAF,
+ 0xE1, 0xF6, 0xFA, 0xEA, 0xC5, 0x87, 0x24, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xFF, 0xFF, 0xFF, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0xFF, 0xFF, 0xF2, 0x0A, 0x47, 0xFF, 0xFF,
+ 0xFF, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0xFF, 0xFF, 0xFF, 0x9F, 0x00, 0x03,
+ 0xE5, 0xFF, 0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x41,
+ 0x00, 0x00, 0x8B, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xFF, 0xFF,
+ 0xE2, 0x01, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xFF,
+ 0xFF, 0xFF, 0x86, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xFF, 0xFF, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x73, 0xFF, 0xFF, 0xFF, 0x29, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0x27, 0x00, 0x00,
+ 0x00, 0x00, 0xC4, 0xFF, 0xFF, 0xCB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xFB, 0xFF, 0xFF, 0x78,
+ 0x00, 0x00, 0x00, 0x17, 0xFD, 0xFF, 0xFF, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF,
+ 0xFF, 0xC9, 0x00, 0x00, 0x00, 0x66, 0xFF, 0xFF, 0xFB, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x55, 0xFF, 0xFF, 0xFE, 0x1C, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x07, 0xEF, 0xFF, 0xFF, 0x6C, 0x00, 0x0F, 0xF9, 0xFF, 0xFF, 0x55, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xFF, 0xFF, 0xBC, 0x00, 0x59, 0xFF, 0xFF, 0xEF, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0xFF, 0xFF, 0xFA, 0x0D, 0xA3, 0xFF, 0xFF, 0x9A,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDB, 0xFF, 0xFF, 0x50, 0xEA, 0xFF,
+ 0xFF, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFF, 0xFF, 0xC1,
+ 0xFF, 0xFF, 0xDE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xFE,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x63, 0xFF, 0xFF, 0xFF, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8,
+ 0xFF, 0xFF, 0xEE, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0xFF, 0xFF, 0x85, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x64, 0xFF, 0xFF, 0xFF, 0x62, 0x96, 0xFF, 0xFF, 0xFF, 0x29, 0x00, 0x00, 0x00,
+ 0x00, 0x21, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xFF, 0xFF,
+ 0x21, 0x54, 0xFF, 0xFF, 0xFF, 0x62, 0x00, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xFF, 0xDF, 0x00, 0x14, 0xFE, 0xFF, 0xFF, 0x9B, 0x00,
+ 0x00, 0x00, 0x00, 0x98, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0x3B, 0x00, 0x00, 0x00, 0x0C, 0xFC, 0xFF,
+ 0xFF, 0x9D, 0x00, 0x00, 0xD1, 0xFF, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x00, 0xD3, 0xFF, 0xFF, 0xB3,
+ 0xFF, 0xFF, 0x77, 0x00, 0x00, 0x00, 0x40, 0xFF, 0xFF, 0xFF, 0x5B, 0x00, 0x00, 0x90, 0xFF, 0xFF,
+ 0xFE, 0x0F, 0x00, 0x00, 0x0F, 0xFD, 0xFF, 0xEE, 0x60, 0xFF, 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x77,
+ 0xFF, 0xFF, 0xFF, 0x1A, 0x00, 0x00, 0x4E, 0xFF, 0xFF, 0xFF, 0x46, 0x00, 0x00, 0x49, 0xFF, 0xFF,
+ 0xB9, 0x2E, 0xFF, 0xFF, 0xEE, 0x01, 0x00, 0x00, 0xAE, 0xFF, 0xFF, 0xD8, 0x00, 0x00, 0x00, 0x0F,
+ 0xFC, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0x80, 0x04, 0xF6, 0xFF, 0xFF, 0x2D, 0x00,
+ 0x00, 0xE5, 0xFF, 0xFF, 0x96, 0x00, 0x00, 0x00, 0x00, 0xCB, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0xBD,
+ 0xFF, 0xFF, 0x45, 0x00, 0xC1, 0xFF, 0xFF, 0x69, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, 0x54, 0x00, 0x00,
+ 0x00, 0x00, 0x8A, 0xFF, 0xFF, 0xEF, 0x01, 0x03, 0xF4, 0xFF, 0xFD, 0x0D, 0x00, 0x86, 0xFF, 0xFF,
+ 0xA5, 0x00, 0x53, 0xFF, 0xFF, 0xFE, 0x14, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x2A,
+ 0x31, 0xFF, 0xFF, 0xCF, 0x00, 0x00, 0x4A, 0xFF, 0xFF, 0xDD, 0x00, 0x8B, 0xFF, 0xFF, 0xD1, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFA, 0xFF, 0xFF, 0x63, 0x66, 0xFF, 0xFF, 0x94, 0x00, 0x00, 0x10,
+ 0xFE, 0xFF, 0xFF, 0x0F, 0xC6, 0xFF, 0xFF, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC5, 0xFF,
+ 0xFF, 0x98, 0x9B, 0xFF, 0xFF, 0x59, 0x00, 0x00, 0x00, 0xD3, 0xFF, 0xFF, 0x44, 0xF9, 0xFF, 0xFF,
+ 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0xCD, 0xCF, 0xFF, 0xFF, 0x1D, 0x00,
+ 0x00, 0x00, 0x98, 0xFF, 0xFF, 0xA2, 0xFF, 0xFF, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x42, 0xFF, 0xFF, 0xFA, 0xFA, 0xFF, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xF5, 0xFF,
+ 0xFF, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xA7,
+ 0x00, 0x00, 0x00, 0x00, 0x21, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xFF, 0xFF, 0xFF, 0xFA, 0x0A, 0x00, 0x00, 0x00,
+ 0x00, 0x0C, 0xDF, 0xFF, 0xFF, 0xED, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA9, 0xFF, 0xFF,
+ 0xFF, 0x46, 0x00, 0x00, 0x42, 0xFE, 0xFF, 0xFF, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF,
+ 0xFF, 0xFF, 0xA2, 0x00, 0x00, 0x00, 0x00, 0x97, 0xFF, 0xFF, 0xFF, 0x43, 0x00, 0x00, 0x00, 0x0A,
+ 0xDE, 0xFF, 0xFF, 0xEB, 0x14, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xE0, 0xFF, 0xFF, 0xDA, 0x08, 0x00,
+ 0x00, 0x8A, 0xFF, 0xFF, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xFF, 0xFF, 0xFF,
+ 0x82, 0x00, 0x2E, 0xFB, 0xFF, 0xFF, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98,
+ 0xFF, 0xFF, 0xF9, 0x2A, 0xC7, 0xFF, 0xFF, 0xF5, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0D, 0xE0, 0xFF, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xD6, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xFF, 0xFF, 0xFF, 0x4D,
+ 0xDC, 0xFF, 0xFF, 0xEE, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFB, 0xFF, 0xFF,
+ 0xA3, 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xCE, 0xFF,
+ 0xFF, 0xED, 0x15, 0x00, 0x00, 0xA5, 0xFF, 0xFF, 0xFF, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B,
+ 0xFF, 0xFF, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x16, 0xEE, 0xFF, 0xFF, 0xDE, 0x0A, 0x00, 0x00, 0x00,
+ 0x29, 0xF8, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xFF, 0xFF, 0xFF, 0x89, 0x00,
+ 0x00, 0x02, 0xC6, 0xFF, 0xFF, 0xF8, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xC3, 0xFF, 0xFF,
+ 0xFB, 0x2E, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A,
+ 0xF9, 0xFF, 0xFF, 0xC8, 0x02, 0x46, 0xFF, 0xFF, 0xFF, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0xA1, 0x00, 0xB2, 0xFF, 0xFF, 0xFD, 0x2F, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0xCF, 0xFF, 0xFF, 0xF3, 0x19, 0x00, 0x24, 0xF9, 0xFF, 0xFF, 0xB5, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x89, 0xFF, 0xFF, 0xFF, 0x3E, 0x00,
+ 0x00, 0x00, 0x07, 0xDF, 0xFF, 0xFF, 0xDD, 0x07, 0x00, 0x00, 0x00, 0x0D, 0xE7, 0xFF, 0xFF, 0xC7,
+ 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xFF, 0xFF,
+ 0xFF, 0x50, 0x00, 0x0F, 0xED, 0xFF, 0xFF, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xCA,
+ 0xFF, 0xFF, 0xD6, 0x03, 0x88, 0xFF, 0xFF, 0xFB, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x38, 0xFE, 0xFF, 0xFF, 0x7E, 0xF6, 0xFF, 0xFF, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xA3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x1A, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0xFF, 0xFF, 0xFF, 0xD2, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x8C, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x8C, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x8C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x8C, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x8C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x8C,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF,
+ 0x8C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9C, 0x00, 0x00, 0x3F, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0xF0, 0xF9, 0xFF, 0xFF, 0xFF, 0x8C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x17, 0xE5, 0xFF, 0xFF, 0xDB, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+ 0xC0, 0xFF, 0xFF, 0xF5, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C,
+ 0xFF, 0xFF, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xFF,
+ 0xFF, 0xFF, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xF2, 0xFF,
+ 0xFF, 0xC9, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xD5, 0xFF, 0xFF,
+ 0xEB, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA9, 0xFF, 0xFF, 0xFD,
+ 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF, 0xFF, 0x7A,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xFB, 0xFF, 0xFF, 0xB2, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xE7, 0xFF, 0xFF, 0xDD, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xC3, 0xFF, 0xFF, 0xF6, 0x2F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x5F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x21, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x24, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x24, 0x00, 0x00, 0x00, 0x25, 0x93, 0xD4, 0xF4, 0xFA, 0xE6,
+ 0xAF, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x19, 0xF5, 0xFF, 0xFF, 0xE1, 0x6A, 0x63, 0xD3, 0xFF, 0xFF,
+ 0xFE, 0x32, 0x00, 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF, 0x4D, 0x00, 0x00, 0x25, 0xFF, 0xFF, 0xFF,
+ 0x8A, 0x00, 0x00, 0x00, 0x0E, 0x22, 0x2D, 0x39, 0x08, 0x00, 0x00, 0x00, 0xED, 0xFF, 0xFF, 0xAC,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x0C, 0xDE, 0xFF, 0xFF, 0xB4, 0x00,
+ 0x00, 0x00, 0x00, 0x26, 0x95, 0xD5, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB4, 0x00, 0x00,
+ 0x00, 0x49, 0xF8, 0xFF, 0xFF, 0xFF, 0xFD, 0xF0, 0xEB, 0xFC, 0xFF, 0xFF, 0xB4, 0x00, 0x00, 0x03,
+ 0xE5, 0xFF, 0xFF, 0xE9, 0x46, 0x04, 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xB4, 0x00, 0x00, 0x2C, 0xFF,
+ 0xFF, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x12, 0xFA, 0xFF, 0xFF, 0xB4, 0x00, 0x00, 0x34, 0xFF, 0xFF,
+ 0xFF, 0xA0, 0x00, 0x00, 0x03, 0xA2, 0xFF, 0xFF, 0xFF, 0xC4, 0x00, 0x00, 0x0D, 0xF8, 0xFF, 0xFF,
+ 0xFC, 0xA0, 0x88, 0xD1, 0xFF, 0xB6, 0xFF, 0xFF, 0xF9, 0x63, 0x34, 0x00, 0x84, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x91, 0x1D, 0xF8, 0xFF, 0xFF, 0xFF, 0xA0, 0x00, 0x01, 0x66, 0xCE, 0xF7, 0xF1,
+ 0xC4, 0x58, 0x00, 0x00, 0x50, 0xC9, 0xE5, 0xD2, 0x68, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x62, 0x0A, 0x80, 0xD6, 0xF6, 0xED, 0xB0, 0x30, 0x00, 0x00, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0xCB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x40, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0xD6,
+ 0xF7, 0x9A, 0x80, 0xC5, 0xFF, 0xFF, 0xFF, 0xDF, 0x06, 0x30, 0xFF, 0xFF, 0xFF, 0xFD, 0x3C, 0x00,
+ 0x00, 0x01, 0xAE, 0xFF, 0xFF, 0xFF, 0x50, 0x30, 0xFF, 0xFF, 0xFF, 0xB3, 0x00, 0x00, 0x00, 0x00,
+ 0x36, 0xFF, 0xFF, 0xFF, 0x92, 0x30, 0xFF, 0xFF, 0xFF, 0x74, 0x00, 0x00, 0x00, 0x00, 0x05, 0xFC,
+ 0xFF, 0xFF, 0xB9, 0x30, 0xFF, 0xFF, 0xFF, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFF, 0xFF,
+ 0xC9, 0x30, 0xFF, 0xFF, 0xFF, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0xFF, 0xC8, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x77, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFD, 0xFF, 0xFF, 0xB6, 0x30, 0xFF, 0xFF,
+ 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, 0x8C, 0x30, 0xFF, 0xFF, 0xFF, 0xFD,
+ 0x34, 0x00, 0x00, 0x01, 0xB4, 0xFF, 0xFF, 0xFF, 0x45, 0x32, 0xFF, 0xFF, 0xFF, 0xEA, 0xF2, 0x90,
+ 0x78, 0xC3, 0xFF, 0xFF, 0xFF, 0xD2, 0x02, 0x36, 0xFF, 0xFF, 0xFF, 0x64, 0xDC, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF0, 0x30, 0x00, 0x41, 0xFF, 0xFF, 0xFF, 0x3F, 0x13, 0x90, 0xDF, 0xF9, 0xE7, 0xA1,
+ 0x21, 0x00, 0x00, 0x00, 0x00, 0x03, 0x5F, 0xB7, 0xE9, 0xFA, 0xEE, 0xBE, 0x60, 0x02, 0x00, 0x00,
+ 0x00, 0x13, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBE, 0x0D, 0x00, 0x01, 0xBA, 0xFF,
+ 0xFF, 0xFF, 0xAF, 0x7D, 0xB2, 0xFF, 0xFF, 0xFF, 0xAB, 0x00, 0x4B, 0xFF, 0xFF, 0xFF, 0x8A, 0x00,
+ 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x32, 0xA6, 0xFF, 0xFF, 0xFD, 0x15, 0x00, 0x00, 0x00, 0x21,
+ 0xF9, 0xEC, 0xDF, 0x67, 0xDC, 0xFF, 0xFF, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xF2, 0xFF, 0xFF, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF4, 0xFF,
+ 0xFF, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0xE0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAC, 0xFF, 0xFF, 0xFE, 0x19, 0x00, 0x00, 0x00,
+ 0x09, 0xF3, 0xEE, 0xE2, 0x80, 0x54, 0xFF, 0xFF, 0xFF, 0x91, 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF,
+ 0xFF, 0x51, 0x02, 0xC4, 0xFF, 0xFF, 0xFF, 0xB0, 0x78, 0xA6, 0xFE, 0xFF, 0xFF, 0xC4, 0x01, 0x00,
+ 0x17, 0xCC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCA, 0x14, 0x00, 0x00, 0x00, 0x05, 0x65,
+ 0xBD, 0xEB, 0xFB, 0xE9, 0xBA, 0x62, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF,
+ 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x00,
+ 0x00, 0x2E, 0xAB, 0xE9, 0xF8, 0xDB, 0x87, 0x0C, 0x7E, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x45, 0xF9,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x84, 0xFF, 0xFF, 0xFF, 0x10, 0x0D, 0xE7, 0xFF, 0xFF, 0xFF,
+ 0xAD, 0x73, 0x96, 0xF9, 0xED, 0xFF, 0xFF, 0xFF, 0x10, 0x67, 0xFF, 0xFF, 0xFF, 0x8C, 0x00, 0x00,
+ 0x00, 0x50, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0xAE, 0xFF, 0xFF, 0xFE, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0xD7, 0xFF, 0xFF, 0xFF, 0x10, 0xD7, 0xFF, 0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0xFF,
+ 0xFF, 0xFF, 0x10, 0xE9, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF,
+ 0x10, 0xE9, 0xFF, 0xFF, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B, 0xFF, 0xFF, 0xFF, 0x10, 0xD9,
+ 0xFF, 0xFF, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0xFF, 0xFF, 0xFF, 0x10, 0xB0, 0xFF, 0xFF,
+ 0xFF, 0x1A, 0x00, 0x00, 0x00, 0x00, 0xD7, 0xFF, 0xFF, 0xFF, 0x10, 0x6C, 0xFF, 0xFF, 0xFF, 0x92,
+ 0x00, 0x00, 0x00, 0x5E, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x11, 0xED, 0xFF, 0xFF, 0xFF, 0xB1, 0x76,
+ 0x9E, 0xFC, 0xE5, 0xFF, 0xFF, 0xFF, 0x12, 0x00, 0x53, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6,
+ 0x74, 0xFF, 0xFF, 0xFF, 0x19, 0x00, 0x00, 0x3D, 0xBA, 0xF1, 0xF4, 0xD4, 0x7C, 0x08, 0x58, 0xFF,
+ 0xFF, 0xFF, 0x25, 0x00, 0x00, 0x07, 0x6A, 0xBE, 0xEC, 0xFA, 0xE9, 0xB5, 0x52, 0x00, 0x00, 0x00,
+ 0x00, 0x1C, 0xD2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA2, 0x03, 0x00, 0x04, 0xCB, 0xFF,
+ 0xFF, 0xE8, 0x6E, 0x4A, 0x7B, 0xF2, 0xFF, 0xFF, 0x76, 0x00, 0x59, 0xFF, 0xFF, 0xFE, 0x32, 0x00,
+ 0x00, 0x00, 0x55, 0xFF, 0xFF, 0xED, 0x08, 0xAF, 0xFF, 0xFF, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x04,
+ 0xF6, 0xFF, 0xFF, 0x46, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x76, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x89, 0xF4, 0xFF,
+ 0xFF, 0xD5, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x22, 0xDF, 0xFF, 0xFF, 0xD9, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAD, 0xFF, 0xFF, 0xFC, 0x12, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xFF, 0xFF, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x42, 0xF4, 0xDD,
+ 0xC6, 0x21, 0x05, 0xCF, 0xFF, 0xFF, 0xFF, 0xA8, 0x6B, 0x89, 0xF0, 0xFF, 0xFF, 0xB1, 0x00, 0x00,
+ 0x20, 0xD8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x70,
+ 0xC3, 0xEE, 0xF9, 0xE2, 0xBA, 0x5A, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xB0, 0xEB, 0xFA,
+ 0xE7, 0x95, 0x00, 0x00, 0x11, 0xE6, 0xFF, 0xFF, 0xFF, 0xFF, 0xB4, 0x00, 0x00, 0x60, 0xFF, 0xFF,
+ 0xFF, 0xB7, 0x6B, 0x53, 0x00, 0x00, 0x85, 0xFF, 0xFF, 0xFF, 0x11, 0x00, 0x00, 0x00, 0x00, 0x8C,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB4, 0x90,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB4, 0x3A, 0x68, 0xBB, 0xFF, 0xFF, 0xFF, 0x68, 0x68,
+ 0x49, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF,
+ 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x8C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xFF,
+ 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xAC, 0xE9, 0xF8, 0xDC, 0x8B, 0x0E, 0x4C, 0xFF, 0xFF,
+ 0xFF, 0x1E, 0x00, 0x48, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0x6A, 0xFF, 0xFF, 0xFF, 0x15,
+ 0x0F, 0xE9, 0xFF, 0xFF, 0xFF, 0xB9, 0x7C, 0x9F, 0xFC, 0xE5, 0xFF, 0xFF, 0xFF, 0x11, 0x69, 0xFF,
+ 0xFF, 0xFF, 0x99, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0xAE, 0xFF, 0xFF, 0xFF,
+ 0x1D, 0x00, 0x00, 0x00, 0x01, 0xDD, 0xFF, 0xFF, 0xFF, 0x10, 0xD8, 0xFF, 0xFF, 0xE3, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x99, 0xFF, 0xFF, 0xFF, 0x10, 0xE9, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x10, 0xEA, 0xFF, 0xFF, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7B,
+ 0xFF, 0xFF, 0xFF, 0x10, 0xDA, 0xFF, 0xFF, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0xFF, 0xFF,
+ 0xFF, 0x10, 0xB3, 0xFF, 0xFF, 0xFF, 0x1B, 0x00, 0x00, 0x00, 0x00, 0xD6, 0xFF, 0xFF, 0xFF, 0x10,
+ 0x70, 0xFF, 0xFF, 0xFF, 0x97, 0x00, 0x00, 0x00, 0x5E, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x15, 0xF1,
+ 0xFF, 0xFF, 0xFF, 0xBA, 0x7F, 0xA4, 0xFC, 0xE8, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x59, 0xFE, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x7E, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x40, 0xBB, 0xF1, 0xF5,
+ 0xD5, 0x7E, 0x08, 0x7E, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00, 0x00, 0x0A, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x9C, 0xFF, 0xFF, 0xEF, 0x00, 0x2F, 0xCD, 0xEC, 0xFF, 0x7D, 0x00, 0x00, 0x00, 0x12, 0xEE,
+ 0xFF, 0xFF, 0xAE, 0x00, 0x07, 0xE1, 0xFF, 0xFF, 0xFC, 0x97, 0x61, 0x7A, 0xDF, 0xFF, 0xFF, 0xFD,
+ 0x38, 0x00, 0x00, 0x35, 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x62, 0x00, 0x00,
+ 0x00, 0x00, 0x12, 0x71, 0xC5, 0xE4, 0xFA, 0xF2, 0xD0, 0x8F, 0x26, 0x00, 0x00, 0x00, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF,
+ 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x60, 0x02, 0x6E, 0xD1, 0xF5, 0xEE, 0xB3, 0x32,
+ 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x58, 0xA9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x36, 0x00,
+ 0x30, 0xFF, 0xFF, 0xFF, 0xAD, 0xFF, 0xD0, 0xBB, 0xF2, 0xFF, 0xFF, 0xFF, 0xC4, 0x00, 0x30, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x74, 0x00, 0x00, 0x22, 0xEA, 0xFF, 0xFF, 0xFF, 0x13, 0x30, 0xFF, 0xFF, 0xFF,
+ 0xD5, 0x01, 0x00, 0x00, 0x00, 0x89, 0xFF, 0xFF, 0xFF, 0x37, 0x30, 0xFF, 0xFF, 0xFF, 0x87, 0x00,
+ 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xFF, 0x43, 0x30, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0x00, 0x00,
+ 0x00, 0x49, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48,
+ 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF,
+ 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44,
+ 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF,
+ 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00,
+ 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x1E, 0xA0, 0xA0, 0xA0, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64,
+ 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00,
+ 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x1B, 0xA0, 0xA0, 0xA0, 0x3E, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF,
+ 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00,
+ 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64,
+ 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00,
+ 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x52, 0xFF, 0xFF, 0xFF, 0x59, 0x3F, 0x9D, 0xE9, 0xFF, 0xFF, 0xFF,
+ 0x34, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xC6, 0x01, 0x5E, 0xF6, 0xFD, 0xE3, 0x9F, 0x15, 0x00, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x2F, 0xF4, 0xFF,
+ 0xFF, 0xAC, 0x02, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x18, 0xE2, 0xFF, 0xFF, 0xC3, 0x08,
+ 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x09, 0xCA, 0xFF, 0xFF, 0xD7, 0x12, 0x00, 0x00, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x01, 0xAA, 0xFF, 0xFF, 0xE6, 0x1F, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x84, 0xFF, 0xFF, 0xF2, 0x31, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0xBB,
+ 0xFF, 0xFF, 0xFF, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xE2, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xE5, 0xFC, 0xFF, 0xFF,
+ 0x89, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0xB7, 0x11, 0x97, 0xFF, 0xFF, 0xFA, 0x2A,
+ 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x14, 0xEE, 0xFF, 0xFF, 0xBF, 0x00, 0x00,
+ 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x6F, 0xFF, 0xFF, 0xFF, 0x5B, 0x00, 0x00, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x04, 0xD6, 0xFF, 0xFF, 0xE7, 0x0F, 0x00, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x00, 0x00, 0x00, 0x46, 0xFF, 0xFF, 0xFF, 0x92, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0xB2, 0xFF, 0xFF, 0xFC, 0x31, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF,
+ 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64,
+ 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x64, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x42, 0xFF, 0xFF, 0xFF, 0x27, 0x0B, 0x89, 0xE1,
+ 0xF6, 0xD5, 0x6D, 0x00, 0x00, 0x12, 0x93, 0xE2, 0xF8, 0xD9, 0x6F, 0x00, 0x00, 0x36, 0xFF, 0xFF,
+ 0xFF, 0x3B, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7A, 0x0A, 0xCE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x7D, 0x00, 0x31, 0xFF, 0xFF, 0xFF, 0xA9, 0xFC, 0xBC, 0xDA, 0xFF, 0xFF, 0xFF, 0xEF, 0x85, 0xFD,
+ 0xBE, 0xD6, 0xFF, 0xFF, 0xFF, 0xF8, 0x11, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0x4D, 0x00, 0x03, 0xBA,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x5A, 0x00, 0x01, 0xAD, 0xFF, 0xFF, 0xFF, 0x52, 0x30, 0xFF, 0xFF, 0xFF,
+ 0xC6, 0x00, 0x00, 0x00, 0x4F, 0xFF, 0xFF, 0xFF, 0xD1, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF,
+ 0x74, 0x30, 0xFF, 0xFF, 0xFF, 0x82, 0x00, 0x00, 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0x91, 0x00, 0x00,
+ 0x00, 0x19, 0xFF, 0xFF, 0xFF, 0x7F, 0x30, 0xFF, 0xFF, 0xFF, 0x69, 0x00, 0x00, 0x00, 0x19, 0xFF,
+ 0xFF, 0xFF, 0x73, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0x80, 0x30, 0xFF, 0xFF, 0xFF, 0x64,
+ 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0x80,
+ 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00,
+ 0x08, 0xFF, 0xFF, 0xFF, 0x80, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF,
+ 0xFF, 0x70, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0x80, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00,
+ 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0x80, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x08,
+ 0xFF, 0xFF, 0xFF, 0x80, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0xFF,
+ 0x70, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0x80, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00,
+ 0x00, 0x18, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xFF, 0xFF, 0x80, 0x42, 0xFF,
+ 0xFF, 0xFF, 0x27, 0x03, 0x6F, 0xD2, 0xF5, 0xEE, 0xB3, 0x32, 0x00, 0x00, 0x36, 0xFF, 0xFF, 0xFF,
+ 0x39, 0xAA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0x36, 0x00, 0x31, 0xFF, 0xFF, 0xFF, 0xA3, 0xFF,
+ 0xD0, 0xBB, 0xF2, 0xFF, 0xFF, 0xFF, 0xC4, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0x74, 0x00, 0x00,
+ 0x22, 0xEA, 0xFF, 0xFF, 0xFF, 0x13, 0x30, 0xFF, 0xFF, 0xFF, 0xD5, 0x01, 0x00, 0x00, 0x00, 0x89,
+ 0xFF, 0xFF, 0xFF, 0x37, 0x30, 0xFF, 0xFF, 0xFF, 0x87, 0x00, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xFF,
+ 0xFF, 0x43, 0x30, 0xFF, 0xFF, 0xFF, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x49, 0xFF, 0xFF, 0xFF, 0x44,
+ 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF,
+ 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00,
+ 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00,
+ 0x00, 0x48, 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48,
+ 0xFF, 0xFF, 0xFF, 0x44, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xFF,
+ 0xFF, 0x44, 0x00, 0x00, 0x00, 0x44, 0xA6, 0xDF, 0xF6, 0xF5, 0xDE, 0xA5, 0x43, 0x00, 0x00, 0x00,
+ 0x00, 0x09, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA6, 0x07, 0x00, 0x00, 0xA5,
+ 0xFF, 0xFF, 0xFF, 0xD0, 0x82, 0x82, 0xD1, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0x3F, 0xFF, 0xFF, 0xFF,
+ 0xAE, 0x02, 0x00, 0x00, 0x03, 0xB7, 0xFF, 0xFF, 0xFF, 0x2E, 0xA2, 0xFF, 0xFF, 0xFF, 0x24, 0x00,
+ 0x00, 0x00, 0x00, 0x33, 0xFF, 0xFF, 0xFF, 0x87, 0xDB, 0xFF, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0xF6, 0xFF, 0xFF, 0xBD, 0xF3, 0xFF, 0xFF, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xE1, 0xFF, 0xFF, 0xD2, 0xF2, 0xFF, 0xFF, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xFF,
+ 0xFF, 0xD1, 0xDA, 0xFF, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF8, 0xFF, 0xFF, 0xB9,
+ 0xA0, 0xFF, 0xFF, 0xFF, 0x24, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0xFF,
+ 0xFF, 0xFF, 0xAD, 0x01, 0x00, 0x00, 0x07, 0xC3, 0xFF, 0xFF, 0xFB, 0x20, 0x00, 0xA5, 0xFF, 0xFF,
+ 0xFF, 0xC7, 0x7A, 0x83, 0xD8, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x09, 0xA8, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x47, 0xA8, 0xE1, 0xF8, 0xF4,
+ 0xDA, 0x9B, 0x32, 0x00, 0x00, 0x00, 0x42, 0xFF, 0xFF, 0xFF, 0x3B, 0x0E, 0x87, 0xD9, 0xF6, 0xEC,
+ 0xAE, 0x2C, 0x00, 0x00, 0x36, 0xFF, 0xFF, 0xFF, 0x5A, 0xD6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6,
+ 0x3A, 0x00, 0x32, 0xFF, 0xFF, 0xFF, 0xE0, 0xF7, 0x9B, 0x80, 0xC9, 0xFF, 0xFF, 0xFF, 0xDA, 0x05,
+ 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0x41, 0x00, 0x00, 0x02, 0xB5, 0xFF, 0xFF, 0xFF, 0x4D, 0x30, 0xFF,
+ 0xFF, 0xFF, 0xC2, 0x00, 0x00, 0x00, 0x00, 0x3B, 0xFF, 0xFF, 0xFF, 0x91, 0x30, 0xFF, 0xFF, 0xFF,
+ 0x7B, 0x00, 0x00, 0x00, 0x00, 0x06, 0xFC, 0xFF, 0xFF, 0xB8, 0x30, 0xFF, 0xFF, 0xFF, 0x61, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xED, 0xFF, 0xFF, 0xC9, 0x30, 0xFF, 0xFF, 0xFF, 0x5E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xEE, 0xFF, 0xFF, 0xC8, 0x30, 0xFF, 0xFF, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x00, 0x07,
+ 0xFD, 0xFF, 0xFF, 0xB6, 0x30, 0xFF, 0xFF, 0xFF, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xFF, 0xFF,
+ 0xFF, 0x8C, 0x30, 0xFF, 0xFF, 0xFF, 0xFE, 0x35, 0x00, 0x00, 0x01, 0xB6, 0xFF, 0xFF, 0xFF, 0x45,
+ 0x30, 0xFF, 0xFF, 0xFF, 0xE9, 0xF3, 0x8F, 0x78, 0xC5, 0xFF, 0xFF, 0xFF, 0xD2, 0x02, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x72, 0xDD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x2F, 0x00, 0x30, 0xFF, 0xFF, 0xFF,
+ 0x63, 0x13, 0x90, 0xDF, 0xF9, 0xE6, 0xA0, 0x20, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x2A, 0xA8, 0xE8, 0xF8, 0xDC, 0x8B, 0x0E, 0x59, 0xFF, 0xFF, 0xFF, 0x1E, 0x00, 0x40,
+ 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0x77, 0xFF, 0xFF, 0xFF, 0x15, 0x0B, 0xE4, 0xFF, 0xFF,
+ 0xFF, 0xB9, 0x7C, 0x9D, 0xFA, 0xEC, 0xFF, 0xFF, 0xFF, 0x11, 0x64, 0xFF, 0xFF, 0xFF, 0x99, 0x00,
+ 0x00, 0x00, 0x54, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0xAC, 0xFF, 0xFF, 0xFF, 0x1C, 0x00, 0x00, 0x00,
+ 0x00, 0xD9, 0xFF, 0xFF, 0xFF, 0x10, 0xD7, 0xFF, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98,
+ 0xFF, 0xFF, 0xFF, 0x10, 0xE9, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
+ 0xFF, 0x10, 0xE9, 0xFF, 0xFF, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xFF, 0xFF, 0xFF, 0x10,
+ 0xD9, 0xFF, 0xFF, 0xE2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xFF, 0xFF, 0x10, 0xB1, 0xFF,
+ 0xFF, 0xFF, 0x1A, 0x00, 0x00, 0x00, 0x01, 0xDF, 0xFF, 0xFF, 0xFF, 0x10, 0x6D, 0xFF, 0xFF, 0xFF,
+ 0x93, 0x00, 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x12, 0xEE, 0xFF, 0xFF, 0xFF, 0xB2,
+ 0x77, 0x9D, 0xFB, 0xE5, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x55, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xCD, 0x7A, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x3F, 0xBB, 0xF1, 0xF4, 0xD4, 0x7F, 0x0A, 0x77,
+ 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF,
+ 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0x10, 0x43, 0xFF, 0xFF, 0xFF, 0x27, 0x30,
+ 0xCA, 0xF9, 0xA7, 0x37, 0xFF, 0xFF, 0xFF, 0x40, 0xE2, 0xFF, 0xFF, 0xB8, 0x31, 0xFF, 0xFF, 0xFF,
+ 0xA6, 0xFF, 0xFF, 0xFF, 0xB8, 0x30, 0xFF, 0xFF, 0xFF, 0xFB, 0xA9, 0x1A, 0x07, 0x17, 0x30, 0xFF,
+ 0xFF, 0xFF, 0xE1, 0x06, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x91, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0xFF, 0xFF, 0xFF, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00,
+ 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF,
+ 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x30,
+ 0xFF, 0xFF, 0xFF, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x9F, 0xD9, 0xF5, 0xF7,
+ 0xDD, 0xB7, 0x56, 0x04, 0x00, 0x00, 0x00, 0x00, 0x7B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xC9, 0x10, 0x00, 0x00, 0x32, 0xFE, 0xFF, 0xFF, 0xAF, 0x6F, 0x66, 0x8A, 0xEF, 0xFF, 0xFF,
+ 0xA2, 0x00, 0x00, 0x7B, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x4A, 0xF1, 0xD6, 0xB4, 0x02,
+ 0x00, 0x7E, 0xFF, 0xFF, 0xF2, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xA5, 0x6D, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x4D, 0xB6, 0xF7,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x39, 0x6E,
+ 0xAE, 0xF9, 0xFF, 0xFF, 0xFF, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29,
+ 0xFB, 0xFF, 0xFF, 0x5A, 0x01, 0x98, 0xC6, 0xEB, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x08, 0xF4, 0xFF,
+ 0xFF, 0x4D, 0x00, 0x95, 0xFF, 0xFF, 0xF3, 0x91, 0x68, 0x61, 0x78, 0xCD, 0xFF, 0xFF, 0xEB, 0x0C,
+ 0x00, 0x0F, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x3F, 0x00, 0x00, 0x00,
+ 0x06, 0x66, 0xB6, 0xE3, 0xF9, 0xFC, 0xEE, 0xC6, 0x7F, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4,
+ 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x94, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0xB0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30,
+ 0xB0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x47, 0x68, 0xF6, 0xFF, 0xFF, 0xC2, 0x68,
+ 0x68, 0x13, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF,
+ 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xF0, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x9A, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xEC, 0xFF, 0xFF, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xFF, 0xFF, 0xFF,
+ 0xA2, 0x81, 0x35, 0x00, 0x00, 0x78, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x00, 0x00, 0x05, 0x82,
+ 0xDF, 0xFA, 0xEB, 0xC3, 0x35, 0x64, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF,
+ 0xFF, 0xFF, 0x10, 0x64, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
+ 0x10, 0x64, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x64,
+ 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x64, 0xFF, 0xFF,
+ 0xFF, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x64, 0xFF, 0xFF, 0xFF, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x64, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0x10, 0x64, 0xFF, 0xFF, 0xFF, 0x31, 0x00, 0x00, 0x00, 0x00,
+ 0x86, 0xFF, 0xFF, 0xFF, 0x10, 0x63, 0xFF, 0xFF, 0xFF, 0x43, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xFF,
+ 0xFF, 0xFF, 0x10, 0x59, 0xFF, 0xFF, 0xFF, 0x6F, 0x00, 0x00, 0x00, 0x0A, 0xEB, 0xFF, 0xFF, 0xFF,
+ 0x10, 0x35, 0xFF, 0xFF, 0xFF, 0xDC, 0x16, 0x00, 0x01, 0x91, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x03,
+ 0xE3, 0xFF, 0xFF, 0xFF, 0xED, 0xB8, 0xD8, 0xFF, 0xA3, 0xFF, 0xFF, 0xFF, 0x12, 0x00, 0x53, 0xFD,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8E, 0x56, 0xFF, 0xFF, 0xFF, 0x17, 0x00, 0x00, 0x45, 0xBF, 0xF3,
+ 0xF4, 0xCB, 0x62, 0x00, 0x45, 0xFF, 0xFF, 0xFF, 0x24, 0xB8, 0xFF, 0xFF, 0xF9, 0x0D, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x8E, 0xFF, 0xFF, 0xFF, 0x29, 0x5E, 0xFF, 0xFF, 0xFF, 0x54, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xCC, 0x00, 0x0D, 0xF6, 0xFF, 0xFF, 0xA2, 0x00, 0x00, 0x00, 0x00,
+ 0x31, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x00, 0xAA, 0xFF, 0xFF, 0xEC, 0x03, 0x00, 0x00, 0x00, 0x82,
+ 0xFF, 0xFF, 0xFC, 0x17, 0x00, 0x00, 0x51, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x00, 0x00, 0xD4, 0xFF,
+ 0xFF, 0xB7, 0x00, 0x00, 0x00, 0x07, 0xEF, 0xFF, 0xFF, 0x8B, 0x00, 0x00, 0x25, 0xFF, 0xFF, 0xFF,
+ 0x5A, 0x00, 0x00, 0x00, 0x00, 0x9D, 0xFF, 0xFF, 0xD9, 0x00, 0x00, 0x76, 0xFF, 0xFF, 0xF3, 0x0A,
+ 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x26, 0x00, 0xC6, 0xFF, 0xFF, 0xA1, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0xE6, 0xFF, 0xFF, 0x72, 0x18, 0xFE, 0xFF, 0xFF, 0x44, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xBA, 0x62, 0xFF, 0xFF, 0xE5, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x36, 0xFF, 0xFF, 0xF7, 0xB2, 0xFF, 0xFF, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xDC, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xD2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x29, 0xFF, 0xFF, 0xFF, 0xFF, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF0, 0xFF, 0xFF, 0x62,
+ 0x00, 0x00, 0x00, 0x02, 0xEF, 0xFF, 0xFF, 0xFF, 0x24, 0x00, 0x00, 0x00, 0x24, 0xFF, 0xFF, 0xFF,
+ 0x26, 0x00, 0xB2, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x34, 0xFF, 0xFF, 0xFF, 0xFF, 0x65, 0x00,
+ 0x00, 0x00, 0x5C, 0xFF, 0xFF, 0xE4, 0x00, 0x00, 0x71, 0xFF, 0xFF, 0xCD, 0x00, 0x00, 0x00, 0x77,
+ 0xFF, 0xFF, 0xF4, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xA2, 0x00, 0x00, 0x30, 0xFF,
+ 0xFF, 0xFA, 0x08, 0x00, 0x00, 0xB9, 0xFF, 0xE7, 0xB0, 0xFF, 0xE7, 0x00, 0x00, 0x00, 0xCB, 0xFF,
+ 0xFF, 0x60, 0x00, 0x00, 0x01, 0xED, 0xFF, 0xFF, 0x38, 0x00, 0x06, 0xF5, 0xFF, 0xAE, 0x7C, 0xFF,
+ 0xFF, 0x29, 0x00, 0x08, 0xFA, 0xFF, 0xFF, 0x1E, 0x00, 0x00, 0x00, 0xAE, 0xFF, 0xFF, 0x6E, 0x00,
+ 0x3F, 0xFF, 0xFF, 0x72, 0x42, 0xFF, 0xFF, 0x6A, 0x00, 0x3B, 0xFF, 0xFF, 0xDC, 0x00, 0x00, 0x00,
+ 0x00, 0x6D, 0xFF, 0xFF, 0xA3, 0x00, 0x81, 0xFF, 0xFF, 0x31, 0x09, 0xF8, 0xFF, 0xAB, 0x00, 0x72,
+ 0xFF, 0xFF, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xD9, 0x00, 0xC4, 0xFF, 0xED, 0x02,
+ 0x00, 0xC0, 0xFF, 0xEB, 0x01, 0xAA, 0xFF, 0xFF, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE9, 0xFF,
+ 0xFE, 0x19, 0xFA, 0xFF, 0xAC, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0x2C, 0xE2, 0xFF, 0xFF, 0x17, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xA9, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x6A, 0x00, 0x00, 0x3E, 0xFF, 0xFF,
+ 0x7C, 0xFF, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF,
+ 0x28, 0x00, 0x00, 0x06, 0xF6, 0xFF, 0xE1, 0xFF, 0xFF, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x27, 0xFF, 0xFF, 0xFF, 0xFF, 0xE5, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0x50,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xFF, 0xFF, 0xFF, 0xA3, 0x00, 0x00, 0x00, 0x00,
+ 0x7A, 0xFF, 0xFF, 0xFF, 0xFD, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xFF, 0xFF,
+ 0xFF, 0x61, 0x00, 0x00, 0x00, 0x00, 0x39, 0xFF, 0xFF, 0xFF, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x3D,
+ 0xFD, 0xFF, 0xFF, 0x95, 0x00, 0x00, 0x00, 0x00, 0x34, 0xFC, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x85,
+ 0xFF, 0xFF, 0xFE, 0x3B, 0x00, 0x00, 0x04, 0xD0, 0xFF, 0xFF, 0xDD, 0x0D, 0x00, 0x00, 0x05, 0xCB,
+ 0xFF, 0xFF, 0xD7, 0x07, 0x00, 0x79, 0xFF, 0xFF, 0xFC, 0x39, 0x00, 0x00, 0x00, 0x00, 0x26, 0xF4,
+ 0xFF, 0xFF, 0x82, 0x24, 0xF7, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xFF,
+ 0xFF, 0xFA, 0xCE, 0xFF, 0xFF, 0xC6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB1, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xF2, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xF1, 0xFF,
+ 0xFF, 0xFF, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xBB, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xEA, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB2, 0xFF, 0xFF, 0xD1, 0x74, 0xFF, 0xFF,
+ 0xF7, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xFF, 0xFF, 0xFD, 0x35, 0x03, 0xCC, 0xFF, 0xFF,
+ 0xCD, 0x05, 0x00, 0x00, 0x00, 0x23, 0xF3, 0xFF, 0xFF, 0x8F, 0x00, 0x00, 0x31, 0xFC, 0xFF, 0xFF,
+ 0x85, 0x00, 0x00, 0x03, 0xC5, 0xFF, 0xFF, 0xE0, 0x0B, 0x00, 0x00, 0x00, 0x8D, 0xFF, 0xFF, 0xFC,
+ 0x3A, 0x00, 0x7B, 0xFF, 0xFF, 0xFF, 0x49, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xE0, 0xFF, 0xFF, 0xDD,
+ 0x0C, 0x9B, 0xFF, 0xFF, 0xFF, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0x25,
+ 0x37, 0xFF, 0xFF, 0xFF, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0xFF, 0xFF, 0xC7, 0x00, 0x00,
+ 0xD3, 0xFF, 0xFF, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0x69, 0x00, 0x00, 0x70,
+ 0xFF, 0xFF, 0xFE, 0x1C, 0x00, 0x00, 0x00, 0x76, 0xFF, 0xFF, 0xF9, 0x12, 0x00, 0x00, 0x13, 0xF9,
+ 0xFF, 0xFF, 0x6E, 0x00, 0x00, 0x00, 0xC4, 0xFF, 0xFF, 0xAE, 0x00, 0x00, 0x00, 0x00, 0xA9, 0xFF,
+ 0xFF, 0xC2, 0x00, 0x00, 0x14, 0xFD, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x46, 0xFF, 0xFF,
+ 0xFD, 0x17, 0x00, 0x5D, 0xFF, 0xFF, 0xEC, 0x05, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE0, 0xFF, 0xFF,
+ 0x67, 0x00, 0xA5, 0xFF, 0xFF, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xB5,
+ 0x00, 0xE8, 0xFF, 0xFF, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xFD, 0xFF, 0xF5, 0x35,
+ 0xFF, 0xFF, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xFF, 0xB3, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
+ 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xEB, 0xFF, 0xFF, 0xFF, 0xC0, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0xFF, 0xFF, 0xFF, 0x63, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9D, 0xFF, 0xFF, 0xF6, 0x0E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xFD, 0xFF, 0xFF, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x73, 0xA1, 0xB9, 0xFE, 0xFF, 0xFF, 0xEA, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xA8, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x92, 0xF4, 0xFB, 0xE2, 0xA7, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x00, 0x40, 0x94, 0x94, 0x94, 0x94, 0x94, 0xDA, 0xFF, 0xFF, 0xFF,
+ 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xF9, 0xFF, 0xFF, 0xBC, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0B, 0xD9, 0xFF, 0xFF, 0xEA, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9F, 0xFF,
+ 0xFF, 0xFF, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xFF, 0xFF, 0xFF, 0x91, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x21, 0xF1, 0xFF, 0xFF, 0xCF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+ 0xC7, 0xFF, 0xFF, 0xF5, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0xFF, 0xFF, 0xFF, 0x63,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0xFD, 0xFF, 0xFF, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xDF, 0xFF, 0xFF, 0xFF, 0xAB, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x09, 0xF4, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,
+ 0xDE, 0xFF, 0xFF, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0E, 0xCF, 0xFF, 0xFF, 0x98, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x06, 0xBD, 0xFF, 0xF7, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0xFF, 0xE0, 0x32, 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, 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, 0x0C, 0xF5,
+ 0xFF, 0xFF, 0xFF, 0xC5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFD, 0xFF, 0xFF, 0xA7, 0xFF, 0xFF, 0xDA, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xFF, 0xFF, 0xE8, 0x28, 0xFF, 0xFF,
+ 0xFF, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD3, 0xFF, 0xFF, 0x99,
+ 0x00, 0xD8, 0xFF, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xFF,
+ 0xFF, 0xFF, 0x47, 0x00, 0x86, 0xFF, 0xFF, 0xEB, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x8D, 0xFF, 0xFF, 0xEE, 0x05, 0x00, 0x32, 0xFF, 0xFF, 0xFF, 0x4D, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0xE6, 0xFF, 0xFF, 0xA0, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xFF, 0xA9, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0xFF, 0xFF, 0xFF, 0x4C, 0x00, 0x00, 0x00, 0x8A, 0xFF,
+ 0xFF, 0xF7, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0xFF, 0xFF, 0xF2, 0x07, 0x00, 0x00,
+ 0x00, 0x36, 0xFF, 0xFF, 0xFF, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xF4, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x1D, 0x00, 0x00, 0x00,
+ 0x00, 0xBA, 0xFF, 0xFF, 0xFD, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD8, 0xFF, 0xFF, 0xFF, 0x77,
+ 0x00, 0x00, 0x00, 0x1A, 0xFD, 0xFF, 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xEB,
+ 0xFF, 0xFF, 0xD4, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xFF, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0xD1, 0xFF, 0xFF, 0xEE, 0x07, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0xFF, 0xFF, 0xFF, 0x8D, 0x00, 0x2D, 0xFF, 0xFF, 0xFF, 0x94,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCD, 0xFF, 0xFF, 0xE6, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xAC, 0xFF, 0xFF, 0xEA, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0xFF, 0xFF, 0xC9, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFF, 0xFF, 0x9A, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xF8, 0x62, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xB9, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0x07, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xF1, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0x4A, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x00, 0x00,
+ 0x13, 0xD7, 0xFF, 0xFF, 0xB3, 0x00, 0x0A, 0xC7, 0xFF, 0xFF, 0xA1, 0x09, 0x03, 0xB3, 0xFF, 0xF9,
+ 0x6A, 0x00, 0x00, 0x87, 0xFF, 0xE5, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C,
+ 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00,
+ 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF,
+ 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00,
+ 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x44,
+ 0xFF, 0xFF, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0xDC, 0xF5, 0xA5, 0x1F, 0x00,
+ 0x00, 0x80, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xF6, 0xFF, 0xFF, 0xFF, 0xF5,
+ 0x77, 0x38, 0xDE, 0xFF, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xFF, 0xB1, 0x35, 0x97,
+ 0xFE, 0xFF, 0xFF, 0xFF, 0xD2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xFF, 0x46, 0x00,
+ 0x00, 0x36, 0xC0, 0xF6, 0xC4, 0x26, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xD7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED, 0x11, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08,
+ 0x44, 0xFF, 0xFF, 0xEB, 0xE8, 0xFF, 0xFF, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF, 0xFF,
+ 0x08, 0x44, 0xFF, 0xFF, 0xF6, 0x65, 0xFF, 0xFF, 0xFA, 0x26, 0x00, 0x00, 0x00, 0x4C, 0xFF, 0xFF,
+ 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x09, 0xD2, 0xFF, 0xFF, 0xB1, 0x00, 0x00, 0x00, 0x4C, 0xFF,
+ 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x0F, 0x45, 0xFF, 0xFF, 0xFF, 0x42, 0x00, 0x00, 0x4C,
+ 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0xB6, 0xFF, 0xFF, 0xD0, 0x02, 0x00,
+ 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x2A, 0xFC, 0xFF, 0xFF, 0x64,
+ 0x00, 0x4C, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x96, 0xFF, 0xFF,
+ 0xE7, 0x0C, 0x4A, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x15, 0xF1,
+ 0xFF, 0xFF, 0x85, 0x44, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00,
+ 0x77, 0xFF, 0xFF, 0xF7, 0x55, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00, 0x00,
+ 0x00, 0x07, 0xDF, 0xFF, 0xFF, 0xC6, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF, 0xFF,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x44, 0xFF, 0xFF,
+ 0xFF, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xFD, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xF7, 0xFF, 0xF2, 0x53, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xEF, 0xFF, 0xD7, 0x29,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE3, 0xFF,
+ 0xAD, 0x0D, 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, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x13, 0x72, 0xB5, 0xE1, 0xF4, 0xF7, 0xE4, 0xBE, 0x7E, 0x1E, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x77, 0xF4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFA, 0x8F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFB,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0x07, 0x00, 0x00, 0x00, 0x75, 0xFF, 0xFF, 0xFF, 0xFA, 0x8D,
+ 0x2B, 0x03, 0x01, 0x21, 0x7A, 0xF3, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0x00, 0x11, 0xF1, 0xFF, 0xFF,
+ 0xFD, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0xF4, 0xFF, 0xFF, 0xFE, 0x2E, 0x00, 0x6C,
+ 0xFF, 0xFF, 0xFF, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF, 0xFF,
+ 0x98, 0x00, 0xAD, 0xFF, 0xFF, 0xFF, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E,
+ 0xFA, 0xFF, 0xFF, 0xDA, 0x00, 0xD5, 0xFF, 0xFF, 0xF3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFF, 0xFE, 0x04, 0xE8, 0xFF, 0xFF, 0xDE, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0xFF, 0xFF, 0xFF, 0x17, 0xE5, 0xFF, 0xFF, 0xE1, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBA, 0xFF, 0xFF, 0xFF, 0x17, 0xD2, 0xFF,
+ 0xFF, 0xF7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0xFF, 0xFC,
+ 0x06, 0xA5, 0xFF, 0xFF, 0xFF, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xFC,
+ 0xFF, 0xFF, 0xD4, 0x00, 0x61, 0xFF, 0xFF, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x74, 0xFF, 0xFF, 0xFF, 0x89, 0x00, 0x0A, 0xEA, 0xFF, 0xFF, 0xFF, 0x5D, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x32, 0xF4, 0xFF, 0xFF, 0xFA, 0x21, 0x00, 0x00, 0x63, 0xFF, 0xFF, 0xFF, 0xFD,
+ 0x97, 0x2E, 0x03, 0x01, 0x20, 0x7A, 0xF3, 0xFF, 0xFF, 0xFF, 0x74, 0x00, 0x00, 0x00, 0x00, 0x91,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA8, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x6B, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x6F, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x6F, 0xB5, 0xE2, 0xF5, 0xFA, 0xE7, 0xBD, 0x77,
+ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xB6,
+ 0xFF, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0,
+ 0xFF, 0xFF, 0xC2, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88,
+ 0xFF, 0xFF, 0x90, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57,
+ 0xFF, 0xF5, 0x59, 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, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0xFF,
+ 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF,
+ 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF,
+ 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70,
+ 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30,
+ 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF, 0xFF,
+ 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xFF,
+ 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF,
+ 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90,
+ 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x90, 0xFF, 0xFF, 0xFF, 0x30, 0x70, 0xFF, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x91, 0xFF, 0xFF, 0xFF, 0x2F, 0x68, 0xFF, 0xFF, 0xFF, 0x5C, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xA1, 0xFF, 0xFF, 0xFF, 0x22, 0x51, 0xFF, 0xFF, 0xFF, 0x8B, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xD8, 0xFF, 0xFF, 0xFC, 0x0A, 0x19, 0xFF, 0xFF, 0xFF, 0xE9, 0x12, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x57, 0xFF, 0xFF, 0xFF, 0xC5, 0x00, 0x00, 0xB6, 0xFF, 0xFF, 0xFF, 0xD0,
+ 0x42, 0x04, 0x00, 0x15, 0x75, 0xF8, 0xFF, 0xFF, 0xFF, 0x55, 0x00, 0x00, 0x28, 0xF0, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFC, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA6, 0x00, 0x00, 0x00, 0x00, 0x36, 0xDE,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x60, 0xAF, 0xE1, 0xF6, 0xFA, 0xEA, 0xC5, 0x87, 0x24, 0x00, 0x00, 0x00, 0x00
+};
diff --git a/sketch/obd2_gauge/bluetooth.h b/sketch/obd2_gauge/bluetooth.h
index 4e4f0b9..f784a7a 100644
--- a/sketch/obd2_gauge/bluetooth.h
+++ b/sketch/obd2_gauge/bluetooth.h
@@ -2,7 +2,8 @@
//----------------------------------
extern bool ignitionOn;
extern bool wokeFromSleep;
-void initScreen();
+extern TaskHandle_t btTaskHandle;
+void initScreen(bool redrawTFT = true);
void plotNoData(uint8_t cell);
void touchBeep();
@@ -26,7 +27,9 @@ String ByteArraytoString(esp_bd_addr_t bt_address) {
void scanBTdevice() {//scan bluetooth device
setRearLedColor(0, 0, 200);//blue led on
+#ifdef SERIAL_DEBUG
Serial.println("Scanning for OBDII Adaptor...");
+#endif
Terminal("Scanning for OBDII Adaptor...",0,48,320,191);
btDeviceCount = 0;
// BTScanResults* btDeviceList = BTSerial.getScanResults(); // maybe accessing from different threads!
@@ -34,7 +37,9 @@ void scanBTdevice() {//scan bluetooth device
// BTAdvertisedDeviceSet*set = reinterpret_cast(pDevice);
// btDeviceList[pDevice->getAddress()] = * set;
String txt = pDevice->toString().c_str();
+#ifdef SERIAL_DEBUG
Serial.printf("Found a new device: %s\n", pDevice->toString().c_str());
+#endif
deviceName[btDeviceCount] = pDevice->getName().c_str();
deviceAddr[btDeviceCount] = pDevice->getAddress().toString().c_str();
btDeviceCount++;
@@ -95,7 +100,10 @@ void scanBTdevice() {//scan bluetooth device
}//while mac address copied to client_addr for connect below code
if (ByteArraytoString(client_addr) != ByteArraytoString(recent_client_addr)) {
pref.putBytes("recent_client",client_addr,sizeof(client_addr));//save new bt addr to pref.
+ memcpy(recent_client_addr, client_addr, sizeof(client_addr));
+#ifdef SERIAL_DEBUG
Serial.println(F("Save a new BT client address"));
+#endif
}
break; // Stop scanning once a matching device is found!
} else {
@@ -109,12 +117,16 @@ void scanBTdevice() {//scan bluetooth device
if (foundOBD2) {
txt = "Connecting to " + client_name +" - " + ByteArraytoString(client_addr);
Terminal(txt,0,48,320,191);
+#ifdef SERIAL_DEBUG
Serial.println(txt);
+#endif
BTSerial.connect(client_addr, 0, sec_mask, role);//connect to OBDII adaptor
bool blink = false;
uint8_t try_count = 0;
while(!BTSerial.connected(1000) && (try_count < 10)) {
+#ifdef SERIAL_DEBUG
Serial.print(F("."));
+#endif
blink =! blink;
if (blink) setRearLedColor(0, 0, 200);
else setRearLedOff();
@@ -126,13 +138,17 @@ void scanBTdevice() {//scan bluetooth device
if (try_count >= 10) {
Terminal("Connect failed!",0,48,320,191);
+#ifdef SERIAL_DEBUG
Serial.println(F("Connect failed!"));
+#endif
BTSerial.disconnect();
setRearLedOff();
foundOBD2 = false;
} else {
Terminal("Connected Successfully!",0,48,320,191);
+#ifdef SERIAL_DEBUG
Serial.println(F("Connected Successfully!"));
+#endif
prompt = true;
updateRearLedTheme(); // Set rear RGB LED to active theme color
foundOBD2 = true;
@@ -140,46 +156,15 @@ void scanBTdevice() {//scan bluetooth device
} else {
Terminal("OBDII Adaptor not found!",0,48,320,191);
+#ifdef SERIAL_DEBUG
Serial.println(F("OBDII Adaptor not found!"));
+#endif
} //foundOBD2
}//scanBTDevice
//---------------------------
//connect to recent obdII for fast connection, skip scanning
void connectLastOBDII() {
- setRearLedColor(0, 0, 200);//blue led on
- String txt = "Connecting to " + client_name +" - " + ByteArraytoString(recent_client_addr);
- Terminal(txt,0,48,320,191);
- Serial.println(txt);
- BTSerial.connect(recent_client_addr, 0, sec_mask, role);//connect to OBDII adaptor
- uint8_t try_count = 0;
- bool blink = false;
- while(!BTSerial.connected(1000) && (try_count <3)) {
- Serial.print(F("."));
- blink =! blink;
- if (blink) setRearLedColor(0, 0, 200);
- else setRearLedOff();
- try_count++;
- }
- if (try_count == 3) {//cannot connect
- Terminal("OBDII Adaptor not found!",0,48,320,191);
- Serial.println(F("OBDII Adaptor not found!"));
- BTSerial.disconnect();
- setRearLedOff();
- foundOBD2 = false;
- } else {
- Terminal("Connected Successfully!",0,48,320,191);
- Serial.println(F("Connected Successfully!"));
- prompt = true;
- updateRearLedTheme(); // Set rear RGB LED to active theme color
- foundOBD2 = true;
- }
-
-}//connectLasbtOBDII
-
-//---------------------------
-// Resume Bluetooth connection with OBDII scanner upon waking from sleep
-bool resumeBluetoothConnection() {
bool hasRecentAddr = false;
for (uint8_t i = 0; i < 6; i++) {
if (recent_client_addr[i] != 0) {
@@ -188,89 +173,59 @@ bool resumeBluetoothConnection() {
}
}
if (!hasRecentAddr) {
+ Terminal("Sin escaner OBDII previo.", 0, 48, 320, 191);
+#ifdef SERIAL_DEBUG
+ Serial.println(F("Sin escaner OBDII previo."));
+#endif
foundOBD2 = false;
- return false;
+ return;
}
- // Draw authentic themed resume card
- tft.fillScreen(TFT_BLACK);
- tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor());
- tft.fillRect(0, 26, 320, 2, getThemePrimaryColor());
- tft.setTextColor(TFT_WHITE);
- tft.drawCentreString("Conexion Bluetooth", 160, 2, 4);
-
- tft.fillRoundRect(14, 44, 292, 114, 6, getThemeDarkColor());
- tft.drawRoundRect(14, 44, 292, 114, 6, getThemeAccentColor());
- tft.fillRoundRect(18, 50, 4, 102, 2, getThemePrimaryColor());
-
- tft.setTextColor(TFT_WHITE, getThemeDarkColor());
- tft.drawCentreString("Reanudando conexion...", 160, 54, 4);
-
- tft.setTextColor(0xBDD7, getThemeDarkColor());
- tft.drawCentreString("Conectando con escaner OBDII", 160, 88, 2);
- String macStr = ByteArraytoString(recent_client_addr);
- tft.drawCentreString(macStr.c_str(), 160, 108, 2);
-
- // Request Bluetooth connection to OBDII scanner
- setRearLedColor(0, 0, 200); // Blue LED indicating connection attempt
- BTSerial.disconnect();
- delay(50);
- BTSerial.connect(recent_client_addr, 0, sec_mask, role);
-
+ setRearLedColor(0, 0, 200);//blue led on
+ String txt = "Connecting to " + client_name +" - " + ByteArraytoString(recent_client_addr);
+ Terminal(txt,0,48,320,191);
+#ifdef SERIAL_DEBUG
+ Serial.println(txt);
+#endif
+ BTSerial.connect(recent_client_addr, 0, sec_mask, role);//connect to OBDII adaptor
uint8_t try_count = 0;
bool blink = false;
- while (!BTSerial.connected(1000) && (try_count < 4)) {
- blink = !blink;
+ while(!BTSerial.connected(1000) && (try_count <3)) {
+#ifdef SERIAL_DEBUG
+ Serial.print(F("."));
+#endif
+ blink =! blink;
if (blink) setRearLedColor(0, 0, 200);
else setRearLedOff();
-
- String dots = "";
- for (uint8_t d = 0; d <= (try_count % 4); d++) dots += ". ";
- tft.setTextColor(getThemePrimaryColor(), getThemeDarkColor());
- tft.drawCentreString(dots.c_str(), 160, 132, 4);
-
- uint16_t tx = 0, ty = 0;
- if (getTouch(&tx, &ty) || digitalRead(SELECTOR_PIN) == LOW) {
- break; // Abort on user interaction
- }
try_count++;
}
-
- if (BTSerial.connected()) {
- tft.setTextColor(TFT_GREEN, getThemeDarkColor());
- tft.drawCentreString("[ OK ] Conectado con exito!", 160, 132, 2);
- setRearLedColor(0, 255, 0);
- delay(200);
-
- // Re-initialize ELM327 protocol and commands for Ford J1850 PWM
- sendATCommand("ATE0", 800); // Echo OFF
- sendATCommand("ATL0", 800); // Linefeeds OFF
- sendATCommand("ATH0", 800); // Headers OFF
- sendATCommand("ATAT1", 800); // Normal adaptive timing
- sendATCommand("ATSP1", 800); // Ford J1850 PWM (Protocol 1)
- String r0100 = sendATCommand("0100", 2500);
- ignitionOn = (r0100.indexOf("41 00") != -1);
-
- foundOBD2 = true;
- prompt = true;
- bt_message = "";
- updateRearLedTheme(); // Restore theme color on rear RGB LED
- touchBeep();
- initScreen();
- lastActivityTime = millis();
- return true;
- } else {
- tft.setTextColor(TFT_RED, getThemeDarkColor());
- tft.drawCentreString("[!] Escaner no disponible", 160, 132, 2);
+ if (try_count == 3) {//cannot connect
+ Terminal("OBDII Adaptor not found!",0,48,320,191);
+#ifdef SERIAL_DEBUG
+ Serial.println(F("OBDII Adaptor not found!"));
+#endif
BTSerial.disconnect();
setRearLedOff();
foundOBD2 = false;
- delay(400);
- initScreen();
- for (uint8_t c = 0; c < 4; c++) {
- plotNoData(c);
- }
- lastActivityTime = millis();
- return false;
+ } else {
+ Terminal("Connected Successfully!",0,48,320,191);
+#ifdef SERIAL_DEBUG
+ Serial.println(F("Connected Successfully!"));
+#endif
+ prompt = true;
+ updateRearLedTheme(); // Set rear RGB LED to active theme color
+ foundOBD2 = true;
}
+
+}//connectLasbtOBDII
+
+//---------------------------
+//---------------------------
+// Resume Bluetooth connection with OBDII scanner upon waking from sleep (en segundo plano, 100% no bloqueante)
+bool resumeBluetoothConnection() {
+ foundOBD2 = false;
+ if (btTaskHandle != NULL) {
+ xTaskNotifyGive(btTaskHandle);
+ }
+ return true;
}
\ No newline at end of file
diff --git a/sketch/obd2_gauge/config.h b/sketch/obd2_gauge/config.h
index e0a9e26..1ef87c4 100644
--- a/sketch/obd2_gauge/config.h
+++ b/sketch/obd2_gauge/config.h
@@ -12,6 +12,25 @@ extern uint8_t autoSleepIndex;
extern uint32_t lastActivityTime;
extern uint8_t beepSoundType;
extern bool wokeFromSleep;
+extern uint8_t transitionType;
+extern volatile bool systemSleeping;
+extern TaskHandle_t btTaskHandle;
+
+// Control de Inactividad de 10 segundos en Menu de Configuracion y Submenus
+static uint32_t lastMenuActivityTime = 0;
+static bool menuExitedDueToTimeout = false;
+
+inline void resetMenuActivity() {
+ lastMenuActivityTime = millis();
+}
+
+inline bool checkMenuTimeout() {
+ if (millis() - lastMenuActivityTime >= 10000) {
+ menuExitedDueToTimeout = true;
+ return true;
+ }
+ return false;
+}
/*------------ SOUND EFFECTS ----------*/
const char* const BEEP_NAME_TABLE[6] = {
@@ -180,9 +199,11 @@ void warningMenu() {
waitForTouchRelease();
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
// 1. Direct touch on any parameter row to toggle ON/OFF
if (ty >= 6 && ty < 184) {
int8_t row = (ty - 10) / 44;
@@ -205,10 +226,11 @@ void warningMenu() {
break;
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
delay(200);
break;
}
- yield();
+ delay(25);
}
}
@@ -342,9 +364,11 @@ void alarmSoundMenu() {
waitForTouchRelease();
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
// 1. Touch on pattern selection (y: 0 to 122)
if (ty < 122) {
int8_t col = (tx < 160) ? 0 : 1;
@@ -379,10 +403,11 @@ void alarmSoundMenu() {
break;
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
delay(200);
break;
}
- yield();
+ delay(25);
}
}
@@ -441,9 +466,11 @@ void beepSoundMenu() {
waitForTouchRelease();
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
// 1. Touch on pattern selection (y: 0 to 122)
if (ty < 122) {
int8_t col = (tx < 160) ? 0 : 1;
@@ -478,10 +505,11 @@ void beepSoundMenu() {
break;
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
delay(200);
break;
}
- yield();
+ delay(25);
}
}
@@ -517,40 +545,68 @@ void enterLightSleep() {
digitalWrite(LED_GREEN_PIN, HIGH);
digitalWrite(LED_BLUE_PIN, HIGH);
- // Cleanly disconnect and terminate Bluetooth before sleep to release remote link
+ systemSleeping = true; // Signal background task to pause Bluetooth attempts
+
+ // Cleanly disconnect active Bluetooth link before sleep to release remote connection
+ Serial.println(">>> [SLEEP] Disconnecting BT link...");
+ Serial.flush();
if (BTSerial.hasClient() || BTSerial.connected() || foundOBD2) {
BTSerial.disconnect();
delay(50);
}
- BTSerial.end();
foundOBD2 = false;
- // 3. Configure wakeup sources:
- // GPIO 36: XPT2046 Touch IRQ (PENIRQ), pulls LOW when touched
- // GPIO 27: Selector Button (SELECTOR_PIN), pulls LOW when pressed
- esp_sleep_enable_ext0_wakeup(GPIO_NUM_36, 0);
- gpio_wakeup_enable((gpio_num_t)SELECTOR_PIN, GPIO_INTR_LOW_LEVEL);
- esp_sleep_enable_gpio_wakeup();
+ // Make sure finger or button is completely released before entering sleep loop
+ waitForTouchRelease();
- // 4. Enter Light Sleep
- esp_light_sleep_start();
+#ifdef SERIAL_DEBUG
+ Serial.println(">>> [SLEEP] Sleeping (display & LEDs off, waiting for touch/button)...");
+ Serial.flush();
+#endif
+
+ // Put GPS module to sleep (UBX backup mode - debe ser lo ultimo antes de entrar en reposo)
+ gpsSleep();
+
+ while (true) {
+ delay(40); // Yields to FreeRTOS, feeds watchdog, minimal CPU activity
+
+ // Check if woken by physical selector button (debounced)
+ if (digitalRead(SELECTOR_PIN) == LOW) {
+ delay(20);
+ if (digitalRead(SELECTOR_PIN) == LOW) {
+#ifdef SERIAL_DEBUG
+ Serial.println(">>> [SLEEP] Woken up by physical button!");
+#endif
+ break;
+ }
+ }
+
+ // Check if woken by touchscreen (debounced)
+ uint16_t dummyX, dummyY;
+ if (getTouch(&dummyX, &dummyY)) {
+ delay(20);
+ if (getTouch(&dummyX, &dummyY)) {
+#ifdef SERIAL_DEBUG
+ Serial.println(">>> [SLEEP] Woken up by touchscreen touch!");
+#endif
+ break;
+ }
+ }
+ }
// === WAKE UP ===
- gpio_wakeup_disable((gpio_num_t)SELECTOR_PIN);
- rtc_gpio_deinit(GPIO_NUM_36);
-
- // Restart Bluetooth controller stack fresh
- BTSerial.begin("OBD2 Gauge", true);
-
- // Restore rear RGB LED status in selected theme color if connected
- if (foundOBD2) {
- updateRearLedTheme();
- }
+#ifdef SERIAL_DEBUG
+ Serial.println(">>> [SLEEP] Successfully woke up from sleep!");
+ Serial.flush();
+#endif
// 5. Wake LCD controller
tft.writecommand(0x11); // ST7789_SLPOUT
delay(120);
+ // Wake up GPS module
+ gpsWake();
+
// 6. Restore backlight smoothly according to current brightness setting (Auto LDR or Manual)
uint8_t wakeTarget = 255;
if (brightnessIndex > 0) {
@@ -586,6 +642,12 @@ void enterLightSleep() {
// 8. Wait for touch to be fully released
waitForTouchRelease();
+ systemSleeping = false;
+ foundOBD2 = false;
+ if (btTaskHandle != NULL) {
+ xTaskNotifyGive(btTaskHandle);
+ }
+
wokeFromSleep = true;
}
@@ -666,9 +728,11 @@ void sleepMenu() {
long holdtimer = 0;
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
// 1. Tap on Auto-Sleep Timeout Tiles (y: 88 to 154)
if (ty >= 88 && ty <= 154) {
int8_t touchedTile = (tx - 8) / 62;
@@ -708,6 +772,7 @@ void sleepMenu() {
}
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
if (!pressed) {
pressed = true;
holdtimer = millis();
@@ -733,6 +798,7 @@ void sleepMenu() {
}
} else {
if (pressed) {
+ resetMenuActivity();
if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1000) {
subSel = (subSel == 0) ? 1 : 0;
drawSleepBottomButtons(subSel);
@@ -742,7 +808,7 @@ void sleepMenu() {
delay(150);
}
}
- yield();
+ delay(25);
}
}
@@ -762,11 +828,7 @@ void restartConfirmationMenu() {
tft.fillRoundRect(18, 50, 4, 92, 2, getThemePrimaryColor()); // Left accent pill
tft.setTextColor(TFT_WHITE, getThemeDarkColor());
- tft.drawCentreString("Desea reiniciar la placa?", 160, 56, 4);
-
- tft.setTextColor(0xBDD7, getThemeDarkColor());
- tft.drawCentreString("El sistema se reiniciara", 160, 92, 2);
- tft.drawCentreString("y volvera a conectar con el OBD2", 160, 112, 2);
+ tft.drawCentreString("Desea reiniciar la placa?", 160, 80, 4);
uint8_t subSel = 1; // 0: REINICIAR, 1: SALIR (por defecto SALIR para seguridad)
@@ -802,9 +864,11 @@ void restartConfirmationMenu() {
long holdtimer = 0;
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
if (ty >= 155 && ty <= 225) {
if (tx >= 10 && tx <= 158) {
// Confirmado: Reiniciar!
@@ -827,6 +891,7 @@ void restartConfirmationMenu() {
}
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
if (!pressed) {
pressed = true;
holdtimer = millis();
@@ -850,6 +915,7 @@ void restartConfirmationMenu() {
}
} else {
if (pressed) {
+ resetMenuActivity();
if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1000) {
subSel = (subSel == 0) ? 1 : 0;
drawSubButtons(subSel);
@@ -859,7 +925,7 @@ void restartConfirmationMenu() {
delay(150);
}
}
- yield();
+ delay(25);
}
}
@@ -952,10 +1018,12 @@ void brightnessMenu() {
waitForTouchRelease();
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
// 1. Direct Touch on Brightness Tiles (y: 106 to 176)
if (ty >= 106 && ty < 176) {
int8_t touchedTile = (tx - 7) / 52;
@@ -976,6 +1044,7 @@ void brightnessMenu() {
break;
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
if (!pressed) {
pressed = true;
holdtimer = millis();
@@ -988,6 +1057,7 @@ void brightnessMenu() {
}
} else {
if (pressed) {
+ resetMenuActivity();
if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1000) {
curChoice = (curChoice + 1) % 6;
brightnessIndex = curChoice;
@@ -999,7 +1069,7 @@ void brightnessMenu() {
delay(150);
}
}
- yield();
+ delay(25);
}
}
@@ -1060,11 +1130,13 @@ void soundsMenu() {
waitForTouchRelease();
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
int8_t executeSelect = -1;
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
if (ty >= 16 && ty < 96) {
executeSelect = 0;
} else if (ty >= 96 && ty < 176) {
@@ -1085,6 +1157,7 @@ void soundsMenu() {
}
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
if (!pressed) {
pressed = true;
holdtimer = millis();
@@ -1096,6 +1169,7 @@ void soundsMenu() {
}
} else {
if (pressed) {
+ resetMenuActivity();
if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1200) {
if (choice == 255) choice = 0;
else choice = (choice + 1) % 3;
@@ -1110,11 +1184,13 @@ void soundsMenu() {
if (executeSelect >= 0) {
if (executeSelect == 0) {
alarmSoundMenu();
+ if (menuExitedDueToTimeout) break;
choice = 255;
drawSoundsUI(choice);
waitForTouchRelease();
} else if (executeSelect == 1) {
beepSoundMenu();
+ if (menuExitedDueToTimeout) break;
choice = 255;
drawSoundsUI(choice);
waitForTouchRelease();
@@ -1124,7 +1200,7 @@ void soundsMenu() {
}
pressed = false;
}
- yield();
+ delay(25);
}
}
@@ -1135,7 +1211,7 @@ void drawThemeHeader() {
tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor());
tft.fillRect(0, 26, 320, 2, getThemePrimaryColor());
tft.setTextColor(TFT_WHITE);
- tft.drawCentreString("Apariencia / Temas", 160, 2, 4);
+ tft.drawCentreString("Temas de Color", 160, 2, 4);
}
void themeMenu() {
@@ -1226,10 +1302,12 @@ void themeMenu() {
long holdtimer = 0;
while (true) {
+ if (checkMenuTimeout()) break;
autoDim();
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
// 1. Touch on theme tiles (y: 100 to 180)
if (ty >= 100 && ty < 182) {
uint8_t col = (tx < 110) ? 0 : ((tx < 212) ? 1 : 2);
@@ -1247,12 +1325,13 @@ void themeMenu() {
}
waitForTouchRelease();
}
- // 2. Touch on [ SALIR ] button (y: 182 to 240)
- else if (ty >= 182 && tx >= 20 && tx <= 300) {
+ // 2. Touch on [ SALIR ] button (y: 178 to 240)
+ else if (ty >= 178 && tx >= 10 && tx <= 310) {
animateExitButtonPress();
break;
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
if (!pressed) {
pressed = true;
holdtimer = millis();
@@ -1262,6 +1341,7 @@ void themeMenu() {
}
} else {
if (pressed) {
+ resetMenuActivity();
if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1000) {
themeIndex = (themeIndex + 1) % 6;
pref.begin("setting", false);
@@ -1274,7 +1354,312 @@ void themeMenu() {
delay(150);
}
}
- yield();
+ delay(25);
+ }
+}
+
+// -------------------------------------------------------------
+// Sub-screen 4: Menu de Transiciones (Deslizamiento / Desvanecimiento)
+// -------------------------------------------------------------
+void drawTransitionHeader() {
+ tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor());
+ tft.fillRect(0, 26, 320, 2, getThemePrimaryColor());
+ tft.setTextColor(TFT_WHITE);
+ tft.drawCentreString("Efecto de Transicion", 160, 2, 4);
+}
+
+void transitionMenu() {
+ tft.fillScreen(TFT_BLACK);
+
+ auto drawTransitionUI = [&]() {
+ drawTransitionHeader();
+
+ const char* titles[2] = { "Deslizamiento", "Desvanecimiento" };
+ const char* descs[2] = { "Barrido lateral continuo", "Mezcla gradual de pantallas" };
+
+ for (uint8_t i = 0; i < 2; i++) {
+ int16_t y = 36 + i * 54;
+ int16_t x = 12;
+ int16_t w = 296;
+ int16_t h = 48;
+ bool isSel = (i == transitionType);
+
+ uint16_t bg = isSel ? getThemeDarkColor() : 0x1082;
+ uint16_t border = isSel ? getThemePrimaryColor() : 0x2104;
+
+ tft.fillRoundRect(x, y, w, h, 6, bg);
+ tft.drawRoundRect(x, y, w, h, 6, border);
+ if (isSel) {
+ tft.drawRoundRect(x + 1, y + 1, w - 2, h - 2, 5, TFT_WHITE);
+ tft.fillRoundRect(x + 2, y + 4, 4, h - 8, 2, getThemePrimaryColor());
+ }
+
+ // Circular radio indicator
+ int16_t cx = x + 24;
+ int16_t cy = y + h / 2;
+ tft.drawCircle(cx, cy, 9, isSel ? TFT_WHITE : 0x4208);
+ if (isSel) {
+ tft.fillCircle(cx, cy, 6, getThemePrimaryColor());
+ } else {
+ tft.fillCircle(cx, cy, 6, TFT_BLACK);
+ }
+
+ // Title & Subtitle
+ tft.setTextColor(isSel ? TFT_WHITE : 0xBDD7, bg);
+ tft.drawString(titles[i], x + 44, y + 7, 4);
+ tft.setTextColor(isSel ? getThemePrimaryColor() : 0x8410, bg);
+ tft.drawString(descs[i], x + 44, y + 29, 2);
+ }
+
+ // Interactive Preview Button [ PROBAR EFECTO ]
+ int16_t px = 60;
+ int16_t py = 146;
+ int16_t pw = 200;
+ int16_t ph = 32;
+ tft.fillRoundRect(px, py, pw, ph, 5, getThemeDarkColor());
+ tft.drawRoundRect(px, py, pw, ph, 5, getThemePrimaryColor());
+ tft.setTextColor(getThemePrimaryColor(), getThemeDarkColor());
+ tft.drawCentreString("PROBAR EFECTO", 160, py + 7, 2);
+
+ // Bottom action button: [ SALIR ] (x: 80, y: 186, w: 160, h: 42)
+ drawExitButton(false);
+ };
+
+ drawTransitionUI();
+ waitForTouchRelease();
+
+ bool pressed = false;
+ long holdtimer = 0;
+
+ while (true) {
+ if (checkMenuTimeout()) break;
+ autoDim();
+ uint16_t tx = 0, ty = 0;
+ if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
+ // 1. Touch on option cards (y: 36 to 142)
+ if (ty >= 36 && ty < 90) {
+ if (transitionType != 0) {
+ transitionType = 0;
+ pref.begin("setting", false);
+ pref.putUChar("trans_type", transitionType);
+ pref.end();
+ touchBeep();
+ drawTransitionUI();
+ }
+ waitForTouchRelease();
+ } else if (ty >= 90 && ty < 144) {
+ if (transitionType != 1) {
+ transitionType = 1;
+ pref.begin("setting", false);
+ pref.putUChar("trans_type", transitionType);
+ pref.end();
+ touchBeep();
+ drawTransitionUI();
+ }
+ waitForTouchRelease();
+ }
+ // 2. Touch on [ PROBAR EFECTO ] (y: 142 to 176)
+ else if (ty >= 142 && ty <= 176 && tx >= 40 && tx <= 280) {
+ touchBeep();
+ // Demonstrate transition across all 4 pages (Page 0 -> Page 1 -> Page 2 -> Page 3 -> Page 0)
+ performScreenTransition(true, 0, 1);
+ delay(350);
+ performScreenTransition(true, 1, 2);
+ delay(350);
+ performScreenTransition(true, 2, 3);
+ delay(350);
+ performScreenTransition(false, 3, 0);
+ delay(250);
+ tft.fillScreen(TFT_BLACK);
+ drawTransitionUI();
+ waitForTouchRelease();
+ }
+ // 3. Bottom button [ SALIR ] (y: 178 to 240)
+ else if (ty >= 178 && tx >= 10 && tx <= 310) {
+ pref.begin("setting", false);
+ pref.putUChar("trans_type", transitionType);
+ pref.end();
+ animateExitButtonPress();
+ break;
+ }
+ } else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
+ if (!pressed) {
+ pressed = true;
+ holdtimer = millis();
+ } else if (millis() - holdtimer > 1000) {
+ // Long press on selector button: Exit and save
+ pref.begin("setting", false);
+ pref.putUChar("trans_type", transitionType);
+ pref.end();
+ animateExitButtonPress();
+ break;
+ }
+ } else {
+ if (pressed) {
+ resetMenuActivity();
+ if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1000) {
+ transitionType = (transitionType == 0) ? 1 : 0;
+ pref.begin("setting", false);
+ pref.putUChar("trans_type", transitionType);
+ pref.end();
+ touchBeep();
+ drawTransitionUI();
+ }
+ pressed = false;
+ delay(150);
+ }
+ }
+ delay(25);
+ }
+}
+
+// -------------------------------------------------------------
+// Sub-screen 3: Menu de Apariencia (Temas de Color y Transiciones)
+// -------------------------------------------------------------
+void drawAppearanceHeader() {
+ tft.fillRectVGradient(0, 0, 320, 26, getThemeAccentColor(), getThemeDarkColor());
+ tft.fillRect(0, 26, 320, 2, getThemePrimaryColor());
+ tft.setTextColor(TFT_WHITE);
+ tft.drawCentreString("Apariencia", 160, 2, 4);
+}
+
+void appearanceMenu() {
+ uint8_t choice = 255;
+ bool pressed = false;
+ long holdtimer = 0;
+
+ auto drawAppearanceUI = [&](uint8_t sel) {
+ tft.fillScreen(TFT_BLACK);
+
+ drawAppearanceHeader();
+
+ // Card 0: Temas de Color (y: 34 to 102, h: 68)
+ uint16_t bg0 = (sel == 0) ? getThemeAccentColor() : getThemeDarkColor();
+ uint16_t border0 = (sel == 0) ? getThemePrimaryColor() : getThemeAccentColor();
+ tft.fillRoundRect(10, 34, 300, 68, 5, bg0);
+ tft.drawRoundRect(10, 34, 300, 68, 5, border0);
+ if (sel == 0) {
+ tft.drawRoundRect(11, 35, 298, 66, 4, TFT_WHITE);
+ } else {
+ tft.fillRoundRect(13, 40, 4, 56, 2, getThemePrimaryColor());
+ }
+ tft.setTextColor(TFT_WHITE, bg0);
+ tft.drawString("Temas de Color", 24, 42, 4);
+ String themeInfo = String(GAUGE_THEMES[themeIndex].name) + " | " + String(GAUGE_THEMES[themeIndex].desc);
+ tft.setTextColor((sel == 0) ? TFT_YELLOW : 0xBDD7, bg0);
+ tft.drawString(themeInfo, 24, 72, 2);
+ tft.fillTriangle(296, 68, 290, 62, 290, 74, (sel == 0) ? TFT_WHITE : getThemePrimaryColor());
+
+ // Card 1: Efecto de Transicion (y: 110 to 178, h: 68)
+ uint16_t bg1 = (sel == 1) ? getThemeAccentColor() : getThemeDarkColor();
+ uint16_t border1 = (sel == 1) ? getThemePrimaryColor() : getThemeAccentColor();
+ tft.fillRoundRect(10, 110, 300, 68, 5, bg1);
+ tft.drawRoundRect(10, 110, 300, 68, 5, border1);
+ if (sel == 1) {
+ tft.drawRoundRect(11, 111, 298, 66, 4, TFT_WHITE);
+ } else {
+ tft.fillRoundRect(13, 116, 4, 56, 2, getThemePrimaryColor());
+ }
+ tft.setTextColor(TFT_WHITE, bg1);
+ tft.drawString("Efecto Transicion", 24, 118, 4);
+ String transInfo = (transitionType == 0) ? "Deslizamiento | Barrido lateral" : "Desvanecimiento | Mezcla gradual";
+ tft.setTextColor((sel == 1) ? TFT_YELLOW : 0xBDD7, bg1);
+ tft.drawString(transInfo, 24, 148, 2);
+ tft.fillTriangle(296, 144, 290, 138, 290, 150, (sel == 1) ? TFT_WHITE : getThemePrimaryColor());
+
+ // Bottom action button: [ SALIR ] (x: 80, y: 186, w: 160, h: 42)
+ drawExitButton(false);
+ if (sel == 2) {
+ tft.drawRoundRect(80, 186, 160, 42, 5, getThemePrimaryColor());
+ tft.drawRoundRect(81, 187, 158, 40, 4, TFT_WHITE);
+ }
+ };
+
+ drawAppearanceUI(choice);
+ waitForTouchRelease();
+
+ while (true) {
+ if (checkMenuTimeout()) break;
+ autoDim();
+ int8_t executeSelect = -1;
+ uint16_t tx = 0, ty = 0;
+
+ if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
+ // 1. Touch on Card 0: Temas de Color (y: 30 to 104)
+ if (ty >= 30 && ty <= 104 && tx >= 10 && tx <= 310) {
+ executeSelect = 0;
+ }
+ // 2. Touch on Card 1: Efecto Transición (y: 106 to 176)
+ else if (ty >= 106 && ty <= 176 && tx >= 10 && tx <= 310) {
+ executeSelect = 1;
+ }
+ // 3. Touch on [ SALIR ] (y: 178 to 240)
+ else if (ty >= 178 && ty <= 240 && tx >= 10 && tx <= 310) {
+ executeSelect = 2;
+ }
+
+ if (executeSelect >= 0) {
+ choice = executeSelect;
+ if (executeSelect == 2) {
+ animateExitButtonPress();
+ break;
+ } else {
+ drawAppearanceUI(choice);
+ touchBeep();
+ waitForTouchRelease();
+ }
+ }
+ } else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
+ if (!pressed) {
+ pressed = true;
+ holdtimer = millis();
+ } else if (millis() - holdtimer > 1000) {
+ if (choice != 255) {
+ executeSelect = choice;
+ }
+ pressed = false;
+ }
+ } else {
+ if (pressed) {
+ resetMenuActivity();
+ if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1000) {
+ if (choice == 255) {
+ choice = 0;
+ } else {
+ choice++;
+ if (choice > 2) choice = 0;
+ }
+ drawAppearanceUI(choice);
+ touchBeep();
+ }
+ pressed = false;
+ delay(150);
+ }
+ }
+
+ if (executeSelect >= 0) {
+ if (executeSelect == 2) {
+ animateExitButtonPress();
+ break;
+ } else if (executeSelect == 0) {
+ themeMenu();
+ if (menuExitedDueToTimeout) break;
+ choice = 255;
+ drawAppearanceUI(choice);
+ waitForTouchRelease();
+ } else if (executeSelect == 1) {
+ transitionMenu();
+ if (menuExitedDueToTimeout) break;
+ choice = 255;
+ drawAppearanceUI(choice);
+ waitForTouchRelease();
+ }
+ }
+ delay(25);
}
}
@@ -1318,7 +1703,7 @@ void drawScrollDownButton(bool canScroll, bool pressed) {
}
// -------------------------------------------------------------
-// Menu Principal con Scroll Lateral (Opcion 3)
+// Menu Principal con Scroll Lateral
// -------------------------------------------------------------
void listMenu(uint8_t choice, uint8_t scrollOffset) {
// Clear list area, scroll column, and bottom area (y: 28 to 240)
@@ -1327,7 +1712,7 @@ void listMenu(uint8_t choice, uint8_t scrollOffset) {
// 1. Draw 3 visible cards from menuList[scrollOffset .. scrollOffset + 2]
for (uint8_t i = 0; i < 3; i++) {
uint8_t itemIdx = scrollOffset + i;
- if (itemIdx >= 6) break;
+ if (itemIdx >= 6) break; // 6 list items (0 to 5), item 6 is Salir
int y = 32 + i * 50;
bool isSel = (choice == itemIdx);
@@ -1352,7 +1737,7 @@ void listMenu(uint8_t choice, uint8_t scrollOffset) {
}
// 2. Right Scroll Column (x: 268 to 310, w: 42)
- uint8_t maxScroll = 3; // 6 total items - 3 visible items = 3
+ uint8_t maxScroll = 3; // 6 total list items - 3 visible items = 3
// Up Button [ ▲ ] (x: 268, y: 32, w: 42, h: 42)
drawScrollUpButton(scrollOffset > 0, false);
@@ -1377,36 +1762,40 @@ void listMenu(uint8_t choice, uint8_t scrollOffset) {
void configMenu() {
uint8_t select = 255;
uint8_t scrollOffset = 0;
- const uint8_t maxScroll = 3; // 6 items - 3 visible = 3
+ const uint8_t maxScroll = 3; // 6 list items - 3 visible = 3
bool pressed = false;
long holdtimer = 0;
+ resetMenuActivity();
+ menuExitedDueToTimeout = false;
+
tft.fillScreen(TFT_BLACK);
drawConfigHeader();
listMenu(select, scrollOffset);
waitForTouchRelease();
while (true) {
- if (wokeFromSleep) break;
+ if (wokeFromSleep || checkMenuTimeout()) break;
autoDim();
int8_t executeSelect = -1;
uint16_t tx = 0, ty = 0;
if (getTouch(&tx, &ty)) {
+ resetMenuActivity();
int8_t touchedOpt = -1;
- // 1. Touch on bottom [ SALIR ] button (x: 10 to 264, y: 182 to 240)
- if (ty >= 182 && ty <= 240 && tx >= 10 && tx < 265) {
+ // 1. Touch on bottom [ SALIR ] button (x: 10 to 264, y: 178 to 240)
+ if (ty >= 178 && ty <= 240 && tx >= 10 && tx < 265) {
touchedOpt = 6; // SALIR
}
- // 2. Touch on the 3 visible cards (x: 10 to 264, y: 28 to 182)
+ // 2. Touch on the 3 visible cards (x: 10 to 264, y: 28 to 176)
else if (tx >= 10 && tx < 265) {
if (ty >= 28 && ty < 78) {
touchedOpt = scrollOffset + 0;
- } else if (ty >= 78 && ty < 129) {
+ } else if (ty >= 78 && ty < 128) {
touchedOpt = scrollOffset + 1;
- } else if (ty >= 129 && ty < 182) {
+ } else if (ty >= 128 && ty <= 176) {
touchedOpt = scrollOffset + 2;
}
}
@@ -1464,6 +1853,7 @@ void configMenu() {
}
}
} else if (digitalRead(SELECTOR_PIN) == LOW) {
+ resetMenuActivity();
if (!pressed) {
pressed = true;
holdtimer = millis();
@@ -1475,6 +1865,7 @@ void configMenu() {
}
} else {
if (pressed) {
+ resetMenuActivity();
if (millis() - holdtimer >= 40 && millis() - holdtimer <= 1200) {
if (select == 255) {
select = scrollOffset;
@@ -1504,6 +1895,7 @@ void configMenu() {
// Option 0: Alarmas (ON/OFF)
if (select == 0) {
warningMenu();
+ if (menuExitedDueToTimeout) break;
tft.fillScreen(TFT_BLACK);
drawConfigHeader();
select = 255;
@@ -1513,6 +1905,7 @@ void configMenu() {
// Option 1: Sonidos (Menu de Sonido de Alarma y Sonido de Click)
else if (select == 1) {
soundsMenu();
+ if (menuExitedDueToTimeout) break;
tft.fillScreen(TFT_BLACK);
drawConfigHeader();
select = 255;
@@ -1522,15 +1915,17 @@ void configMenu() {
// Option 2: Brillo de Pantalla
else if (select == 2) {
brightnessMenu();
+ if (menuExitedDueToTimeout) break;
tft.fillScreen(TFT_BLACK);
drawConfigHeader();
select = 255;
listMenu(select, scrollOffset);
waitForTouchRelease();
}
- // Option 3: Apariencia / Temas
+ // Option 3: Apariencia (Temas y Transiciones)
else if (select == 3) {
- themeMenu();
+ appearanceMenu();
+ if (menuExitedDueToTimeout) break;
tft.fillScreen(TFT_BLACK);
drawConfigHeader();
select = 255;
@@ -1540,6 +1935,7 @@ void configMenu() {
// Option 4: Modo Dormir
else if (select == 4) {
sleepMenu();
+ if (wokeFromSleep || menuExitedDueToTimeout) break;
tft.fillScreen(TFT_BLACK);
drawConfigHeader();
select = 255;
@@ -1549,6 +1945,7 @@ void configMenu() {
// Option 5: Reiniciar
else if (select == 5) {
restartConfirmationMenu();
+ if (menuExitedDueToTimeout) break;
tft.fillScreen(TFT_BLACK);
drawConfigHeader();
select = 255;
@@ -1564,7 +1961,7 @@ void configMenu() {
pressed = false;
prompt = true;
}
- delay(30);
+ delay(25);
}
tft.fillScreen(TFT_BLACK);
}
diff --git a/sketch/obd2_gauge/gps_clock.h b/sketch/obd2_gauge/gps_clock.h
new file mode 100644
index 0000000..a751773
--- /dev/null
+++ b/sketch/obd2_gauge/gps_clock.h
@@ -0,0 +1,393 @@
+#pragma once
+
+#include "DSEG7_Modern_BoldItalic_34.h"
+#include "DSEG7_Modern_BoldItalic_80.h"
+#include "Latin_Sans_Bold_26.h"
+#include "gps_module.h"
+#include
+#include
+
+extern TFT_eSPI tft;
+extern uint16_t getThemePrimaryColor();
+extern uint16_t getThemeBgColor();
+extern int8_t currentScreenPage;
+extern int8_t fullScreenCell;
+
+// Spanish 3-letter Month abbreviations
+static const char *const SPANISH_MONTHS_SHORT[12] = {
+ "ENE", "FEB", "MAR", "ABR", "MAY", "JUN",
+ "JUL", "AGO", "SEP", "OCT", "NOV", "DIC"};
+
+// Previous values for flicker-free redrawing
+static uint8_t oldClockHour = 255;
+static uint8_t oldClockMin = 255;
+static uint8_t oldClockSec = 255;
+static uint8_t oldClockDay = 255;
+static uint8_t oldClockMonth = 255;
+static uint16_t oldClockYear = 0;
+
+// Draw Full-Width Date Card (Bottom) - Clean, prominent, automotive badge
+// layout
+inline void drawDateCardContent(int cardX, int cardY, int cardW, int cardH,
+ uint16_t cardBg, int x_offset = 0) {
+ if (cardX + cardW <= 0 || cardX >= 320)
+ return;
+
+ uint16_t priCol = getThemePrimaryColor();
+ uint16_t cardBorder = 0x2945;
+ uint8_t wday = gpsDayOfWeek % 7;
+ uint8_t mIdx = (gpsMonth >= 1 && gpsMonth <= 12) ? (gpsMonth - 1) : 0;
+ String mShort = SPANISH_MONTHS_SHORT[mIdx];
+ String dayStr = (gpsDay < 10) ? "0" + String(gpsDay) : String(gpsDay);
+
+ // Left vertical accent bar
+ tft.fillRoundRect(cardX + 6, cardY + 8, 4, cardH - 16, 2, priCol);
+
+ // --- CALENDAR BADGE (Left side: 80x82 px) ---
+ int bx = cardX + 20;
+ int by = cardY + 14;
+ int bw = 80;
+ int bh = 82;
+
+ // Badge body
+ tft.fillRoundRect(bx, by, bw, bh, 6, 0x18C3);
+ tft.drawRoundRect(bx, by, bw, bh, 6, cardBorder);
+
+ // Badge top header (Month abbreviated in Primary Color)
+ tft.fillRoundRect(bx, by, bw, 24, 6, priCol);
+ tft.fillRect(bx, by + 18, bw, 6, priCol); // flatten lower header corners
+ tft.setTextColor(TFT_BLACK, priCol);
+ tft.drawCentreString(mShort.c_str(), bx + (bw / 2), by + 4, 2);
+
+ // Badge body: Big Day Number in DSEG7 34pt
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(TFT_WHITE, 0x18C3);
+ tft.setTextPadding(0);
+ tft.drawCentreString(dayStr.c_str(), bx + (bw / 2), by + 34, 1);
+ tft.unloadFont();
+
+ // --- DATE DETAILS (Right side) ---
+ int tx = cardX + 116;
+
+ tft.loadFont(Latin_Sans_Bold_26);
+ tft.setTextPadding(0);
+
+ // Day of week in Latin Sans Bold 26 (Smooth antialiased, White, uppercase)
+ tft.setTextColor(TFT_WHITE, cardBg);
+ tft.drawString(SPANISH_DAYS[wday], tx, cardY + 22, 1);
+
+ // Elegant two-tone horizontal dividing line
+ tft.drawFastHLine(tx, cardY + 54, 180, cardBorder);
+ tft.drawFastHLine(tx, cardY + 54, 50, priCol);
+
+ // Year in Latin Sans Bold 26 (Smooth antialiased, Primary color)
+ tft.setTextColor(priCol, cardBg);
+ tft.drawString(String(gpsYear).c_str(), tx, cardY + 64, 1);
+
+ tft.unloadFont();
+}
+
+// Authentic DSEG7 antialiased dot alpha matrix (from
+// DSEG7_Modern_BoldItalic_80)
+static const uint8_t DSEG7_DOT_ALPHA[11][13] PROGMEM = {
+ {0x21, 0xEB, 0xEE, 0xED, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEC, 0xEE, 0xD7,
+ 0x0A},
+ {0x35, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDC,
+ 0x00},
+ {0x47, 0xFE, 0xFB, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFB, 0xFE, 0xC5,
+ 0x00},
+ {0x5C, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xB2,
+ 0x00},
+ {0x71, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x9E,
+ 0x00},
+ {0x86, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x88,
+ 0x00},
+ {0x9C, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x73,
+ 0x00},
+ {0xB1, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0x5E,
+ 0x00},
+ {0xC5, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0x49,
+ 0x00},
+ {0xD7, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x35,
+ 0x00},
+ {0xE6, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 0x24,
+ 0x00},
+};
+
+// Helper: Draw authentic 7-segment italic colon dots with true anti-aliasing
+// matching DSEG7 typography
+inline void draw7SegDot(TFT_eSPI &dst, int x0, int y0, uint16_t color,
+ uint16_t bgCol, int clip_h = 240) {
+ dst.startWrite();
+ for (int dy = 0; dy < 11; dy++) {
+ int curY = y0 + dy;
+ if (curY < 0 || curY >= clip_h)
+ continue;
+
+ int fl = 0;
+ int fxs = 0;
+ for (int dx = 0; dx < 13; dx++) {
+ uint8_t a = pgm_read_byte(&(DSEG7_DOT_ALPHA[dy][dx]));
+ int curX = x0 + dx;
+ if (a == 0) {
+ if (fl) {
+ dst.drawFastHLine(fxs, curY, fl, color);
+ fl = 0;
+ }
+ dst.drawPixel(curX, curY, bgCol);
+ } else if (a >= 235) {
+ if (fl == 0)
+ fxs = curX;
+ fl++;
+ } else {
+ if (fl) {
+ dst.drawFastHLine(fxs, curY, fl, color);
+ fl = 0;
+ }
+ dst.drawPixel(curX, curY, dst.alphaBlend(a, color, bgCol));
+ }
+ }
+ if (fl) {
+ dst.drawFastHLine(fxs, curY, fl, color);
+ }
+ }
+ dst.endWrite();
+}
+
+inline void draw7SegColon(TFT_eSPI &dst, int clkX, int clkY, uint16_t color,
+ uint16_t bgCol = 0x10A2, int clip_h = 240) {
+ // Top dot: centered at clkX + 156.5, font row 19..29 (y = clkY + 30)
+ draw7SegDot(dst, clkX + 151, clkY + 30, color, bgCol, clip_h);
+ // Bottom dot: centered at clkX + 153.5, font row 53..63 (y = clkY + 64)
+ draw7SegDot(dst, clkX + 148, clkY + 64, color, bgCol, clip_h);
+}
+
+// Synchronize tracking variables without touching TFT display (zero flicker)
+inline void syncGpsClockTrackers() {
+ oldClockHour = gpsHour;
+ oldClockMin = gpsMinute;
+ oldClockSec = gpsSecond;
+ oldClockDay = gpsDay;
+ oldClockMonth = gpsMonth;
+ oldClockYear = gpsYear;
+}
+
+// Draw full Page 2 (GPS Clock Screen)
+inline void drawGpsClockScreen(int x_offset = 0) {
+ if (x_offset == 0) {
+ tft.fillScreen(TFT_BLACK);
+ } else {
+ tft.fillRect(0, 0, x_offset, 240, TFT_BLACK);
+ }
+
+ uint16_t priCol = getThemePrimaryColor();
+ uint16_t cardBg = 0x10A2; // Dark graphite navy
+ uint16_t cardBorder = 0x2945; // Subtle edge border
+ bool synced = isGpsSynced();
+
+ // 1. TOP BAR (y = 0 to 18)
+ // Center: 4 Pagination Indicator Dots [○ ○ ● ○] (Page 3 active)
+ tft.fillCircle(139 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive
+ tft.fillCircle(153 + x_offset, 2, 2, 0x5AEB); // Page 2 inactive
+ tft.fillCircle(167 + x_offset, 2, 2, priCol); // Page 3 active
+ tft.fillCircle(181 + x_offset, 2, 2, 0x5AEB); // Page 4 inactive
+
+ // 2. MAIN CLOCK CARD (y = 19 to 121, height = 102, width = 312, x = 4)
+ int clkX = 4 + x_offset;
+ int clkY = 19;
+ int clkW = 312;
+ int clkH = 102;
+
+ tft.fillRoundRect(clkX, clkY, clkW, clkH, 6, cardBg);
+ tft.drawRoundRect(clkX, clkY, clkW, clkH, 6, cardBorder);
+
+ // Left vertical accent bar
+ tft.fillRoundRect(clkX + 6, clkY + 8, 4, clkH - 16, 2, priCol);
+
+ // Digits: Hours HH (Right aligned to clkX + 142) & Minutes MM (Left aligned
+ // to clkX + 170)
+ String hStr = (gpsHour < 10) ? "0" + String(gpsHour) : String(gpsHour);
+ String mStr = (gpsMinute < 10) ? "0" + String(gpsMinute) : String(gpsMinute);
+
+ tft.loadFont(DSEG7_Modern_BoldItalic_80);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(0);
+ tft.drawRightString(hStr.c_str(), clkX + 142, clkY + 11, 1);
+ tft.drawString(mStr.c_str(), clkX + 170, clkY + 11, 1);
+ tft.unloadFont();
+
+ // Blinking Colon Dots between HH and MM (italic 7-segment style with
+ // anti-aliasing)
+ uint16_t colonCol = (gpsSecond % 2 == 0) ? priCol : 0x18C3;
+ draw7SegColon(tft, clkX, clkY, colonCol, cardBg);
+
+ // 3. FULL-WIDTH DATE CARD (y = 125 to 235, height = 110, width = 312, x = 4)
+ int dateX = 4 + x_offset;
+ int dateY = 125;
+ int dateW = 312;
+ int dateH = 110;
+ tft.fillRoundRect(dateX, dateY, dateW, dateH, 6, cardBg);
+ tft.drawRoundRect(dateX, dateY, dateW, dateH, 6, cardBorder);
+ drawDateCardContent(dateX, dateY, dateW, dateH, cardBg, x_offset);
+
+ // Sync state trackers
+ syncGpsClockTrackers();
+}
+
+// 100% Flicker-Free Live Refresh for resting Page 2
+inline void updateGpsClockScreen(bool forceRedraw = false) {
+ if (currentScreenPage != 2 || fullScreenCell != -1)
+ return;
+
+ uint16_t priCol = getThemePrimaryColor();
+ uint16_t cardBg = 0x10A2;
+ int clkX = 4;
+ int clkY = 19;
+
+ // 1. Seconds change: update colon dots only (no seconds text)
+ if (forceRedraw || gpsSecond != oldClockSec) {
+ oldClockSec = gpsSecond;
+
+ // Colon dots (italic 7-segment style with anti-aliasing)
+ uint16_t colonCol = (gpsSecond % 2 == 0) ? priCol : 0x18C3;
+ draw7SegColon(tft, clkX, clkY, colonCol, cardBg);
+ }
+
+ // 2. Minutes change: update MM digits
+ if (forceRedraw || gpsMinute != oldClockMin) {
+ oldClockMin = gpsMinute;
+ tft.fillRect(clkX + 166, clkY + 10, 142, 82, cardBg);
+ String mStr =
+ (gpsMinute < 10) ? "0" + String(gpsMinute) : String(gpsMinute);
+ tft.loadFont(DSEG7_Modern_BoldItalic_80);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(0);
+ tft.drawString(mStr.c_str(), clkX + 170, clkY + 11, 1);
+ tft.unloadFont();
+ }
+
+ // 3. Hours change: update HH digits
+ if (forceRedraw || gpsHour != oldClockHour) {
+ oldClockHour = gpsHour;
+ tft.fillRect(clkX + 12, clkY + 10, 136, 82, cardBg);
+ String hStr = (gpsHour < 10) ? "0" + String(gpsHour) : String(gpsHour);
+ tft.loadFont(DSEG7_Modern_BoldItalic_80);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(0);
+ tft.drawRightString(hStr.c_str(), clkX + 142, clkY + 11, 1);
+ tft.unloadFont();
+ }
+
+ // 4. Date changed (midnight rollover): redraw full date card
+ if (forceRedraw || gpsDay != oldClockDay || gpsMonth != oldClockMonth ||
+ gpsYear != oldClockYear) {
+ oldClockDay = gpsDay;
+ oldClockMonth = gpsMonth;
+ oldClockYear = gpsYear;
+ int dateX = 4;
+ int dateY = 125;
+ int dateW = 312;
+ int dateH = 110;
+ tft.fillRoundRect(dateX, dateY, dateW, dateH, 6, cardBg);
+ tft.drawRoundRect(dateX, dateY, dateW, dateH, 6, 0x2945);
+ drawDateCardContent(dateX, dateY, dateW, dateH, cardBg, 0);
+ }
+}
+
+// Render Page 2 into double-buffered sprite slice for slide / fade transitions
+inline void renderPage2ToSprite(TFT_eSprite &s, int x_offset, int y_slice,
+ int slice_h) {
+ uint16_t priCol = getThemePrimaryColor();
+ uint16_t cardBg = 0x10A2;
+ uint16_t cardBorder = 0x2945;
+
+ // 1. TOP BAR in top slice (pagination dots rendered by caller or top bar)
+
+ // 2. MAIN CLOCK CARD (y = 19 to 121)
+ int clkX = 4 + x_offset;
+ int clkY = 19 - y_slice;
+ int clkW = 312;
+ int clkH = 102;
+
+ if (clkX + clkW > 0 && clkX < 320 && clkY + clkH > 0 && clkY < slice_h) {
+ s.fillRoundRect(clkX, clkY, clkW, clkH, 6, cardBg);
+ s.drawRoundRect(clkX, clkY, clkW, clkH, 6, cardBorder);
+ s.fillRoundRect(clkX + 6, clkY + 8, 4, clkH - 16, 2, priCol);
+
+ // Colon dots during transition (italic 7-segment style with anti-aliasing)
+ draw7SegColon(s, clkX, clkY, priCol, cardBg, slice_h);
+
+ // Digits: Hours HH (Right aligned to clkX + 142) & Minutes MM (Left aligned
+ // to clkX + 170)
+ String hStr = (gpsHour < 10) ? "0" + String(gpsHour) : String(gpsHour);
+ String mStr =
+ (gpsMinute < 10) ? "0" + String(gpsMinute) : String(gpsMinute);
+
+ int digitY = clkY + 11;
+ if (digitY + 75 > 0 && digitY < slice_h) {
+ s.setTextWrap(false, false);
+ s.setTextColor(priCol, cardBg);
+ s.setTextPadding(0);
+ s.drawRightString(hStr.c_str(), clkX + 142, digitY, 8);
+ s.drawString(mStr.c_str(), clkX + 170, digitY, 8);
+ }
+ }
+
+ // 3. FULL-WIDTH DATE CARD (y = 125 to 235) - Calendar Badge + Details
+ int dateX = 4 + x_offset;
+ int dateY = 125 - y_slice;
+ int dateW = 312;
+ int dateH = 110;
+
+ if (dateX + dateW > 0 && dateX < 320 && dateY + dateH > 0 &&
+ dateY < slice_h) {
+ s.fillRoundRect(dateX, dateY, dateW, dateH, 6, cardBg);
+ s.drawRoundRect(dateX, dateY, dateW, dateH, 6, cardBorder);
+ s.fillRoundRect(dateX + 6, dateY + 8, 4, dateH - 16, 2, priCol);
+
+ // Badge container
+ int bx = dateX + 20;
+ int by = dateY + 14;
+ int bw = 80;
+ int bh = 82;
+ if (by + bh > 0 && by < slice_h) {
+ s.fillRoundRect(bx, by, bw, bh, 6, 0x18C3);
+ s.drawRoundRect(bx, by, bw, bh, 6, cardBorder);
+
+ // Header strip
+ s.fillRoundRect(bx, by, bw, 24, 6, priCol);
+ s.fillRect(bx, by + 18, bw, 6, priCol);
+ uint8_t mIdx = (gpsMonth >= 1 && gpsMonth <= 12) ? (gpsMonth - 1) : 0;
+ s.setTextColor(TFT_BLACK, priCol);
+ s.drawCentreString(SPANISH_MONTHS_SHORT[mIdx], bx + (bw / 2), by + 4, 2);
+
+ // Badge day number
+ String dayStr = (gpsDay < 10) ? "0" + String(gpsDay) : String(gpsDay);
+ s.fontLoaded = true;
+ s.setTextColor(TFT_WHITE, 0x18C3);
+ s.setTextPadding(0);
+ s.drawCentreString(dayStr.c_str(), bx + (bw / 2), by + 34, 1);
+ s.fontLoaded = false;
+ }
+
+ int tx = dateX + 116;
+ uint8_t wday = gpsDayOfWeek % 7;
+ int dwY = dateY + 22;
+ if (dwY + 26 > 0 && dwY < slice_h) {
+ s.setTextColor(TFT_WHITE, cardBg);
+ s.drawString(SPANISH_DAYS[wday], tx, dwY, 4);
+ }
+
+ int divY = dateY + 54;
+ if (divY > 0 && divY < slice_h) {
+ s.drawFastHLine(tx, divY, 180, cardBorder);
+ s.drawFastHLine(tx, divY, 50, priCol);
+ }
+
+ int yrY = dateY + 64;
+ if (yrY + 26 > 0 && yrY < slice_h) {
+ s.setTextColor(priCol, cardBg);
+ s.drawString(String(gpsYear).c_str(), tx, yrY, 4);
+ }
+ }
+}
diff --git a/sketch/obd2_gauge/gps_icon_aa_data.h b/sketch/obd2_gauge/gps_icon_aa_data.h
new file mode 100644
index 0000000..c68f13b
--- /dev/null
+++ b/sketch/obd2_gauge/gps_icon_aa_data.h
@@ -0,0 +1,160 @@
+// Generated from accurate vector model matching gps_icon.jpg / 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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
+
+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,
+};
diff --git a/sketch/obd2_gauge/gps_module.h b/sketch/obd2_gauge/gps_module.h
new file mode 100644
index 0000000..88d335b
--- /dev/null
+++ b/sketch/obd2_gauge/gps_module.h
@@ -0,0 +1,625 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include "gps_icon_aa_data.h"
+
+// Hardware Serial Mapping on ESP32-2432S028 (CYD)
+// Conector UART (P1):
+// Pin 1: Vin 5V (Alimentacion directa USB / Regulador)
+// Pin 2: GPIO 1 (TX0 del ESP32, envia comandos a RX del GPS)
+// Pin 3: GPIO 3 (RX0 del ESP32, recibe sentencias NMEA desde TX del GPS)
+// Pin 4: GND (Masa comun)
+#ifndef GPS_RX_PIN
+#define GPS_RX_PIN 3
+#endif
+#ifndef GPS_TX_PIN
+#define GPS_TX_PIN 1
+#endif
+#define GPS_BAUDRATE 9600
+
+// GPS Hardware Serial en ESP32 UART0 (Hardware Serial nativo de pines 3 y 1)
+#define gpsSerial Serial
+static TinyGPSPlus gps;
+
+// Timezone offset for Argentina / ART (UTC-3)
+#define DEFAULT_TIMEZONE_OFFSET -3
+
+// Spanish Day and Month names
+const char *const SPANISH_DAYS[7] = {
+ "DOMINGO", "LUNES", "MARTES", "MIERCOLES", "JUEVES", "VIERNES", "SABADO"};
+
+const char *const SPANISH_MONTHS[12] = {
+ "ENERO", "FEBRERO", "MARZO", "ABRIL", "MAYO", "JUNIO",
+ "JULIO", "AGOSTO", "SEPTIEMBRE", "OCTUBRE", "NOVIEMBRE", "DICIEMBRE"};
+
+const uint8_t DAYS_IN_MONTH_TABLE[12] = {31, 28, 31, 30, 31, 30,
+ 31, 31, 30, 31, 30, 31};
+
+// GPS State & Telemetry
+static int8_t gpsTimezoneOffset = DEFAULT_TIMEZONE_OFFSET;
+static uint8_t gpsHour = 18;
+static uint8_t gpsMinute = 0;
+static uint8_t gpsSecond = 0;
+static uint16_t gpsYear = 2026;
+static uint8_t gpsMonth = 9;
+static uint8_t gpsDay = 25;
+static uint8_t gpsDayOfWeek = 5; // 0=Dom, 1=Lun, ..., 5=Vie, 6=Sab
+static bool gpsSynced = false;
+static bool gpsHasFix = false;
+static uint8_t gpsSats = 0;
+static float gpsSpeedKmph = 0.0f;
+static float gpsAltitudeM = 0.0f;
+static float gpsCourseDeg = 0.0f;
+static bool gpsCourseValid = false;
+static double originLat = 0.0;
+static double originLon = 0.0;
+static bool hasOriginLocation = false;
+static bool gpsMovedDistanceReached = false;
+static const double GPS_MIN_MOVEMENT_METERS = 50.0;
+static const float GPS_MAX_HDOP_FOR_COURSE = 2.5f; // Umbral maximo de HDOP para considerar rumbo valido
+static const float GPS_MAX_HDOP_FOR_ALT = 2.2f; // Umbral maximo de HDOP para considerar altitud valida
+static const uint8_t GPS_MIN_SATS_FOR_ALT = 4; // Minimo 4 satelites para Fix 3D real
+static bool gpsAltitudeValid = false;
+static float gpsHdop = 99.9f;
+static double gpsLatitude = 0.0;
+static double gpsLongitude = 0.0;
+static bool gpsLocationValid = false;
+static uint32_t lastGpsSpeedTime = 0;
+static uint32_t lastGpsFixTime = 0;
+static bool gpsIsSleeping = false;
+static uint32_t lastSecTicker = 0;
+static uint32_t lastGpsByteTime = 0;
+
+// GPS sync state
+inline bool isGpsSynced() { return gpsSynced && (gpsSats >= 3); }
+
+// Compass direction helpers
+inline const char *getCompassDirectionShort(float deg) {
+ if (deg < 0.0f)
+ deg += 360.0f;
+ if (deg >= 360.0f)
+ deg -= 360.0f;
+ const char *const dirs[] = {"N", "NE", "E", "SE", "S", "SO", "O", "NO"};
+ int idx = (int)((deg + 22.5f) / 45.0f) % 8;
+ return dirs[idx];
+}
+
+inline const char *getCompassDirectionFull(float deg) {
+ if (deg < 0.0f)
+ deg += 360.0f;
+ if (deg >= 360.0f)
+ deg -= 360.0f;
+ const char *const dirs[] = {"NORTE", "NORESTE", "ESTE", "SURESTE",
+ "SUR", "SUROESTE", "OESTE", "NOROESTE"};
+ int idx = (int)((deg + 22.5f) / 45.0f) % 8;
+ return dirs[idx];
+}
+
+// Leap year check
+inline bool isLeapYearGps(uint16_t year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+}
+
+// Days in month calculation
+inline uint8_t getDaysInMonthGps(uint8_t month, uint16_t year) {
+ if (month < 1 || month > 12)
+ return 30;
+ if (month == 2 && isLeapYearGps(year))
+ return 29;
+ return DAYS_IN_MONTH_TABLE[month - 1];
+}
+
+// Sakamoto's algorithm for day of week (0 = Sunday, 1 = Monday, ..., 6 =
+// Saturday)
+inline uint8_t calcDayOfWeekGps(int y, int m, int d) {
+ static const int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+ if (m < 3)
+ y -= 1;
+ return (y + y / 4 - y / 100 + y / 400 + t[m - 1] + d) % 7;
+}
+
+// Configure GPS module (U-blox UBX + MediaTek PMTK + Casic PCAS)
+inline void configureGpsModule() {
+ // A. U-blox UBX-CFG-NAV5: Dynamic platform model = 4 (Automotive / Coche)
+ const uint8_t ubxNav5Auto[] = {
+ 0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x04, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00,
+ 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x16, 0xDC};
+ gpsSerial.write(ubxNav5Auto, sizeof(ubxNav5Auto));
+ gpsSerial.flush();
+ delay(15);
+
+ // B. Standard NMEA sentences configuration (GGA, RMC, GSA, GSV, VTG)
+ // MediaTek / MT3339 / MT3333
+ gpsSerial.println("$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28");
+ // Zhongke Micro CASIC / ATGM336H: Activar todas las constelaciones y sentencias
+ gpsSerial.println("$PCAS03,1,1,1,1,1,1,0,0,0,0,,,0,0*03");
+ gpsSerial.flush();
+}
+
+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"};
+
+static uint32_t activeGpsBaud = 38400;
+static bool gpsUartLocked = false;
+static uint8_t gpsBaudScanIdx = 0;
+static uint32_t lastBaudSwitchTime = 0;
+static uint32_t lastValidSentenceTime = 0;
+static char gpsDiagLabel[28] = "EN ESPERA DE SENAL";
+
+// Initialize GPS Hardware Serial UART0 (conector UART: Pin 3 GPIO 3, Pin 2 GPIO 1)
+inline void setupGPS() {
+ gpsSerial.end();
+ delay(15);
+ gpsSerial.setRxBufferSize(512); // Must be before begin()!
+
+ // Cargar baudrate guardado en memoria no volatil (o arrancar en 38400 por defecto)
+ activeGpsBaud = pref.getUInt("gps_baud", 38400);
+ if (activeGpsBaud != 9600 && activeGpsBaud != 38400 && activeGpsBaud != 115200 && activeGpsBaud != 57600) {
+ activeGpsBaud = 38400;
+ }
+
+ // Ubicar indice correspondiente
+ gpsBaudScanIdx = 0;
+ for (uint8_t i = 0; i < 4; i++) {
+ if (CANDIDATE_BAUDS[i] == activeGpsBaud) {
+ gpsBaudScanIdx = i;
+ break;
+ }
+ }
+
+ gpsSerial.begin(activeGpsBaud, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
+ delay(25);
+ while (gpsSerial.available() > 0) {
+ gpsSerial.read();
+ }
+
+ lastSecTicker = millis();
+ lastGpsByteTime = millis();
+ lastBaudSwitchTime = millis();
+ lastValidSentenceTime = 0;
+ gpsUartLocked = false;
+ snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "%s", CANDIDATE_LABELS[gpsBaudScanIdx]);
+
+ // Check if ESP32 internal RTC already has valid time
+ time_t now = time(NULL);
+ if (now > 1600000000) {
+ struct tm *loc = gmtime(&now);
+ if (loc) {
+ gpsHour = loc->tm_hour;
+ gpsMinute = loc->tm_min;
+ gpsSecond = loc->tm_sec;
+ gpsDay = loc->tm_mday;
+ gpsMonth = loc->tm_mon + 1;
+ gpsYear = loc->tm_year + 1900;
+ gpsDayOfWeek = loc->tm_wday;
+ gpsSynced = true;
+ }
+ }
+
+#ifdef SERIAL_DEBUG
+ Serial.printf(
+ "[GPS] HardwareSerial UART0 inicializado en RX:%d, TX:%d a %d bps\n",
+ GPS_RX_PIN, GPS_TX_PIN, activeGpsBaud);
+#endif
+}
+
+// Put GPS module to sleep (UBX backup mode + NMEA Standby for MediaTek &
+// Casic/ATGM336)
+inline void gpsSleep() {
+ if (gpsIsSleeping)
+ return;
+
+ // 1. NMEA Standby command for MediaTek (MTK3339 / MT3333 / Adafruit GPS)
+ gpsSerial.println("$PMTK161,0*28");
+
+ // 2. CASIC Standby command for ATGM336H / Zhongke Micro
+ gpsSerial.println("$PCAS12,1*5A");
+ gpsSerial.flush();
+ delay(10);
+
+ // 3. UBX-RXM-PMREQ command: put U-blox GPS to sleep (Backup mode)
+ // IMPORTANTE: Debe enviarse al final de todo ya que U-blox tiene "Wake-on-UART"
+ // y cualquier carácter o flanco posterior en la línea RX lo despierta inmediatamente.
+ const uint8_t ubxSleep[] = {0xB5, 0x62, 0x02, 0x41, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4D, 0x3B};
+ gpsSerial.write(ubxSleep, sizeof(ubxSleep));
+ gpsSerial.flush();
+
+ gpsIsSleeping = true;
+ gpsLocationValid = false;
+ gpsSpeedKmph = 0.0f;
+}
+
+// Wake on UART: send burst of bytes to awaken U-blox / MTK / ATGM336
+inline void gpsWake() {
+ if (!gpsIsSleeping)
+ return;
+
+ // 1. Wake U-blox: burst of dummy bytes to trigger Wake-on-UART
+ for (uint8_t i = 0; i < 32; i++) {
+ gpsSerial.write(0xFF);
+ }
+
+ // 2. Wake MediaTek / NMEA: send CR/LF dummy string
+ gpsSerial.println();
+ gpsSerial.println("$PMTK000*32");
+ gpsSerial.flush();
+
+ // Clear serial RX buffer so corrupted burst bytes don't confuse TinyGPSPlus
+ delay(30);
+ while (gpsSerial.available() > 0) {
+ gpsSerial.read();
+ }
+
+ gpsIsSleeping = false;
+ lastSecTicker = millis();
+
+ // Restore atomic time from ESP32 internal RTC immediately upon wake
+ time_t now = time(NULL);
+ if (now > 1600000000) {
+ struct tm *loc = gmtime(&now);
+ if (loc) {
+ gpsHour = loc->tm_hour;
+ gpsMinute = loc->tm_min;
+ gpsSecond = loc->tm_sec;
+ gpsDay = loc->tm_mday;
+ gpsMonth = loc->tm_mon + 1;
+ gpsYear = loc->tm_year + 1900;
+ gpsDayOfWeek = loc->tm_wday;
+ }
+ }
+
+#ifdef SERIAL_DEBUG
+ Serial.println(F(">>> [GPS] Modulo GPS despertado (Wake-up)."));
+#endif
+}
+
+// Process incoming NMEA stream from GPS
+inline void processGPS() {
+ if (gpsIsSleeping)
+ return;
+
+ // 1. Process incoming bytes directly
+ while (gpsSerial.available() > 0) {
+ char c = (char)gpsSerial.read();
+ gps.encode(c);
+ lastGpsByteTime = millis();
+ }
+
+ // 2. Lock check: Verified valid NMEA sentence with valid checksum!
+ if (gps.passedChecksum() >= 1) {
+ lastValidSentenceTime = millis();
+ if (!gpsUartLocked) {
+ 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);
+ configureGpsModule();
+ }
+ }
+
+ // 3. Auto-Scan across bauds only if not yet locked
+ if (!gpsUartLocked) {
+ if (millis() - lastBaudSwitchTime > 1800) {
+ lastBaudSwitchTime = millis();
+ gpsBaudScanIdx = (gpsBaudScanIdx + 1) % 4;
+ activeGpsBaud = CANDIDATE_BAUDS[gpsBaudScanIdx];
+ gpsSerial.end();
+ delay(15);
+ gpsSerial.setRxBufferSize(512);
+ gpsSerial.begin(activeGpsBaud, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
+ while (gpsSerial.available() > 0) {
+ gpsSerial.read();
+ }
+ gps = TinyGPSPlus();
+ snprintf(gpsDiagLabel, sizeof(gpsDiagLabel), "%s", CANDIDATE_LABELS[gpsBaudScanIdx]);
+ }
+ } else {
+ // Si pasan mas de 10 segundos sin recibir NINGUNA trama valida, re-escanear
+ if (millis() - lastValidSentenceTime > 10000) {
+ gpsUartLocked = false;
+ lastBaudSwitchTime = millis();
+ }
+ }
+
+ // Update Satellites count
+ if (gps.satellites.isValid()) {
+ gpsSats = gps.satellites.value();
+ }
+
+ // Update HDOP & Altitude
+ if (gps.hdop.isValid()) {
+ gpsHdop = gps.hdop.hdop();
+ }
+ if (gps.altitude.isValid() && (gps.altitude.age() < 3000) &&
+ (gpsSats >= GPS_MIN_SATS_FOR_ALT) && (gpsHdop <= GPS_MAX_HDOP_FOR_ALT)) {
+ float rawAlt = (float)gps.altitude.meters();
+ if (!gpsAltitudeValid || gpsAltitudeM == 0.0f) {
+ gpsAltitudeM = rawAlt;
+ } else {
+ // Filtro exponencial suave (EMA 80/20) para amortiguar ruido vertical
+ gpsAltitudeM = (gpsAltitudeM * 0.80f) + (rawAlt * 0.20f);
+ }
+ gpsAltitudeValid = true;
+ } else {
+ if (gpsHdop > GPS_MAX_HDOP_FOR_ALT || gpsSats < GPS_MIN_SATS_FOR_ALT || !gps.altitude.isValid()) {
+ gpsAltitudeValid = false;
+ }
+ }
+
+ // Update Coordinates and track initial position for movement threshold (>= 50m)
+ if (gps.location.isValid()) {
+ gpsLatitude = gps.location.lat();
+ gpsLongitude = gps.location.lng();
+ gpsLocationValid = (gps.location.age() < 4000);
+
+ // Fijar punto de origen la primera vez que se sincroniza posicion
+ if (!hasOriginLocation && isGpsSynced()) {
+ originLat = gpsLatitude;
+ originLon = gpsLongitude;
+ hasOriginLocation = true;
+ }
+ } else {
+ gpsLocationValid = false;
+ }
+
+ // Update Speed & Course from GPS
+ if (gps.speed.isValid() && gps.speed.age() < 2500) {
+ float rawSpd = (float)gps.speed.kmph();
+ // Filtro de ruido en reposo (Deadband automotriz):
+ // Si la velocidad es menor a 4.5 km/h, se considera vehiculo detenido (0 km/h)
+ // para evitar fluctuaciones Doppler y ruido de antena cuando el auto esta parado.
+ if (rawSpd < 4.5f) {
+ gpsSpeedKmph = 0.0f;
+ // CLAVE: Mientras este quieto y aun no haya superado el umbral de movimiento,
+ // anclamos el punto de origen a la posicion actual para que la deriva de la antena
+ // en reposo NUNCA acumule distancia ni active el rumbo.
+ if (!gpsMovedDistanceReached && gpsLocationValid) {
+ originLat = gpsLatitude;
+ originLon = gpsLongitude;
+ }
+ } else {
+ // Filtro exponencial suave (EMA) para suavizar saltos y transiciones:
+ if (gpsSpeedKmph < 0.1f) {
+ gpsSpeedKmph = rawSpd; // Respuesta instantanea al arrancar
+ } else {
+ gpsSpeedKmph = (gpsSpeedKmph * 0.35f) + (rawSpd * 0.65f); // 65% lectura actual, 35% anterior
+ }
+
+ // Si el auto esta rodando y la señal es precisa (HDOP bajo), comprobar si ya supero los 50 metros desde el origen
+ if (hasOriginLocation && !gpsMovedDistanceReached && gpsLocationValid && (gpsHdop <= GPS_MAX_HDOP_FOR_COURSE)) {
+ double distFromOrigin = TinyGPSPlus::distanceBetween(originLat, originLon, gpsLatitude, gpsLongitude);
+ if (distFromOrigin >= GPS_MIN_MOVEMENT_METERS) {
+ gpsMovedDistanceReached = true;
+ }
+ }
+ }
+ lastGpsSpeedTime = millis();
+ if (gpsSats >= 3) {
+ gpsHasFix = true;
+ }
+
+ // Actualizacion de Rumbo / Brujula (Heading / Course Over Ground):
+ // REQUISITO ESTRICTO:
+ // 1. Si no se recorrieron al menos 50 metros en movimiento real, NO muestra nada ("---").
+ // 2. Si el HDOP es alto (> 2.5), NO calcula rumbo para evitar derivas por antena o precision degradada.
+ // 3. Una vez superados los 50 metros y rodando (>= 4.5 km/h) con buen HDOP, calcula y muestra el rumbo real.
+ // 4. Al detenerse (habiendo recorrido ya los 50 metros), CONGELA el rumbo para que no oscile en reposo.
+ if (gpsMovedDistanceReached) {
+ if (rawSpd >= 4.5f && gps.course.isValid() && (gpsHdop <= GPS_MAX_HDOP_FOR_COURSE)) {
+ float newCourse = (float)gps.course.deg();
+ if (!gpsCourseValid) {
+ gpsCourseDeg = newCourse;
+ gpsCourseValid = true;
+ } else {
+ // Suavizado angular circular (maneja el cruce 0 <-> 360 grados sin tirones)
+ float diff = newCourse - gpsCourseDeg;
+ if (diff > 180.0f) diff -= 360.0f;
+ if (diff < -180.0f) diff += 360.0f;
+ gpsCourseDeg += diff * 0.45f;
+ if (gpsCourseDeg < 0.0f) gpsCourseDeg += 360.0f;
+ if (gpsCourseDeg >= 360.0f) gpsCourseDeg -= 360.0f;
+ }
+ }
+ }
+ }
+
+ // Update Atomic Time and Date with Timezone Offset
+ if (gps.time.isValid() && gps.date.isValid() &&
+ (gps.time.isUpdated() || gps.date.isUpdated())) {
+ struct tm t;
+ memset(&t, 0, sizeof(t));
+ t.tm_year = gps.date.year() - 1900;
+ t.tm_mon = gps.date.month() - 1;
+ t.tm_mday = gps.date.day();
+ t.tm_hour = gps.time.hour();
+ t.tm_min = gps.time.minute();
+ t.tm_sec = gps.time.second();
+ t.tm_isdst = 0;
+ time_t utc = mktime(&t);
+ time_t local = utc + ((int32_t)gpsTimezoneOffset * 3600);
+ struct tm *loc = gmtime(&local);
+ if (loc) {
+ gpsHour = loc->tm_hour;
+ gpsMinute = loc->tm_min;
+ gpsSecond = loc->tm_sec;
+ gpsDay = loc->tm_mday;
+ gpsMonth = loc->tm_mon + 1;
+ gpsYear = loc->tm_year + 1900;
+ gpsDayOfWeek = loc->tm_wday; // 0=Dom, 1=Lun, ..., 6=Sab
+ gpsSynced = true;
+ lastGpsFixTime = millis();
+ lastSecTicker = millis();
+
+ // Synchronize ESP32 internal system RTC clock (POSIX time)
+ struct timeval tv = {.tv_sec = local, .tv_usec = 0};
+ settimeofday(&tv, NULL);
+ }
+ }
+
+ // Smooth internal 1-second ticker (keeps clock ticking smoothly even if GPS
+ // drops a frame or indoor)
+ if (millis() - lastSecTicker >= 1000) {
+ uint32_t elapsed = (millis() - lastSecTicker) / 1000;
+ lastSecTicker += elapsed * 1000;
+ if (!gps.time.isUpdated()) {
+ gpsSecond += elapsed;
+ if (gpsSecond >= 60) {
+ uint8_t addM = gpsSecond / 60;
+ gpsSecond %= 60;
+ gpsMinute += addM;
+ if (gpsMinute >= 60) {
+ uint8_t addH = gpsMinute / 60;
+ gpsMinute %= 60;
+ gpsHour = (gpsHour + addH) % 24;
+ if (gpsHour == 0 && addH > 0) {
+ // Day rollover
+ gpsDay++;
+ uint8_t maxD = getDaysInMonthGps(gpsMonth, gpsYear);
+ if (gpsDay > maxD) {
+ gpsDay = 1;
+ gpsMonth++;
+ if (gpsMonth > 12) {
+ gpsMonth = 1;
+ gpsYear++;
+ }
+ }
+ gpsDayOfWeek = (gpsDayOfWeek + 1) % 7;
+ }
+ }
+ }
+ }
+ }
+}
+
+// True when GPS has satellite lock and is actively providing vehicle telemetry
+inline bool isGpsSpeedValid() {
+ return isGpsSynced() && (millis() - lastGpsByteTime < 3500);
+}
+
+// Get current GPS speed in km/h
+inline float getGpsSpeed() { return gpsSpeedKmph; }
+
+// Course / Heading valid state (only valid after moving >= 50 meters and with good HDOP)
+inline bool isGpsCourseValid() {
+ return gpsCourseValid && gpsMovedDistanceReached && (gpsHdop <= GPS_MAX_HDOP_FOR_COURSE);
+}
+
+// Altitude valid state (requires true 3D Fix: >= 4 sats and HDOP <= 2.2)
+inline bool isGpsAltitudeValid() {
+ return isGpsSynced() && gpsAltitudeValid && (gpsSats >= GPS_MIN_SATS_FOR_ALT) && (gpsHdop <= GPS_MAX_HDOP_FOR_ALT);
+}
+
+// =============================================================================
+// GPS DIAGNOSTICS & TELEMETRY
+// =============================================================================
+inline uint32_t getGpsCharsProcessed() { return gps.charsProcessed(); }
+inline uint32_t getGpsPassedChecksum() { return gps.passedChecksum(); }
+inline uint32_t getGpsFailedChecksum() { return gps.failedChecksum(); }
+inline bool isGpsSerialReceiving() {
+ return (gps.charsProcessed() > 0 && (millis() - lastGpsByteTime < 2500));
+}
+
+inline const char *getGpsDiagMessage() {
+ if (gpsIsSleeping)
+ return "MODO REPOSO";
+ if (!gpsUartLocked) {
+ return gpsDiagLabel;
+ }
+ if (millis() - lastGpsByteTime > 4000) {
+ return "SIN DATOS RX";
+ }
+ if (isGpsSynced()) {
+ return "PRECISION OPTIMA";
+ }
+ if (gpsSats > 0) {
+ return "SINTONIZANDO SATS";
+ }
+ return gpsDiagLabel;
+}
+
+// =============================================================================
+// ANIMATED SATELLITE ICON (Fiel y Exacto a gps_icon.jpg)
+// 3 Estados en Búsqueda:
+// 0: Satélite SIN ondas
+// 1: Satélite con PRIMERA onda
+// 2: Satélite con PRIMERA y SEGUNDA onda
+// -> vuelve a 0 (de nuevo satélite sin ondas)
+// Cuando fija satélites (gpsSats >= 3): queda fijo con ambas ondas encendidas
+// =============================================================================
+inline uint8_t getGpsWaveAnimState() {
+ if (isGpsSynced()) {
+ return 2; // Satélite con las dos ondas encendidas fijas
+ }
+ // Ciclo continuo de 3 pasos cada 350ms: 0 -> 1 -> 2 -> 0 -> 1 -> 2 ...
+ return (millis() / 350) % 3;
+}
+
+// Icono estandar antialiased (18x18 px) para Cuadrante 1 Velocidad, Reloj GPS y Barra Superior
+template
+inline void drawGpsSatelliteIcon(T &d, int x, int y, uint16_t bodyColor,
+ uint16_t waveColor, uint16_t bgColor,
+ uint8_t waveState) {
+ int idx = 0;
+ for (int row = 0; row < 18; row++) {
+ for (int col = 0; col < 18; col++, idx++) {
+ uint8_t a_body = pgm_read_byte(&GPS_BODY_AA_18[idx]);
+ uint8_t a_w1 = (waveState >= 1) ? pgm_read_byte(&GPS_WAVE1_AA_18[idx]) : 0;
+ uint8_t a_w2 = (waveState >= 2) ? pgm_read_byte(&GPS_WAVE2_AA_18[idx]) : 0;
+
+ uint8_t a_w_max = pgm_read_byte(&GPS_WAVE1_AA_18[idx]) | pgm_read_byte(&GPS_WAVE2_AA_18[idx]);
+ if (a_body < 10 && a_w_max < 10) continue;
+
+ uint16_t c = bgColor;
+ uint8_t a_w = (a_w1 > a_w2) ? a_w1 : a_w2;
+ if (a_w >= 10) {
+ c = (a_w >= 245) ? waveColor : tft.alphaBlend(a_w, waveColor, bgColor);
+ }
+ if (a_body >= 10) {
+ c = (a_body >= 245) ? bodyColor : tft.alphaBlend(a_body, bodyColor, c);
+ }
+ d.drawPixel(x + col, y + row, c);
+ }
+ }
+}
+
+// Icono grande antialiased (28x28 px) para Tarjeta 2 de Telemetria en gps_nav.h
+template
+inline void drawLargeGpsSatelliteIcon(T &d, int x, int y, uint16_t bodyColor,
+ uint16_t waveColor, uint16_t bgColor,
+ uint8_t waveState) {
+ int idx = 0;
+ for (int row = 0; row < 28; row++) {
+ for (int col = 0; col < 28; col++, idx++) {
+ uint8_t a_body = pgm_read_byte(&GPS_BODY_AA_28[idx]);
+ uint8_t a_w1 = (waveState >= 1) ? pgm_read_byte(&GPS_WAVE1_AA_28[idx]) : 0;
+ uint8_t a_w2 = (waveState >= 2) ? pgm_read_byte(&GPS_WAVE2_AA_28[idx]) : 0;
+
+ uint8_t a_w_max = pgm_read_byte(&GPS_WAVE1_AA_28[idx]) | pgm_read_byte(&GPS_WAVE2_AA_28[idx]);
+ if (a_body < 10 && a_w_max < 10) continue;
+
+ uint16_t c = bgColor;
+ uint8_t a_w = (a_w1 > a_w2) ? a_w1 : a_w2;
+ if (a_w >= 10) {
+ c = (a_w >= 245) ? waveColor : tft.alphaBlend(a_w, waveColor, bgColor);
+ }
+ if (a_body >= 10) {
+ c = (a_body >= 245) ? bodyColor : tft.alphaBlend(a_body, bodyColor, c);
+ }
+ d.drawPixel(x + col, y + row, c);
+ }
+ }
+}
+
diff --git a/sketch/obd2_gauge/gps_nav.h b/sketch/obd2_gauge/gps_nav.h
new file mode 100644
index 0000000..443e4db
--- /dev/null
+++ b/sketch/obd2_gauge/gps_nav.h
@@ -0,0 +1,502 @@
+#pragma once
+
+#include
+#include
+#include "gps_module.h"
+#include "DSEG7_Modern_BoldItalic_34.h"
+
+extern TFT_eSPI tft;
+extern uint16_t getThemePrimaryColor();
+extern uint16_t getThemeBgColor();
+extern int8_t currentScreenPage;
+extern int8_t fullScreenCell;
+
+// Previous values for flicker-free redrawing of Page 3
+static float oldNavAlt = -999.0f;
+static float oldNavCourse = -999.0f;
+static uint8_t oldNavSats = 255;
+static float oldNavHdop = -999.0f;
+static double oldNavLat = 0.0;
+static double oldNavLon = 0.0;
+static bool oldNavFix = false;
+
+static int lastNavAltRound = -999;
+static bool lastNavAltValid = false;
+static int lastNavCrsRound = -999;
+static bool lastNavHasCourse = false;
+static const char* lastNavCardDir = "";
+static uint8_t lastNavCardSats = 255;
+static bool lastNavSatsSync = false;
+static float lastNavCardHdop = -999.0f;
+static char lastNavCardDiag[32] = "";
+static bool lastNavDiagSynced = false;
+static uint8_t oldNavWaveState = 255;
+static bool oldNavSynced = false;
+static uint32_t lastNavPageEnterTime = 0;
+
+// Synchronize tracking variables without touching TFT display (zero flicker)
+inline void syncGpsNavTrackers() {
+ lastNavPageEnterTime = millis();
+ bool synced = isGpsSynced();
+ bool altValid = isGpsAltitudeValid();
+ lastNavAltRound = altValid ? (int)round(gpsAltitudeM) : -999;
+ lastNavAltValid = altValid;
+ oldNavAlt = gpsAltitudeM;
+
+ bool hasCourse = synced && isGpsCourseValid();
+ lastNavCrsRound = hasCourse ? (int)round(gpsCourseDeg) : -999;
+ lastNavHasCourse = hasCourse;
+ oldNavCourse = gpsCourseDeg;
+ lastNavCardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
+
+ lastNavCardSats = gpsSats;
+ lastNavSatsSync = synced;
+ oldNavSats = gpsSats;
+
+ lastNavCardHdop = gpsHdop;
+ oldNavHdop = gpsHdop;
+
+ strncpy(lastNavCardDiag, getGpsDiagMessage(), sizeof(lastNavCardDiag) - 1);
+ lastNavCardDiag[sizeof(lastNavCardDiag) - 1] = '\0';
+ lastNavDiagSynced = synced;
+
+ oldNavWaveState = getGpsWaveAnimState();
+ oldNavSynced = synced;
+
+ oldNavLat = gpsLatitude;
+ oldNavLon = gpsLongitude;
+ oldNavFix = synced;
+}
+
+// Large animated satellite icon faithful to gps_icon.jpg for the Navigation Dashboard
+inline void drawLargeSatelliteIcon(int x, int y, uint16_t color, uint8_t waveState = 2, uint16_t bgColor = 0x10A2) {
+ drawLargeGpsSatelliteIcon(tft, x, y, TFT_WHITE, color, bgColor, waveState);
+}
+
+// Large animated satellite icon for double-buffered sprite slice
+inline void drawLargeSatelliteIconSprite(TFT_eSprite &s, int x, int y, uint16_t color, uint8_t waveState = 2, uint16_t bgColor = 0x10A2) {
+ drawLargeGpsSatelliteIcon(s, x, y, TFT_WHITE, color, bgColor, waveState);
+}
+
+// Draw full Page 3 (GPS Navigation & Telemetry Screen: Altitud, Rumbo, Satélites, Coordenadas)
+inline void drawGpsNavScreen(int x_offset = 0) {
+ if (x_offset == 0) {
+ tft.fillScreen(TFT_BLACK);
+ } else {
+ tft.fillRect(0, 0, x_offset, 240, TFT_BLACK);
+ }
+
+ uint16_t priCol = getThemePrimaryColor();
+ uint16_t cardBg = 0x10A2; // Dark graphite navy
+ uint16_t cardBorder = 0x2945; // Subtle edge border
+ bool synced = isGpsSynced();
+
+ // 1. TOP BAR (y = 0 to 18)
+ // Left: Screen Title
+ tft.setTextColor(0x8410, TFT_BLACK);
+ tft.drawString("TELEMETRIA GPS", 8 + x_offset, 5, 1);
+
+ // Center: 4 Pagination Indicator Dots [○ ○ ○ ●] (Page 4 active)
+ tft.fillCircle(139 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive
+ tft.fillCircle(153 + x_offset, 2, 2, 0x5AEB); // Page 2 inactive
+ tft.fillCircle(167 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive
+ tft.fillCircle(181 + x_offset, 2, 2, priCol); // Page 4 active
+
+ // 2. CARD 0 (Top Left): ALTITUD (x = 4, y = 19, w = 152, h = 102)
+ int c0X = 4 + x_offset;
+ int c0Y = 19;
+ int cW = 152;
+ int cH1 = 102;
+ tft.fillRoundRect(c0X, c0Y, cW, cH1, 6, cardBg);
+ tft.drawRoundRect(c0X, c0Y, cW, cH1, 6, cardBorder);
+ tft.fillRoundRect(c0X + 6, c0Y + 8, 4, cH1 - 16, 2, priCol);
+
+ tft.setTextColor(0x8410, cardBg);
+ tft.drawString("ALTITUD", c0X + 16, c0Y + 8, 2);
+ tft.setTextColor(0x9CD3, cardBg);
+ tft.drawString("MSNM", c0X + 96, c0Y + 8, 2);
+
+ bool altValid = isGpsAltitudeValid();
+ String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---";
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(0);
+ tft.drawRightString(altStr.c_str(), c0X + 140, c0Y + 28, 1);
+ tft.unloadFont();
+
+ // 3. CARD 1 (Top Right): RUMBO / BRÚJULA (x = 164, y = 19, w = 152, h = 102)
+ int c1X = 164 + x_offset;
+ int c1Y = 19;
+ tft.fillRoundRect(c1X, c1Y, cW, cH1, 6, cardBg);
+ tft.drawRoundRect(c1X, c1Y, cW, cH1, 6, cardBorder);
+ tft.fillRoundRect(c1X + 6, c1Y + 8, 4, cH1 - 16, 2, priCol);
+
+ tft.setTextColor(0x8410, cardBg);
+ tft.drawString("RUMBO / BRUJULA", c1X + 16, c1Y + 8, 2);
+
+ bool hasCourse = synced && isGpsCourseValid();
+ String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---";
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(0);
+ tft.drawRightString(crsStr.c_str(), c1X + 130, c1Y + 28, 1);
+ tft.unloadFont();
+ if (hasCourse) {
+ tft.drawCircle(c1X + 137, c1Y + 33, 3, priCol);
+ tft.drawCircle(c1X + 137, c1Y + 33, 2, priCol);
+ } else {
+ tft.fillCircle(c1X + 137, c1Y + 33, 4, cardBg);
+ }
+
+ const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
+ tft.setTextColor(TFT_WHITE, cardBg);
+ tft.drawString(cardDir, c1X + 16, c1Y + 74, 2);
+
+ // 4. CARD 2 (Bottom Left): SATÉLITES GPS EN GRANDE (x = 4, y = 125, w = 152, h = 110)
+ int c2X = 4 + x_offset;
+ int c2Y = 125;
+ int cH2 = 110;
+ tft.fillRoundRect(c2X, c2Y, cW, cH2, 6, cardBg);
+ tft.drawRoundRect(c2X, c2Y, cW, cH2, 6, cardBorder);
+ tft.fillRoundRect(c2X + 6, c2Y + 8, 4, cH2 - 16, 2, priCol);
+
+ tft.setTextColor(0x8410, cardBg);
+ tft.drawString("SATELITES GPS", c2X + 16, c2Y + 8, 2);
+
+ // Large Satellite Icon (animated body + waves)
+ uint8_t waveState = getGpsWaveAnimState();
+ drawLargeSatelliteIcon(c2X + 16, c2Y + 32, priCol, waveState, cardBg);
+
+ // Large Satellites Count (DSEG7 34pt)
+ String satStr = String(gpsSats);
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(0);
+ tft.drawRightString(satStr.c_str(), c2X + 140, c2Y + 26, 1);
+ tft.unloadFont();
+
+ // Telemetry details: HDOP precision & live diagnostic status
+ String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---");
+ tft.setTextColor(TFT_WHITE, cardBg);
+ tft.drawString(hdopStr.c_str(), c2X + 16, c2Y + 68, 2);
+
+ tft.setTextColor(synced ? 0x9CD3 : priCol, cardBg);
+ tft.drawString(getGpsDiagMessage(), c2X + 16, c2Y + 90, 1);
+
+ // 5. CARD 3 (Bottom Right): COORDENADAS GPS (x = 164, y = 125, w = 152, h = 110)
+ int c3X = 164 + x_offset;
+ int c3Y = 125;
+ tft.fillRoundRect(c3X, c3Y, cW, cH2, 6, cardBg);
+ tft.drawRoundRect(c3X, c3Y, cW, cH2, 6, cardBorder);
+ tft.fillRoundRect(c3X + 6, c3Y + 8, 4, cH2 - 16, 2, priCol);
+
+ tft.setTextColor(0x8410, cardBg);
+ tft.drawString("COORDENADAS", c3X + 16, c3Y + 8, 2);
+
+ tft.setTextColor(0x8410, cardBg);
+ tft.drawString("LAT", c3X + 16, c3Y + 26, 1);
+ String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°";
+ tft.setTextColor(priCol, cardBg);
+ tft.drawString(latStr.c_str(), c3X + 16, c3Y + 38, 2);
+
+ tft.setTextColor(0x8410, cardBg);
+ tft.drawString("LON", c3X + 16, c3Y + 64, 1);
+ String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°";
+ tft.setTextColor(priCol, cardBg);
+ tft.drawString(lonStr.c_str(), c3X + 16, c3Y + 76, 2);
+
+ // Sync state trackers
+ syncGpsNavTrackers();
+}
+
+// 100% Flicker-Free Live Refresh for resting Page 3 (GPS Navigation & Telemetry)
+inline void updateGpsNavScreen(bool forceRedraw = false) {
+ if (currentScreenPage != 3 || fullScreenCell != -1) return;
+
+ // Post-transition stabilization: during the first 400ms after landing on Page 3,
+ // do not wipe or repaint cards over SPI. Any incoming serial bytes from GPS
+ // are safely absorbed into tracking variables so there is ZERO blackout/blink!
+ if (!forceRedraw && (millis() - lastNavPageEnterTime < 400)) {
+ syncGpsNavTrackers();
+ return;
+ }
+
+ uint16_t priCol = getThemePrimaryColor();
+ uint16_t cardBg = 0x10A2;
+ bool synced = isGpsSynced();
+
+ // 1. Altitude changed: update Card 0 digits only
+ bool altValid = isGpsAltitudeValid();
+ int curAltRound = altValid ? (int)round(gpsAltitudeM) : -999;
+ if (forceRedraw || curAltRound != lastNavAltRound || altValid != lastNavAltValid) {
+ lastNavAltRound = curAltRound;
+ lastNavAltValid = altValid;
+ oldNavAlt = gpsAltitudeM;
+ int c0X = 4;
+ int c0Y = 19;
+ String altStr = altValid ? String(curAltRound) : "---";
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(126);
+ tft.drawRightString(altStr.c_str(), c0X + 140, c0Y + 28, 1);
+ tft.setTextPadding(0);
+ tft.unloadFont();
+ }
+
+ // 2. Course / Heading changed: update Card 1
+ bool hasCourse = synced && isGpsCourseValid();
+ int curCrsRound = hasCourse ? (int)round(gpsCourseDeg) : -999;
+ int c1X = 164;
+ int c1Y = 19;
+
+ if (forceRedraw || curCrsRound != lastNavCrsRound || hasCourse != lastNavHasCourse) {
+ lastNavCrsRound = curCrsRound;
+ oldNavCourse = gpsCourseDeg;
+ lastNavHasCourse = hasCourse;
+ String crsStr = hasCourse ? String(curCrsRound) : "---";
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(85);
+ tft.drawRightString(crsStr.c_str(), c1X + 130, c1Y + 28, 1);
+ tft.setTextPadding(0);
+ tft.unloadFont();
+
+ if (hasCourse) {
+ tft.drawCircle(c1X + 137, c1Y + 33, 3, priCol);
+ tft.drawCircle(c1X + 137, c1Y + 33, 2, priCol);
+ } else {
+ tft.fillCircle(c1X + 137, c1Y + 33, 4, cardBg);
+ }
+ }
+
+ // Direction name changed (e.g. NORTE, SUR)
+ const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
+ if (forceRedraw || strcmp(cardDir, lastNavCardDir) != 0) {
+ lastNavCardDir = cardDir;
+ tft.setTextPadding(128);
+ tft.setTextColor(TFT_WHITE, cardBg);
+ tft.drawString(cardDir, c1X + 16, c1Y + 74, 2);
+ tft.setTextPadding(0);
+ }
+
+ // 3. Card 2: Satellites, HDOP and Diagnostic status
+ int c2X = 4;
+ int c2Y = 125;
+
+ // 3a. Satellites Count (DSEG7 34pt)
+ if (forceRedraw || gpsSats != lastNavCardSats || synced != lastNavSatsSync) {
+ lastNavCardSats = gpsSats;
+ lastNavSatsSync = synced;
+ oldNavSats = gpsSats;
+ String satStr = String(gpsSats);
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(priCol, cardBg);
+ tft.setTextPadding(85);
+ tft.drawRightString(satStr.c_str(), c2X + 140, c2Y + 26, 1);
+ tft.setTextPadding(0);
+ tft.unloadFont();
+ }
+
+ // 3b. HDOP precision
+ if (forceRedraw || abs(gpsHdop - lastNavCardHdop) >= 0.2f) {
+ lastNavCardHdop = gpsHdop;
+ oldNavHdop = gpsHdop;
+ String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---");
+ tft.setTextPadding(130);
+ tft.setTextColor(TFT_WHITE, cardBg);
+ tft.drawString(hdopStr.c_str(), c2X + 16, c2Y + 68, 2);
+ tft.setTextPadding(0);
+ }
+
+ // 3c. Diagnostic status message ("PRECISION OPTIMA", "SIN SEÑAL GPS", etc.)
+ // Decoupled: only redrawn when message text actually changes, with setTextPadding (zero flicker!)
+ const char* curDiag = getGpsDiagMessage();
+ if (forceRedraw || strcmp(curDiag, lastNavCardDiag) != 0 || synced != lastNavDiagSynced) {
+ strncpy(lastNavCardDiag, curDiag, sizeof(lastNavCardDiag) - 1);
+ lastNavCardDiag[sizeof(lastNavCardDiag) - 1] = '\0';
+ lastNavDiagSynced = synced;
+ tft.setTextPadding(130);
+ tft.setTextColor(synced ? 0x9CD3 : priCol, cardBg);
+ tft.drawString(curDiag, c2X + 16, c2Y + 90, 1);
+ tft.setTextPadding(0);
+ }
+
+ // 3d. Large Satellite Icon: only redrawn if waveState or sync changes
+ uint8_t curWaveState = getGpsWaveAnimState();
+ if (forceRedraw || curWaveState != oldNavWaveState || synced != oldNavSynced) {
+ oldNavWaveState = curWaveState;
+ oldNavSynced = synced;
+ drawLargeSatelliteIcon(c2X + 16, c2Y + 32, priCol, curWaveState, cardBg);
+ }
+
+ // 4. Coordinates changed: update Card 3 (setTextPadding avoids fillRect flicker)
+ if (forceRedraw || abs(gpsLatitude - oldNavLat) >= 0.00005 || abs(gpsLongitude - oldNavLon) >= 0.00005) {
+ oldNavLat = gpsLatitude;
+ oldNavLon = gpsLongitude;
+ int c3X = 164;
+ int c3Y = 125;
+ String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°";
+ tft.setTextPadding(130);
+ tft.setTextColor(priCol, cardBg);
+ tft.drawString(latStr.c_str(), c3X + 16, c3Y + 38, 2);
+
+ String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°";
+ tft.drawString(lonStr.c_str(), c3X + 16, c3Y + 76, 2);
+ tft.setTextPadding(0);
+ }
+}
+
+// Render Page 3 into double-buffered sprite slice for slide / fade transitions
+inline void renderPage3ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) {
+ uint16_t priCol = getThemePrimaryColor();
+ uint16_t cardBg = 0x10A2;
+ uint16_t cardBorder = 0x2945;
+ bool synced = isGpsSynced();
+
+ // 1. TOP BAR in top slice
+ if (y_slice == 0) {
+ s.setTextColor(0x8410, TFT_BLACK);
+ s.drawString("TELEMETRIA GPS", 8 + x_offset, 5, 1);
+ }
+
+ // 2. CARD 0 (Top Left): ALTITUD (y = 19 to 121)
+ int c0X = 4 + x_offset;
+ int c0Y = 19 - y_slice;
+ int cW = 152;
+ int cH1 = 102;
+ if (c0X + cW > 0 && c0X < 320 && c0Y + cH1 > 0 && c0Y < slice_h) {
+ s.fillRoundRect(c0X, c0Y, cW, cH1, 6, cardBg);
+ s.drawRoundRect(c0X, c0Y, cW, cH1, 6, cardBorder);
+ s.fillRoundRect(c0X + 6, c0Y + 8, 4, cH1 - 16, 2, priCol);
+
+ if (c0Y + 24 > 0 && c0Y + 8 < slice_h) {
+ s.setTextColor(0x8410, cardBg);
+ s.drawString("ALTITUD", c0X + 16, c0Y + 8, 2);
+ s.setTextColor(0x9CD3, cardBg);
+ s.drawString("MSNM", c0X + 96, c0Y + 8, 2);
+ }
+ bool altValid = isGpsAltitudeValid();
+ String altStr = altValid ? String((int)round(gpsAltitudeM)) : "---";
+ int valY = c0Y + 28;
+ if (valY < slice_h && valY + 38 > 0) {
+ s.fontLoaded = true;
+ s.setTextColor(priCol, cardBg);
+ s.setTextPadding(0);
+ s.drawRightString(altStr.c_str(), c0X + 140, valY, 1);
+ s.fontLoaded = false;
+ }
+ }
+
+ // 3. CARD 1 (Top Right): RUMBO (y = 19 to 121)
+ int c1X = 164 + x_offset;
+ int c1Y = 19 - y_slice;
+ if (c1X + cW > 0 && c1X < 320 && c1Y + cH1 > 0 && c1Y < slice_h) {
+ s.fillRoundRect(c1X, c1Y, cW, cH1, 6, cardBg);
+ s.drawRoundRect(c1X, c1Y, cW, cH1, 6, cardBorder);
+ s.fillRoundRect(c1X + 6, c1Y + 8, 4, cH1 - 16, 2, priCol);
+
+ if (c1Y + 24 > 0 && c1Y + 8 < slice_h) {
+ s.setTextColor(0x8410, cardBg);
+ s.drawString("RUMBO / BRUJULA", c1X + 16, c1Y + 8, 2);
+ }
+ bool hasCourse = synced && isGpsCourseValid();
+ String crsStr = hasCourse ? String((int)round(gpsCourseDeg)) : "---";
+ int valY = c1Y + 28;
+ if (valY < slice_h && valY + 38 > 0) {
+ s.fontLoaded = true;
+ s.setTextColor(priCol, cardBg);
+ s.setTextPadding(0);
+ s.drawRightString(crsStr.c_str(), c1X + 130, valY, 1);
+ s.fontLoaded = false;
+ if (hasCourse) {
+ int circY = c1Y + 33;
+ if (circY + 4 > 0 && circY - 4 < slice_h) {
+ s.drawCircle(c1X + 137, circY, 3, priCol);
+ s.drawCircle(c1X + 137, circY, 2, priCol);
+ }
+ }
+ }
+ int dirY = c1Y + 74;
+ if (dirY + 16 > 0 && dirY < slice_h) {
+ const char* cardDir = hasCourse ? getCompassDirectionFull(gpsCourseDeg) : "---";
+ s.setTextColor(TFT_WHITE, cardBg);
+ s.drawString(cardDir, c1X + 16, dirY, 2);
+ }
+ }
+
+ // 4. CARD 2 (Bottom Left): SATÉLITES GPS (y = 125 to 235)
+ int c2X = 4 + x_offset;
+ int c2Y = 125 - y_slice;
+ int cH2 = 110;
+ if (c2X + cW > 0 && c2X < 320 && c2Y + cH2 > 0 && c2Y < slice_h) {
+ s.fillRoundRect(c2X, c2Y, cW, cH2, 6, cardBg);
+ s.drawRoundRect(c2X, c2Y, cW, cH2, 6, cardBorder);
+ s.fillRoundRect(c2X + 6, c2Y + 8, 4, cH2 - 16, 2, priCol);
+
+ if (c2Y + 24 > 0 && c2Y + 8 < slice_h) {
+ s.setTextColor(0x8410, cardBg);
+ s.drawString("SATELITES GPS", c2X + 16, c2Y + 8, 2);
+ }
+ uint8_t waveState = getGpsWaveAnimState();
+ int icnY = c2Y + 32;
+ if (icnY + 28 > 0 && icnY < slice_h) {
+ drawLargeSatelliteIconSprite(s, c2X + 16, icnY, priCol, waveState, cardBg);
+ }
+ int satY = c2Y + 26;
+ if (satY < slice_h && satY + 38 > 0) {
+ String satStr = String(gpsSats);
+ s.fontLoaded = true;
+ s.setTextColor(priCol, cardBg);
+ s.setTextPadding(0);
+ s.drawRightString(satStr.c_str(), c2X + 140, satY, 1);
+ s.fontLoaded = false;
+ }
+ int hdopY = c2Y + 68;
+ if (hdopY < slice_h && hdopY + 16 > 0) {
+ String hdopStr = "HDOP: " + (synced ? String(gpsHdop, 1) : "---");
+ s.setTextColor(TFT_WHITE, cardBg);
+ s.drawString(hdopStr.c_str(), c2X + 16, hdopY, 2);
+ }
+ int qualY = c2Y + 90;
+ if (qualY + 10 > 0 && qualY < slice_h) {
+ s.setTextColor(synced ? 0x9CD3 : priCol, cardBg);
+ s.drawString(getGpsDiagMessage(), c2X + 16, qualY, 1);
+ }
+ }
+
+ // 5. CARD 3 (Bottom Right): COORDENADAS (y = 125 to 235)
+ int c3X = 164 + x_offset;
+ int c3Y = 125 - y_slice;
+ if (c3X + cW > 0 && c3X < 320 && c3Y + cH2 > 0 && c3Y < slice_h) {
+ s.fillRoundRect(c3X, c3Y, cW, cH2, 6, cardBg);
+ s.drawRoundRect(c3X, c3Y, cW, cH2, 6, cardBorder);
+ s.fillRoundRect(c3X + 6, c3Y + 8, 4, cH2 - 16, 2, priCol);
+
+ if (c3Y + 24 > 0 && c3Y + 8 < slice_h) {
+ s.setTextColor(0x8410, cardBg);
+ s.drawString("COORDENADAS", c3X + 16, c3Y + 8, 2);
+ }
+ int latHdrY = c3Y + 26;
+ if (latHdrY < slice_h && latHdrY + 10 > 0) {
+ s.setTextColor(0x8410, cardBg);
+ s.drawString("LAT", c3X + 16, latHdrY, 1);
+ }
+ int latValY = c3Y + 38;
+ if (latValY < slice_h && latValY + 18 > 0) {
+ String latStr = gpsLocationValid ? (String(gpsLatitude, 5) + "°") : "--.-----°";
+ s.setTextColor(priCol, cardBg);
+ s.drawString(latStr.c_str(), c3X + 16, latValY, 2);
+ }
+ int lonHdrY = c3Y + 64;
+ if (lonHdrY < slice_h && lonHdrY + 10 > 0) {
+ s.setTextColor(0x8410, cardBg);
+ s.drawString("LON", c3X + 16, lonHdrY, 1);
+ }
+ int lonValY = c3Y + 76;
+ if (lonValY < slice_h && lonValY + 18 > 0) {
+ String lonStr = gpsLocationValid ? (String(gpsLongitude, 5) + "°") : "--.-----°";
+ s.setTextColor(priCol, cardBg);
+ s.drawString(lonStr.c_str(), c3X + 16, lonValY, 2);
+ }
+ }
+}
diff --git a/sketch/obd2_gauge/meter.h b/sketch/obd2_gauge/meter.h
index fb8ae09..8596beb 100644
--- a/sketch/obd2_gauge/meter.h
+++ b/sketch/obd2_gauge/meter.h
@@ -1,8 +1,12 @@
// All Meters display function
-#include "DSEG7_Modern_BoldItalic_80.h"
-#include "DSEG7_Modern_BoldItalic_44.h"
-#include "DSEG7_Modern_BoldItalic_34.h"
#include "DSEG7_Modern_BoldItalic_24.h"
+#include "DSEG7_Modern_BoldItalic_34.h"
+#include "DSEG7_Modern_BoldItalic_44.h"
+#include "DSEG7_Modern_BoldItalic_80.h"
+#include "Latin_Sans_Bold_26.h"
+
+extern TFT_eSprite sprTrans;
+extern bool sprTransAllocated;
const uint16_t screenWidth = tft.height(); // landscape
const uint16_t screenHeight = tft.width();
@@ -35,21 +39,25 @@ uint8_t pidReadSkip[maxpidIndex] = {}; // skip reading 0 - 3; 3 = max delay read
uint8_t pidCurrentSkip[maxpidIndex] = {}; // current skip counter
uint8_t engine_off_count = 0; // counter to indicate engine is off then sleep
float old_data[maxpidIndex] = {-999, -999, -999, -999, -999}; // keep old data
-float last_valid_data[maxpidIndex] = {-999, -999, -999, -999, -999}; // keep last valid data for instant redraw
+float last_valid_data[maxpidIndex] = {
+ -999, -999, -999, -999, -999}; // keep last valid data for instant redraw
float currentRpm = 0.0f; // Current engine RPM for sleep/inactivity tracking
-float currentEngineLoad = 0.0f; // Current Engine Load % (PID 0104)
-bool isEcoOptimal = false; // Flag: RPM 1900-2550 & Load 45-82%
-bool badgeEcoDrawn = false; // Tracking rendered status on display
+float currentEngineLoad = 0.0f; // Current Engine Load % (PID 0104)
+bool isEcoOptimal = false; // Flag: RPM 1900-2550 & Load 45-82%
+bool badgeEcoDrawn = false; // Tracking rendered status on display
extern uint32_t lastActivityTime; // Activity timestamp for auto-sleep
-bool ignitionOn = true; // True if ECU is actively communicating (contacto puesto)
-uint8_t consecutiveEcuFails = 0; // Counter for consecutive ECU communication failures
+bool ignitionOn =
+ true; // True if ECU is actively communicating (contacto puesto)
+uint8_t consecutiveEcuFails =
+ 0; // Counter for consecutive ECU communication failures
// Arc warning flash (blinking alarm when gauge is in TFT_RED)
bool arcFlashVisible = true;
uint32_t lastArcFlashTime = 0;
-bool wasInRedAlarm[4] = { false, false, false, false };
+bool wasInRedAlarm[4] = {false, false, false, false};
-// Color for inactive meter blocks (medium-dark graphite grey, lighter than 0x2124 but darker than 0x7BEF)
+// Color for inactive meter blocks (medium-dark graphite grey, lighter than
+// 0x2124 but darker than 0x7BEF)
const uint16_t METER_INACTIVE_COLOR = 0x39E7; // RGB ~ (58, 60, 58)
// backlight variable average counter
@@ -60,14 +68,16 @@ const float factoryECUOff = 12.5; // default off voltage
float ecu_off_volt = factoryECUOff; // turn engine off if voltage lower
// than 12.4
-// Cache theme gradients to ensure 100% bit-for-bit identical background restoration
+// Cache theme gradients to ensure 100% bit-for-bit identical background
+// restoration
uint16_t cachedThemeColor = 0xFFFF;
uint16_t themeGradQuad[82];
uint16_t themeGradHeader[145];
void checkUpdateThemeGradients() {
uint16_t currentTheme = getThemeBgColor();
- if (currentTheme == cachedThemeColor) return;
+ if (currentTheme == cachedThemeColor)
+ return;
cachedThemeColor = currentTheme;
float delta = -255.0f / 82.0f;
@@ -93,22 +103,30 @@ inline uint16_t getInstrumentBgColor(int y, bool isQuad, int ry = 0) {
checkUpdateThemeGradients();
if (isQuad) {
int k = y - ry;
- if (k < 0) return themeGradQuad[0];
- if (k >= 82) return TFT_BLACK;
+ if (k < 0)
+ return themeGradQuad[0];
+ if (k >= 82)
+ return TFT_BLACK;
return themeGradQuad[k];
} else {
- if (y < 0) return themeGradHeader[0];
- if (y >= 145) return TFT_BLACK;
+ if (y < 0)
+ return themeGradHeader[0];
+ if (y >= 145)
+ return TFT_BLACK;
return themeGradHeader[y];
}
}
-// Shared line/block buffer for fast SPI push (up to 64x64 max icon size, ~8KB static RAM)
+// Shared line/block buffer for fast SPI push (up to 64x64 max icon size, ~8KB
+// static RAM)
static uint16_t s_iconRenderBuf[64 * 64];
-// Draw icon with true 8-bit alpha channel blended dynamically against current instrument background
-void drawIconWithAlpha(int x, int y, int w, int h, const unsigned short *colors, const unsigned char *alphas, bool isQuad, int ry = 0) {
- if (w * h > 64 * 64) return;
+// Draw icon with true 8-bit alpha channel blended dynamically against current
+// instrument background
+void drawIconWithAlpha(int x, int y, int w, int h, const unsigned short *colors,
+ const unsigned char *alphas, bool isQuad, int ry = 0) {
+ if (w * h > 64 * 64)
+ return;
for (int j = 0; j < h; j++) {
int curY = y + j;
@@ -134,17 +152,22 @@ void drawIconWithAlpha(int x, int y, int w, int h, const unsigned short *colors,
void drawPidIcon(int x, int y, const String &pidStr) {
int ry = (y >= 120) ? 121 : 0;
if (pidStr == "0105") {
- drawIconWithAlpha(x, y, 38, 37, icon_temp_quad, icon_temp_quad_alpha, true, ry);
+ drawIconWithAlpha(x, y, 38, 37, icon_temp_quad, icon_temp_quad_alpha, true,
+ ry);
} else if (pidStr == "010D") {
- drawIconWithAlpha(x, y, 42, 42, icon_speed_quad, icon_speed_quad_alpha, true, ry);
+ drawIconWithAlpha(x, y, 42, 42, icon_speed_quad, icon_speed_quad_alpha,
+ true, ry);
} else if (pidStr == "010C") {
- drawIconWithAlpha(x, y, 42, 43, icon_rpm_quad, icon_rpm_quad_alpha, true, ry);
+ drawIconWithAlpha(x, y, 42, 43, icon_rpm_quad, icon_rpm_quad_alpha, true,
+ ry);
} else if (pidStr == "0142" || pidStr == "ATRV") {
- drawIconWithAlpha(x, y, 46, 35, icon_battery_quad, icon_battery_quad_alpha, true, ry);
+ drawIconWithAlpha(x, y, 46, 35, icon_battery_quad, icon_battery_quad_alpha,
+ true, ry);
}
}
-// Clear parameter icon for 4-quadrant view by restoring exact background lines (100% invisible restoration)
+// Clear parameter icon for 4-quadrant view by restoring exact background lines
+// (100% invisible restoration)
void clearPidIcon(int x, int y, const String &pidStr) {
int w = 42;
int h = 42;
@@ -184,11 +207,13 @@ void drawPidIconHeader(int x, int y, const String &pidStr) {
} else if (pidStr == "010C") {
drawIconWithAlpha(11, 4, 42, 43, icon_rpm_quad, icon_rpm_quad_alpha, false);
} else if (pidStr == "0142" || pidStr == "ATRV") {
- drawIconWithAlpha(8, 2, 48, 48, icon_battery_48, icon_battery_48_alpha, false);
+ drawIconWithAlpha(8, 2, 48, 48, icon_battery_48, icon_battery_48_alpha,
+ false);
}
}
-// Restore ambient vignette background over the header icon area during alarm blink OFF phase (100% invisible)
+// Restore ambient vignette background over the header icon area during alarm
+// blink OFF phase (100% invisible)
void clearPidIconHeader(int x = 8, int y = 2, int w = 50, int h = 52) {
for (int j = 0; j < h; j++) {
int curY = y + j;
@@ -202,7 +227,8 @@ void clearPidIconHeader(int x = 8, int y = 2, int w = 50, int h = 52) {
tft.pushImage(x, y, w, h, s_iconRenderBuf);
}
-// Draw or clear full screen instrument title during normal display and alarm blinking
+// Draw or clear full screen instrument title during normal display and alarm
+// blinking
void drawFullScreenTitle(const String &label, uint16_t color) {
checkUpdateThemeGradients();
for (int y = 10; y <= 40; y++) {
@@ -215,8 +241,23 @@ 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)
+// 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;
@@ -229,7 +270,6 @@ void updateEcoBadge(bool forceRedraw = false) {
return;
}
- bool isQuad = (fullScreenCell < 0 || fullScreenCell >= 4);
int bx = 0, by = 0, bw = 0, bh = 0;
if (isQuad) {
@@ -277,80 +317,89 @@ void updateEcoBadge(bool forceRedraw = false) {
badgeEcoDrawn = newEco;
}
-// Full Screen Exponential Segmented Block Bar Graph (20 blocks: 3mm width x 4mm height, 0.5mm gap, Anti-Aliased)
+// Full Screen Exponential Segmented Block Bar Graph (20 blocks: 3mm width x 4mm
+// height, 0.5mm gap, Anti-Aliased)
const uint8_t NUM_BAR_BLOCKS = 20;
-const int16_t blockMinX[20] PROGMEM = { 0, 5, 11, 17, 25, 34, 46, 59, 76, 95, 114, 134, 154, 174, 193, 213, 232, 251, 271, 290 };
-const int16_t blockMaxX[20] PROGMEM = { 28, 34, 40, 47, 55, 65, 76, 89, 104, 121, 138, 157, 175, 194, 214, 233, 252, 271, 290, 310 };
-const int16_t blockMinY[20] PROGMEM = { 202, 183, 164, 146, 127, 110, 94, 80, 70, 63, 59, 56, 54, 53, 52, 52, 52, 52, 52, 52 };
-const int16_t blockMaxY[20] PROGMEM = { 226, 208, 190, 172, 155, 139, 124, 111, 100, 92, 87, 83, 80, 79, 78, 77, 77, 77, 77, 77 };
+const int16_t blockMinX[20] PROGMEM = {0, 5, 11, 17, 25, 34, 46,
+ 59, 76, 95, 114, 134, 154, 174,
+ 193, 213, 232, 251, 271, 290};
+const int16_t blockMaxX[20] PROGMEM = {28, 34, 40, 47, 55, 65, 76,
+ 89, 104, 121, 138, 157, 175, 194,
+ 214, 233, 252, 271, 290, 310};
+const int16_t blockMinY[20] PROGMEM = {202, 183, 164, 146, 127, 110, 94,
+ 80, 70, 63, 59, 56, 54, 53,
+ 52, 52, 52, 52, 52, 52};
+const int16_t blockMaxY[20] PROGMEM = {226, 208, 190, 172, 155, 139, 124,
+ 111, 100, 92, 87, 83, 80, 79,
+ 78, 77, 77, 77, 77, 77};
const float blockNx[20][4] PROGMEM = {
- { 0.96709f, -0.26991f, -0.96711f, 0.24260f },
- { 0.95775f, -0.30696f, -0.95776f, 0.26991f },
- { 0.94416f, -0.35424f, -0.94418f, 0.30696f },
- { 0.92359f, -0.41544f, -0.92365f, 0.35424f },
- { 0.89126f, -0.49508f, -0.89141f, 0.41544f },
- { 0.83905f, -0.59638f, -0.83936f, 0.49508f },
- { 0.75533f, -0.71496f, -0.75581f, 0.59638f },
- { 0.63154f, -0.83065f, -0.63179f, 0.71496f },
- { 0.47873f, -0.91631f, -0.47824f, 0.83065f },
- { 0.33039f, -0.96398f, -0.32962f, 0.91631f },
- { 0.21408f, -0.98563f, -0.21356f, 0.96398f },
- { 0.13436f, -0.99446f, -0.13412f, 0.98563f },
- { 0.08319f, -0.99789f, -0.08309f, 0.99446f },
- { 0.05123f, -0.99920f, -0.05120f, 0.99789f },
- { 0.03149f, -0.99970f, -0.03148f, 0.99920f },
- { 0.01934f, -0.99989f, -0.01934f, 0.99970f },
- { 0.01188f, -0.99996f, -0.01187f, 0.99989f },
- { 0.00729f, -0.99998f, -0.00729f, 0.99996f },
- { 0.00448f, -0.99999f, -0.00448f, 0.99998f },
- { 0.00275f, -1.00000f, -0.00275f, 0.99999f },
+ {0.96709f, -0.26991f, -0.96711f, 0.24260f},
+ {0.95775f, -0.30696f, -0.95776f, 0.26991f},
+ {0.94416f, -0.35424f, -0.94418f, 0.30696f},
+ {0.92359f, -0.41544f, -0.92365f, 0.35424f},
+ {0.89126f, -0.49508f, -0.89141f, 0.41544f},
+ {0.83905f, -0.59638f, -0.83936f, 0.49508f},
+ {0.75533f, -0.71496f, -0.75581f, 0.59638f},
+ {0.63154f, -0.83065f, -0.63179f, 0.71496f},
+ {0.47873f, -0.91631f, -0.47824f, 0.83065f},
+ {0.33039f, -0.96398f, -0.32962f, 0.91631f},
+ {0.21408f, -0.98563f, -0.21356f, 0.96398f},
+ {0.13436f, -0.99446f, -0.13412f, 0.98563f},
+ {0.08319f, -0.99789f, -0.08309f, 0.99446f},
+ {0.05123f, -0.99920f, -0.05120f, 0.99789f},
+ {0.03149f, -0.99970f, -0.03148f, 0.99920f},
+ {0.01934f, -0.99989f, -0.01934f, 0.99970f},
+ {0.01188f, -0.99996f, -0.01187f, 0.99989f},
+ {0.00729f, -0.99998f, -0.00729f, 0.99996f},
+ {0.00448f, -0.99999f, -0.00448f, 0.99998f},
+ {0.00275f, -1.00000f, -0.00275f, 0.99999f},
};
const float blockNy[20][4] PROGMEM = {
- { 0.25444f, 0.96289f, -0.25437f, -0.97013f },
- { 0.28762f, 0.95172f, -0.28757f, -0.96289f },
- { 0.32950f, 0.93516f, -0.32942f, -0.95172f },
- { 0.38338f, 0.90962f, -0.38323f, -0.93516f },
- { 0.45348f, 0.86885f, -0.45320f, -0.90962f },
- { 0.54405f, 0.80270f, -0.54358f, -0.86885f },
- { 0.65535f, 0.69916f, -0.65480f, -0.80270f },
- { 0.77534f, 0.55679f, -0.77514f, -0.69916f },
- { 0.87796f, 0.40047f, -0.87823f, -0.55679f },
- { 0.94384f, 0.26596f, -0.94412f, -0.40047f },
- { 0.97682f, 0.16890f, -0.97693f, -0.26596f },
- { 0.99093f, 0.10510f, -0.99097f, -0.16890f },
- { 0.99653f, 0.06486f, -0.99654f, -0.10510f },
- { 0.99869f, 0.03990f, -0.99869f, -0.06486f },
- { 0.99950f, 0.02452f, -0.99950f, -0.03990f },
- { 0.99981f, 0.01506f, -0.99981f, -0.02452f },
- { 0.99993f, 0.00924f, -0.99993f, -0.01506f },
- { 0.99997f, 0.00568f, -0.99997f, -0.00924f },
- { 0.99999f, 0.00348f, -0.99999f, -0.00568f },
- { 1.00000f, 0.00223f, -1.00000f, -0.00348f },
+ {0.25444f, 0.96289f, -0.25437f, -0.97013f},
+ {0.28762f, 0.95172f, -0.28757f, -0.96289f},
+ {0.32950f, 0.93516f, -0.32942f, -0.95172f},
+ {0.38338f, 0.90962f, -0.38323f, -0.93516f},
+ {0.45348f, 0.86885f, -0.45320f, -0.90962f},
+ {0.54405f, 0.80270f, -0.54358f, -0.86885f},
+ {0.65535f, 0.69916f, -0.65480f, -0.80270f},
+ {0.77534f, 0.55679f, -0.77514f, -0.69916f},
+ {0.87796f, 0.40047f, -0.87823f, -0.55679f},
+ {0.94384f, 0.26596f, -0.94412f, -0.40047f},
+ {0.97682f, 0.16890f, -0.97693f, -0.26596f},
+ {0.99093f, 0.10510f, -0.99097f, -0.16890f},
+ {0.99653f, 0.06486f, -0.99654f, -0.10510f},
+ {0.99869f, 0.03990f, -0.99869f, -0.06486f},
+ {0.99950f, 0.02452f, -0.99950f, -0.03990f},
+ {0.99981f, 0.01506f, -0.99981f, -0.02452f},
+ {0.99993f, 0.00924f, -0.99993f, -0.01506f},
+ {0.99997f, 0.00568f, -0.99997f, -0.00924f},
+ {0.99999f, 0.00348f, -0.99999f, -0.00568f},
+ {1.00000f, 0.00223f, -1.00000f, -0.00348f},
};
const float blockC[20][4] PROGMEM = {
- { -57.0917f, -194.0217f, 79.0747f, 212.4570f },
- { -63.7256f, -171.8745f, 85.7128f, 191.2717f },
- { -71.2233f, -148.4987f, 93.2024f, 169.1245f },
- { -79.6909f, -123.2275f, 101.6563f, 145.7487f },
- { -89.0635f, -95.0180f, 111.0070f, 120.4775f },
- { -98.7898f, -62.4921f, 120.7056f, 92.2680f },
- { -107.1996f, -24.6792f, 129.0998f, 59.7421f },
- { -111.1224f, 16.8839f, 133.0287f, 21.9292f },
- { -107.7811f, 57.2365f, 129.6718f, -19.6339f },
- { -98.3836f, 92.0853f, 120.2644f, -59.9865f },
- { -87.1827f, 121.1162f, 109.0982f, -94.8353f },
- { -77.3181f, 146.0701f, 99.2734f, -123.8662f },
- { -69.7527f, 168.5916f, 91.7322f, -148.8201f },
- { -64.3207f, 189.7308f, 86.3119f, -171.3416f },
- { -60.5502f, 210.0867f, 82.5466f, -192.4808f },
- { -57.9834f, 229.9930f, 79.9819f, -212.8367f },
- { -56.2577f, 249.6371f, 78.2571f, -232.7430f },
- { -55.1079f, 269.1261f, 77.1076f, -252.3871f },
- { -54.3470f, 288.5222f, 76.3469f, -271.8761f },
- { -53.8454f, 307.8565f, 75.8453f, -291.2722f },
+ {-57.0917f, -194.0217f, 79.0747f, 212.4570f},
+ {-63.7256f, -171.8745f, 85.7128f, 191.2717f},
+ {-71.2233f, -148.4987f, 93.2024f, 169.1245f},
+ {-79.6909f, -123.2275f, 101.6563f, 145.7487f},
+ {-89.0635f, -95.0180f, 111.0070f, 120.4775f},
+ {-98.7898f, -62.4921f, 120.7056f, 92.2680f},
+ {-107.1996f, -24.6792f, 129.0998f, 59.7421f},
+ {-111.1224f, 16.8839f, 133.0287f, 21.9292f},
+ {-107.7811f, 57.2365f, 129.6718f, -19.6339f},
+ {-98.3836f, 92.0853f, 120.2644f, -59.9865f},
+ {-87.1827f, 121.1162f, 109.0982f, -94.8353f},
+ {-77.3181f, 146.0701f, 99.2734f, -123.8662f},
+ {-69.7527f, 168.5916f, 91.7322f, -148.8201f},
+ {-64.3207f, 189.7308f, 86.3119f, -171.3416f},
+ {-60.5502f, 210.0867f, 82.5466f, -192.4808f},
+ {-57.9834f, 229.9930f, 79.9819f, -212.8367f},
+ {-56.2577f, 249.6371f, 78.2571f, -232.7430f},
+ {-55.1079f, 269.1261f, 77.1076f, -252.3871f},
+ {-54.3470f, 288.5222f, 76.3469f, -271.8761f},
+ {-53.8454f, 307.8565f, 75.8453f, -291.2722f},
};
// Draw an individual solid rectangular block with smooth subpixel anti-aliasing
@@ -364,7 +413,7 @@ inline void drawBarBlock(uint8_t i, uint16_t color) {
for (uint8_t k = 0; k < 4; k++) {
nx[k] = pgm_read_float(&blockNx[i][k]);
ny[k] = pgm_read_float(&blockNy[i][k]);
- c[k] = pgm_read_float(&blockC[i][k]);
+ c[k] = pgm_read_float(&blockC[i][k]);
}
tft.startWrite();
@@ -375,9 +424,11 @@ inline void drawBarBlock(uint8_t i, uint16_t color) {
float min_d = 999.0f;
for (uint8_t k = 0; k < 4; k++) {
float d = px * nx[k] + py * ny[k] + c[k];
- if (d < min_d) min_d = d;
+ if (d < min_d)
+ min_d = d;
}
- if (min_d <= -0.5f) continue;
+ if (min_d <= -0.5f)
+ continue;
if (color == TFT_BLACK) {
tft.drawPixel(x, y, TFT_BLACK);
} else {
@@ -393,82 +444,92 @@ inline void drawBarBlock(uint8_t i, uint16_t color) {
tft.endWrite();
}
-// 4-Quadrant Exponential Segmented Block Bar Graph (20 blocks per 160x120 quadrant, Anti-Aliased)
-const int16_t quadBlockMinX[20] PROGMEM = { 5, 8, 12, 15, 20, 24, 30, 37, 45, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 144 };
-const int16_t quadBlockMaxX[20] PROGMEM = { 18, 21, 24, 28, 33, 38, 44, 50, 58, 65, 74, 82, 91, 100, 109, 118, 127, 136, 145, 154 };
-const int16_t quadBlockMinY[20] PROGMEM = { 100, 91, 83, 74, 66, 59, 51, 45, 40, 36, 33, 31, 30, 29, 28, 28, 27, 27, 27, 27 };
-const int16_t quadBlockMaxY[20] PROGMEM = { 112, 103, 95, 87, 79, 72, 65, 59, 53, 49, 46, 43, 41, 40, 39, 38, 38, 37, 37, 37 };
+// 4-Quadrant Exponential Segmented Block Bar Graph (20 blocks per 160x120
+// quadrant, Anti-Aliased)
+const int16_t quadBlockMinX[20] PROGMEM = {5, 8, 12, 15, 20, 24, 30,
+ 37, 45, 53, 62, 71, 80, 89,
+ 98, 107, 117, 126, 135, 144};
+const int16_t quadBlockMaxX[20] PROGMEM = {18, 21, 24, 28, 33, 38, 44,
+ 50, 58, 65, 74, 82, 91, 100,
+ 109, 118, 127, 136, 145, 154};
+const int16_t quadBlockMinY[20] PROGMEM = {100, 91, 83, 74, 66, 59, 51,
+ 45, 40, 36, 33, 31, 30, 29,
+ 28, 28, 27, 27, 27, 27};
+const int16_t quadBlockMaxY[20] PROGMEM = {112, 103, 95, 87, 79, 72, 65,
+ 59, 53, 49, 46, 43, 41, 40,
+ 39, 38, 38, 37, 37, 37};
const float quadBlockNx[20][4] PROGMEM = {
- { -0.94779f, -0.31891f, 0.94779f, 0.31891f },
- { -0.93472f, -0.35539f, 0.93472f, 0.35539f },
- { -0.91662f, -0.39976f, 0.91662f, 0.39976f },
- { -0.89115f, -0.45372f, 0.89115f, 0.45372f },
- { -0.85466f, -0.51919f, 0.85466f, 0.51919f },
- { -0.80234f, -0.59687f, 0.80234f, 0.59687f },
- { -0.72909f, -0.68442f, 0.72909f, 0.68442f },
- { -0.63267f, -0.77442f, 0.63267f, 0.77442f },
- { -0.51899f, -0.85478f, 0.51899f, 0.85478f },
- { -0.40261f, -0.91537f, 0.40261f, 0.91537f },
- { -0.29860f, -0.95438f, 0.29860f, 0.95438f },
- { -0.21498f, -0.97662f, 0.21498f, 0.97662f },
- { -0.15215f, -0.98836f, 0.15215f, 0.98836f },
- { -0.10673f, -0.99429f, 0.10673f, 0.99429f },
- { -0.07452f, -0.99722f, 0.07452f, 0.99722f },
- { -0.05192f, -0.99865f, 0.05192f, 0.99865f },
- { -0.03612f, -0.99935f, 0.03612f, 0.99935f },
- { -0.02513f, -0.99968f, 0.02513f, 0.99968f },
- { -0.01747f, -0.99985f, 0.01747f, 0.99985f },
- { -0.01214f, -0.99993f, 0.01214f, 0.99993f },
+ {-0.94779f, -0.31891f, 0.94779f, 0.31891f},
+ {-0.93472f, -0.35539f, 0.93472f, 0.35539f},
+ {-0.91662f, -0.39976f, 0.91662f, 0.39976f},
+ {-0.89115f, -0.45372f, 0.89115f, 0.45372f},
+ {-0.85466f, -0.51919f, 0.85466f, 0.51919f},
+ {-0.80234f, -0.59687f, 0.80234f, 0.59687f},
+ {-0.72909f, -0.68442f, 0.72909f, 0.68442f},
+ {-0.63267f, -0.77442f, 0.63267f, 0.77442f},
+ {-0.51899f, -0.85478f, 0.51899f, 0.85478f},
+ {-0.40261f, -0.91537f, 0.40261f, 0.91537f},
+ {-0.29860f, -0.95438f, 0.29860f, 0.95438f},
+ {-0.21498f, -0.97662f, 0.21498f, 0.97662f},
+ {-0.15215f, -0.98836f, 0.15215f, 0.98836f},
+ {-0.10673f, -0.99429f, 0.10673f, 0.99429f},
+ {-0.07452f, -0.99722f, 0.07452f, 0.99722f},
+ {-0.05192f, -0.99865f, 0.05192f, 0.99865f},
+ {-0.03612f, -0.99935f, 0.03612f, 0.99935f},
+ {-0.02513f, -0.99968f, 0.02513f, 0.99968f},
+ {-0.01747f, -0.99985f, 0.01747f, 0.99985f},
+ {-0.01214f, -0.99993f, 0.01214f, 0.99993f},
};
const float quadBlockNy[20][4] PROGMEM = {
- { -0.31891f, 0.94779f, 0.31891f, -0.94779f },
- { -0.35539f, 0.93472f, 0.35539f, -0.93472f },
- { -0.39976f, 0.91662f, 0.39976f, -0.91662f },
- { -0.45372f, 0.89115f, 0.45372f, -0.89115f },
- { -0.51919f, 0.85466f, 0.51919f, -0.85466f },
- { -0.59687f, 0.80234f, 0.59687f, -0.80234f },
- { -0.68442f, 0.72909f, 0.68442f, -0.72909f },
- { -0.77442f, 0.63267f, 0.77442f, -0.63267f },
- { -0.85478f, 0.51899f, 0.85478f, -0.51899f },
- { -0.91537f, 0.40261f, 0.91537f, -0.40261f },
- { -0.95438f, 0.29860f, 0.95438f, -0.29860f },
- { -0.97662f, 0.21498f, 0.97662f, -0.21498f },
- { -0.98836f, 0.15215f, 0.98836f, -0.15215f },
- { -0.99429f, 0.10673f, 0.99429f, -0.10673f },
- { -0.99722f, 0.07452f, 0.99722f, -0.07452f },
- { -0.99865f, 0.05192f, 0.99865f, -0.05192f },
- { -0.99935f, 0.03612f, 0.99935f, -0.03612f },
- { -0.99968f, 0.02513f, 0.99968f, -0.02513f },
- { -0.99985f, 0.01747f, 0.99985f, -0.01747f },
- { -0.99993f, 0.01214f, 0.99993f, -0.01214f },
+ {-0.31891f, 0.94779f, 0.31891f, -0.94779f},
+ {-0.35539f, 0.93472f, 0.35539f, -0.93472f},
+ {-0.39976f, 0.91662f, 0.39976f, -0.91662f},
+ {-0.45372f, 0.89115f, 0.45372f, -0.89115f},
+ {-0.51919f, 0.85466f, 0.51919f, -0.85466f},
+ {-0.59687f, 0.80234f, 0.59687f, -0.80234f},
+ {-0.68442f, 0.72909f, 0.68442f, -0.72909f},
+ {-0.77442f, 0.63267f, 0.77442f, -0.63267f},
+ {-0.85478f, 0.51899f, 0.85478f, -0.51899f},
+ {-0.91537f, 0.40261f, 0.91537f, -0.40261f},
+ {-0.95438f, 0.29860f, 0.95438f, -0.29860f},
+ {-0.97662f, 0.21498f, 0.97662f, -0.21498f},
+ {-0.98836f, 0.15215f, 0.98836f, -0.15215f},
+ {-0.99429f, 0.10673f, 0.99429f, -0.10673f},
+ {-0.99722f, 0.07452f, 0.99722f, -0.07452f},
+ {-0.99865f, 0.05192f, 0.99865f, -0.05192f},
+ {-0.99935f, 0.03612f, 0.99935f, -0.03612f},
+ {-0.99968f, 0.02513f, 0.99968f, -0.02513f},
+ {-0.99985f, 0.01747f, 0.99985f, -0.01747f},
+ {-0.99993f, 0.01214f, 0.99993f, -0.01214f},
};
const float quadBlockC[20][4] PROGMEM = {
- { 49.3731f, -92.9228f, -41.1731f, 100.9228f },
- { 52.9126f, -82.0215f, -44.7126f, 90.0215f },
- { 56.7515f, -70.4863f, -48.5515f, 78.4863f },
- { 60.8192f, -58.1314f, -52.6192f, 66.1314f },
- { 64.9598f, -44.6227f, -56.7598f, 52.6227f },
- { 68.8007f, -29.6371f, -60.6007f, 37.6371f },
- { 71.6789f, -12.9798f, -63.4789f, 20.9798f },
- { 72.7161f, 5.1281f, -64.5161f, 2.8719f },
- { 71.2620f, 23.7112f, -63.0620f, -15.7112f },
- { 67.5191f, 41.3967f, -59.3191f, -33.3967f },
- { 62.4933f, 57.2743f, -54.2933f, -49.2743f },
- { 57.2945f, 71.2057f, -49.0945f, -63.2057f },
- { 52.6178f, 83.5542f, -44.4178f, -75.5542f },
- { 48.7270f, 94.7806f, -40.5270f, -86.7806f },
- { 45.6295f, 105.2669f, -37.4295f, -97.2669f },
- { 43.2291f, 115.2686f, -35.0291f, -107.2686f },
- { 41.3977f, 124.9666f, -33.1977f, -116.9666f },
- { 40.0175f, 134.4587f, -31.8175f, -126.4587f },
- { 38.9847f, 143.8219f, -30.7847f, -135.8219f },
- { 38.2169f, 153.0966f, -30.0169f, -145.0966f },
+ {49.3731f, -92.9228f, -41.1731f, 100.9228f},
+ {52.9126f, -82.0215f, -44.7126f, 90.0215f},
+ {56.7515f, -70.4863f, -48.5515f, 78.4863f},
+ {60.8192f, -58.1314f, -52.6192f, 66.1314f},
+ {64.9598f, -44.6227f, -56.7598f, 52.6227f},
+ {68.8007f, -29.6371f, -60.6007f, 37.6371f},
+ {71.6789f, -12.9798f, -63.4789f, 20.9798f},
+ {72.7161f, 5.1281f, -64.5161f, 2.8719f},
+ {71.2620f, 23.7112f, -63.0620f, -15.7112f},
+ {67.5191f, 41.3967f, -59.3191f, -33.3967f},
+ {62.4933f, 57.2743f, -54.2933f, -49.2743f},
+ {57.2945f, 71.2057f, -49.0945f, -63.2057f},
+ {52.6178f, 83.5542f, -44.4178f, -75.5542f},
+ {48.7270f, 94.7806f, -40.5270f, -86.7806f},
+ {45.6295f, 105.2669f, -37.4295f, -97.2669f},
+ {43.2291f, 115.2686f, -35.0291f, -107.2686f},
+ {41.3977f, 124.9666f, -33.1977f, -116.9666f},
+ {40.0175f, 134.4587f, -31.8175f, -126.4587f},
+ {38.9847f, 143.8219f, -30.7847f, -135.8219f},
+ {38.2169f, 153.0966f, -30.0169f, -145.0966f},
};
-// Draw an individual solid rectangular block for 4-quadrant view with smooth subpixel anti-aliasing
+// Draw an individual solid rectangular block for 4-quadrant view with smooth
+// subpixel anti-aliasing
inline void drawQuadBarBlock(uint8_t cell, uint8_t i, uint16_t color) {
int16_t ox = cell4_x[cell];
int16_t oy = cell4_y[cell];
@@ -481,7 +542,7 @@ inline void drawQuadBarBlock(uint8_t cell, uint8_t i, uint16_t color) {
for (uint8_t k = 0; k < 4; k++) {
nx[k] = pgm_read_float(&quadBlockNx[i][k]);
ny[k] = pgm_read_float(&quadBlockNy[i][k]);
- c[k] = pgm_read_float(&quadBlockC[i][k]);
+ c[k] = pgm_read_float(&quadBlockC[i][k]);
}
tft.startWrite();
@@ -493,9 +554,11 @@ inline void drawQuadBarBlock(uint8_t cell, uint8_t i, uint16_t color) {
float min_d = 999.0f;
for (uint8_t k = 0; k < 4; k++) {
float d = px * nx[k] + py * ny[k] + c[k];
- if (d < min_d) min_d = d;
+ if (d < min_d)
+ min_d = d;
}
- if (min_d <= -0.5f) continue;
+ if (min_d <= -0.5f)
+ continue;
int16_t sx = ox + x;
if (color == TFT_BLACK) {
tft.drawPixel(sx, sy, TFT_BLACK);
@@ -772,7 +835,7 @@ void plotVBar(uint8_t cell, float data, int warn, int min, int max,
}
tft.setTextPadding(w - 6);
tft.drawRightString(result.c_str(), x + w - 4,
- screenHeight - (screenHeight * 0.12) + 1, 4);
+ screenHeight - (screenHeight * 0.12) + 1, 4);
tft.setTextPadding(0);
old_data[pidIndex] = data; // update old data
} // if
@@ -847,7 +910,8 @@ int get7SegWidth(const char *str, uint8_t dW, uint8_t t) {
return total;
}
-// Draw blue header background for a 160x120 quadrant (25px height) with lateral and vertical smooth fade
+// Draw blue header background for a 160x120 quadrant (25px height) with lateral
+// and vertical smooth fade
void drawQuadHeaderGradient(uint8_t cell) {
int16_t ox = cell4_x[cell];
int16_t oy = cell4_y[cell];
@@ -873,7 +937,8 @@ void drawQuadHeaderGradient(uint8_t cell) {
hx = t * t * (3.0f - 2.0f * t);
} else if (x > (int16_t)x_right_start) {
float t = (159.0f - (float)x) / fade_w_right;
- if (t < 0.0f) t = 0.0f;
+ if (t < 0.0f)
+ t = 0.0f;
hx = t * t * (3.0f - 2.0f * t);
}
float b = vy * hx;
@@ -905,55 +970,183 @@ inline void drawQuadrantDividers() {
// Determine display color for meter:
// Instrumentos: Color del tema activo (normal) o TFT_RED (alarma/peligro)
inline uint16_t getMeterColor(uint8_t pidIdx, float data, int warn = -1) {
- if (pidIdx >= 4) return getThemePrimaryColor();
+ if (pidIdx >= 4)
+ return getThemePrimaryColor();
float warnVal = (warn > 0) ? (float)warn : warningValue[pidIdx].toFloat();
if (pidIdx == 0) { // 0 = Temperatura ECT (PID 0105)
- if (warnVal <= 0 || warnVal == 106.0f) warnVal = 110.0f;
- if (data >= warnVal) return TFT_RED;
+ if (warnVal <= 0 || warnVal == 106.0f)
+ warnVal = 110.0f;
+ if (data >= warnVal)
+ return TFT_RED;
return getThemePrimaryColor();
}
if (pidIdx == 1) { // 1 = RPM (PID 010C)
- if (warnVal <= 0) warnVal = 5000.0f;
- if (data >= warnVal) return TFT_RED;
+ if (warnVal <= 0)
+ warnVal = 5000.0f;
+ if (data >= warnVal)
+ return TFT_RED;
return getThemePrimaryColor();
}
if (pidIdx == 2) { // 2 = Voltaje (PID 0142)
- if (warnVal <= 0) warnVal = 15.0f;
- // Si el dato es 0 (contacto apagado / desconectado) o motor detenido, no disparar alarma por < 12V
- if (data <= 1.0f) return getThemePrimaryColor();
+ if (warnVal <= 0)
+ warnVal = 15.0f;
+ // Si el dato es 0 (contacto apagado / desconectado) o motor detenido, no
+ // disparar alarma por < 12V
+ if (data <= 1.0f)
+ return getThemePrimaryColor();
if (currentRpm <= 0.0f) {
- if (data >= warnVal) return TFT_RED;
+ if (data >= warnVal)
+ return TFT_RED;
return getThemePrimaryColor();
}
- if (data < 12.0f || data >= warnVal) return TFT_RED;
+ if (data < 12.0f || data >= warnVal)
+ return TFT_RED;
return getThemePrimaryColor();
}
if (pidIdx == 3) { // 3 = Velocidad (PID 010D)
- if (warnVal <= 0) warnVal = 130.0f;
- if (data >= warnVal) return TFT_RED;
+ if (warnVal <= 0)
+ warnVal = 130.0f;
+ if (data >= warnVal)
+ return TFT_RED;
return getThemePrimaryColor();
}
- if (warnVal > 0 && data >= warnVal) return TFT_RED;
+ if (warnVal > 0 && data >= warnVal)
+ return TFT_RED;
return getThemePrimaryColor();
}
-// Compute block count (0..NUM_BAR_BLOCKS) with sub-integer floating point precision
+// Compute block count (0..NUM_BAR_BLOCKS) with sub-integer floating point
+// precision
inline int calcMeterBlocks(float val, int min, int max) {
float range = (float)(max - min);
- if (range <= 0.0f) return 0;
- if (val <= (float)min) return 0;
- if (val >= (float)max) return NUM_BAR_BLOCKS;
+ if (range <= 0.0f)
+ return 0;
+ if (val <= (float)min)
+ return 0;
+ if (val >= (float)max)
+ return NUM_BAR_BLOCKS;
int b = (int)(((val - (float)min) / range) * (float)NUM_BAR_BLOCKS + 0.5f);
- if (b < 0) b = 0;
- if (b > NUM_BAR_BLOCKS) b = NUM_BAR_BLOCKS;
+ if (b < 0)
+ b = 0;
+ if (b > NUM_BAR_BLOCKS)
+ b = NUM_BAR_BLOCKS;
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);
+}
+inline void clearFullscreenGpsIcon() {
+ uint16_t c1 = tft.alphaBlend((uint8_t)(255 * (145 - 3) / 145),
+ getThemeBgColor(), TFT_BLACK);
+ uint16_t c2 = tft.alphaBlend((uint8_t)(255 * (145 - 25) / 145),
+ getThemeBgColor(), TFT_BLACK);
+ tft.fillRectVGradient(294, 3, 24, 22, c1, c2);
+}
-// 4-quadrant Exponential Segmented Block Meter (160x120 px) - identical to full screen style
+// 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) {
+ if (fullScreenCell != 1)
+ return;
+ clearFullscreenGpsIcon();
+ uint16_t waveCol = getThemePrimaryColor();
+ uint16_t bodyCol = TFT_WHITE;
+ uint16_t bgCol = tft.alphaBlend((uint8_t)(255 * (145 - 5) / 145),
+ getThemeBgColor(), TFT_BLACK);
+ drawGpsSatelliteIcon(tft, 296, 5, bodyCol, waveCol, bgCol, waveState);
+}
+
+static uint8_t lastGpsIconWaveState = 255;
+static bool lastGpsIconSyncState = false;
+static bool lastGpsIconSpeed = false;
+static int8_t lastGpsIconPage = -1;
+static int8_t lastGpsIconFsCell = -2;
+
+inline void syncGpsIconAnimTrackers(int8_t page, int8_t fsCell) {
+ lastGpsIconWaveState = getGpsWaveAnimState();
+ lastGpsIconSyncState = isGpsSynced();
+ lastGpsIconSpeed = isGpsSpeedValid();
+ lastGpsIconPage = page;
+ lastGpsIconFsCell = fsCell;
+}
+
+// Live update of satellite icon animation in active screen
+inline void updateGpsIconAnim() {
+ bool curGpsSpeed = isGpsSpeedValid();
+ int8_t curPage = currentScreenPage;
+ int8_t curFsCell = fullScreenCell;
+
+ // Si el GPS no esta activo entregando velocidad, no hay icono en pantalla
+ if (!curGpsSpeed) {
+ if (lastGpsIconSpeed) {
+ // Si antes estaba visible y se desconecto/perdio fix, restaurar el
+ // gradiente de fondo
+ if (curPage == 0 && curFsCell == -1) {
+ clearPage0GpsIcon();
+ } else if (curFsCell == 1) {
+ clearFullscreenGpsIcon();
+ }
+ lastGpsIconSpeed = false;
+ }
+ return;
+ }
+
+ uint8_t curWaveState = getGpsWaveAnimState();
+ bool curSync = isGpsSynced();
+
+ if (curWaveState == lastGpsIconWaveState && curSync == lastGpsIconSyncState &&
+ curGpsSpeed == lastGpsIconSpeed && curPage == lastGpsIconPage &&
+ curFsCell == lastGpsIconFsCell) {
+ return;
+ }
+ lastGpsIconWaveState = curWaveState;
+ lastGpsIconSyncState = curSync;
+ lastGpsIconSpeed = curGpsSpeed;
+ lastGpsIconPage = curPage;
+ lastGpsIconFsCell = curFsCell;
+
+ if (curPage == 0 && curFsCell == -1) {
+ drawPage0GpsIcon(curWaveState, curSync);
+ } else if (curFsCell == 1) {
+ drawFullscreenGpsIcon(curWaveState, curSync);
+ }
+}
+
+// Synchronize Page 0 tracking variables without touching TFT display (zero
+// flicker)
+inline void syncPage0Trackers() {
+ badgeEcoDrawn = isEcoOptimal;
+ for (uint8_t c = 0; c < 4; c++) {
+ old_data[c] = last_valid_data[c];
+ wasInRedAlarm[c] = false;
+ }
+ syncGpsIconAnimTrackers(0, -1);
+}
+
+// 4-quadrant Exponential Segmented Block Meter (160x120 px) - identical to full
+// screen style
void arcMeter4(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
int x = cell4_x[cell];
int y = cell4_y[cell];
@@ -990,6 +1183,9 @@ void arcMeter4(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
tft.unloadFont();
old_data[cell] = -999;
}
+ if (cell == 1 && isGpsSpeedValid()) {
+ drawPage0GpsIcon(getGpsWaveAnimState(), isGpsSynced());
+ }
}
void plotArc4(uint8_t cell, float data, int warn, int min, int max, bool digit,
@@ -1001,12 +1197,15 @@ void plotArc4(uint8_t cell, float data, int warn, int min, int max, bool digit,
uint8_t pidIdx = pidInCell[cell];
uint16_t col = getMeterColor(pidIdx, data, warn);
- uint16_t oldCol = (old_data[cell] <= -900) ? 0 : getMeterColor(pidIdx, old_data[cell], warn);
+ uint16_t oldCol = (old_data[cell] <= -900)
+ ? 0
+ : getMeterColor(pidIdx, old_data[cell], warn);
String numStr = String(data, digit);
int newBlocks = calcMeterBlocks(data, min, max);
- int oldBlocks = (old_data[cell] <= -900) ? 0 : calcMeterBlocks(old_data[cell], min, max);
+ int oldBlocks =
+ (old_data[cell] <= -900) ? 0 : calcMeterBlocks(old_data[cell], min, max);
bool colorChanged = (col != oldCol);
if (col == TFT_RED) {
@@ -1067,6 +1266,12 @@ void plotArc4(uint8_t cell, float data, int warn, int min, int max, bool digit,
tft.setTextPadding(0);
tft.unloadFont();
old_data[cell] = data;
+
+ // Draw GPS Satellite Icon & indicator in top-right corner of cell 1
+ // (Velocidad)
+ if (cell == 1 && isGpsSpeedValid()) {
+ drawPage0GpsIcon(getGpsWaveAnimState(), isGpsSynced());
+ }
}
void fullScreenMeter(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
@@ -1077,16 +1282,26 @@ void fullScreenMeter(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
unit = "V";
if (redrawHeader) {
- // Ambient vignette gradient from y=0 fading to black (matching 4 quadrants style)
+ // Ambient vignette gradient from y=0 fading to black (matching 4 quadrants
+ // style)
tft.fillRectVGradient(0, 0, 320, 145, getThemeBgColor(), TFT_BLACK);
tft.fillRect(0, 145, 320, 95, TFT_BLACK);
drawPidIconHeader(10, 4, pidConfig[pid][2]);
- uint16_t titleCol = (getMeterColor(pid, last_valid_data[cell]) == TFT_RED && alarmEnabled[pid]) ? TFT_RED : TFT_WHITE;
+ uint16_t titleCol = (getMeterColor(pid, last_valid_data[cell]) == TFT_RED &&
+ alarmEnabled[pid])
+ ? TFT_RED
+ : TFT_WHITE;
tft.setTextColor(titleCol);
- tft.drawCentreString(label.c_str(), 175, 12, 4);
+ tft.drawCentreString(label, 175, 12, 4);
+ if (cell == 1 && isGpsSpeedValid()) {
+ drawFullscreenGpsIcon(getGpsWaveAnimState(), isGpsSynced());
+ }
} else {
- tft.fillRectVGradient(0, 51, 320, 94, tft.alphaBlend((uint8_t)(255 * (145 - 51) / 145), getThemeBgColor(), TFT_BLACK), TFT_BLACK);
+ tft.fillRectVGradient(0, 51, 320, 94,
+ tft.alphaBlend((uint8_t)(255 * (145 - 51) / 145),
+ getThemeBgColor(), TFT_BLACK),
+ TFT_BLACK);
tft.fillRect(0, 145, 320, 95, TFT_BLACK);
}
@@ -1099,7 +1314,8 @@ void fullScreenMeter(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
old_data[cell] = -999;
plotFullScreen(cell, last_valid_data[cell], warn, min, max, digit, unit);
} else {
- // Exponential segmented block mode full screen (18 discrete blocks, 3mm x 5mm, 1mm gap)
+ // Exponential segmented block mode full screen (18 discrete blocks, 3mm x
+ // 5mm, 1mm gap)
for (uint8_t i = 0; i < NUM_BAR_BLOCKS; i++) {
drawBarBlock(i, METER_INACTIVE_COLOR);
}
@@ -1120,12 +1336,15 @@ void plotFullScreen(uint8_t cell, float data, int warn, int min, int max,
return;
uint8_t pidIdx = pidInCell[cell];
uint16_t col = getMeterColor(pidIdx, data, warn);
- uint16_t oldCol = (old_data[cell] <= -900) ? 0 : getMeterColor(pidIdx, old_data[cell], warn);
+ uint16_t oldCol = (old_data[cell] <= -900)
+ ? 0
+ : getMeterColor(pidIdx, old_data[cell], warn);
String numStr = String(data, digit);
int newBlocks = calcMeterBlocks(data, min, max);
- int oldBlocks = (old_data[cell] <= -900) ? 0 : calcMeterBlocks(old_data[cell], min, max);
+ int oldBlocks =
+ (old_data[cell] <= -900) ? 0 : calcMeterBlocks(old_data[cell], min, max);
bool colorChanged = (col != oldCol);
if (col == TFT_RED) {
@@ -1190,11 +1409,21 @@ void plotFullScreen(uint8_t cell, float data, int warn, int min, int max,
tft.setTextPadding(0);
tft.unloadFont();
old_data[cell] = data;
+
+ if (cell == 1 && isGpsSpeedValid()) {
+ drawFullscreenGpsIcon(getGpsWaveAnimState(), isGpsSynced());
+ }
}
-// Display "---" (no data) with all blocks inactive on specified quadrant or full screen
+// Display "---" (no data) with all blocks inactive on specified quadrant or
+// full screen
void plotNoData(uint8_t cell) {
- if (cell >= 4) return;
+ if (cell >= 4)
+ return;
+ if (cell == 1 && isGpsSpeedValid()) {
+ return; // Si el GPS esta activo y entregando velocidad, nunca pisar con
+ // "---"
+ }
if (old_data[cell] <= -900.0f && last_valid_data[cell] <= -900.0f) {
return; // Already showing no data, skip redraw to avoid SPI overhead
}
@@ -1236,71 +1465,99 @@ void plotNoData(uint8_t cell) {
}
}
-// Draw parameter icon into sprite slice with true alpha channel
-inline void drawPidIconToSprite(TFT_eSprite &s, int ix, int iy, const String &pidStr, int slice_h) {
+// Draw parameter icon into sprite slice with true 8-bit alpha channel blended
+// against theme background
+inline void drawPidIconToSprite(TFT_eSprite &s, int ix, int iy,
+ const String &pidStr, int y_slice, int slice_h,
+ int global_ry) {
int w = 42, h = 42;
const unsigned short *colors = nullptr;
const unsigned char *alphas = nullptr;
if (pidStr == "0105") {
- w = 38; h = 37;
- colors = icon_temp_quad; alphas = icon_temp_quad_alpha;
+ w = 38;
+ h = 37;
+ colors = icon_temp_quad;
+ alphas = icon_temp_quad_alpha;
} else if (pidStr == "010D") {
- w = 42; h = 42;
- colors = icon_speed_quad; alphas = icon_speed_quad_alpha;
+ w = 42;
+ h = 42;
+ colors = icon_speed_quad;
+ alphas = icon_speed_quad_alpha;
} else if (pidStr == "010C") {
- w = 42; h = 43;
- colors = icon_rpm_quad; alphas = icon_rpm_quad_alpha;
+ w = 42;
+ h = 43;
+ colors = icon_rpm_quad;
+ alphas = icon_rpm_quad_alpha;
} else if (pidStr == "0142" || pidStr == "ATRV") {
- w = 46; h = 35;
- colors = icon_battery_quad; alphas = icon_battery_quad_alpha;
+ w = 46;
+ h = 35;
+ colors = icon_battery_quad;
+ alphas = icon_battery_quad_alpha;
}
- if (!colors || !alphas) return;
+ if (!colors || !alphas)
+ return;
int startJ = (iy < 0) ? -iy : 0;
int endJ = (iy + h > slice_h) ? (slice_h - iy) : h;
- if (startJ >= endJ) return;
+ if (startJ >= endJ)
+ return;
for (int j = startJ; j < endJ; j++) {
int curY = iy + j;
+ int globalY = y_slice + curY;
+ uint16_t bgCol = getInstrumentBgColor(globalY, true, global_ry);
int rowOffset = j * w;
for (int i = 0; i < w; i++) {
int curX = ix + i;
- if (curX < 0 || curX >= 320) continue;
+ if (curX < 0 || curX >= 320)
+ continue;
uint8_t a = pgm_read_byte(&alphas[rowOffset + i]);
- if (a >= 30) {
- uint16_t fg = pgm_read_word(&colors[rowOffset + i]);
+ if (a == 0)
+ continue;
+ uint16_t fg = pgm_read_word(&colors[rowOffset + i]);
+ if (a >= 250) {
s.drawPixel(curX, curY, fg);
+ } else {
+ s.drawPixel(curX, curY, fastBlend(a, fg, bgCol));
}
}
}
}
-// Draw an individual solid curved block into sprite slice with subpixel anti-aliasing
-inline void drawQuadBarBlockToSprite(TFT_eSprite &s, int16_t qx, int16_t qy, uint8_t i, uint16_t color, int slice_h) {
+// Draw an individual solid curved block into sprite slice with subpixel
+// anti-aliasing
+inline void drawQuadBarBlockToSprite(TFT_eSprite &s, int16_t qx, int16_t qy,
+ uint8_t i, uint16_t color, int slice_h) {
int16_t minX = pgm_read_word(&quadBlockMinX[i]);
int16_t maxX = pgm_read_word(&quadBlockMaxX[i]);
int16_t minY = pgm_read_word(&quadBlockMinY[i]);
int16_t maxY = pgm_read_word(&quadBlockMaxY[i]);
- if (qx + maxX < 0 || qx + minX >= 320) return;
- if (qy + maxY < 0 || qy + minY >= slice_h) return;
+ if (qx + maxX < 0 || qx + minX >= 320)
+ return;
+ if (qy + maxY < 0 || qy + minY >= slice_h)
+ return;
int16_t startY = minY;
int16_t endY = maxY;
- if (qy + startY < 0) startY = -qy;
- if (qy + endY >= slice_h) endY = slice_h - 1 - qy;
+ if (qy + startY < 0)
+ startY = -qy;
+ if (qy + endY >= slice_h)
+ endY = slice_h - 1 - qy;
int16_t startX = minX;
int16_t endX = maxX;
- if (qx + startX < 0) startX = -qx;
- if (qx + endX >= 320) endX = 319 - qx;
+ if (qx + startX < 0)
+ startX = -qx;
+ if (qx + endX >= 320)
+ endX = 319 - qx;
float nx[4], ny[4], c[4];
for (uint8_t k = 0; k < 4; k++) {
nx[k] = pgm_read_float(&quadBlockNx[i][k]);
ny[k] = pgm_read_float(&quadBlockNy[i][k]);
- c[k] = pgm_read_float(&quadBlockC[i][k]);
+ c[k] = pgm_read_float(&quadBlockC[i][k]);
}
for (int16_t y = startY; y <= endY; y++) {
@@ -1311,9 +1568,11 @@ inline void drawQuadBarBlockToSprite(TFT_eSprite &s, int16_t qx, int16_t qy, uin
float min_d = 999.0f;
for (uint8_t k = 0; k < 4; k++) {
float d = px * nx[k] + py * ny[k] + c[k];
- if (d < min_d) min_d = d;
+ if (d < min_d)
+ min_d = d;
}
- if (min_d <= -0.5f) continue;
+ if (min_d <= -0.5f)
+ continue;
int16_t sx = qx + x;
if (color == TFT_BLACK) {
s.drawPixel(sx, sy, TFT_BLACK);
@@ -1330,7 +1589,8 @@ inline void drawQuadBarBlockToSprite(TFT_eSprite &s, int16_t qx, int16_t qy, uin
}
// Render Page 0 (4 Quadrants) into double-buffered Sprite slice
-inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) {
+inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice,
+ int slice_h) {
checkUpdateThemeGradients();
uint16_t divCol = getThemeDivColor();
uint16_t priCol = getThemePrimaryColor();
@@ -1339,9 +1599,11 @@ inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s
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 ry = ((c & 2) ? 121 : 0) - y_slice;
+ 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;
+ 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);
@@ -1356,10 +1618,13 @@ inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s
uint8_t pidIdx = pidInCell[c];
- // 2. Real high-definition parameter icon (thermometer, speedometer, tachometer, battery)
- drawPidIconToSprite(s, qx + 5, qy + 3, pidConfig[pidIdx][2], slice_h);
+ // 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)
+ // 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();
@@ -1367,14 +1632,16 @@ inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s
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;
+ 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 (pixel-perfect match with resting screen)
+ // 4. Digital value string using authentic DSEG7 34pt font (pixel-perfect
+ // match with resting screen)
String numStr = (val <= -900.0f) ? "---" : String(val, digit);
int textY = qy + 74;
if (textY < slice_h && textY + 40 > 0) {
@@ -1384,6 +1651,15 @@ inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s
s.drawRightString(numStr.c_str(), qx + 152, textY, 1);
s.fontLoaded = false;
}
+
+ // GPS Satellite icon in top right of Quadrant 1 (Velocidad: cell 1)
+ if (c == 1 && isGpsSpeedValid() && y_slice == 0) {
+ uint16_t waveCol = 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
@@ -1415,14 +1691,17 @@ inline void renderPage0ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s
}
// Render Page 1 (3 Temperature Cards) into double-buffered Sprite slice
-inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int slice_h) {
+inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice,
+ int slice_h) {
for (uint8_t cardIdx = 0; cardIdx < 3; cardIdx++) {
int cardY = (6 + cardIdx * 77) - y_slice;
int cardH = 73;
int cardX = 4 + x_offset;
int cardW = 312;
- if (cardX + cardW <= 0 || cardX >= 320 || cardY + cardH <= 0 || cardY >= slice_h) continue;
+ if (cardX + cardW <= 0 || cardX >= 320 || cardY + cardH <= 0 ||
+ cardY >= slice_h)
+ continue;
uint16_t cardBg = 0x10A2;
uint16_t cardBorder = 0x2945;
@@ -1431,38 +1710,55 @@ inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s
s.drawRoundRect(cardX, cardY, cardW, cardH, 6, cardBorder);
uint16_t accentCol = getThemePrimaryColor();
- const char* titleStr = "";
- const char* subStr = "";
+ const char *titleStr = "";
+ const char *subStr = "";
float val = 0.0f;
float minRange = 0.0f, maxRange = 100.0f;
bool isInt = false;
if (cardIdx == 0) {
- titleStr = "MOTOR / REFRIGERANTE";
+ titleStr = "AGUA";
subStr = ignitionOn ? "ECU OBD-II (PID 0105)" : "ECU OBD-II (STANDBY)";
val = tempEngineCoolant;
- minRange = 60.0f; maxRange = 120.0f; isInt = true;
- if (val >= 110.0f) accentCol = TFT_RED;
- else if (val >= 100.0f) accentCol = TFT_YELLOW;
+ minRange = 60.0f;
+ maxRange = 120.0f;
+ isInt = true;
+ if (val >= 110.0f)
+ accentCol = TFT_RED;
+ else if (val >= 100.0f)
+ accentCol = TFT_YELLOW;
} else if (cardIdx == 1) {
- titleStr = "HABITACULO / INTERIOR";
- subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1" : "DS18B20 SENSOR 1 (DEMO)";
+ titleStr = "INTERIOR";
+ subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1 (GPIO 22)"
+ : "DS18B20 SENSOR 1";
val = tempInterior;
- minRange = 10.0f; maxRange = 40.0f;
+ minRange = 10.0f;
+ maxRange = 40.0f;
accentCol = 0x07F3;
} else {
- titleStr = "AMBIENTE / EXTERIOR";
- subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2" : "DS18B20 SENSOR 2 (DEMO)";
+ titleStr = "EXTERIOR";
+ subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2 (GPIO 22)"
+ : (dsSensorCount == 1 ? "ESTIMADA (-4.5`C)"
+ : "DS18B20 SENSOR 2");
val = tempExterior;
- minRange = -10.0f; maxRange = 50.0f;
+ minRange = -10.0f;
+ maxRange = 50.0f;
accentCol = 0x3D7F;
}
s.fillRoundRect(cardX + 6, cardY + 8, 4, cardH - 16, 2, accentCol);
- s.setTextColor(TFT_WHITE, cardBg);
- s.drawString(titleStr, cardX + 16, cardY + 8, 2);
+ // Smooth anti-aliased title in transition sprite (zero mallocs, 100%
+ // smooth)
+ if (!sprTriTitleReady)
+ initTriTempTitles();
+ if (sprTriTitleReady && sprTriTitle[cardIdx].created()) {
+ sprTriTitle[cardIdx].pushToSprite(&s, cardX + 16, cardY + 6);
+ } else {
+ s.setTextColor(TFT_WHITE, cardBg);
+ s.drawString(titleStr, cardX + 16, cardY + 6, 4);
+ }
s.setTextColor(0x8410, cardBg);
- s.drawString(subStr, cardX + 16, cardY + 28, 1);
+ s.drawString(subStr, cardX + 16, cardY + 34, 1);
// Progress bar
int barX = cardX + 16;
@@ -1470,163 +1766,393 @@ inline void renderPage1ToSprite(TFT_eSprite &s, int x_offset, int y_slice, int s
int barW = 120;
s.fillRoundRect(barX, barY, barW, 8, 3, 0x18C3);
float ratio = (val - minRange) / (maxRange - minRange);
- if (ratio < 0.0f) ratio = 0.0f;
- if (ratio > 1.0f) ratio = 1.0f;
+ if (ratio < 0.0f)
+ ratio = 0.0f;
+ if (ratio > 1.0f)
+ ratio = 1.0f;
int fillW = (int)(barW * ratio);
- if (fillW < 4) fillW = 4;
+ if (fillW < 4)
+ fillW = 4;
s.fillRoundRect(barX, barY, fillW, 8, 3, accentCol);
// Value text using authentic DSEG7 34pt font matching drawTriTempCard
- String numStr = (val <= -900.0f) ? "---" : (isInt ? String((int)val) : String(val, 1));
- int numY = cardY + 22;
+ String numStr =
+ (val <= -900.0f) ? "---" : (isInt ? String((int)val) : String(val, 1));
+ int numY = cardY + 20;
if (numY < slice_h && numY + 40 > 0) {
s.fontLoaded = true;
s.setTextColor(accentCol, cardBg);
s.setTextPadding(0);
- s.drawRightString(numStr.c_str(), cardX + 262, numY, 1);
+ s.drawRightString(numStr.c_str(), cardX + 270, numY, 1);
s.fontLoaded = false;
+ // Unit "`C" during transition animation aligned to digit base
+ s.setTextColor(accentCol, cardBg);
+ s.drawString("`C", cardX + 276, cardY + 37, 2);
}
-
- s.setTextColor(0xC618, cardBg);
- s.drawString("`C", cardX + 268, cardY + 32, 2);
}
}
// 100% Flicker-Free Double-Buffered Mobile Slide Transition
// Safe 25KB sprite with pixel-perfect clipping and smooth ease-out pacing
-inline void animateSlideTransition(bool swipeLeft) {
- Serial.println(">>> [DB] animateSlideTransition ENTER");
- const int slice_h = 40; // 320x40 = only 25KB (100% safe, leaves 70KB+ free for Bluetooth)
+inline void animateSlideTransition(bool swipeLeft, int8_t fromPage = -1,
+ int8_t toPage = -1) {
+ if (fromPage < 0)
+ fromPage = currentScreenPage;
+ if (toPage < 0)
+ toPage = (fromPage == 0) ? 1 : 0;
+#ifdef SERIAL_DEBUG
+ Serial.printf(">>> [DB] animateSlideTransition ENTER: fromPage=%d, "
+ "toPage=%d, swipeLeft=%d\n",
+ fromPage, toPage, swipeLeft);
+#endif
+ const int slice_h = 40;
const uint8_t numSlices = 240 / slice_h; // exactly 6 slices
- TFT_eSprite spr(&tft);
- if (spr.createSprite(320, slice_h) == nullptr) {
- Serial.println(">>> [DB] spr.createSprite FAILED (nullptr)!");
+ if (!sprTransAllocated) {
+ initScreen(true);
return; // Safety fallback
}
- spr.setTextFont(1); // CRITICAL: initializes textfont=1 and clears uninitialized gfxFont=NULL
- spr.setFreeFont(NULL); // CRITICAL: prevents crash in textWidth() when drawing Font 1 on stack sprite
- spr.loadFont(DSEG7_Modern_BoldItalic_34);
- spr.fontLoaded = false; // Default to standard fonts until DSEG7 digits are drawn
- Serial.printf(">>> [DB] Sprite created OK with DSEG7. Heap: %u, MaxAlloc: %u\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap());
const uint8_t FRAMES = 28;
// High-density sine ease-out deceleration over 28 frames:
- // Ultra-fine 10-18px initial steps smoothly tapering to 1-3px for zero visible refresh chop
+ // Ultra-fine 10-18px initial steps smoothly tapering to 1-3px for zero
+ // visible refresh chop
const int16_t step_shifts[FRAMES] = {
- 18, 36, 54, 71, 89, 106, 122, 139, 155, 170,
- 185, 200, 213, 226, 239, 250, 261, 271, 280, 288,
- 296, 302, 307, 312, 315, 318, 319, 320
- };
+ 18, 36, 54, 71, 89, 106, 122, 139, 155, 170, 185, 200, 213, 226,
+ 239, 250, 261, 271, 280, 288, 296, 302, 307, 312, 315, 318, 319, 320};
for (uint8_t f = 0; f < FRAMES; f++) {
- if (f == 0 || f == FRAMES / 2 || f == FRAMES - 1) {
- Serial.printf(">>> [DB] Frame %d / %d (heap: %u)\n", f, FRAMES, ESP.getFreeHeap());
- }
int16_t shift = step_shifts[f];
int16_t splitX = swipeLeft ? (320 - shift) : shift;
- int16_t x0 = swipeLeft ? (-shift) : (shift - 320);
- int16_t x1 = swipeLeft ? (320 - shift) : shift;
for (uint8_t s = 0; s < numSlices; s++) {
int y_slice = s * slice_h;
- spr.fillSprite(TFT_BLACK);
+ sprTrans.fillSprite(TFT_BLACK);
- if (splitX > 0) {
- spr.setViewport(0, 0, splitX, slice_h, false);
- renderPage0ToSprite(spr, x0, y_slice, slice_h);
+ if (swipeLeft) {
+ // Swipe Left: fromPage on left moving left, toPage on right moving left
+ if (splitX > 0) {
+ sprTrans.setViewport(0, 0, splitX, slice_h, false);
+ if (fromPage == 0)
+ renderPage0ToSprite(sprTrans, -shift, y_slice, slice_h);
+ else if (fromPage == 1)
+ renderPage1ToSprite(sprTrans, -shift, y_slice, slice_h);
+ else if (fromPage == 2)
+ renderPage2ToSprite(sprTrans, -shift, y_slice, slice_h);
+ else
+ renderPage3ToSprite(sprTrans, -shift, y_slice, slice_h);
+ }
+ if (splitX < 320) {
+ sprTrans.setViewport(splitX, 0, 320 - splitX, slice_h, false);
+ if (toPage == 0)
+ renderPage0ToSprite(sprTrans, 320 - shift, y_slice, slice_h);
+ else if (toPage == 1)
+ renderPage1ToSprite(sprTrans, 320 - shift, y_slice, slice_h);
+ else if (toPage == 2)
+ renderPage2ToSprite(sprTrans, 320 - shift, y_slice, slice_h);
+ else
+ renderPage3ToSprite(sprTrans, 320 - shift, y_slice, slice_h);
+ }
+ } else {
+ // Swipe Right: toPage on left moving right, fromPage on right moving
+ // right
+ if (splitX > 0) {
+ sprTrans.setViewport(0, 0, splitX, slice_h, false);
+ if (toPage == 0)
+ renderPage0ToSprite(sprTrans, shift - 320, y_slice, slice_h);
+ else if (toPage == 1)
+ renderPage1ToSprite(sprTrans, shift - 320, y_slice, slice_h);
+ else if (toPage == 2)
+ renderPage2ToSprite(sprTrans, shift - 320, y_slice, slice_h);
+ else
+ renderPage3ToSprite(sprTrans, shift - 320, y_slice, slice_h);
+ }
+ if (splitX < 320) {
+ sprTrans.setViewport(splitX, 0, 320 - splitX, slice_h, false);
+ if (fromPage == 0)
+ renderPage0ToSprite(sprTrans, shift, y_slice, slice_h);
+ else if (fromPage == 1)
+ renderPage1ToSprite(sprTrans, shift, y_slice, slice_h);
+ else if (fromPage == 2)
+ renderPage2ToSprite(sprTrans, shift, y_slice, slice_h);
+ else
+ renderPage3ToSprite(sprTrans, shift, y_slice, slice_h);
+ }
}
- if (splitX < 320) {
- spr.setViewport(splitX, 0, 320 - splitX, slice_h, false);
- renderPage1ToSprite(spr, x1, y_slice, slice_h);
- }
- spr.resetViewport();
+ sprTrans.resetViewport();
// Dividing neon seam line
if (splitX > 0 && splitX < 320) {
- spr.drawFastVLine(splitX, 0, slice_h, getThemePrimaryColor());
- spr.drawFastVLine(splitX + (swipeLeft ? 1 : -1), 0, slice_h, TFT_WHITE);
+ sprTrans.drawFastVLine(splitX, 0, slice_h, getThemePrimaryColor());
+ int16_t whiteLineX = splitX + (swipeLeft ? 1 : -1);
+ if (whiteLineX >= 0 && whiteLineX < 320) {
+ sprTrans.drawFastVLine(whiteLineX, 0, slice_h, TFT_WHITE);
+ }
}
- // Pagination indicator dots on top slice
+ // Pagination indicator dots on top slice (4 dots for 4 pages)
if (y_slice == 0) {
- spr.fillCircle(153, 2, 2, (splitX > 160) ? getThemePrimaryColor() : 0x5AEB);
- spr.fillCircle(167, 2, 2, (splitX <= 160) ? getThemePrimaryColor() : 0x5AEB);
+ uint8_t activeDot = (shift < 160) ? fromPage : toPage;
+ sprTrans.fillCircle(139, 2, 2,
+ (activeDot == 0) ? getThemePrimaryColor() : 0x5AEB);
+ sprTrans.fillCircle(153, 2, 2,
+ (activeDot == 1) ? getThemePrimaryColor() : 0x5AEB);
+ sprTrans.fillCircle(167, 2, 2,
+ (activeDot == 2) ? getThemePrimaryColor() : 0x5AEB);
+ sprTrans.fillCircle(181, 2, 2,
+ (activeDot == 3) ? getThemePrimaryColor() : 0x5AEB);
}
// Direct single SPI push per slice: ZERO visual flicker!
- spr.pushSprite(0, y_slice);
+ sprTrans.pushSprite(0, y_slice);
+ yield();
}
yield(); // Feed watchdog and let Bluetooth run smoothly on background core
delay(7); // Ultra-smooth high density pacing (~330ms total)
}
- spr.fontLoaded = true;
- spr.unloadFont();
- spr.deleteSprite(); // Release the 25KB
- Serial.printf(">>> [DB] animateSlideTransition LEAVE (heap: %u)\n", ESP.getFreeHeap());
+ sprTrans.fontLoaded = false;
+}
+
+// 100% Flicker-Free Double-Buffered Crossfade Transition
+// Ultra-fast parallel 32-bit RGB565 blend with zero static RAM allocation
+inline void animateFadeTransition(int8_t fromPage = -1, int8_t toPage = -1) {
+ if (fromPage < 0)
+ fromPage = currentScreenPage;
+ if (toPage < 0)
+ toPage = (fromPage == 0) ? 1 : 0;
+#ifdef SERIAL_DEBUG
+ Serial.printf(">>> [DB] animateFadeTransition ENTER: fromPage=%d, toPage=%d, "
+ "heap=%u, maxAlloc=%u\n",
+ fromPage, toPage, ESP.getFreeHeap(), ESP.getMaxAllocHeap());
+#endif
+
+ const int slice_h = 40;
+ const uint8_t numSlices = 240 / slice_h; // exactly 6 slices
+
+ if (!sprTransAllocated) {
+ initScreen(true);
+ return;
+ }
+
+ TFT_eSprite sprB(&tft);
+ if (sprB.createSprite(320, slice_h) == nullptr) {
+ animateSlideTransition(true, fromPage, toPage);
+ return;
+ }
+
+ sprB.setTextFont(1);
+ sprB.setFreeFont(NULL);
+ sprB.loadFont(DSEG7_Modern_BoldItalic_34);
+ sprB.fontLoaded = false;
+
+ const uint8_t FRAMES = 10;
+ // Perceptual crossfade steps (0 to 32)
+ const uint8_t alpha_steps[FRAMES] = {3, 6, 10, 14, 18, 22, 26, 29, 31, 32};
+ const int totalPixels = 320 * slice_h;
+
+ for (uint8_t f = 0; f < FRAMES; f++) {
+ uint8_t alpha = alpha_steps[f];
+
+ for (uint8_t s = 0; s < numSlices; s++) {
+ int y_slice = s * slice_h;
+
+ sprTrans.fillSprite(TFT_BLACK);
+ sprB.fillSprite(TFT_BLACK);
+
+ if (fromPage == 0)
+ renderPage0ToSprite(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
+ renderPage3ToSprite(sprTrans, 0, y_slice, slice_h);
+
+ if (toPage == 0)
+ renderPage0ToSprite(sprB, 0, y_slice, slice_h);
+ else if (toPage == 1)
+ renderPage1ToSprite(sprB, 0, y_slice, slice_h);
+ else if (toPage == 2)
+ renderPage2ToSprite(sprB, 0, y_slice, slice_h);
+ else
+ renderPage3ToSprite(sprB, 0, y_slice, slice_h);
+
+ uint16_t *pA = (uint16_t *)sprTrans.getPointer();
+ uint16_t *pB = (uint16_t *)sprB.getPointer();
+
+ if (pA && pB) {
+ for (int i = 0; i < totalPixels; i++) {
+ uint16_t rawA = pA[i];
+ uint16_t rawB = pB[i];
+ if (rawA == rawB)
+ continue;
+
+ // Convert byte-swapped format from sprite to RGB565
+ uint16_t c1 = (rawA >> 8) | (rawA << 8);
+ uint16_t c2 = (rawB >> 8) | (rawB << 8);
+
+ // Fast parallel 32-bit blend of Red/Blue and Green
+ uint32_t rb1 = c1 & 0xF81F;
+ uint32_t g1 = c1 & 0x07E0;
+ uint32_t rb2 = c2 & 0xF81F;
+ uint32_t g2 = c2 & 0x07E0;
+
+ uint32_t rb = ((rb1 * (32 - alpha) + rb2 * alpha) >> 5) & 0xF81F;
+ uint32_t g = ((g1 * (32 - alpha) + g2 * alpha) >> 5) & 0x07E0;
+
+ uint16_t blended = (uint16_t)(rb | g);
+ pA[i] = (blended >> 8) | (blended << 8);
+ }
+ }
+
+ // Pagination indicator dots on top slice (4 dots for 4 pages)
+ if (y_slice == 0) {
+ uint8_t activeDot = (alpha < 16) ? fromPage : toPage;
+ sprTrans.fillCircle(139, 2, 2,
+ (activeDot == 0) ? getThemePrimaryColor() : 0x5AEB);
+ sprTrans.fillCircle(153, 2, 2,
+ (activeDot == 1) ? getThemePrimaryColor() : 0x5AEB);
+ sprTrans.fillCircle(167, 2, 2,
+ (activeDot == 2) ? getThemePrimaryColor() : 0x5AEB);
+ sprTrans.fillCircle(181, 2, 2,
+ (activeDot == 3) ? getThemePrimaryColor() : 0x5AEB);
+ }
+
+ sprTrans.pushSprite(0, y_slice);
+ yield();
+ }
+
+ yield();
+ delay(10);
+ }
+
+ sprB.fontLoaded = true;
+ sprB.unloadFont();
+ sprB.deleteSprite();
+ sprTrans.fontLoaded = false;
+}
+
+// Master transition dispatcher based on user configuration
+inline void performScreenTransition(bool swipeLeft, int8_t fromPage = -1,
+ int8_t toPage = -1) {
+ if (fromPage < 0)
+ fromPage = currentScreenPage;
+ if (toPage < 0)
+ toPage = (fromPage + 1) % 4;
+ if (transitionType == 1) {
+ animateFadeTransition(fromPage, toPage);
+ } else {
+ animateSlideTransition(swipeLeft, fromPage, toPage);
+ }
}
/*-------------------*/
// initialize display
-void initScreen() {
+void initScreen(bool redrawTFT) {
terminalLineCount = 0;
- Serial.printf(">>> [DB] initScreen START: page=%d, fullScreenCell=%d, heap=%u\n", currentScreenPage, fullScreenCell, ESP.getFreeHeap());
+#ifdef SERIAL_DEBUG
+ Serial.printf(">>> [DB] initScreen START: page=%d, fullScreenCell=%d, "
+ "redrawTFT=%d, heap=%u\n",
+ currentScreenPage, fullScreenCell, redrawTFT,
+ ESP.getFreeHeap());
+#endif
for (int i = 0; i < 4; i++) {
pidList[i] = pidConfig[pidInCell[i]][2]; // get PID to list
pidReadSkip[i] = pidConfig[pidInCell[i]][6].toInt(); // get read skip
pidCurrentSkip[i] = pidReadSkip[i]; // skip for skip reading pid
- old_data[i] = -999; // clear old data
}
for (int i = 4; i < maxpidIndex; i++) {
pidList[i] = pidConfig[i][2]; // get background PIDs (e.g. 0104)
pidReadSkip[i] = pidConfig[i][6].toInt(); // get read skip
pidCurrentSkip[i] = pidReadSkip[i];
+ }
+
+ if (!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) {
+ syncTriTempTrackers();
+ } else if (currentScreenPage == 2) {
+ syncGpsClockTrackers();
+ } else if (currentScreenPage == 3) {
+ syncGpsNavTrackers();
+ } else if (currentScreenPage == 0 && fullScreenCell == -1) {
+ syncPage0Trackers();
+ }
+#ifdef SERIAL_DEBUG
+ Serial.printf(">>> [DB] initScreen (NO REDRAW) FINISHED OK (heap: %u)\n",
+ ESP.getFreeHeap());
+#endif
+ return;
+ }
+
+ for (int i = 0; i < maxpidIndex; i++) {
old_data[i] = -999;
}
+
if (fullScreenCell >= 0 && fullScreenCell < 4) {
+#ifdef SERIAL_DEBUG
Serial.println(">>> [DB] initScreen: drawing fullScreenMeter");
+#endif
fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell]);
} else if (currentScreenPage == 1) {
+#ifdef SERIAL_DEBUG
Serial.println(">>> [DB] initScreen: drawing drawTriTempScreen(0)");
+#endif
drawTriTempScreen(0);
+ } else if (currentScreenPage == 2) {
+#ifdef SERIAL_DEBUG
+ Serial.println(">>> [DB] initScreen: drawing drawGpsClockScreen(0)");
+#endif
+ drawGpsClockScreen(0);
+ } else if (currentScreenPage == 3) {
+#ifdef SERIAL_DEBUG
+ Serial.println(">>> [DB] initScreen: drawing drawGpsNavScreen(0)");
+#endif
+ drawGpsNavScreen(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 [● ○]
- tft.fillCircle(153, 2, 2, getThemePrimaryColor()); // Page 1 active
- tft.fillCircle(167, 2, 2, 0x5AEB); // Page 2 inactive
+ // Pagination indicator dots [● ○ ○ ○] (Page 1 active)
+ tft.fillCircle(139, 2, 2, getThemePrimaryColor()); // Page 1 active
+ tft.fillCircle(153, 2, 2, 0x5AEB); // Page 2 inactive
+ tft.fillCircle(167, 2, 2, 0x5AEB); // Page 3 inactive
+ tft.fillCircle(181, 2, 2, 0x5AEB); // Page 4 inactive
}
if (currentScreenPage == 0 && fullScreenCell == -1) {
badgeEcoDrawn = false;
updateEcoBadge(true);
}
- Serial.printf(">>> [DB] initScreen FINISHED OK (heap: %u)\n", ESP.getFreeHeap());
+#ifdef SERIAL_DEBUG
+ Serial.printf(">>> [DB] initScreen FINISHED OK (heap: %u)\n",
+ ESP.getFreeHeap());
+#endif
}
/*-------------------------------*/
-// get A B from response return in global A B variable; returns true only if valid OBD2 data bytes were parsed
+// get A B from response return in global A B variable; returns true only if
+// valid OBD2 data bytes were parsed
bool getAB(String elm_rsp, String currentPid) { // 41 05 2A 3C 01>
A = 0;
B = 0;
if (currentPid == "ATRV") {
return true;
}
- if (elm_rsp.indexOf("NO DATA") != -1 ||
- elm_rsp.indexOf("UNABLE") != -1 ||
- elm_rsp.indexOf("ERROR") != -1 ||
- elm_rsp.indexOf("STOPPED") != -1 ||
- elm_rsp.indexOf("BUS BUSY") != -1 ||
- elm_rsp.indexOf("TIMEOUT") != -1 ||
- elm_rsp.indexOf("SEARCHING") != -1 ||
- elm_rsp.indexOf("?") != -1 ||
+ if (elm_rsp.indexOf("NO DATA") != -1 || elm_rsp.indexOf("UNABLE") != -1 ||
+ elm_rsp.indexOf("ERROR") != -1 || elm_rsp.indexOf("STOPPED") != -1 ||
+ elm_rsp.indexOf("BUS BUSY") != -1 || elm_rsp.indexOf("TIMEOUT") != -1 ||
+ elm_rsp.indexOf("SEARCHING") != -1 || elm_rsp.indexOf("?") != -1 ||
elm_rsp.indexOf("BUFFER FULL") != -1) {
return false;
}
@@ -1636,8 +2162,10 @@ bool getAB(String elm_rsp, String currentPid) { // 41 05 2A 3C 01>
elm_rsp.replace('\n', ' ');
elm_rsp.replace('>', ' ');
- // Extract expected PID hex byte (e.g. "0105" -> "05", "010C" -> "0C", "010D" -> "0D")
- String pidByte = (currentPid.length() >= 4) ? currentPid.substring(2) : currentPid;
+ // Extract expected PID hex byte (e.g. "0105" -> "05", "010C" -> "0C", "010D"
+ // -> "0D")
+ String pidByte =
+ (currentPid.length() >= 4) ? currentPid.substring(2) : currentPid;
pidByte.toUpperCase();
bool needB = (pidByte == "0C"); // RPM requires both A and B!
@@ -1649,9 +2177,11 @@ bool getAB(String elm_rsp, String currentPid) { // 41 05 2A 3C 01>
while (startIdx < elm_rsp.length() && elm_rsp.charAt(startIdx) == ' ') {
startIdx++;
}
- if (startIdx >= elm_rsp.length()) break;
+ if (startIdx >= elm_rsp.length())
+ break;
int endIdx = elm_rsp.indexOf(' ', startIdx);
- if (endIdx == -1) endIdx = elm_rsp.length();
+ if (endIdx == -1)
+ endIdx = elm_rsp.length();
tokens[tokenCount++] = elm_rsp.substring(startIdx, endIdx);
startIdx = endIdx + 1;
}
@@ -1678,11 +2208,13 @@ bool getAB(String elm_rsp, String currentPid) { // 41 05 2A 3C 01>
}
}
- // 2. Compact string fallback (in case adapter sends responses without spaces, e.g. "410D32")
+ // 2. Compact string fallback (in case adapter sends responses without spaces,
+ // e.g. "410D32")
String compact = "";
for (int i = 0; i < elm_rsp.length(); i++) {
char c = elm_rsp.charAt(i);
- if (c != ' ') compact += (char)toupper(c);
+ if (c != ' ')
+ compact += (char)toupper(c);
}
String targetPrefix = "41" + pidByte;
int fIdx = compact.indexOf(targetPrefix);
@@ -1691,7 +2223,8 @@ bool getAB(String elm_rsp, String currentPid) { // 41 05 2A 3C 01>
if (needB) {
if (dataIdx + 4 <= compact.length()) {
A = strtol(compact.substring(dataIdx, dataIdx + 2).c_str(), NULL, 16);
- B = strtol(compact.substring(dataIdx + 2, dataIdx + 4).c_str(), NULL, 16);
+ B = strtol(compact.substring(dataIdx + 2, dataIdx + 4).c_str(), NULL,
+ 16);
return true;
}
} else {
@@ -1718,7 +2251,31 @@ bool getAB(String elm_rsp, String currentPid) { // 41 05 2A 3C 01>
return false;
} // getAB
-/*-------------------------------*/
+
+// Update vehicle speed from GPS on quadrant 1 (Velocidad) and fullscreen if
+// active
+inline void updateGpsSpeedometer() {
+ if (!isGpsSpeedValid())
+ return;
+ float spd = round(getGpsSpeed());
+ if (spd > 0.0f)
+ lastActivityTime = millis();
+
+ // Only update if integer speed changed or was invalid
+ static float lastRenderedGpsSpd = -999.0f;
+ if (spd != lastRenderedGpsSpd || last_valid_data[1] <= -900.0f) {
+ lastRenderedGpsSpd = spd;
+ 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");
+ }
+ }
+}
+
void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
if (pidNo >= maxpidIndex)
return;
@@ -1764,14 +2321,19 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
String s = response;
s.trim();
int vIdx = s.indexOf('V');
- if (vIdx == -1) vIdx = s.indexOf('v');
- if (vIdx != -1) s = s.substring(0, vIdx);
+ if (vIdx == -1)
+ vIdx = s.indexOf('v');
+ if (vIdx != -1)
+ s = s.substring(0, vIdx);
int lastSpace = s.lastIndexOf(' ');
- if (lastSpace != -1) s = s.substring(lastSpace + 1);
+ if (lastSpace != -1)
+ s = s.substring(lastSpace + 1);
int lastCr = s.lastIndexOf('\r');
- if (lastCr != -1) s = s.substring(lastCr + 1);
+ if (lastCr != -1)
+ s = s.substring(lastCr + 1);
int lastLf = s.lastIndexOf('\n');
- if (lastLf != -1) s = s.substring(lastLf + 1);
+ if (lastLf != -1)
+ s = s.substring(lastLf + 1);
s.trim();
data = s.toFloat();
if (data <= 1.0f) {
@@ -1791,8 +2353,9 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
if (isFailed) {
pidFailCount[pidNo]++;
- // Debounce automotriz: si un paquete cae por ruido en el bus J1850, NUNCA poner en cero la pantalla,
- // simplemente mantener el valor anterior. Solo si falla 10 veces seguidas declarar perdida de contacto.
+ // Debounce automotriz: si un paquete cae por ruido en el bus J1850, NUNCA
+ // poner en cero la pantalla, simplemente mantener el valor anterior. Solo
+ // si falla 10 veces seguidas declarar perdida de contacto.
if (pidFailCount[pidNo] >= 10) {
if (formula == 3) {
currentRpm = 0.0f; // Motor detenido / sin RPM
@@ -1802,7 +2365,8 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
if (consecutiveEcuFails >= 6) {
ignitionOn = false;
updateEcoBadge();
- // Contacto cerrado sostenido: limpiar todos los instrumentos a "---" (sin datos)
+ // Contacto cerrado sostenido: limpiar todos los instrumentos a "---"
+ // (sin datos)
for (uint8_t c = 0; c < 4; c++) {
plotNoData(c);
}
@@ -1812,7 +2376,8 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
return; // SALE SIN TOCAR LA PANTALLA NI PONERLA EN CERO
}
- // Si la respuesta fue valida, reseteamos contadores de fallo y confirmamos contacto puesto
+ // Si la respuesta fue valida, reseteamos contadores de fallo y confirmamos
+ // contacto puesto
pidFailCount[pidNo] = 0;
consecutiveEcuFails = 0;
ignitionOn = true;
@@ -1864,7 +2429,13 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
break;
case 7:
data = A; // km/h (PID 010D Vehicle Speed)
- if (data > 0.0f) lastActivityTime = millis();
+ if (data > 0.0f)
+ lastActivityTime = millis();
+ // If GPS speed is active and locked, prioritize GPS speed over OBD-II
+ if (isGpsSpeedValid()) {
+ bt_message = "";
+ return;
+ }
break;
case 8:
// Ya parseado y validado arriba en data
@@ -1873,7 +2444,8 @@ void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
return;
}
- // Si el motor esta en contacto pero apagado (RPM == 0), mostrar "---" en RPM en vez de 0
+ // Si el motor esta en contacto pero apagado (RPM == 0), mostrar "---" en RPM
+ // en vez de 0
if (formula == 3 && data <= 0.0f) {
currentRpm = 0.0f;
updateEcoBadge();
@@ -2103,7 +2675,8 @@ void handleAlarmSound(bool anyAlarmActive, bool flashTick = false) {
}
}
-// Non-blocking handler to blink/flash arcs and numerical values on alarm (when in TFT_RED)
+// Non-blocking handler to blink/flash arcs and numerical values on alarm (when
+// in TFT_RED)
void handleArcAlarmFlash() {
uint32_t now = millis();
bool toggleTick = false;
@@ -2124,7 +2697,8 @@ void handleArcAlarmFlash() {
uint16_t col = getMeterColor(pidIdx, data, warn);
bool inRed = (col == TFT_RED);
bool isAlarmActive = alarmEnabled[pidIdx];
- if (inRed && isAlarmActive) anyInRedAlarm = true;
+ if (inRed && isAlarmActive)
+ anyInRedAlarm = true;
bool digit = pidConfig[pidIdx][7].toInt();
String numStr = String(data, digit);
@@ -2189,7 +2763,8 @@ void handleArcAlarmFlash() {
uint16_t col = getMeterColor(pidIdx, data, warn);
bool inRed = (col == TFT_RED);
bool isAlarmActive = alarmEnabled[pidIdx];
- if (inRed && isAlarmActive) anyInRedAlarm = true;
+ if (inRed && isAlarmActive)
+ anyInRedAlarm = true;
bool digit = pidConfig[pidIdx][7].toInt();
String numStr = String(data, digit);
int x = cell4_x[cell];
@@ -2245,7 +2820,8 @@ void handleArcAlarmFlash() {
}
}
} else {
- // Page 1 (Tri-Temperature screen): check alarm conditions for acoustic warning without touching screen graphics
+ // Page 1 (Tri-Temperature screen): check alarm conditions for acoustic
+ // warning without touching screen graphics
for (uint8_t cell = 0; cell < 4; cell++) {
if (last_valid_data[cell] != -999) {
uint8_t pidIdx = pidInCell[cell];
@@ -2254,7 +2830,8 @@ void handleArcAlarmFlash() {
uint16_t col = getMeterColor(pidIdx, data, warn);
bool inRed = (col == TFT_RED);
bool isAlarmActive = alarmEnabled[pidIdx];
- if (inRed && isAlarmActive) anyInRedAlarm = true;
+ if (inRed && isAlarmActive)
+ anyInRedAlarm = true;
}
}
}
@@ -2288,7 +2865,8 @@ void updateBacklightFade() {
// auto dim backlight function
void autoDim() {
if (brightnessIndex > 0) {
- uint8_t manualVal = BRIGHTNESS_PWM_TABLE[brightnessIndex > 5 ? 5 : brightnessIndex];
+ uint8_t manualVal =
+ BRIGHTNESS_PWM_TABLE[brightnessIndex > 5 ? 5 : brightnessIndex];
if (targetBacklight != manualVal) {
targetBacklight = manualVal;
}
diff --git a/sketch/obd2_gauge/obd2_gauge.ino b/sketch/obd2_gauge/obd2_gauge.ino
index 7925fd7..130cc47 100644
--- a/sketch/obd2_gauge/obd2_gauge.ino
+++ b/sketch/obd2_gauge/obd2_gauge.ino
@@ -5,30 +5,30 @@
* Esquema de partición: huge_app (3MB APP / No OTA / LittleFS)
*=============================================================================*/
-//Intermal temperature sensor function declaration
+// Intermal temperature sensor function declaration
#ifdef __cplusplus
extern "C" {
#endif
- uint8_t temprature_sens_read();
+uint8_t temprature_sens_read();
#ifdef __cplusplus
}
#endif
-uint8_t temprature_sens_read(); //declare intermal temperature sensor functio
+uint8_t temprature_sens_read(); // declare intermal temperature sensor functio
-//Macro
-#define array_length(x) (sizeof(x) / sizeof(x[0])) //macro to calculate array length
+// Macro
+#define array_length(x) \
+ (sizeof(x) / sizeof(x[0])) // macro to calculate array length
-//library load
-#include //save permanent data
-Preferences pref; //create preference
+// library load
+#include //save permanent data
+Preferences pref; // create preference
#include
-#include //Hardware-specific library
+#include //Hardware-specific library
-
-//Pin configuration
-#define SCA_PIN 21 //not used same pin with TFT_BL
-#define SCL_PIN 22 //not used (avaialable) CAN_TX
-#define ADC_PIN 35 //not used (available) CAN_RX
+// Pin configuration
+#define SCA_PIN 21 // not used same pin with TFT_BL
+#define SCL_PIN 22 // not used (avaialable) CAN_TX
+#define ADC_PIN 35 // not used (available) CAN_RX
// Verified Physical Hardware Mapping on ESP32-2432S028:
// GPIO 4 = RED LED
// GPIO 16 = GREEN LED
@@ -40,18 +40,19 @@ Preferences pref; //create preference
#define LED_RED_CH 4
#define LED_GREEN_CH 5
#define LED_BLUE_CH 6
-#define LDR_PIN 34 //LDR sensor
-#define BUZZER_PIN 26 //speaker
-#define SELECTOR_PIN 27 //push button
+#define LDR_PIN 34 // LDR sensor
+#define BUZZER_PIN 26 // speaker
+#define SELECTOR_PIN 27 // push button
// setting PWM properties
#define backlightChannel 0
#define buzzerChannel 2
-//DISPLAY
+// DISPLAY
TFT_eSPI tft = TFT_eSPI();
+TFT_eSprite sprTrans(&tft);
+bool sprTransAllocated = false;
#define TFT_GREY 0x5AEB
-
// ELM327 PIDs Configuration
/*------ PIDS total 4 -------------------
Array data -> { Label , unit, pid, fomula, min, max, skip, digit, warn }
@@ -66,38 +67,53 @@ digit = want to show digit or not
warn = default warning value
*/
const String pidConfig[5][9] = {
- //[pid][data]
- { "Temperatura", "`C", "0105", "1", "60", "120", "2", "0", "110" }, //0 = Temperatura (0105)
- { "RPM", "rpm", "010C", "3", "0", "6000", "0", "0", "5000" }, //1 = RPM (010C)
- { "Voltaje", "V", "ATRV", "8", "10", "16", "1", "1", "15" }, //2 = Voltaje Bateria (ATRV)
- { "Velocidad", "km/h", "010D", "7", "0", "160", "0", "0", "130" }, //3 = Velocidad (010D)
- { "Carga", "%", "0104", "2", "0", "100", "0", "0", "85" } //4 = Carga Motor (0104)
+ //[pid][data]
+ {"Temperatura", "`C", "0105", "1", "60", "120", "2", "0",
+ "110"}, // 0 = Temperatura (0105)
+ {"RPM", "rpm", "010C", "3", "0", "6000", "0", "0",
+ "5000"}, // 1 = RPM (010C)
+ {"Voltaje", "V", "ATRV", "8", "10", "16", "1", "1",
+ "15"}, // 2 = Voltaje Bateria (ATRV)
+ {"Velocidad", "km/h", "010D", "7", "0", "160", "0", "0",
+ "130"}, // 3 = Velocidad (010D)
+ {"Carga", "%", "0104", "2", "0", "100", "0", "0",
+ "85"} // 4 = Carga Motor (0104)
};
-//hold warning value
-String warningValue[5] = { "110", "5000", "15.0", "130", "85" };
-bool alarmEnabled[5] = { true, true, true, true, false };
+// hold warning value
+String warningValue[5] = {"110", "5000", "15.0", "130", "85"};
+bool alarmEnabled[5] = {true, true, true, true, false};
-// Buzzer volume control table (0: Silencio, 1: Ultra Bajo, 2: Muy Bajo, 3: Bajo/Casio, 4: Medio, 5: Alto)
-const uint16_t VOL_DUTY_TABLE[6] = { 0, 2, 8, 25, 80, 250 };
-const char* const VOL_NAME_TABLE[6] = { "SILENCIO", "ULTRA BAJO", "MUY BAJO", "BAJO (CASIO)", "MEDIO", "ALTO" };
+// Buzzer volume control table (0: Silencio, 1: Ultra Bajo, 2: Muy Bajo, 3:
+// Bajo/Casio, 4: Medio, 5: Alto)
+const uint16_t VOL_DUTY_TABLE[6] = {0, 2, 8, 25, 80, 250};
+const char *const VOL_NAME_TABLE[6] = {"SILENCIO", "ULTRA BAJO", "MUY BAJO",
+ "BAJO (CASIO)", "MEDIO", "ALTO"};
uint8_t buzzerVolumeLevel = 3; // Default 3: Bajo (Casio suave)
-uint8_t beepSoundType = 0; // Default 0: Casio Chirp (0: Casio Chirp, 1: Click Corto, 2: Tono Suave, 3: Tick Digital, 4: Doble Pip, 5: Campana Retro)
-uint8_t alarmVolumeLevel = 3; // Default 3: Bajo/Casio (Volumen independiente de alarma)
-uint8_t alarmSoundType = 0; // Default 0: Casio Doble (0: Casio Doble, 1: Sincronizado, 2: Bitonal, 3: Triple Urgente)
-uint8_t autoSleepIndex = 2; // Default 2: 1 min (0: OFF, 1: 30s, 2: 1m, 3: 2m, 4: 5m)
+uint8_t beepSoundType =
+ 0; // Default 0: Casio Chirp (0: Casio Chirp, 1: Click Corto, 2: Tono Suave,
+ // 3: Tick Digital, 4: Doble Pip, 5: Campana Retro)
+uint8_t alarmVolumeLevel =
+ 3; // Default 3: Bajo/Casio (Volumen independiente de alarma)
+uint8_t alarmSoundType = 0; // Default 0: Casio Doble (0: Casio Doble, 1:
+ // Sincronizado, 2: Bitonal, 3: Triple Urgente)
+uint8_t autoSleepIndex =
+ 2; // Default 2: 1 min (0: OFF, 1: 30s, 2: 1m, 3: 2m, 4: 5m)
uint32_t lastActivityTime = 0; // Inactivity tracker for auto-sleep
-bool wokeFromSleep = false; // Flag set when waking from sleep to resume Bluetooth
+bool wokeFromSleep =
+ false; // Flag set when waking from sleep to resume Bluetooth
-// Screen brightness settings (0: AUTO LDR, 1: 5%, 2: 15%, 3: 35%, 4: 65%, 5: 100%)
-uint8_t brightnessIndex = 0; // Default 0: AUTO
-const uint8_t BRIGHTNESS_PWM_TABLE[6] = { 255, 12, 40, 90, 165, 255 };
-const char* const BRIGHTNESS_NAME_TABLE[6] = { "AUTO", "5%", "15%", "35%", "65%", "100%" };
+// Screen brightness settings (0: AUTO LDR, 1: 5%, 2: 15%, 3: 35%, 4: 65%, 5:
+// 100%)
+uint8_t brightnessIndex = 0; // Default 0: AUTO
+const uint8_t BRIGHTNESS_PWM_TABLE[6] = {255, 12, 40, 90, 165, 255};
+const char *const BRIGHTNESS_NAME_TABLE[6] = {"AUTO", "5%", "15%",
+ "35%", "65%", "100%"};
// Instrument Cluster & Menu Themes
struct GaugeTheme {
- const char* name;
- const char* desc;
+ const char *name;
+ const char *desc;
uint16_t primaryColor; // Primary accent, borders, active glow, scroll thumb
uint16_t accentColor; // Header gradient start, button resting background
uint16_t divColor; // 4-Quadrant divider crosshair line
@@ -109,38 +125,51 @@ struct GaugeTheme {
};
const GaugeTheme GAUGE_THEMES[6] = {
- { "Azul Cian", "Digital Moderno", 0x07FF, 0x03B9, 0x0254, 0x0110, 0x01D3, 0, 180, 255 }, // 0: Vivid Electric Cyan
- { "Verde Sport", "Cockpit Clasico", 0x07E0, 0x0446, 0x0283, 0x0141, 0x0205, 0, 255, 30 }, // 1: Vivid Emerald Green
- { "Ambar BMW", "Nocturno M-Power", 0xFDE0, 0x93C0, 0x5240, 0x2120, 0x41E0, 255, 120, 0 }, // 2: Vivid M-Power Glowing Amber
- { "Blanco Puro", "OLED Precision", 0xFFFF, 0x6B6D, 0x4228, 0x18E3, 0x2965, 220, 230, 255 }, // 3: Pure Crisp White
- { "Naranja Sport","McLaren Papaya", 0xFD20, 0x9260, 0x5160, 0x20A0, 0x4140, 255, 80, 0 }, // 4: Vivid Papaya Orange (reemplaza rojo)
- { "Cyber Violet", "Neon Nightrunner", 0xD1BF, 0x7119, 0x40CE, 0x1846, 0x2869, 200, 0, 255 } // 5: Vivid Electric Violet
+ {"Azul Cian", "Digital Moderno", 0x07FF, 0x03B9, 0x0254, 0x0110, 0x01D3, 0,
+ 180, 255}, // 0: Vivid Electric Cyan
+ {"Verde Sport", "Cockpit Clasico", 0x07E0, 0x0446, 0x0283, 0x0141, 0x0205,
+ 0, 255, 30}, // 1: Vivid Emerald Green
+ {"Ambar BMW", "Nocturno M-Power", 0xFDE0, 0x93C0, 0x5240, 0x2120, 0x41E0,
+ 255, 120, 0}, // 2: Vivid M-Power Glowing Amber
+ {"Blanco Puro", "OLED Precision", 0xFFFF, 0x6B6D, 0x4228, 0x18E3, 0x2965,
+ 220, 230, 255}, // 3: Pure Crisp White
+ {"Naranja Sport", "McLaren Papaya", 0xFD20, 0x9260, 0x5160, 0x20A0, 0x4140,
+ 255, 80, 0}, // 4: Vivid Papaya Orange (reemplaza rojo)
+ {"Cyber Violet", "Neon Nightrunner", 0xD1BF, 0x7119, 0x40CE, 0x1846, 0x2869,
+ 200, 0, 255} // 5: Vivid Electric Violet
};
uint8_t themeIndex = 0; // Default 0: Azul Cian
+uint8_t transitionType =
+ 0; // 0: Deslizamiento (Slide), 1: Desvanecimiento (Fade)
inline uint16_t getThemePrimaryColor() {
- if (themeIndex >= 6) return 0x07FF;
+ if (themeIndex >= 6)
+ return 0x07FF;
return GAUGE_THEMES[themeIndex].primaryColor;
}
inline uint16_t getThemeAccentColor() {
- if (themeIndex >= 6) return 0x234F;
+ if (themeIndex >= 6)
+ return 0x234F;
return GAUGE_THEMES[themeIndex].accentColor;
}
inline uint16_t getThemeDivColor() {
- if (themeIndex >= 6) return 0x3412;
+ if (themeIndex >= 6)
+ return 0x3412;
return GAUGE_THEMES[themeIndex].divColor;
}
inline uint16_t getThemeDarkColor() {
- if (themeIndex >= 6) return 0x1105;
+ if (themeIndex >= 6)
+ return 0x1105;
return GAUGE_THEMES[themeIndex].darkColor;
}
inline uint16_t getThemeBgColor() {
- if (themeIndex >= 6) return 0x19C8;
+ if (themeIndex >= 6)
+ return 0x19C8;
return GAUGE_THEMES[themeIndex].bgColor;
}
@@ -167,9 +196,9 @@ void setRearLedOff() {
}
void updateRearLedTheme() {
- if (themeIndex >= 6) return;
- setRearLedColor(GAUGE_THEMES[themeIndex].ledR,
- GAUGE_THEMES[themeIndex].ledG,
+ if (themeIndex >= 6)
+ return;
+ setRearLedColor(GAUGE_THEMES[themeIndex].ledR, GAUGE_THEMES[themeIndex].ledG,
GAUGE_THEMES[themeIndex].ledB);
}
@@ -179,10 +208,26 @@ void updateRearLedTheme() {
2 - Voltaje
3 - Velocidad
*/
-const uint8_t pidInCell[4] = { 0, 3, 1, 2 }; // 4 Cuadrantes: 0=Refrigerante, 1=Velocidad, 2=Revoluciones, 3=Voltaje
+const uint8_t pidInCell[4] = {
+ 0, 3, 1,
+ 2}; // 4 Cuadrantes: 0=Refrigerante, 1=Velocidad, 2=Revoluciones, 3=Voltaje
-int8_t fullScreenCell = -1; // -1 = 4 cuadrantes / multi-pagina, 0..3 = pantalla completa de ese cuadrante
-int8_t currentScreenPage = 0; // 0 = 4 cuadrantes, 1 = Tri-Temperatura (Agua + Int + Ext)
+int8_t fullScreenCell = -1; // -1 = 4 cuadrantes / multi-pagina, 0..3 = pantalla
+ // completa de ese cuadrante
+int8_t currentScreenPage =
+ 0; // 0 = 4 cuadrantes, 1 = Tri-Temperatura, 2 = Reloj GPS / Fecha, 3 =
+ // Telemetria GPS (Altitud, Rumbo, Sats, Coordenadas)
+
+inline void setScreenPage(int8_t page) {
+ if (page < 0)
+ page = 0;
+ 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)
@@ -190,39 +235,43 @@ uint8_t dsSensorCount = 0; // Cantidad de sensores 1-Wire detectados
// User configuration here to change display >
/*---------------------------*/
-//if NO DATA value will set to 0 to skip reading
-uint8_t layout = 0; //pidInCelltype 0-4 EEPROM 0x00
-const uint8_t max_layout = 1; //single screen mode
-uint8_t pidIndex = 0; //point to PID List
-const uint8_t maxpidIndex = array_length(pidConfig); //total 7 pids in picConfig
-bool prompt = false; //bt elm ready flag
-bool skip = false; //pid skip flag
-uint8_t A = 0; //get A
-uint8_t B = 0; //get B
+// if NO DATA value will set to 0 to skip reading
+uint8_t layout = 0; // pidInCelltype 0-4 EEPROM 0x00
+const uint8_t max_layout = 1; // single screen mode
+uint8_t pidIndex = 0; // point to PID List
+const uint8_t maxpidIndex = array_length(pidConfig); // total 7 pids in
+ // picConfig
+bool prompt = false; // bt elm ready flag
+bool skip = false; // pid skip flag
+uint8_t A = 0; // get A
+uint8_t B = 0; // get B
-//Global variable
-const uint32_t SLEEP_DURATION = 180 * 1000000; //180 sec* us = 3 min
+// Global variable
+const uint32_t SLEEP_DURATION = 180 * 1000000; // 180 sec* us = 3 min
const uint8_t MAX_TERMINAL_LINES = 13;
-String line[MAX_TERMINAL_LINES]; //buffer for each line in terminal function
+String line[MAX_TERMINAL_LINES]; // buffer for each line in terminal function
uint8_t terminalLineCount = 0;
-bool dim = false; //back light dim flag
-const uint8_t LIGHT_LEVEL = 40; //backlight control by light level Higher = darker light
+bool dim = false; // back light dim flag
+const uint8_t LIGHT_LEVEL =
+ 40; // backlight control by light level Higher = darker light
-
-//BLUETOOTH
-String bt_message = ""; //bluetooth message buffer
-String se_message = ""; //serial port message buffer
-String deviceName[8] = { "", "", "", "", "", "", "", "" }; //discoveryBT name
-String deviceAddr[8] = { "", "", "", "", "", "", "", "" }; //discover BT addr
-uint8_t btDeviceCount = 0; //discovered bluetooth devices counter
-#define BT_DISCOVER_TIME 5000 //bluetooth discoery time
-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
-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
+// BLUETOOTH
+String bt_message = ""; // bluetooth message buffer
+String deviceName[8] = {"", "", "", "", "", "", "", ""}; // discoveryBT name
+String deviceAddr[8] = {"", "", "", "", "", "", "", ""}; // discover BT addr
+uint8_t btDeviceCount = 0; // discovered bluetooth devices counter
+#define BT_DISCOVER_TIME 5000 // bluetooth discoery time
+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
+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
bool foundOBD2 = false;
-BluetoothSerial BTSerial; //bluetooth serial device
+BluetoothSerial BTSerial; // bluetooth serial device
//---- Include Header File ---
#include "image.h"
@@ -233,7 +282,6 @@ XPT2046_TouchscreenSOFTSPI ts(XPT2046_CS);
/*----------- global function ---------*/
-
/*------------------*/
// Draw a single console boot entry with authentic status badges
void drawTerminalEntry(int lineIdx, const String &rawText) {
@@ -285,17 +333,24 @@ void drawTerminalEntry(int lineIdx, const String &rawText) {
}
}
-// TERMINAL: Authentic system boot console style loading lines from top to bottom
-void Terminal(String texts, uint16_t x = 0, uint16_t y = 0, uint16_t w = 320, uint16_t h = 240) {
- if (texts.length() == 0) return;
+// TERMINAL: Authentic system boot console style loading lines from top to
+// bottom
+void Terminal(String texts, uint16_t x = 0, uint16_t y = 0, uint16_t w = 320,
+ uint16_t h = 240) {
+ if (texts.length() == 0)
+ return;
// Auto-format standard messages to console style if not already tagged
if (!texts.startsWith("[")) {
if (texts.indexOf("OK") != -1 || texts.indexOf("Success") != -1) {
texts = "[ OK ] " + texts;
- } else if (texts.indexOf("error") != -1 || texts.indexOf("failed") != -1 || texts.indexOf("not found") != -1) {
+ } else if (texts.indexOf("error") != -1 || texts.indexOf("failed") != -1 ||
+ texts.indexOf("not found") != -1) {
texts = "[FAILED] " + texts;
- } else if (texts.indexOf("Scanning") != -1 || texts.indexOf("Iniciando") != -1 || texts.indexOf("Conectando") != -1 || texts.indexOf("Insert") != -1) {
+ } else if (texts.indexOf("Scanning") != -1 ||
+ texts.indexOf("Iniciando") != -1 ||
+ texts.indexOf("Conectando") != -1 ||
+ texts.indexOf("Insert") != -1) {
texts = "[ INFO ] " + texts;
} else {
texts = " " + texts;
@@ -323,7 +378,8 @@ void Terminal(String texts, uint16_t x = 0, uint16_t y = 0, uint16_t w = 320, ui
// Send AT command or PID and wait synchronously for '>' prompt character
String sendATCommand(const String &cmd, uint32_t timeoutMs = 2000) {
- while (BTSerial.available() > 0) BTSerial.read(); // flush RX buffer
+ while (BTSerial.available() > 0)
+ BTSerial.read(); // flush RX buffer
BTSerial.print(cmd + "\r");
String resp = "";
uint32_t start = millis();
@@ -346,9 +402,12 @@ String sendATCommand(const String &cmd, uint32_t timeoutMs = 2000) {
}
//---- Include Header File --------------------
-//#include "image.h"
+// #include "image.h"
#include "bluetooth.h"
#include "tri_temp.h"
+#include "gps_module.h"
+#include "gps_clock.h"
+#include "gps_nav.h"
#include "meter.h"
#include "config.h"
@@ -363,11 +422,14 @@ void initializeELM327() {
Terminal("[ OK ] Dispositivo: " + rZ);
}
- // 2. Base Configuration: Echo OFF, Linefeeds OFF, Headers OFF, Standard Adaptive Timing
- sendATCommand("ATE0", 1000); // Echo OFF
- sendATCommand("ATL0", 1000); // Linefeeds OFF
- sendATCommand("ATH0", 1000); // Headers OFF
- sendATCommand("ATAT1", 1000); // Adaptive timing normal (avoids timeouts on slower Ford ECUs)
+ // 2. Base Configuration: Echo OFF, Linefeeds OFF, Headers OFF, Standard
+ // Adaptive Timing
+ sendATCommand("ATE0", 1000); // Echo OFF
+ sendATCommand("ATL0", 1000); // Linefeeds OFF
+ sendATCommand("ATH0", 1000); // Headers OFF
+ sendATCommand(
+ "ATAT1",
+ 1000); // Adaptive timing normal (avoids timeouts on slower Ford ECUs)
// 3. Test Battery Voltage via ATRV (direct from OBD pin 16)
String rVolt = sendATCommand("ATRV", 1500);
@@ -375,9 +437,11 @@ void initializeELM327() {
Terminal("[ OK ] Bateria: " + rVolt);
}
- // 4. Conectar directamente al protocolo del auto (Ford J1850 PWM - Protocolo 1)
+ // 4. Conectar directamente al protocolo del auto (Ford J1850 PWM - Protocolo
+ // 1)
Terminal("[ INFO ] Conectando Ford J1850 PWM (SCP)...");
- sendATCommand("ATSP1", 1000); // Forzar protocolo 1 directamente (evita busqueda lenta)
+ sendATCommand("ATSP1",
+ 1000); // Forzar protocolo 1 directamente (evita busqueda lenta)
String r0100 = sendATCommand("0100", 5000);
if (r0100.indexOf("41 00") != -1) {
@@ -396,13 +460,63 @@ void initializeELM327() {
prompt = true;
}
+// Background Bluetooth connection task on FreeRTOS Core 0 (100% non-blocking
+// for UI & GPS)
+TaskHandle_t btTaskHandle = NULL;
+volatile bool btConnectedTrigger = false;
+volatile bool systemSleeping = false;
+
+void btBackgroundTask(void *pvParameters) {
+ while (true) {
+ if (!systemSleeping && !foundOBD2) {
+ bool hasRecentAddr = false;
+ for (uint8_t i = 0; i < 6; i++) {
+ if (recent_client_addr[i] != 0) {
+ hasRecentAddr = true;
+ break;
+ }
+ }
+ if (hasRecentAddr) {
+ setRearLedColor(0, 0, 200);
+ BTSerial.disconnect();
+ 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;
+ }
+ } else {
+ setRearLedOff();
+ foundOBD2 = false;
+ }
+ }
+ }
+ ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(3500));
+ }
+}
+
//----SET UP -----------------------------------
void setup() {
- //pin configuration
- analogSetAttenuation(ADC_0db); // 0dB(1.0 ครั้ง) 0~800mV for LDR
- pinMode(LDR_PIN, ANALOG); //ldr analog input read brightness
- pinMode(BUZZER_PIN, OUTPUT); //speaker
- pinMode(SELECTOR_PIN, INPUT_PULLUP); //button
+ // pin configuration
+ analogSetAttenuation(ADC_0db); // 0dB(1.0 ครั้ง) 0~800mV for LDR
+ pinMode(LDR_PIN, ANALOG); // ldr analog input read brightness
+ pinMode(BUZZER_PIN, OUTPUT); // speaker
+ pinMode(SELECTOR_PIN, INPUT_PULLUP); // button
// Rear RGB LED PWM setup (Channels 4, 5, 6 - Timers 2 & 3)
ledcSetup(LED_RED_CH, 5000, 8);
ledcSetup(LED_GREEN_CH, 5000, 8);
@@ -411,42 +525,49 @@ void setup() {
ledcAttachPin(LED_GREEN_PIN, LED_GREEN_CH);
ledcAttachPin(LED_BLUE_PIN, LED_BLUE_CH);
setRearLedOff();
- //pwm setup
- ledcSetup(buzzerChannel, 1500, 10); //buzzer 10 bit
- ledcSetup(backlightChannel, 12000, 8); //backlight 8 bit
- ledcAttachPin(BUZZER_PIN, buzzerChannel); //attach buzzer
+ // pwm setup
+ ledcSetup(buzzerChannel, 1500, 10); // buzzer 10 bit
+ ledcSetup(backlightChannel, 12000, 8); // backlight 8 bit
+ ledcAttachPin(BUZZER_PIN, buzzerChannel); // attach buzzer
- //init communication
- Serial.begin(115200);
- //memory check
+ // memory check
psramInit();
+
+#ifdef SERIAL_DEBUG
+ Serial.begin(115200);
Serial.println(F("\n---------------------------------------\n"));
- Serial.printf("Free Internal Heap %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize());
- Serial.printf("Free SPIRam Heap %d/%d\n", ESP.getFreePsram(), ESP.getPsramSize());
- Serial.printf("ChipRevision %d, Cpu Freq %d, SDK Version %s\n", ESP.getChipRevision(), ESP.getCpuFreqMHz(), ESP.getSdkVersion());
- Serial.printf("Flash Size %d, Flash Speed %d\n", ESP.getFlashChipSize(), ESP.getFlashChipSpeed());
+ Serial.printf("Free Internal Heap %d/%d\n", ESP.getFreeHeap(),
+ ESP.getHeapSize());
+ Serial.printf("Free SPIRam Heap %d/%d\n", ESP.getFreePsram(),
+ ESP.getPsramSize());
+ Serial.printf("ChipRevision %d, Cpu Freq %d, SDK Version %s\n",
+ ESP.getChipRevision(), ESP.getCpuFreqMHz(),
+ ESP.getSdkVersion());
+ Serial.printf("Flash Size %d, Flash Speed %d\n", ESP.getFlashChipSize(),
+ ESP.getFlashChipSpeed());
Serial.println(F("\n---------------------------------------\n"));
+ Serial.printf(">>> [BOOT] Reset reason: %d, wakeup cause: %d\n",
+ esp_reset_reason(), esp_sleep_get_wakeup_cause());
Serial.println(F("<< OBD2 Gauge >>"));
+#endif
-
-
- //init TFT display
+ // init TFT display
tft.init();
- tft.setRotation(1); //landcape
+ tft.setRotation(1); // landcape
// Start SoftSPI for the touchscreen and init the touchscreen
tsSoftSpi = new SoftSPI(XPT2046_MOSI, XPT2046_MISO, XPT2046_CLK);
ts.begin(tsSoftSpi);
ts.setRotation(1);
- touch_calibrate(); // initialize / load touch calibration
- //testTouch();//test touchscreen
+ touch_calibrate(); // initialize / load touch calibration
+ // testTouch();//test touchscreen
- tft.setSwapBytes(true); // to display correct image colors
+ tft.setSwapBytes(true); // to display correct image colors
- //backlight ledcAttachPin must be set after tft.init()
- ledcAttachPin(TFT_BL, backlightChannel); //attach backlight
- ledcWrite(backlightChannel, 255); //full bright
- tft.fillScreen(TFT_BLACK); // Consola de arranque de sistema
+ // backlight ledcAttachPin must be set after tft.init()
+ ledcAttachPin(TFT_BL, backlightChannel); // attach backlight
+ ledcWrite(backlightChannel, 255); // full bright
+ tft.fillScreen(TFT_BLACK); // Consola de arranque de sistema
terminalLineCount = 0;
Terminal("[ OK ] ESP32-D0WD Xtensa LX6 Dual-Core @ 240MHz");
@@ -462,11 +583,11 @@ void setup() {
Terminal("[ OK ] Input: XPT2046 Resistive Touch Calibrated");
delay(40);
- //init variable
+ // init variable
pref.begin("setting", false);
/* Create a namespace called "setting" with read/write mode
{ pref.get...(key,default)
-layout : UShort
+layout : UShort
showsystem: bool
tempOffset : uint16_t
warning value: uint16_t
@@ -474,9 +595,11 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6]
}*/
layout = 0;
pref.putUShort("layout", 0);
- for (uint8_t i = 0; i < maxpidIndex; i++) { //read warning value from pid name if not found load from default
+ for (uint8_t i = 0; i < maxpidIndex;
+ i++) { // read warning value from pid name if not found load from default
warningValue[i] = pref.getString(pidConfig[i][0].c_str(), pidConfig[i][8]);
- if (i == 0 && (warningValue[0] == "106" || warningValue[0] == "0" || warningValue[0] == "")) {
+ if (i == 0 && (warningValue[0] == "106" || warningValue[0] == "0" ||
+ warningValue[0] == "")) {
warningValue[0] = "110";
pref.putString(pidConfig[0][0].c_str(), "110");
}
@@ -484,97 +607,180 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6]
alarmEnabled[i] = pref.getBool(almKey.c_str(), (i < 4));
}
buzzerVolumeLevel = pref.getUChar("b_vol", 3);
- if (buzzerVolumeLevel > 5) buzzerVolumeLevel = 3;
+ if (buzzerVolumeLevel > 5)
+ buzzerVolumeLevel = 3;
beepSoundType = pref.getUChar("b_type", 0);
- if (beepSoundType > 5) beepSoundType = 0;
+ if (beepSoundType > 5)
+ beepSoundType = 0;
alarmVolumeLevel = pref.getUChar("alm_vol", 3);
- if (alarmVolumeLevel > 5) alarmVolumeLevel = 3;
+ if (alarmVolumeLevel > 5)
+ alarmVolumeLevel = 3;
alarmSoundType = pref.getUChar("alm_type", 0);
- if (alarmSoundType > 5) alarmSoundType = 0;
+ if (alarmSoundType > 5)
+ alarmSoundType = 0;
autoSleepIndex = pref.getUChar("auto_sleep", 2);
- if (autoSleepIndex > 4) autoSleepIndex = 2;
+ if (autoSleepIndex > 4)
+ autoSleepIndex = 2;
brightnessIndex = pref.getUChar("b_bright", 0);
- if (brightnessIndex > 5) brightnessIndex = 0;
+ if (brightnessIndex > 5)
+ brightnessIndex = 0;
themeIndex = pref.getUChar("theme_idx", 0);
- if (themeIndex > 5) themeIndex = 0;
+ if (themeIndex > 5)
+ themeIndex = 0;
+ transitionType = pref.getUChar("trans_type", 0);
+ if (transitionType > 1)
+ transitionType = 0;
+ currentScreenPage = pref.getChar("scr_page", 0);
+ if (currentScreenPage < 0 || currentScreenPage > 3)
+ currentScreenPage = 0;
if (brightnessIndex > 0) {
targetBacklight = BRIGHTNESS_PWM_TABLE[brightnessIndex];
currentBacklight = (float)targetBacklight;
ledcWrite(backlightChannel, targetBacklight);
}
lastActivityTime = millis();
- pref.getBytes("recent_client", recent_client_addr, pref.getBytesLength("recent_client")); //read last bt address
-
+ pref.getBytes("recent_client", recent_client_addr,
+ pref.getBytesLength("recent_client")); // read last bt address
updateRearLedTheme(); // Sync rear RGB LED with loaded theme
- Terminal("[ OK ] Settings: Theme " + String(GAUGE_THEMES[themeIndex].name) + " (LED RGB)");
+ Terminal("[ OK ] Settings: Theme " + String(GAUGE_THEMES[themeIndex].name) +
+ " (LED RGB)");
+ Terminal("[ OK ] Settings: Transicion " +
+ String(transitionType == 0 ? "Deslizamiento" : "Desvanecimiento"));
+ delay(40);
+
+ // Initialize persistent transition sprite (pre-allocated once at boot in clean heap)
+ if (sprTrans.createSprite(320, 40) != nullptr) {
+ sprTransAllocated = true;
+ sprTrans.setTextFont(1);
+ sprTrans.setFreeFont(NULL);
+ sprTrans.loadFont(DSEG7_Modern_BoldItalic_34);
+ sprTrans.fontLoaded = false;
+ Terminal("[ OK ] UI: Persistent 25KB Transition Engine Online");
+ } else {
+ Terminal("[ WARN ] UI: Transition buffer alloc failed");
+ }
delay(40);
// Initialize 1-Wire DS18B20 digital temperature bus
initTriTemp();
if (dsSensorCount > 0) {
- Terminal("[ OK ] 1-Wire: " + String(dsSensorCount) + "x DS18B20 Temp Sensors on GPIO 22");
+ Terminal("[ OK ] 1-Wire: " + String(dsSensorCount) +
+ "x DS18B20 Temp Sensors on GPIO 22");
} else {
Terminal("[ OK ] 1-Wire: DS18B20 Bus Online (GPIO 22)");
}
delay(40);
- //start bluetooth
+ // Initialize GPS UART0 on GPIO 3 (RX) and GPIO 1 (TX)
+ setupGPS();
+ Terminal("[ OK ] GPS: N32+ / U-blox UART Online (GPIO 3/1 @ 9600)");
+ delay(40);
+
+ // Initialize Bluetooth Controller stack
Terminal("[ INFO ] Initializing Bluetooth Controller...");
if (!BTSerial.begin("OBD2 Gauge", true)) {
+#ifdef SERIAL_DEBUG
Serial.println(F("Bluetooth..error!"));
+#endif
Terminal("[FAILED] Bluetooth controller initialization error!");
- abort();
} else {
+#ifdef SERIAL_DEBUG
Serial.println(F("Bluetooth..OK"));
+#endif
Terminal("[ OK ] Bluetooth SPP Master stack online");
}
#ifdef SKIP_CONNECTION
foundOBD2 = true;
+#else
+ foundOBD2 = false;
#endif
- //Connect to ELM327
- connectLastOBDII(); //try connect last BT
- while (!foundOBD2) { //not success try scan and connect another OBD2
- scanBTdevice();
- autoDim(); //auto backlight handle
+ // Lanzar tarea FreeRTOS en Core 0 para conexion y reconexion Bluetooth
+ // continua en segundo plano (100% no bloqueante)
+ xTaskCreatePinnedToCore(btBackgroundTask, "btTask", 4096, NULL, 1,
+ &btTaskHandle, 0);
- if (digitalRead(SELECTOR_PIN) == LOW) { //button pressed
- delay(50);
- if (digitalRead(SELECTOR_PIN) == LOW) {
- configMenu();
- waitForTouchRelease();
+ // Iniciar DIRECTA e INSTANTANEAMENTE en la pantalla del Reloj GPS y Fecha
+ // (Pagina 2)
+ currentScreenPage = 2;
+ setScreenPage(currentScreenPage);
+ fullScreenCell = -1;
+ initScreen(true);
+ beepbeep();
+ prompt = true;
+ bt_message = "";
+ //----------------------
+} // setup
+
+/*###################################*/
+void loop() {
+ updateBacklightFade(); // non-blocking smooth backlight fade handler
+ handleArcAlarmFlash(); // non-blocking arc alarm flash handler (blinks when in
+ // TFT_RED)
+
+ // Trigger automatico: Cuando el Bluetooth conecta en segundo plano (Core 0),
+ // pasar a pantalla OBD!
+ if (btConnectedTrigger) {
+ btConnectedTrigger = false;
+ setScreenPage(0); // Cambiar a tablero de 4 cuadrantes OBD!
+ fullScreenCell = -1;
+ initScreen(true);
+ touchBeep();
+ lastActivityTime = millis();
+ }
+
+ // Evaluacion inicial: Si tras el arranque no encuentra ni Bluetooth ni GPS,
+ // pasar a la pantalla de las Temperaturas (Pagina 1)
+ static bool bootSearchEvaluated = false;
+ if (!bootSearchEvaluated && millis() >= 7000) {
+ bootSearchEvaluated = true;
+ bool isBt = BTSerial.connected() && foundOBD2;
+ if (!isBt && !isGpsSynced()) {
+ if (currentScreenPage == 2 && fullScreenCell == -1) {
+ setScreenPage(1); // Ir a la pantalla de las Temperaturas (Pagina 1)
+ fullScreenCell = -1;
+ initScreen(true);
+ lastActivityTime = millis();
}
}
}
- //Initialize ELM327
- initializeELM327();
- //----------------------
-} //setup
-
-/*###################################*/
-void loop() {
- updateBacklightFade(); // non-blocking smooth backlight fade handler
- handleArcAlarmFlash(); // non-blocking arc alarm flash handler (blinks when in TFT_RED)
-
- // Non-blocking periodic reading of DS18B20 digital temperature sensors (every 1s)
+ // Non-blocking periodic reading of DS18B20 digital temperature sensors (every
+ // 1s)
static uint32_t lastDsReadTime = 0;
if (millis() - lastDsReadTime >= 1000) {
lastDsReadTime = millis();
updateTriTempSensors(false);
}
+ // Continuous background processing of GPS NMEA stream and speedometer
+ // calculation
+ processGPS();
+ updateGpsSpeedometer();
+ updateGpsIconAnim();
+
+ // Non-blocking live refresh for resting Page 2 (GPS Clock & Date)
+ if (currentScreenPage == 2 && fullScreenCell == -1) {
+ updateGpsClockScreen(false);
+ }
+
+ // Non-blocking live refresh for resting Page 3 (GPS Navigation & Telemetry)
+ if (currentScreenPage == 3 && fullScreenCell == -1) {
+ updateGpsNavScreen(false);
+ }
+
if (wokeFromSleep) {
wokeFromSleep = false;
lastActivityTime = millis();
- resumeBluetoothConnection();
+ initScreen(true);
}
// SCAN TOUCHSCREEN & BUTTON
// - Double click: switch screen page (Page 0 <-> Page 1) with slide animation
- // - Single click: toggle fullscreen for tapped quadrant (or refresh sensors on Tri-Temp)
+ // - Single click: toggle fullscreen for tapped quadrant (or refresh sensors
+ // on Tri-Temp)
// - Long press (> 1s): open configuration menu
// - Physical selector button: open configuration menu
uint16_t touchX = 0, touchY = 0;
@@ -591,19 +797,16 @@ void loop() {
static uint16_t lastTouchX = 0, lastTouchY = 0;
static bool longPressHandled = false;
- // Double-tap state machine
- static bool waitingForDoubleTap = false;
- static uint32_t firstTapReleaseTime = 0;
- static uint16_t firstTapX = 0, firstTapY = 0;
-
if (btnPressed) {
delay(50);
if (digitalRead(SELECTOR_PIN) == LOW) {
- waitingForDoubleTap = false;
touchBeep();
configMenu();
waitForTouchRelease();
lastActivityTime = millis();
+ if (wokeFromSleep) {
+ wokeFromSleep = false;
+ }
initScreen();
}
} else if (isTouched) {
@@ -619,19 +822,53 @@ void loop() {
lastTouchX = touchX;
lastTouchY = touchY;
- // Long press check: finger held stationary for > 1000ms
- int16_t currentDistX = abs((int16_t)lastTouchX - (int16_t)touchStartX);
- int16_t currentDistY = abs((int16_t)lastTouchY - (int16_t)touchStartY);
+ int16_t liveDeltaX = (int16_t)lastTouchX - (int16_t)touchStartX;
+ int16_t liveDeltaY = (int16_t)lastTouchY - (int16_t)touchStartY;
- if (!longPressHandled && (millis() - touchStartTime > 1000) && currentDistX < 35 && currentDistY < 35) {
- longPressHandled = true;
- waitingForDoubleTap = false;
- touchBeep(); // Confirmation chirp
- configMenu();
- waitForTouchRelease();
- lastActivityTime = millis();
- initScreen();
- isTouching = false;
+ // FAST LIVE SWIPE DETECTION:
+ // Trigger immediately as soon as finger moves horizontally >= 28px
+ // (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 -> 0)
+ longPressHandled = true;
+ touchBeep();
+ uint8_t nextPage = (currentScreenPage + 1) % 4;
+ performScreenTransition(true, currentScreenPage, nextPage);
+ setScreenPage(nextPage);
+ initScreen(false); // Seamless: keep transition frame without blanking
+ // to black
+ waitForTouchRelease();
+ isTouching = false;
+ lastActivityTime = millis();
+ } else if (fullScreenCell == -1 && liveDeltaX > 28 &&
+ abs(liveDeltaY) < 85) {
+ // SWIPE RIGHT: Transition to previous page (0 -> 3 -> 2 -> 1 -> 0)
+ longPressHandled = true;
+ touchBeep();
+ uint8_t nextPage =
+ (currentScreenPage == 0) ? 3 : (currentScreenPage - 1);
+ performScreenTransition(false, currentScreenPage, nextPage);
+ setScreenPage(nextPage);
+ initScreen(false); // Seamless: keep transition frame without blanking
+ // to black
+ waitForTouchRelease();
+ isTouching = false;
+ lastActivityTime = millis();
+ } else if ((millis() - touchStartTime > 1000) && abs(liveDeltaX) < 30 &&
+ abs(liveDeltaY) < 30) {
+ // Long press (> 1s) held stationary: Open Config Menu immediately!
+ longPressHandled = true;
+ touchBeep(); // Confirmation chirp
+ configMenu();
+ waitForTouchRelease();
+ lastActivityTime = millis();
+ if (wokeFromSleep) {
+ wokeFromSleep = false;
+ }
+ initScreen();
+ isTouching = false;
+ }
}
}
} else {
@@ -641,201 +878,170 @@ void loop() {
uint32_t pressDuration = millis() - touchStartTime;
if (!longPressHandled) {
- int16_t deltaX = abs((int16_t)lastTouchX - (int16_t)touchStartX);
- int16_t deltaY = abs((int16_t)lastTouchY - (int16_t)touchStartY);
+ int16_t deltaX = (int16_t)lastTouchX - (int16_t)touchStartX;
+ int16_t deltaY = (int16_t)lastTouchY - (int16_t)touchStartY;
- // Anti-noise: require at least 30ms of contact and displacement < 40px (clean tap)
- if (pressDuration >= 30 && deltaX < 40 && deltaY < 40) {
- touchBeep(); // Crisp acoustic feedback on confirmed tap!
-
- if (waitingForDoubleTap && (millis() - firstTapReleaseTime <= 400)) {
- // DOUBLE CLICK DETECTED: Switch Screen Page!
- waitingForDoubleTap = false;
- Serial.println(">>> [DB] DOUBLE CLICK DETECTED!");
- Serial.printf(">>> [DB] Free heap: %u, MaxAlloc: %u\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap());
-
- if (fullScreenCell != -1) {
+ // Quick flick swipe fallback in case finger was lifted very rapidly
+ // (disabled in full-screen mode)
+ if (fullScreenCell == -1 && deltaX < -25 && abs(deltaY) < 85) {
+ // SWIPE LEFT
+ longPressHandled = true;
+ touchBeep();
+ uint8_t nextPage = (currentScreenPage + 1) % 4;
+ performScreenTransition(true, currentScreenPage, nextPage);
+ setScreenPage(nextPage);
+ initScreen(false); // Seamless: keep transition frame without blanking
+ // to black
+ waitForTouchRelease();
+ isTouching = false;
+ lastActivityTime = millis();
+ } else if (fullScreenCell == -1 && deltaX > 25 && abs(deltaY) < 85) {
+ // SWIPE RIGHT
+ longPressHandled = true;
+ touchBeep();
+ uint8_t nextPage =
+ (currentScreenPage == 0) ? 3 : (currentScreenPage - 1);
+ performScreenTransition(false, currentScreenPage, nextPage);
+ setScreenPage(nextPage);
+ initScreen(false); // Seamless: keep transition frame without blanking
+ // to black
+ waitForTouchRelease();
+ isTouching = false;
+ lastActivityTime = millis();
+ } else if (pressDuration >= 30 && abs(deltaX) < 35 &&
+ abs(deltaY) < 35) {
+ // 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;
}
-
- if (currentScreenPage == 0) {
- Serial.println(">>> [DB] Transitioning Page 0 -> 1");
- animateSlideTransition(true);
- Serial.println(">>> [DB] Slide done. Setting currentScreenPage = 1");
- currentScreenPage = 1;
- Serial.println(">>> [DB] Calling initScreen()...");
- initScreen();
- Serial.println(">>> [DB] initScreen() completed.");
- } else {
- Serial.println(">>> [DB] Transitioning Page 1 -> 0");
- animateSlideTransition(false);
- Serial.println(">>> [DB] Slide done. Setting currentScreenPage = 0");
- currentScreenPage = 0;
- Serial.println(">>> [DB] Calling initScreen()...");
- initScreen();
- Serial.println(">>> [DB] initScreen() completed.");
- }
- waitForTouchRelease();
- lastActivityTime = millis();
- } else {
- // First tap of a potential double click: start waiting window
- waitingForDoubleTap = true;
- firstTapReleaseTime = millis();
- firstTapX = lastTouchX;
- firstTapY = lastTouchY;
- Serial.printf(">>> [DB] First tap (X=%d, Y=%d), waiting for 2nd tap...\n", firstTapX, firstTapY);
+ initScreen();
+ } else if (currentScreenPage == 1) {
+ // Tap on Tri-Temp screen: immediate sensor refresh
+ updateTriTempSensors(true);
+ } else if (currentScreenPage == 2) {
+ // Tap on GPS Clock screen: immediate time and telemetry refresh
+ updateGpsClockScreen(true);
+ } else if (currentScreenPage == 3) {
+ // Tap on GPS Navigation screen: immediate telemetry refresh
+ updateGpsNavScreen(true);
}
+ waitForTouchRelease();
+ lastActivityTime = millis();
}
}
}
-
- // Check if double-tap timeout has expired -> execute SINGLE CLICK action!
- if (waitingForDoubleTap && (millis() - firstTapReleaseTime > 400)) {
- waitingForDoubleTap = false;
-
- if (currentScreenPage == 0) {
- if (fullScreenCell == -1) {
- // Switch to full screen for tapped quadrant
- int quad = (firstTapX < 160) ? ((firstTapY < 120) ? 0 : 2) : ((firstTapY < 120) ? 1 : 3);
- fullScreenCell = quad;
- } else {
- // Return to 4 quadrants view
- fullScreenCell = -1;
- }
- initScreen();
- } else {
- // Tap on Tri-Temp screen: immediate sensor refresh
- updateTriTempSensors(true);
- }
- lastActivityTime = millis();
- }
}
// AUTO-SLEEP BY INACTIVITY
- // If auto-sleep is enabled, and screen is not being held, and engine is stopped (RPM <= 300)
- if (autoSleepIndex > 0 && !isTouching && !btnPressed && !longPressHandled && !waitingForDoubleTap && currentRpm <= 300.0f) {
- uint32_t timeoutMs = (uint32_t)AUTO_SLEEP_SEC_TABLE[autoSleepIndex] * 1000UL;
+ // If auto-sleep is enabled, and screen is not being held, and engine is
+ // stopped (RPM <= 300)
+ if (autoSleepIndex > 0 && !isTouching && !btnPressed && !longPressHandled &&
+ currentRpm <= 300.0f) {
+ uint32_t timeoutMs =
+ (uint32_t)AUTO_SLEEP_SEC_TABLE[autoSleepIndex] * 1000UL;
if (millis() - lastActivityTime >= timeoutMs) {
enterLightSleep();
wokeFromSleep = false; // handled directly here
lastActivityTime = millis();
- resumeBluetoothConnection();
+ initScreen(true);
}
}
#ifndef SKIP_CONNECTION
- // Check Bluetooth connection state: if adapter disconnected or lost power, show "---" (no data)
- if (!BTSerial.connected() || !foundOBD2) {
- static uint32_t lastBtReconnect = 0;
+ // Check Bluetooth connection state: if adapter disconnected or lost power,
+ // switch immediately to GPS Clock Screen (Page 2) and show "---" on OBD
+ // parameters
+ static bool wasBtConnected = false;
+ bool isBtConnected = BTSerial.connected() && foundOBD2;
+
+ // Si se pierde el enlace Bluetooth: si hay GPS pasa al Reloj (Pag 2), si no
+ // hay GPS pasa a Temperaturas (Pag 1)
+ if (!isBtConnected && wasBtConnected) {
+ wasBtConnected = false;
currentRpm = 0.0f;
ignitionOn = false;
- for (uint8_t c = 0; c < 4; c++) {
- plotNoData(c);
- }
- if (millis() - lastBtReconnect > 3000) {
- lastBtReconnect = millis();
-#ifdef SERIAL_DEBUG
- Serial.println(F("BT desconectado. Reintentando reconexion..."));
-#endif
- setRearLedColor(0, 0, 200);
- BTSerial.disconnect();
- if (BTSerial.connect(recent_client_addr, 0, sec_mask, role)) {
- uint8_t tries = 0;
- while (!BTSerial.connected(500) && tries < 4) {
- tries++;
- }
- if (BTSerial.connected()) {
- 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();
- initScreen();
- touchBeep();
- lastActivityTime = millis();
- } else {
- BTSerial.disconnect();
- setRearLedOff();
- foundOBD2 = false;
- }
- } else {
- setRearLedOff();
- foundOBD2 = false;
+ foundOBD2 = false;
+ uint8_t fallbackPage = isGpsSynced() ? 2 : 1;
+ setScreenPage(fallbackPage);
+ fullScreenCell = -1;
+ initScreen(true);
+ setRearLedOff();
+ lastActivityTime = millis();
+ return;
+ }
+ wasBtConnected = isBtConnected;
+
+ 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);
}
}
+ foundOBD2 =
+ false; // Core 0 background task will handle reconnection smoothly!
return;
}
#endif
//----------------------
- //BLUETOOTH read
+ // BLUETOOTH read
static uint32_t lastPidSendTime = 0;
- // Watchdog: If waiting for prompt for more than 5000ms, recover safely without touching screen
+ // Watchdog: If waiting for prompt for more than 5000ms, recover safely
+ // without touching screen
if (!prompt && (millis() - lastPidSendTime > 5000)) {
#ifdef SERIAL_DEBUG
- Serial.printf("PID timeout for %s, recovering...\n", pidList[pidIndex].c_str());
+ Serial.printf("PID timeout for %s, recovering...\n",
+ pidList[pidIndex].c_str());
#endif
BTSerial.flush();
bt_message = "";
prompt = true;
- skip = true; // IMPORTANT: skip updateMeter so timeout never puts zeros on screen!
+ skip = true; // IMPORTANT: skip updateMeter so timeout never puts zeros on
+ // screen!
}
while (BTSerial.available() > 0) {
char incomingChar = BTSerial.read();
- if (incomingChar == '>') { //check prompt
- BTSerial.flush(); //clear tx
+ if (incomingChar == '>') { // check prompt
+ BTSerial.flush(); // clear tx
#ifdef SERIAL_DEBUG
Serial.println(bt_message);
#endif
-#ifdef TERMINAL //for degbugging
+#ifdef TERMINAL // for degbugging
getAB(bt_message, pidList[pidIndex]);
- Terminal(bt_message + "->" + String(A) + "," + String(B), 0, 48, 320, 191);
+ Terminal(bt_message + "->" + String(A) + "," + String(B), 0, 48, 320,
+ 191);
#endif
- prompt = true; //pid response ready to calculate & display
+ prompt = true; // pid response ready to calculate & display
} else {
- bt_message.concat(incomingChar); //keep elm327 respond
+ bt_message.concat(incomingChar); // keep elm327 respond
}
}
- //----------------------
- //SERIAL PORT read
- if (Serial.available() > 0) {
- char incomingChar = Serial.read();
- //check CR/NL
- if ((incomingChar != '\r') && (incomingChar != '\n')) {
- se_message.concat(incomingChar);
- //Serial.print(incomingChar,HEX);
- } else {
- if (se_message != "") {
-#ifdef TERMINAL
- Terminal(se_message, 0, 48, 320, 191); //display terminal
-#endif
- BTSerial.print(se_message + "\r");
- se_message = "";
- Serial.flush();
- } //if se_message
-
- } //else if incomingChar
- } //if Serial.available
-
- //Send another PID to elm327
- if (prompt) { //> ELM327 ready! -> request next PID
+ // Send another PID to elm327
+ if (prompt) { //> ELM327 ready! -> request next PID
if (!skip && bt_message.length() > 0) {
- updateMeter(pidIndex, bt_message); //update meter screen
+ updateMeter(pidIndex, bt_message); // update meter screen
}
bt_message = "";
if (!ignitionOn) {
// Contacto cerrado / Standby:
- // No bombardear el bus OBD de la ECU continuamente (evita que parpadee la luz verde del scanner y drena bateria).
- // Solo enviar una sonda suave de RPM cada 1500ms para detectar cuando se vuelve a poner en contacto.
+ // No bombardear el bus OBD de la ECU continuamente (evita que parpadee la
+ // luz verde del scanner y drena bateria). Solo enviar una sonda suave de
+ // RPM cada 1500ms para detectar cuando se vuelve a poner en contacto.
static uint32_t lastIgnitionProbeTime = 0;
static uint32_t lastDimCheck = 0;
if (millis() - lastDimCheck >= 500) {
@@ -861,25 +1067,27 @@ void loop() {
}
} else {
// Operacion normal: ciclo continuo y veloz de PIDs
- prompt = false; //no prompt from ELM327
- pidIndex++; //point to next pid
+ prompt = false; // no prompt from ELM327
+ pidIndex++; // point to next pid
if (pidIndex >= maxpidIndex) {
- pidIndex = 0; //back to pid 0
- autoDim(); //auto backlight handle
+ pidIndex = 0; // back to pid 0
+ autoDim(); // auto backlight handle
}
- if (pidCurrentSkip[pidIndex] <= 0) { //end of skip loop , go next index
- pidCurrentSkip[pidIndex] = pidReadSkip[pidIndex]; //read max delay loop to current
+ if (pidCurrentSkip[pidIndex] <= 0) { // end of skip loop , go next index
+ pidCurrentSkip[pidIndex] =
+ pidReadSkip[pidIndex]; // read max delay loop to current
skip = false;
- BTSerial.print(pidList[pidIndex] + "\r"); //send PID request
+ BTSerial.print(pidList[pidIndex] + "\r"); // send PID request
lastPidSendTime = millis();
- } else { //skip sending request
- pidCurrentSkip[pidIndex] = pidCurrentSkip[pidIndex] - 1; //decrese loop count
+ } else { // skip sending request
+ pidCurrentSkip[pidIndex] =
+ pidCurrentSkip[pidIndex] - 1; // decrese loop count
prompt = true;
skip = true;
- } //else
+ } // else
}
- } //if prompt
+ } // if prompt
/*------------------*/
-} // loop
+} // loop
diff --git a/sketch/obd2_gauge/touchscreen.h b/sketch/obd2_gauge/touchscreen.h
index 3cf45a9..554c46b 100644
--- a/sketch/obd2_gauge/touchscreen.h
+++ b/sketch/obd2_gauge/touchscreen.h
@@ -4,28 +4,32 @@
#include "XPT2046_TouchscreenSOFTSPI.h"
// Touchscreen pins for Cheap Yellow Display (CYD)
-#define XPT2046_MOSI 32 // T_DIN
-#define XPT2046_MISO 39 // T_OUT
-#define XPT2046_CLK 25 // T_CLK
-#define XPT2046_CS 33 // T_CS
+#define XPT2046_MOSI 32 // T_DIN
+#define XPT2046_MISO 39 // T_OUT
+#define XPT2046_CLK 25 // T_CLK
+#define XPT2046_CS 33 // T_CS
extern SoftSPI *tsSoftSpi;
extern XPT2046_TouchscreenSOFTSPI ts;
#define TFT_GREY 0x5AEB
#define _height 240
-#define _width 320
+#define _width 320
// Get mapped touchscreen coordinates in landscape (320x240)
bool getTouch(uint16_t *x, uint16_t *y) {
- if (!tsSoftSpi) return false;
+ if (!tsSoftSpi)
+ return false;
if (ts.touched(tsSoftSpi)) {
TS_Point p = ts.getPoint(tsSoftSpi);
- // Filter noise: require valid touch pressure and coordinates within active screen
- if (p.z < 160) return false;
- if (p.x < 150 || p.x > 3850 || p.y < 150 || p.y > 3850) return false;
+ // Filter noise: require valid touch pressure and coordinates within active
+ // screen
+ if (p.z < 160)
+ return false;
+ if (p.x < 150 || p.x > 3850 || p.y < 150 || p.y > 3850)
+ return false;
int16_t tx = map(p.x, 200, 3700, 0, 320);
int16_t ty = map(p.y, 240, 3800, 0, 240);
@@ -39,16 +43,19 @@ bool getTouch(uint16_t *x, uint16_t *y) {
// Direct touch check using pressure Z
inline bool isScreenPressed() {
- if (!tsSoftSpi) return false;
+ if (!tsSoftSpi)
+ return false;
return ts.touched(tsSoftSpi);
}
-// Wait until both touchscreen and selector button are completely released with 50ms continuous debounce
+// Wait until both touchscreen and selector button are completely released with
+// 30ms continuous debounce and 250ms maximum hardware safety timeout
inline void waitForTouchRelease() {
uint16_t dummyX, dummyY;
uint8_t releaseCount = 0;
- while (releaseCount < 5) {
- if ((tsSoftSpi && ts.touched(tsSoftSpi)) || getTouch(&dummyX, &dummyY) || (digitalRead(SELECTOR_PIN) == LOW)) {
+ uint32_t startWait = millis();
+ while (releaseCount < 3 && (millis() - startWait < 250)) {
+ if (getTouch(&dummyX, &dummyY) || (digitalRead(SELECTOR_PIN) == LOW)) {
releaseCount = 0;
} else {
releaseCount++;
diff --git a/sketch/obd2_gauge/tri_temp.h b/sketch/obd2_gauge/tri_temp.h
index 5902505..4e3ff91 100644
--- a/sketch/obd2_gauge/tri_temp.h
+++ b/sketch/obd2_gauge/tri_temp.h
@@ -5,6 +5,7 @@
#include
#include
#include "DSEG7_Modern_BoldItalic_34.h"
+#include "Latin_Sans_Bold_26.h"
// Hardware 1-Wire Pin: Conector CN1 Pin 3 (GPIO 22)
#define ONE_WIRE_BUS_PIN 22
@@ -33,12 +34,41 @@ static DallasTemperature dsSensors(&oneWireObj);
static float oldTriCoolant = -999.0f;
static float oldTriInterior = -999.0f;
static float oldTriExterior = -999.0f;
+static bool oldTriIgnition = true;
+static uint8_t oldTriDsSensorCount = 255;
+
+// Pre-rendered smooth title sprites for Page 1 (AGUA, INTERIOR, EXTERIOR)
+// 100% anti-aliased Latin_Sans_Bold_26: 0 mallocs during transitions and zero flicker
+static TFT_eSprite sprTriTitle[3] = { TFT_eSprite(&tft), TFT_eSprite(&tft), TFT_eSprite(&tft) };
+static bool sprTriTitleReady = false;
+
+inline void initTriTempTitles() {
+ if (sprTriTitleReady) return;
+ uint16_t cardBg = 0x10A2;
+ const char* titles[3] = { "AGUA", "INTERIOR", "EXTERIOR" };
+ const int widths[3] = { 80, 136, 136 };
+
+ for (int i = 0; i < 3; i++) {
+ if (sprTriTitle[i].createSprite(widths[i], 26) != nullptr) {
+ sprTriTitle[i].loadFont(Latin_Sans_Bold_26);
+ sprTriTitle[i].setTextPadding(0);
+ sprTriTitle[i].fillSprite(cardBg);
+ sprTriTitle[i].setTextColor(TFT_WHITE, cardBg);
+ sprTriTitle[i].drawString(titles[i], 0, 0, 1);
+ sprTriTitle[i].unloadFont();
+ }
+ }
+ sprTriTitleReady = true;
+}
// Initialize 1-Wire sensors
inline void initTriTemp() {
+ initTriTempTitles();
dsSensors.begin();
dsSensors.setWaitForConversion(false); // Non-blocking conversion (0ms wait)
dsSensorCount = dsSensors.getDeviceCount();
+ oldTriDsSensorCount = dsSensorCount;
+ oldTriIgnition = ignitionOn;
if (dsSensorCount > 0) {
dsSensors.requestTemperatures();
}
@@ -47,6 +77,109 @@ inline void initTriTemp() {
#endif
}
+// 8-bit anti-aliased alpha mask for digital 7-segment italic "°C" (30x26)
+const uint8_t s_dsegUnitAlpha[30 * 26] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x10, 0x11, 0x11, 0x12, 0x0E, 0x00, 0x01, 0x00,
+ 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7A, 0xED, 0xEC, 0xED, 0xED, 0xE4, 0x2A, 0x00, 0x02,
+ 0x02, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x11, 0x03, 0x00, 0x01,
+ 0x00, 0x00, 0x01, 0x02, 0x0C, 0xA2, 0xFF, 0xF2, 0xF3, 0xF4, 0xF1, 0xFF, 0xD2, 0x12, 0x04,
+ 0x00, 0x01, 0x97, 0xEE, 0xEC, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEC, 0xEE, 0xC1, 0x13, 0x01,
+ 0x00, 0x00, 0x04, 0x00, 0x5C, 0xFF, 0x9F, 0x10, 0x14, 0x10, 0x13, 0xD9, 0xFF, 0x2B, 0x00,
+ 0x12, 0xB6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x77, 0x00,
+ 0x00, 0x00, 0x04, 0x00, 0x92, 0xFC, 0x63, 0x00, 0x01, 0x00, 0x19, 0xFC, 0xE1, 0x05, 0x31,
+ 0xD8, 0xFF, 0xFC, 0xFE, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFB, 0xF9, 0x89, 0x0B, 0x02,
+ 0x00, 0x00, 0x01, 0x00, 0xC8, 0xFF, 0x38, 0x05, 0x0B, 0x05, 0x4E, 0xFF, 0xAD, 0x00, 0xD4,
+ 0xFF, 0xFC, 0x66, 0x27, 0x2A, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x23, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x15, 0xF3, 0xE9, 0x00, 0x00, 0x00, 0x00, 0x79, 0xFF, 0x73, 0x09, 0xF8,
+ 0xFD, 0xF6, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00,
+ 0x00, 0x02, 0x00, 0x2A, 0xFF, 0xDF, 0x7A, 0x7C, 0x7D, 0x7A, 0xDB, 0xFC, 0x28, 0x3B, 0xFF,
+ 0xFF, 0xD5, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x00, 0x84, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF1, 0x46, 0x00, 0x7E, 0xFF,
+ 0xFF, 0xA3, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x70, 0x7B, 0x7A, 0x7A, 0x79, 0x22, 0x00, 0x00, 0xAF, 0xFF,
+ 0xFF, 0x6E, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0xDF, 0xFF,
+ 0xFF, 0x3A, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x27, 0xFB, 0xFF,
+ 0xED, 0x11, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x57, 0xFF, 0xFF,
+ 0xC5, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x8A, 0xFF, 0xFF,
+ 0x91, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xC0, 0xFF, 0xFF,
+ 0x5D, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0C, 0xE9, 0xFF, 0xFC,
+ 0x2D, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x34, 0xFF, 0xFF, 0xE4,
+ 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x67, 0xFF, 0xFF, 0xB6,
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x9C, 0xFF, 0xFF, 0x82,
+ 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xCF, 0xFF, 0xFF, 0x4D,
+ 0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x16, 0xF2, 0xFF, 0xF7, 0x1A,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x4B, 0xFF, 0xFE, 0xD8, 0x12,
+ 0x12, 0x12, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x20, 0xE4, 0xFF, 0xF7, 0xED,
+ 0xEE, 0xED, 0xED, 0xED, 0xED, 0xED, 0xEB, 0xED, 0x93, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0xFA, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x67, 0xF9, 0xF6,
+ 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF8, 0xF7, 0xE9, 0x5B, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x28, 0x2A,
+ 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x29, 0x2A, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static uint16_t s_unitRenderBuf[30 * 26];
+
+// Draw 100% anti-aliased digital 7-segment italic unit "°C" matching DSEG7 typography and color
+inline void draw7SegDegreeC(TFT_eSPI &dst, int bx, int by, uint16_t color, uint16_t bgCol) {
+ for (int j = 0; j < 26; j++) {
+ int rowOffset = j * 30;
+ for (int i = 0; i < 30; i++) {
+ uint8_t a = pgm_read_byte(&s_dsegUnitAlpha[rowOffset + i]);
+ if (a == 0) {
+ s_unitRenderBuf[rowOffset + i] = bgCol;
+ } else if (a >= 250) {
+ s_unitRenderBuf[rowOffset + i] = color;
+ } else {
+ s_unitRenderBuf[rowOffset + i] = fastBlend(a, color, bgCol);
+ }
+ }
+ }
+ bool oldSwap = dst.getSwapBytes();
+ dst.setSwapBytes(true);
+ dst.pushImage(bx, by, 30, 26, s_unitRenderBuf);
+ dst.setSwapBytes(oldSwap);
+}
+
+// Double-buffered 120x8 progress bar sprite (zero jitter)
+static TFT_eSprite sprBar(&tft);
+static bool sprBarCreated = false;
+static int oldTriFillW[3] = { -1, -1, -1 };
+static uint16_t oldTriAccentCol[3] = { 0, 0, 0 };
+
+inline void drawTriTempBar(int barX, int barY, int fillW, uint16_t accentCol) {
+ if (!sprBarCreated) {
+ sprBarCreated = (sprBar.createSprite(120, 8) != nullptr);
+ }
+ if (sprBarCreated) {
+ sprBar.fillRoundRect(0, 0, 120, 8, 3, 0x18C3);
+ if (fillW > 0) {
+ sprBar.fillRoundRect(0, 0, fillW, 8, 3, accentCol);
+ }
+ sprBar.pushSprite(barX, barY);
+ } else {
+ tft.fillRoundRect(barX, barY, 120, 8, 3, 0x18C3);
+ if (fillW > 0) {
+ tft.fillRoundRect(barX, barY, fillW, 8, 3, accentCol);
+ }
+ }
+}
+
// Draw a single temperature card with card index (0: Motor, 1: Interior, 2: Exterior)
inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) {
int cardY = 6 + cardIdx * 77; // y = 6, 83, 160
@@ -71,7 +204,7 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) {
bool isInt = false;
if (cardIdx == 0) {
- titleStr = "MOTOR / REFRIGERANTE";
+ titleStr = "AGUA";
subStr = ignitionOn ? "ECU OBD-II (PID 0105)" : "ECU OBD-II (STANDBY)";
minRange = 60.0f;
maxRange = 120.0f;
@@ -80,14 +213,14 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) {
else if (val < 110.0f) accentCol = TFT_YELLOW;
else accentCol = TFT_RED;
} else if (cardIdx == 1) {
- titleStr = "HABITACULO / INTERIOR";
- subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1 (GPIO 22)" : "DS18B20 SENSOR 1 (DEMO)";
+ titleStr = "INTERIOR";
+ subStr = (dsSensorCount > 0) ? "DS18B20 SENSOR 1 (GPIO 22)" : "DS18B20 SENSOR 1";
minRange = 10.0f;
maxRange = 40.0f;
accentCol = 0x07F3; // Mint Green
} else {
- titleStr = "AMBIENTE / EXTERIOR";
- subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2 (GPIO 22)" : (dsSensorCount == 1 ? "ESTIMADA (-4.5`C)" : "DS18B20 SENSOR 2 (DEMO)");
+ titleStr = "EXTERIOR";
+ subStr = (dsSensorCount > 1) ? "DS18B20 SENSOR 2 (GPIO 22)" : (dsSensorCount == 1 ? "ESTIMADA (-4.5`C)" : "DS18B20 SENSOR 2");
minRange = -10.0f;
maxRange = 50.0f;
accentCol = 0x3D7F; // Sky Blue
@@ -96,26 +229,40 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) {
// Left vertical accent bar
tft.fillRoundRect(cardX + 6, cardY + 8, 4, cardH - 16, 2, accentCol);
- // Titles
- tft.setTextColor(TFT_WHITE, cardBg);
- tft.drawString(titleStr, cardX + 16, cardY + 8, 2);
+ // Titles (Smooth anti-aliased font)
+ if (!sprTriTitleReady) initTriTempTitles();
+ if (sprTriTitleReady && sprTriTitle[cardIdx].created()) {
+ sprTriTitle[cardIdx].pushSprite(cardX + 16, cardY + 6);
+ } else {
+ tft.loadFont(Latin_Sans_Bold_26);
+ tft.setTextPadding(0);
+ tft.setTextColor(TFT_WHITE, cardBg);
+ tft.drawString(titleStr, cardX + 16, cardY + 6, 1);
+ tft.unloadFont();
+ }
tft.setTextColor(0x8410, cardBg); // Soft gray
- tft.drawString(subStr, cardX + 16, cardY + 28, 1);
+ tft.drawString(subStr, cardX + 16, cardY + 34, 1);
// 3. Horizontal Progress Gauge Bar
int barX = cardX + 16;
int barY = cardY + 50;
int barW = 120;
- int barH = 8;
- tft.fillRoundRect(barX, barY, barW, barH, 3, 0x18C3); // Trough background
float ratio = (val - minRange) / (maxRange - minRange);
if (ratio < 0.0f) ratio = 0.0f;
if (ratio > 1.0f) ratio = 1.0f;
int fillW = (int)(barW * ratio);
if (fillW < 4) fillW = 4;
- tft.fillRoundRect(barX, barY, fillW, barH, 3, accentCol);
+
+ if (x_offset == 0) {
+ drawTriTempBar(barX, barY, fillW, accentCol);
+ oldTriFillW[cardIdx] = fillW;
+ oldTriAccentCol[cardIdx] = accentCol;
+ } else {
+ tft.fillRoundRect(barX, barY, barW, 8, 3, 0x18C3);
+ tft.fillRoundRect(barX, barY, fillW, 8, 3, accentCol);
+ }
// 4. Digital Number using DSEG7 Modern Bold Italic 34pt
String numStr;
@@ -129,43 +276,163 @@ inline void drawTriTempCard(uint8_t cardIdx, float val, int x_offset = 0) {
tft.loadFont(DSEG7_Modern_BoldItalic_34);
tft.setTextColor(accentCol, cardBg);
- tft.setTextPadding(100);
- tft.drawRightString(numStr.c_str(), cardX + 262, cardY + 22, 1);
tft.setTextPadding(0);
+ tft.drawRightString(numStr.c_str(), cardX + 270, cardY + 20, 1);
tft.unloadFont();
- // Unit "°C"
- tft.setTextColor(0xC618, cardBg);
- tft.drawString("`C", cardX + 268, cardY + 32, 2);
+ // Unit "°C" in authentic 7-segment digital italic matching number typography and aligned to digit base
+ if (x_offset == 0) {
+ draw7SegDegreeC(tft, cardX + 276, cardY + 28, accentCol, cardBg);
+ } else {
+ tft.setTextColor(accentCol, cardBg);
+ tft.drawString("`C", cardX + 276, cardY + 37, 2);
+ }
+}
+
+// Flicker-free dynamic value update for a single temperature card
+inline void updateTriTempCardValue(uint8_t cardIdx, float val, float oldVal) {
+ if (currentScreenPage != 1 || fullScreenCell != -1) return;
+
+ int cardY = 6 + cardIdx * 77; // y = 6, 83, 160
+ int cardH = 73;
+ int cardX = 4;
+ uint16_t cardBg = 0x10A2; // Dark graphite navy
+
+ float minRange = 0.0f, maxRange = 100.0f;
+ bool isInt = false;
+ uint16_t accentCol = getThemePrimaryColor();
+ uint16_t oldAccentCol = getThemePrimaryColor();
+
+ if (cardIdx == 0) {
+ minRange = 60.0f;
+ maxRange = 120.0f;
+ isInt = true;
+ if (val < 100.0f) accentCol = getThemePrimaryColor();
+ else if (val < 110.0f) accentCol = TFT_YELLOW;
+ else accentCol = TFT_RED;
+
+ if (oldVal < 100.0f) oldAccentCol = getThemePrimaryColor();
+ else if (oldVal < 110.0f) oldAccentCol = TFT_YELLOW;
+ else oldAccentCol = TFT_RED;
+
+ // Subtitle update if ignition state changed
+ if (ignitionOn != oldTriIgnition) {
+ oldTriIgnition = ignitionOn;
+ const char* subStr = ignitionOn ? "ECU OBD-II (PID 0105)" : "ECU OBD-II (STANDBY)";
+ tft.fillRect(cardX + 16, cardY + 34, 130, 10, cardBg);
+ tft.setTextColor(0x8410, cardBg);
+ tft.drawString(subStr, cardX + 16, cardY + 34, 1);
+ }
+ } else if (cardIdx == 1) {
+ minRange = 10.0f;
+ maxRange = 40.0f;
+ accentCol = 0x07F3; // Mint Green
+ oldAccentCol = accentCol;
+ } else {
+ minRange = -10.0f;
+ maxRange = 50.0f;
+ accentCol = 0x3D7F; // Sky Blue
+ oldAccentCol = accentCol;
+ }
+
+ // 1. Accent color changed: update vertical accent bar and unit "°C"
+ if (accentCol != oldAccentCol) {
+ tft.fillRoundRect(cardX + 6, cardY + 8, 4, cardH - 16, 2, accentCol);
+ draw7SegDegreeC(tft, cardX + 276, cardY + 28, accentCol, cardBg);
+ }
+
+ // 2. Horizontal Progress Gauge Bar update (only redraw when pixel fill changes or accent changes)
+ int barX = cardX + 16;
+ int barY = cardY + 50;
+ int barW = 120;
+
+ 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;
+
+ if (fillW != oldTriFillW[cardIdx] || accentCol != oldTriAccentCol[cardIdx]) {
+ drawTriTempBar(barX, barY, fillW, accentCol);
+ oldTriFillW[cardIdx] = fillW;
+ oldTriAccentCol[cardIdx] = accentCol;
+ }
+
+ // 3. Digital Number update using DSEG7 Modern Bold Italic 34pt (setTextPadding prevents flicker)
+ String numStr;
+ if (val <= -900.0f) {
+ numStr = "---";
+ } else if (isInt) {
+ numStr = String((int)val);
+ } else {
+ numStr = String(val, 1);
+ }
+
+ tft.loadFont(DSEG7_Modern_BoldItalic_34);
+ tft.setTextColor(accentCol, cardBg);
+ tft.setTextPadding(114);
+ tft.drawRightString(numStr.c_str(), cardX + 270, cardY + 20, 1);
+ tft.setTextPadding(0);
+ tft.unloadFont();
+}
+
+// Synchronize tracking variables without touching TFT display (zero flicker)
+inline void syncTriTempTrackers() {
+ oldTriCoolant = tempEngineCoolant;
+ oldTriInterior = tempInterior;
+ oldTriExterior = tempExterior;
+ oldTriIgnition = ignitionOn;
+ oldTriDsSensorCount = dsSensorCount;
+
+ float vals[3] = { tempEngineCoolant, tempInterior, tempExterior };
+ float minR[3] = { 60.0f, 10.0f, -10.0f };
+ float maxR[3] = { 120.0f, 40.0f, 50.0f };
+ uint16_t defAcc[3] = { getThemePrimaryColor(), 0x07F3, 0x3D7F };
+ for (int i = 0; i < 3; i++) {
+ 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;
+ oldTriFillW[i] = fillW;
+
+ uint16_t acc = defAcc[i];
+ if (i == 0) {
+ if (vals[i] >= 110.0f) acc = TFT_RED;
+ else if (vals[i] >= 100.0f) acc = TFT_YELLOW;
+ }
+ oldTriAccentCol[i] = acc;
+ }
}
// Draw full Tri-Temp screen
inline void drawTriTempScreen(int x_offset = 0) {
- if (x_offset == 0) {
- tft.fillScreen(TFT_BLACK);
- } else {
+ if (x_offset != 0) {
tft.fillRect(0, 0, x_offset, 240, TFT_BLACK);
+ } else {
+ tft.fillRect(0, 0, 320, 6, TFT_BLACK);
+ tft.fillRect(0, 233, 320, 7, TFT_BLACK);
}
drawTriTempCard(0, tempEngineCoolant, x_offset);
drawTriTempCard(1, tempInterior, x_offset);
drawTriTempCard(2, tempExterior, x_offset);
- oldTriCoolant = tempEngineCoolant;
- oldTriInterior = tempInterior;
- oldTriExterior = tempExterior;
+ syncTriTempTrackers();
- // Draw Page Indicator: [○ ●] (Page 2 active)
- tft.fillCircle(153, 2, 2, 0x5AEB); // Page 1 inactive
- tft.fillCircle(167, 2, 2, getThemePrimaryColor()); // Page 2 active
+ // Draw Page Indicator: [○ ● ○ ○] (Page 2 of 4 active)
+ tft.fillCircle(139 + x_offset, 2, 2, 0x5AEB); // Page 1 inactive
+ tft.fillCircle(153 + x_offset, 2, 2, getThemePrimaryColor()); // Page 2 active
+ tft.fillCircle(167 + x_offset, 2, 2, 0x5AEB); // Page 3 inactive
+ tft.fillCircle(181 + x_offset, 2, 2, 0x5AEB); // Page 4 inactive
}
// Update coolant reading dynamically without redrawing entire screen
inline void updateTriTempCoolant(float val) {
tempEngineCoolant = val;
if (currentScreenPage != 1 || fullScreenCell != -1) return;
- if (val != oldTriCoolant) {
- drawTriTempCard(0, val, 0);
+ if (val != oldTriCoolant || ignitionOn != oldTriIgnition) {
+ updateTriTempCardValue(0, val, oldTriCoolant);
oldTriCoolant = val;
}
}
@@ -209,13 +476,22 @@ inline void updateTriTempSensors(bool forceRedraw = false) {
}
if (currentScreenPage == 1 && fullScreenCell == -1) {
- if (forceRedraw || abs(tempInterior - oldTriInterior) >= 0.1f) {
+ // If sensor count changed (e.g. plug / unplug), redraw full cards so subtitles update
+ if (dsSensorCount != oldTriDsSensorCount) {
+ oldTriDsSensorCount = dsSensorCount;
drawTriTempCard(1, tempInterior, 0);
- oldTriInterior = tempInterior;
- }
- if (forceRedraw || abs(tempExterior - oldTriExterior) >= 0.1f) {
drawTriTempCard(2, tempExterior, 0);
+ oldTriInterior = tempInterior;
oldTriExterior = tempExterior;
+ } else {
+ if (forceRedraw || abs(tempInterior - oldTriInterior) >= 0.1f) {
+ updateTriTempCardValue(1, tempInterior, oldTriInterior);
+ oldTriInterior = tempInterior;
+ }
+ if (forceRedraw || abs(tempExterior - oldTriExterior) >= 0.1f) {
+ updateTriTempCardValue(2, tempExterior, oldTriExterior);
+ oldTriExterior = tempExterior;
+ }
}
}
}