feat: cluster digital Ford Ka Endura 1.3 con badge táctico ECO, alarmas sincronizadas y emulador OBD2
- Agregado badge táctico [ ECO ] en tiempo real (1900-2550 RPM, 45-82% carga motor) - Sincronización de alarma visual destellante con titileo del título en pantalla completa - Reajuste de escala y umbrales de temperatura (120°C fondo de escala, 110°C alarma) - Configuración de emulador Bluetooth con modo fijo --eco y ciclo dinámico - Limpieza de carpetas y archivos obsoletos del proyecto original - Actualización completa de documentación en README.md
This commit is contained in:
parent
0315854a12
commit
d4f889b901
10 changed files with 4433 additions and 1706 deletions
23
LICENSE.md
23
LICENSE.md
|
|
@ -1,23 +0,0 @@
|
||||||
MIT No Commercial Use License
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
Non-Commercial Use Restriction:
|
|
||||||
The Software and any derivative works thereof must not be used, distributed,
|
|
||||||
or otherwise exploited for commercial purposes or for any financial benefit.
|
|
||||||
134
README.md
134
README.md
|
|
@ -1,61 +1,99 @@
|
||||||
# OBD2 Gauge (ESP32 Bluetooth)
|
# Cluster Digital OBD-II ESP32 CYD (Ford Ka Endura 1.3)
|
||||||
|
|
||||||
## This repository has been archived and is no longer maintained. (October 24th, 2025)
|
Instrumento digital automotriz de alta precisión basado en la placa **ESP32 CYD (ESP32-2432S028)** con pantalla táctil resistiva TFT de 2.8" (320x240). Se comunica de forma inalámbrica vía Bluetooth SPP con un escáner **ELM327 v1.5** conectado al puerto OBD-II del vehículo.
|
||||||
|
|
||||||
A fully functional code-updated
|
El sistema fue diseñado y calibrado a medida para el motor **Ford Ka Endura-E 1.3** (y es compatible con instalaciones de GNC).
|
||||||
Last Update: September 27th, 2024
|
|
||||||
|
|
||||||
This is an ESP32 OBD2 Gauge for vehicles that are compatible with OBD2
|
---
|
||||||
(Special made for FORD vehicle)
|
|
||||||
- Prototype https://youtu.be/PkQaUJbzTNM
|
|
||||||
- Current Model Short Demo https://youtu.be/vvBIeim7XTE
|
|
||||||
|
|
||||||
|
|
||||||
## Software:
|
## Características Principales
|
||||||
- Arduino IDE 2.3.2 + ESP32 arduino core 2.0.17
|
|
||||||
- * IMPORTANT * this project won't work with ESP32 arduiino core 3.x.x
|
|
||||||
- modified TFT_eSPI library 2.5.43
|
|
||||||
|
|
||||||
## Hardware
|
### 1. Cuadrante de 4 Instrumentos Simultáneos
|
||||||
- ESP32 TFT2.8 320x240 with "RESISTIVE" touch board.
|
* **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).
|
||||||
buy here https://s.click.aliexpress.com/e/_Ew95gMl
|
* **Velocidad del Vehículo (PID 010D):** Rango 0 a 160 km/h.
|
||||||
- ELM327 Bluetooth adaptor (recommended v1.5 not v2.1)
|
* **Revoluciones por Minuto (PID 010C):** Rango 0 a 6.000 RPM.
|
||||||
Highly recommend ELM327 Bluetooth adaptor https://s.click.aliexpress.com/e/_oo3THvG
|
* **Voltaje de Batería (Comando ATRV):** Rango 10.0V a 16.0V en tiempo real directo del borne del ELM327.
|
||||||
- A small speaker for mobile phone 1 Watt 8 ohm 9x28 mmm.
|
|
||||||
- A push button 6x6x5 2 legs
|
|
||||||
- 3d print enclosure
|
|
||||||
- 12v to 5v dc micro usb power regulator module
|
|
||||||
- Gauge magnetic stand
|
|
||||||
|
|
||||||
## Features:
|
### 2. Modo Pantalla Completa (Full Screen)
|
||||||
- Show vehicle data 7 pids
|
* Tocando cualquier cuadrante, la pantalla se expande a vista completa para ese parámetro.
|
||||||
* MAP - manifold air pressure (PSI)
|
* Escala curvada de 20 bloques con **antialiasing de subpíxel** para bordes ultra suaves sin efecto serrucho.
|
||||||
* PCM - pcm voltage (volt)
|
* Tipografía digital retrofuturista **DSEG7 Modern 80pt** con coma decimal integrada y actualización veloz sin parpadeo (*flicker-free*).
|
||||||
* ENG LOAD - engine load (%)
|
* Alarma visual sincronizada: el icono, el arco y el título parpadean en rojo de forma simultánea ante sobretemperatura o sobretensión.
|
||||||
* ENG SPD - engine speed (RPM)
|
|
||||||
* Coolant - coolant temperature (c)
|
|
||||||
* Oil Temp - engine oil temperature (c)
|
|
||||||
* TFT - Ford transmission fluid temperature (c)
|
|
||||||
|
|
||||||
- 8 layout page selectable display
|
### 3. Badge Táctico de Máxima Eficiencia `[ ECO ]`
|
||||||
- DTC read and clear function (Engine warning light only)
|
* Calcula en tiempo real el punto óptimo de funcionamiento y entrega de torque (Sweet Spot) para el Ford Ka Endura 1.3:
|
||||||
- Warning when parameter reaches setting value
|
* **RPM:** Entre 1.900 y 2.550 RPM (pico de par motor y mejor rendimiento volumétrico).
|
||||||
- Adjustable warning value for each PIDs
|
* **Carga de Motor (PID 0104 en segundo plano):** Entre 45% y 82% (apertura de mariposa con mínimas pérdidas por bombeo, antes del enriquecimiento a plena carga).
|
||||||
- Configurable CPU overheat protection
|
* **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.
|
||||||
- Configurable gauge Auto turn on/off
|
* Al salir del rango óptimo, se apaga de inmediato restaurando de forma invisible el degradado del fondo.
|
||||||
- Auto screen brightness
|
|
||||||
- Change off screen to user screen with micro SDcard
|
|
||||||
- Firmware updatable (micro SDcard and WiFi)
|
|
||||||
- VIN Read on the About page.
|
|
||||||
|
|
||||||
|
### 4. 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).
|
||||||
|
* Casio Digital (doble chirp suave no invasivo).
|
||||||
|
* Tono bitonal clásico.
|
||||||
|
* Alarma continua urgente.
|
||||||
|
|
||||||
## [Flash Firmware Online](https://vaandcob.github.io/webpage/src/index.html)
|
### 5. 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%).
|
||||||
|
* **Auto-Sleep:** Apagado automático de pantalla y suspensión ligera al detener el motor (RPM <= 300) tras el tiempo configurado (30s, 1m, 2m, 5m u OFF).
|
||||||
|
|
||||||
-------------------------------------------------------
|
---
|
||||||
[](https://www.buymeacoffee.com/vaandcob)
|
|
||||||
|
|
||||||
|
## Hardware Requerido
|
||||||
|
|
||||||
|
1. **Placa ESP32 CYD 2.8" (ESP32-2432S028):**
|
||||||
|
* Pantalla TFT 320x240 con controlador ILI9341 / ST7789.
|
||||||
|
* Pantalla táctil resistiva con controlador XPT2046.
|
||||||
|
* Sensor de luz LDR en pin analógico.
|
||||||
|
* LED RGB integrado.
|
||||||
|
2. **Escáner OBD-II Bluetooth:**
|
||||||
|
* Adaptador ELM327 v1.5 (muy recomendado con chip PIC18F25K80, evitar clones v2.1 de baja calidad).
|
||||||
|
3. **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:**
|
||||||
|
* Módulo convertidor 12V a 5V USB (conectado a positivo bajo llave o directo si se utiliza auto-sleep).
|
||||||
|
|
||||||
## Emulador OBD2 / ELM327 Bluetooth incluido
|
---
|
||||||
En la carpeta se incluye el script para emular un adaptador ELM327 v1.5 y la ECU del vehículo vía Bluetooth SPP en Linux. Permite probar y validar la pantalla CYD en banco de pruebas sin necesidad de conectarse a un auto real.
|
|
||||||
Consulta [emulator/README.md](emulator/README.md) para ver requisitos y modo de uso.
|
## Compilación y Carga de Firmware
|
||||||
|
|
||||||
|
El proyecto se encuentra preparado para compilarse tanto con `arduino-cli` como con Arduino IDE:
|
||||||
|
|
||||||
|
### Requisitos de Software
|
||||||
|
* **ESP32 Arduino Core:** Versión 2.0.17 (las ramas 3.x no son compatibles con el esquema de partición).
|
||||||
|
* **Librería TFT_eSPI:** 2.5.43 o superior con la configuración de pines del CYD.
|
||||||
|
* **Esquema de Partición:** `huge_app` (3MB APP / No OTA / LittleFS).
|
||||||
|
|
||||||
|
### Compilación desde Terminal
|
||||||
|
```bash
|
||||||
|
arduino-cli compile -b esp32:esp32:esp32:PartitionScheme=huge_app sketch/obd2_gauge
|
||||||
|
```
|
||||||
|
|
||||||
|
### Carga al Dispositivo
|
||||||
|
```bash
|
||||||
|
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32:PartitionScheme=huge_app sketch/obd2_gauge
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Emulador de Pruebas Bluetooth en Banco
|
||||||
|
|
||||||
|
En la carpeta `emulator/` se incluye el script `obd2_emulator.py` para simular un adaptador ELM327 v1.5 y la ECU del auto en Linux vía Bluetooth SPP:
|
||||||
|
|
||||||
|
### Modo ECO Fijo (Recomendado para calibración visual)
|
||||||
|
Mantiene los parámetros fijados en el punto dulce (2.250 RPM, 58% carga, 14.2V, 90°C) con el badge `[ ECO ]` permanentemente encendido:
|
||||||
|
```bash
|
||||||
|
python3 emulator/obd2_emulator.py --eco
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modo Ciclo de Demostración
|
||||||
|
Ejecuta un ciclo dinámico de 36 segundos que incluye batería baja, ralentí, 10 segundos continuos en crucero ECO, aceleración exigida y fondo de escala con alarmas:
|
||||||
|
```bash
|
||||||
|
python3 emulator/obd2_emulator.py
|
||||||
|
```
|
||||||
|
|
||||||
|
*Nota: Mientras el emulador corre en la terminal, podés presionar `e` + Enter para activar el modo ECO fijo o `c` + Enter para volver al ciclo dinámico.*
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,13 @@ Script en Python que emula un adaptador OBD-II Bluetooth ELM327 v1.5 completo y
|
||||||
|
|
||||||
2. Ejecuta el emulador:
|
2. Ejecuta el emulador:
|
||||||
```bash
|
```bash
|
||||||
|
# Modo normal (ciclo dinámico con fase ECO de 10s):
|
||||||
python3 emulator/obd2_emulator.py
|
python3 emulator/obd2_emulator.py
|
||||||
|
|
||||||
|
# O modo ECO fijo permanente (RPM 2250, Carga 58%):
|
||||||
|
python3 emulator/obd2_emulator.py --eco
|
||||||
```
|
```
|
||||||
|
*Nota: En la terminal podés presionar 'e' + Enter para activar ECO fijo o 'c' + Enter para volver al ciclo.*
|
||||||
|
|
||||||
3. Enciende la pantalla ESP32 CYD. Se conectará automáticamente por Bluetooth SPP (Canal 1 RFCOMM).
|
3. Enciende la pantalla ESP32 CYD. Se conectará automáticamente por Bluetooth SPP (Canal 1 RFCOMM).
|
||||||
4. El emulador responderá a los PIDs estándar de OBD2:
|
4. El emulador responderá a los PIDs estándar de OBD2:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import math
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
import argparse
|
||||||
import dbus
|
import dbus
|
||||||
import dbus.service
|
import dbus.service
|
||||||
from dbus.mainloop.glib import DBusGMainLoop
|
from dbus.mainloop.glib import DBusGMainLoop
|
||||||
|
|
@ -19,78 +20,98 @@ from gi.repository import GLib
|
||||||
# Vehicle Simulation State
|
# Vehicle Simulation State
|
||||||
class VehicleState:
|
class VehicleState:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.rpm = 1800.0 # RPM
|
self.rpm = 2250.0 # RPM (Sweet spot default)
|
||||||
self.target_rpm = 2200.0 # Target RPM for smooth transitions
|
self.target_rpm = 2200.0 # Target RPM for smooth transitions
|
||||||
self.speed = 45.0 # km/h
|
self.speed = 70.0 # km/h
|
||||||
self.coolant_temp = 88.0 # °C
|
self.coolant_temp = 90.0 # °C
|
||||||
self.oil_temp = 92.0 # °C
|
self.oil_temp = 93.0 # °C
|
||||||
self.trans_temp = 82.0 # °C (Ford T6 221E1C)
|
self.trans_temp = 82.0 # °C (Ford T6 221E1C)
|
||||||
self.load = 32.0 # % (0-100)
|
self.load = 58.0 # % (Sweet spot: 45% - 82%)
|
||||||
self.map_psi = 14.7 # psi (Manifold Absolute Pressure)
|
self.map_psi = 14.7 # psi (Manifold Absolute Pressure)
|
||||||
self.pcm_volt = 14.2 # Volts (MUST be > 12.5V or gauge will sleep)
|
self.pcm_volt = 14.2 # Volts (MUST be > 12.5V or gauge will sleep)
|
||||||
self.auto_simulate = True
|
self.auto_simulate = True
|
||||||
|
self.mode = "cycle" # "cycle" or "eco" (modo fijo)
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
def update_simulation(self):
|
def update_simulation(self):
|
||||||
"""Simulates driving telemetry with clear transitions through color zones and holds at max scale"""
|
"""Simulates driving telemetry with clear transitions through color zones and holds at max scale"""
|
||||||
t = time.time()
|
t = time.time()
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if self.auto_simulate:
|
if not self.auto_simulate:
|
||||||
# 32-second demonstration cycle:
|
return
|
||||||
# 0 - 5s: FASE 1 - Batería Baja Crítica (<12V) -> ROJO con alarma destellante (10.8V, 3 bloques)
|
|
||||||
# 5 - 9s: FASE 2 - Batería en reposo / baja carga (12.0 - 13.2V) -> AMARILLO (12.4V, 8 bloques)
|
|
||||||
# 9 - 16s: FASE 3 - Marcha normal / óptima -> Todo en VERDE (RPM 2200, Vel 60, Temp 92°C, Volt 14.2V)
|
|
||||||
# 16 - 21s: FASE 4 - Precaución / carga alta -> Todo en AMARILLO (RPM 4200, Vel 120, Temp 102°C, Volt 15.0V)
|
|
||||||
# 21 - 27s: FASE 5 - MÁXIMO ABSOLUTO (100% de escala) -> Todo en ROJO con alarma destellante (RPM 6000, Vel 160, Temp 120°C, Volt 16.0V)
|
|
||||||
# 27 - 32s: FASE 6 - Descenso suave de vuelta al inicio
|
|
||||||
cycle_period = 32.0
|
|
||||||
phase = t % cycle_period
|
|
||||||
|
|
||||||
if phase < 5.0:
|
if self.mode == "eco":
|
||||||
# Batería baja crítica (<12V) -> ROJO destellante
|
# MODO ECO FIJO: Bloquea telemetría permanentemente en el punto dulce óptimo
|
||||||
self.pcm_volt = 10.8
|
self.pcm_volt = 14.2
|
||||||
self.rpm = 800.0
|
self.rpm = 2250.0 # Rango ECO: 1900 - 2550 RPM
|
||||||
self.speed = 0.0
|
self.load = 58.0 # Rango ECO: 45% - 82%
|
||||||
self.coolant_temp = 86.0
|
self.speed = 70.0 # km/h
|
||||||
elif phase < 9.0:
|
self.coolant_temp = 90.0 # °C
|
||||||
# Batería en reposo / baja carga (12.0 - 13.2V) -> AMARILLO
|
self.oil_temp = 93.0
|
||||||
self.pcm_volt = 12.4
|
self.map_psi = 14.5
|
||||||
self.rpm = 800.0
|
self.trans_temp = 82.0
|
||||||
self.speed = 0.0
|
return
|
||||||
self.coolant_temp = 88.0
|
|
||||||
elif phase < 16.0:
|
|
||||||
# Marcha normal -> VERDE
|
|
||||||
p = (phase - 9.0) / 7.0
|
|
||||||
self.pcm_volt = 14.2
|
|
||||||
self.rpm = 1500.0 + p * (2600.0 - 1500.0)
|
|
||||||
self.speed = 30.0 + p * (75.0 - 30.0)
|
|
||||||
self.coolant_temp = 90.0 + p * (94.0 - 90.0)
|
|
||||||
elif phase < 21.0:
|
|
||||||
# Exigido / autopista -> AMARILLO
|
|
||||||
p = (phase - 16.0) / 5.0
|
|
||||||
self.pcm_volt = 14.9 + p * 0.2 # 14.9 a 15.1V (Amarillo)
|
|
||||||
self.rpm = 3800.0 + p * (4800.0 - 3800.0) # 3800 a 4800 (Amarillo)
|
|
||||||
self.speed = 112.0 + p * (128.0 - 112.0) # 112 a 128 (Amarillo)
|
|
||||||
self.coolant_temp = 99.0 + p * (104.0 - 99.0) # 99 a 104 (Amarillo)
|
|
||||||
elif phase < 27.0:
|
|
||||||
# MÁXIMO ABSOLUTO (100% de escala) -> ROJO con alarma destellante
|
|
||||||
self.pcm_volt = 16.0
|
|
||||||
self.rpm = 6000.0
|
|
||||||
self.speed = 160.0
|
|
||||||
self.coolant_temp = 120.0
|
|
||||||
else:
|
|
||||||
# Retorno progresivo
|
|
||||||
p = (phase - 27.0) / 5.0
|
|
||||||
self.pcm_volt = 16.0 - p * (16.0 - 10.8)
|
|
||||||
self.rpm = 6000.0 - p * (6000.0 - 800.0)
|
|
||||||
self.speed = 160.0 - p * 160.0
|
|
||||||
self.coolant_temp = 120.0 - p * (120.0 - 86.0)
|
|
||||||
|
|
||||||
# Correlated auxiliary telemetry
|
# Ciclo de demostración de 36 segundos con FASE ECO dedicada y prolongada:
|
||||||
self.map_psi = 10.0 + 16.0 * ((self.rpm - 800.0) / 5200.0)
|
# 0 - 4s: FASE 1 - Batería Baja Crítica (<12V) -> ROJO con alarma destellante (10.8V)
|
||||||
self.load = 15.0 + 75.0 * ((self.rpm - 800.0) / 5200.0)
|
# 4 - 8s: FASE 2 - Ralentí y reposo (12.4V, 850 RPM, Carga 28%)
|
||||||
self.oil_temp = self.coolant_temp + 3.0
|
# 8 - 18s: FASE 3 - CRUCERO ÓPTIMO [ ECO ] (10 seg continuos: 2200 RPM, Carga 58%, 70 km/h, 14.2V) -> BADGE ACTIVO
|
||||||
self.trans_temp = 82.0 + 1.0 * math.cos(t * 0.09)
|
# 18 - 24s: FASE 4 - Exigido / autopista (3800 - 4800 RPM, Carga 88%, 120 km/h, 102°C) -> Sale de ECO
|
||||||
|
# 24 - 30s: FASE 5 - MÁXIMO ABSOLUTO (100% escala: 6000 RPM, 160 km/h, 120°C, 16.0V) -> Todo en ROJO con alarma
|
||||||
|
# 30 - 36s: FASE 6 - Descenso suave de vuelta al inicio
|
||||||
|
cycle_period = 36.0
|
||||||
|
phase = t % cycle_period
|
||||||
|
|
||||||
|
if phase < 4.0:
|
||||||
|
# Batería baja crítica (<12V) -> ROJO destellante
|
||||||
|
self.pcm_volt = 10.8
|
||||||
|
self.rpm = 800.0
|
||||||
|
self.speed = 0.0
|
||||||
|
self.coolant_temp = 86.0
|
||||||
|
self.load = 25.0
|
||||||
|
elif phase < 8.0:
|
||||||
|
# Batería en reposo / baja carga (12.0 - 13.2V) -> AMARILLO
|
||||||
|
self.pcm_volt = 12.4
|
||||||
|
self.rpm = 850.0
|
||||||
|
self.speed = 0.0
|
||||||
|
self.coolant_temp = 88.0
|
||||||
|
self.load = 28.0
|
||||||
|
elif phase < 18.0:
|
||||||
|
# FASE ECO: Marcha y crucero óptimo durante 10 segundos continuos (RPM: 2150-2300, Carga: 56%-62%)
|
||||||
|
p = (phase - 8.0) / 10.0
|
||||||
|
self.pcm_volt = 14.2
|
||||||
|
self.rpm = 2200.0 + math.sin(p * math.pi) * 100.0
|
||||||
|
self.load = 58.0 + math.sin(p * math.pi) * 4.0
|
||||||
|
self.speed = 65.0 + p * 10.0
|
||||||
|
self.coolant_temp = 90.0 + p * 2.0
|
||||||
|
elif phase < 24.0:
|
||||||
|
# Exigido / autopista -> AMARILLO
|
||||||
|
p = (phase - 18.0) / 6.0
|
||||||
|
self.pcm_volt = 14.9 + p * 0.2
|
||||||
|
self.rpm = 3800.0 + p * (4800.0 - 3800.0)
|
||||||
|
self.load = 85.0 + p * 8.0
|
||||||
|
self.speed = 112.0 + p * (128.0 - 112.0)
|
||||||
|
self.coolant_temp = 99.0 + p * (104.0 - 99.0)
|
||||||
|
elif phase < 30.0:
|
||||||
|
# MÁXIMO ABSOLUTO (100% de escala) -> ROJO con alarma destellante
|
||||||
|
self.pcm_volt = 16.0
|
||||||
|
self.rpm = 6000.0
|
||||||
|
self.load = 96.0
|
||||||
|
self.speed = 160.0
|
||||||
|
self.coolant_temp = 120.0
|
||||||
|
else:
|
||||||
|
# Retorno progresivo
|
||||||
|
p = (phase - 30.0) / 6.0
|
||||||
|
self.pcm_volt = 16.0 - p * (16.0 - 10.8)
|
||||||
|
self.rpm = 6000.0 - p * (6000.0 - 800.0)
|
||||||
|
self.load = 96.0 - p * (96.0 - 25.0)
|
||||||
|
self.speed = 160.0 - p * 160.0
|
||||||
|
self.coolant_temp = 120.0 - p * (120.0 - 86.0)
|
||||||
|
|
||||||
|
# Correlated auxiliary telemetry
|
||||||
|
self.map_psi = 10.0 + 16.0 * ((self.rpm - 800.0) / 5200.0)
|
||||||
|
self.oil_temp = self.coolant_temp + 3.0
|
||||||
|
self.trans_temp = 82.0 + 1.0 * math.cos(t * 0.09)
|
||||||
|
|
||||||
state = VehicleState()
|
state = VehicleState()
|
||||||
|
|
||||||
|
|
@ -310,10 +331,18 @@ def handle_client_stream(sock):
|
||||||
sock.sendall(response.encode("latin1"))
|
sock.sendall(response.encode("latin1"))
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now - last_display_time > 0.4:
|
if now - last_display_time > 0.25:
|
||||||
last_display_time = now
|
last_display_time = now
|
||||||
cmd_display = cmd if cmd else "<CR>"
|
cmd_display = cmd if cmd else "<CR>"
|
||||||
print(f"\r[ECT: {state.coolant_temp:3.0f}°C | RPM: {state.rpm:4.0f} | SPEED: {state.speed:3.0f} km/h | Volt: {state.pcm_volt:4.1f}V | CMD: {cmd_display:6s} -> {response.strip()}]", end="", flush=True)
|
with state.lock:
|
||||||
|
c_rpm = state.rpm
|
||||||
|
c_load = state.load
|
||||||
|
c_temp = state.coolant_temp
|
||||||
|
c_spd = state.speed
|
||||||
|
c_volt = state.pcm_volt
|
||||||
|
is_eco = (1900.0 <= c_rpm <= 2550.0 and 45.0 <= c_load <= 82.0)
|
||||||
|
eco_badge = "\033[92;1m[ECO ACTIVO]\033[0m" if is_eco else "\033[90m[ECO OFF]\033[0m"
|
||||||
|
print(f"\r[RPM: {c_rpm:4.0f} | Carga: {c_load:2.0f}% {eco_badge} | {c_temp:3.0f}°C | {c_spd:3.0f} km/h | {c_volt:4.1f}V | CMD: {cmd_display:5s} -> {response.strip():14s}] ", end="", flush=True)
|
||||||
|
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
pass
|
pass
|
||||||
|
|
@ -338,9 +367,26 @@ def simulation_worker():
|
||||||
|
|
||||||
|
|
||||||
def run_emulator():
|
def run_emulator():
|
||||||
print("=" * 65)
|
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%%)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.eco or args.mode == "eco":
|
||||||
|
state.mode = "eco"
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
print(" ESP32 CYD OBD2 Bluetooth Emulator (ELM327 v1.5)")
|
print(" ESP32 CYD OBD2 Bluetooth Emulator (ELM327 v1.5)")
|
||||||
print("=" * 65)
|
print(" Soporte para Badge Táctico ECO / OPT (Ford Ka Endura 1.3)")
|
||||||
|
print("=" * 70)
|
||||||
|
if state.mode == "eco":
|
||||||
|
print(">>> MODO ACTUAL: ECO FIJO (RPM: 2250, Carga: 58%, Vel: 70 km/h) <<<")
|
||||||
|
else:
|
||||||
|
print(">>> MODO ACTUAL: CICLO DINÁMICO (Fase ECO activa entre 8s y 18s) <<<")
|
||||||
|
print("Comandos interactivos en consola:")
|
||||||
|
print(" - Escribe 'e' + [Enter]: Activar MODO ECO FIJO (permanente)")
|
||||||
|
print(" - Escribe 'c' + [Enter]: Activar MODO CICLO AUTOMÁTICO")
|
||||||
|
print(" - Presiona Ctrl+C o escribe 'q': Salir\n")
|
||||||
|
|
||||||
# Start simulation thread
|
# Start simulation thread
|
||||||
sim_thread = threading.Thread(target=simulation_worker, daemon=True)
|
sim_thread = threading.Thread(target=simulation_worker, daemon=True)
|
||||||
|
|
@ -423,12 +469,24 @@ def run_emulator():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
rlist, _, _ = select.select([server_sock], [], [], 1.0)
|
rlist, _, _ = select.select([server_sock, sys.stdin], [], [], 1.0)
|
||||||
if server_sock in rlist:
|
if server_sock in rlist:
|
||||||
client_sock, client_info = server_sock.accept()
|
client_sock, client_info = server_sock.accept()
|
||||||
print(f"\n[Bluetooth Socket] Conexión entrante aceptada desde {client_info}!")
|
print(f"\n[Bluetooth Socket] Conexión entrante aceptada desde {client_info}!")
|
||||||
t = threading.Thread(target=handle_client_stream, args=(client_sock,), daemon=True)
|
t = threading.Thread(target=handle_client_stream, args=(client_sock,), daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
|
if sys.stdin in rlist:
|
||||||
|
line = sys.stdin.readline().strip().lower()
|
||||||
|
if line in ["e", "eco"]:
|
||||||
|
with state.lock:
|
||||||
|
state.mode = "eco"
|
||||||
|
print("\n\n>>> MODO ECO FIJO ACTIVADO: RPM=2250, Carga=58% (Badge [ ECO ] permanente) <<<\n")
|
||||||
|
elif line in ["c", "cycle"]:
|
||||||
|
with state.lock:
|
||||||
|
state.mode = "cycle"
|
||||||
|
print("\n\n>>> MODO CICLO AUTOMÁTICO ACTIVADO (Fase ECO activa entre 8s y 18s) <<<\n")
|
||||||
|
elif line in ["q", "quit", "exit"]:
|
||||||
|
break
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nDeteniendo emulador...")
|
print("\nDeteniendo emulador...")
|
||||||
finally:
|
finally:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
/* Bluetooth function */
|
/* Bluetooth function */
|
||||||
//----------------------------------
|
//----------------------------------
|
||||||
|
extern bool ignitionOn;
|
||||||
|
extern bool wokeFromSleep;
|
||||||
|
void initScreen();
|
||||||
|
void plotNoData(uint8_t cell);
|
||||||
|
void touchBeep();
|
||||||
|
|
||||||
//convert bt address to text
|
//convert bt address to text
|
||||||
//{0x00,0x1d,0xa5,0x00,0x12,0x92} -> 00:1d:a5:00:12:92
|
//{0x00,0x1d,0xa5,0x00,0x12,0x92} -> 00:1d:a5:00:12:92
|
||||||
String ByteArraytoString(esp_bd_addr_t bt_address) {
|
String ByteArraytoString(esp_bd_addr_t bt_address) {
|
||||||
|
|
@ -19,7 +25,7 @@ String ByteArraytoString(esp_bd_addr_t bt_address) {
|
||||||
/*=======================*/
|
/*=======================*/
|
||||||
|
|
||||||
void scanBTdevice() {//scan bluetooth device
|
void scanBTdevice() {//scan bluetooth device
|
||||||
digitalWrite(LED_BLUE_PIN,LOW);//blue led on
|
setRearLedColor(0, 0, 200);//blue led on
|
||||||
Serial.println("Scanning for OBDII Adaptor...");
|
Serial.println("Scanning for OBDII Adaptor...");
|
||||||
Terminal("Scanning for OBDII Adaptor...",0,48,320,191);
|
Terminal("Scanning for OBDII Adaptor...",0,48,320,191);
|
||||||
btDeviceCount = 0;
|
btDeviceCount = 0;
|
||||||
|
|
@ -39,7 +45,7 @@ void scanBTdevice() {//scan bluetooth device
|
||||||
#ifdef SERIAL_DEBUG
|
#ifdef SERIAL_DEBUG
|
||||||
Serial.println("Discovering stopped");
|
Serial.println("Discovering stopped");
|
||||||
#endif
|
#endif
|
||||||
digitalWrite(LED_BLUE_PIN,HIGH);//blue led off
|
setRearLedOff();//blue led off
|
||||||
delay(1000);//redsicovery delay
|
delay(1000);//redsicovery delay
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -110,7 +116,8 @@ void scanBTdevice() {//scan bluetooth device
|
||||||
while(!BTSerial.connected(1000) && (try_count < 10)) {
|
while(!BTSerial.connected(1000) && (try_count < 10)) {
|
||||||
Serial.print(F("."));
|
Serial.print(F("."));
|
||||||
blink =! blink;
|
blink =! blink;
|
||||||
digitalWrite(LED_BLUE_PIN,blink);//blue led offset
|
if (blink) setRearLedColor(0, 0, 200);
|
||||||
|
else setRearLedOff();
|
||||||
if (blink) tft.setTextColor(TFT_BLUE,TFT_BLACK);
|
if (blink) tft.setTextColor(TFT_BLUE,TFT_BLACK);
|
||||||
else tft.setTextColor(TFT_BLACK,TFT_BLACK);
|
else tft.setTextColor(TFT_BLACK,TFT_BLACK);
|
||||||
tft.drawRightString("$",319,210,4);
|
tft.drawRightString("$",319,210,4);
|
||||||
|
|
@ -121,12 +128,13 @@ void scanBTdevice() {//scan bluetooth device
|
||||||
Terminal("Connect failed!",0,48,320,191);
|
Terminal("Connect failed!",0,48,320,191);
|
||||||
Serial.println(F("Connect failed!"));
|
Serial.println(F("Connect failed!"));
|
||||||
BTSerial.disconnect();
|
BTSerial.disconnect();
|
||||||
|
setRearLedOff();
|
||||||
foundOBD2 = false;
|
foundOBD2 = false;
|
||||||
} else {
|
} else {
|
||||||
Terminal("Connected Successfully!",0,48,320,191);
|
Terminal("Connected Successfully!",0,48,320,191);
|
||||||
Serial.println(F("Connected Successfully!"));
|
Serial.println(F("Connected Successfully!"));
|
||||||
prompt = true;
|
prompt = true;
|
||||||
digitalWrite(LED_GREEN_PIN, LOW);//green led
|
updateRearLedTheme(); // Set rear RGB LED to active theme color
|
||||||
foundOBD2 = true;
|
foundOBD2 = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +147,7 @@ void scanBTdevice() {//scan bluetooth device
|
||||||
//---------------------------
|
//---------------------------
|
||||||
//connect to recent obdII for fast connection, skip scanning
|
//connect to recent obdII for fast connection, skip scanning
|
||||||
void connectLastOBDII() {
|
void connectLastOBDII() {
|
||||||
digitalWrite(LED_BLUE_PIN,LOW);//blue led on
|
setRearLedColor(0, 0, 200);//blue led on
|
||||||
String txt = "Connecting to " + client_name +" - " + ByteArraytoString(recent_client_addr);
|
String txt = "Connecting to " + client_name +" - " + ByteArraytoString(recent_client_addr);
|
||||||
Terminal(txt,0,48,320,191);
|
Terminal(txt,0,48,320,191);
|
||||||
Serial.println(txt);
|
Serial.println(txt);
|
||||||
|
|
@ -149,20 +157,120 @@ void connectLastOBDII() {
|
||||||
while(!BTSerial.connected(1000) && (try_count <3)) {
|
while(!BTSerial.connected(1000) && (try_count <3)) {
|
||||||
Serial.print(F("."));
|
Serial.print(F("."));
|
||||||
blink =! blink;
|
blink =! blink;
|
||||||
digitalWrite(LED_BLUE_PIN,blink);//blue led offset
|
if (blink) setRearLedColor(0, 0, 200);
|
||||||
|
else setRearLedOff();
|
||||||
try_count++;
|
try_count++;
|
||||||
}
|
}
|
||||||
if (try_count == 3) {//cannot connect
|
if (try_count == 3) {//cannot connect
|
||||||
Terminal("OBDII Adaptor not found!",0,48,320,191);
|
Terminal("OBDII Adaptor not found!",0,48,320,191);
|
||||||
Serial.println(F("OBDII Adaptor not found!"));
|
Serial.println(F("OBDII Adaptor not found!"));
|
||||||
BTSerial.disconnect();
|
BTSerial.disconnect();
|
||||||
|
setRearLedOff();
|
||||||
foundOBD2 = false;
|
foundOBD2 = false;
|
||||||
} else {
|
} else {
|
||||||
Terminal("Connected Successfully!",0,48,320,191);
|
Terminal("Connected Successfully!",0,48,320,191);
|
||||||
Serial.println(F("Connected Successfully!"));
|
Serial.println(F("Connected Successfully!"));
|
||||||
prompt = true;
|
prompt = true;
|
||||||
digitalWrite(LED_GREEN_PIN, LOW);//green led
|
updateRearLedTheme(); // Set rear RGB LED to active theme color
|
||||||
foundOBD2 = true;
|
foundOBD2 = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}//connectLasbtOBDII
|
}//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) {
|
||||||
|
hasRecentAddr = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasRecentAddr) {
|
||||||
|
foundOBD2 = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
uint8_t try_count = 0;
|
||||||
|
bool blink = false;
|
||||||
|
while (!BTSerial.connected(1000) && (try_count < 4)) {
|
||||||
|
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);
|
||||||
|
BTSerial.disconnect();
|
||||||
|
setRearLedOff();
|
||||||
|
foundOBD2 = false;
|
||||||
|
delay(400);
|
||||||
|
initScreen();
|
||||||
|
for (uint8_t c = 0; c < 4; c++) {
|
||||||
|
plotNoData(c);
|
||||||
|
}
|
||||||
|
lastActivityTime = millis();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -49,9 +49,17 @@ Preferences pref; //create preference
|
||||||
#define SCA_PIN 21 //not used same pin with TFT_BL
|
#define SCA_PIN 21 //not used same pin with TFT_BL
|
||||||
#define SCL_PIN 22 //not used (avaialable) CAN_TX
|
#define SCL_PIN 22 //not used (avaialable) CAN_TX
|
||||||
#define ADC_PIN 35 //not used (available) CAN_RX
|
#define ADC_PIN 35 //not used (available) CAN_RX
|
||||||
|
// Verified Physical Hardware Mapping on ESP32-2432S028:
|
||||||
|
// GPIO 4 = RED LED
|
||||||
|
// GPIO 16 = GREEN LED
|
||||||
|
// GPIO 17 = BLUE LED
|
||||||
#define LED_RED_PIN 4
|
#define LED_RED_PIN 4
|
||||||
#define LED_GREEN_PIN 16
|
#define LED_GREEN_PIN 16
|
||||||
#define LED_BLUE_PIN 17
|
#define LED_BLUE_PIN 17
|
||||||
|
// PWM Channels 4, 5, 6 on Timers 2 & 3 (isolated from Timer 1 buzzer)
|
||||||
|
#define LED_RED_CH 4
|
||||||
|
#define LED_GREEN_CH 5
|
||||||
|
#define LED_BLUE_CH 6
|
||||||
#define LDR_PIN 34 //LDR sensor
|
#define LDR_PIN 34 //LDR sensor
|
||||||
#define BUZZER_PIN 26 //speaker
|
#define BUZZER_PIN 26 //speaker
|
||||||
#define SELECTOR_PIN 27 //push button
|
#define SELECTOR_PIN 27 //push button
|
||||||
|
|
@ -64,41 +72,126 @@ TFT_eSPI tft = TFT_eSPI();
|
||||||
#define TFT_GREY 0x5AEB
|
#define TFT_GREY 0x5AEB
|
||||||
|
|
||||||
|
|
||||||
//ELM327 init https://www.elmelectronics.com/wp-content/uploads/2016/07/ELM327DS.pdf
|
// ELM327 PIDs Configuration
|
||||||
const uint8_t elm327InitCount = 8;
|
/*------ PIDS total 4 -------------------
|
||||||
const String elm327Init[elm327InitCount] = { "ATZ", "ATBRD23", "ATSP0", "ATAT2", "ATL0", "ATH0", "ATE0", "0100" }; //agressive wait response time from ECU
|
Array data -> { Label , unit, pid, fomula, min, max, skip, digit, warn }
|
||||||
//const String elm327Init[elm327InitCount] = {"ATZ","ATPP0CSV35","ATPP0CON","ATSP0","ATAT2","ATL0","ATH0","ATE0","0100"};//agressive wait response time from ECU
|
|
||||||
|
|
||||||
/*------ PIDS total 7 now -------------------
|
|
||||||
Array data -> { Label , unit, pid, fomula, min, max, ,skip, digit, warn }
|
|
||||||
label = Pid label show on meter
|
label = Pid label show on meter
|
||||||
unit = unit for pid
|
unit = unit for pid
|
||||||
pid = string pid 0104
|
pid = string pid (e.g. 0105, 010C, 010D, ATRV)
|
||||||
formula = formula no to calculate
|
formula = formula no to calculate (1: ECT, 3: RPM, 7: Speed, 8: ATRV)
|
||||||
min = lowest value
|
min = lowest value
|
||||||
max = highest value
|
max = highest value
|
||||||
skip = delay reading 0 - 3; 3 = max delay read
|
skip = delay reading 0 - 3; 3 = max delay read
|
||||||
digit = want to show digit or not
|
digit = want to show digit or not
|
||||||
warn = default warning value
|
warn = default warning value
|
||||||
*/
|
*/
|
||||||
const String pidConfig[4][9] = {
|
const String pidConfig[5][9] = {
|
||||||
//[pid][data]
|
//[pid][data]
|
||||||
{ "Temperatura", "`C", "0105", "1", "0", "120", "2", "0", "106" }, //0 = Temperatura (0105)
|
{ "Temperatura", "`C", "0105", "1", "60", "120", "2", "0", "110" }, //0 = Temperatura (0105)
|
||||||
{ "RPM", "rpm", "010C", "3", "0", "6000", "0", "0", "5000" }, //1 = RPM (010C)
|
{ "RPM", "rpm", "010C", "3", "0", "6000", "0", "0", "5000" }, //1 = RPM (010C)
|
||||||
{ "Voltaje", "V", "0142", "4", "10", "16", "1", "1", "15" }, //2 = Voltaje (0142)
|
{ "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)
|
{ "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
|
//hold warning value
|
||||||
String warningValue[4] = { "106", "5000", "15.0", "130" };
|
String warningValue[5] = { "110", "5000", "15.0", "130", "85" };
|
||||||
bool alarmEnabled[4] = { true, true, true, true };
|
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)
|
// 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 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" };
|
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 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 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 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
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
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
|
||||||
|
uint16_t darkColor; // Header gradient end / deep background
|
||||||
|
uint16_t bgColor; // Subtle desaturated ambient vignette background
|
||||||
|
uint8_t ledR; // Rear RGB LED Red (0..255)
|
||||||
|
uint8_t ledG; // Rear RGB LED Green (0..255)
|
||||||
|
uint8_t ledB; // Rear RGB LED Blue (0..255)
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t themeIndex = 0; // Default 0: Azul Cian
|
||||||
|
|
||||||
|
inline uint16_t getThemePrimaryColor() {
|
||||||
|
if (themeIndex >= 6) return 0x07FF;
|
||||||
|
return GAUGE_THEMES[themeIndex].primaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t getThemeAccentColor() {
|
||||||
|
if (themeIndex >= 6) return 0x234F;
|
||||||
|
return GAUGE_THEMES[themeIndex].accentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t getThemeDivColor() {
|
||||||
|
if (themeIndex >= 6) return 0x3412;
|
||||||
|
return GAUGE_THEMES[themeIndex].divColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t getThemeDarkColor() {
|
||||||
|
if (themeIndex >= 6) return 0x1105;
|
||||||
|
return GAUGE_THEMES[themeIndex].darkColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t getThemeBgColor() {
|
||||||
|
if (themeIndex >= 6) return 0x19C8;
|
||||||
|
return GAUGE_THEMES[themeIndex].bgColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control functions for the Rear RGB LED (Active-LOW, Common Anode)
|
||||||
|
void setRearLedColor(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
// Ensure pins are attached to LEDC
|
||||||
|
ledcAttachPin(LED_RED_PIN, LED_RED_CH);
|
||||||
|
ledcAttachPin(LED_GREEN_PIN, LED_GREEN_CH);
|
||||||
|
ledcAttachPin(LED_BLUE_PIN, LED_BLUE_CH);
|
||||||
|
// Duty cycle for 8-bit (0..255): 255 = 3.3V (OFF), 0 = 0V (FULL ON)
|
||||||
|
ledcWrite(LED_RED_CH, 255 - r);
|
||||||
|
ledcWrite(LED_GREEN_CH, 255 - g);
|
||||||
|
ledcWrite(LED_BLUE_CH, 255 - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRearLedOff() {
|
||||||
|
ledcWrite(LED_RED_CH, 255);
|
||||||
|
ledcWrite(LED_GREEN_CH, 255);
|
||||||
|
ledcWrite(LED_BLUE_CH, 255);
|
||||||
|
// Drive pins directly HIGH as well for guaranteed zero leakage during sleep
|
||||||
|
digitalWrite(LED_RED_PIN, HIGH);
|
||||||
|
digitalWrite(LED_GREEN_PIN, HIGH);
|
||||||
|
digitalWrite(LED_BLUE_PIN, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateRearLedTheme() {
|
||||||
|
if (themeIndex >= 6) return;
|
||||||
|
setRearLedColor(GAUGE_THEMES[themeIndex].ledR,
|
||||||
|
GAUGE_THEMES[themeIndex].ledG,
|
||||||
|
GAUGE_THEMES[themeIndex].ledB);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
0 - Refrigerante
|
0 - Refrigerante
|
||||||
|
|
@ -108,7 +201,6 @@ uint8_t alarmSoundType = 0; // Default 0: Casio Doble (0: Casio Doble, 1: Sin
|
||||||
*/
|
*/
|
||||||
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
|
||||||
|
|
||||||
bool cellIsNumeric[4] = { false, false, false, false }; // false = Reloj (Arco), true = Solo Numero
|
|
||||||
int8_t fullScreenCell = -1; // -1 = 4 cuadrantes, 0..3 = pantalla completa de ese cuadrante
|
int8_t fullScreenCell = -1; // -1 = 4 cuadrantes, 0..3 = pantalla completa de ese cuadrante
|
||||||
// User configuration here to change display >
|
// User configuration here to change display >
|
||||||
|
|
||||||
|
|
@ -125,7 +217,9 @@ uint8_t B = 0; //get B
|
||||||
|
|
||||||
//Global variable
|
//Global variable
|
||||||
const uint32_t SLEEP_DURATION = 180 * 1000000; //180 sec* us = 3 min
|
const uint32_t SLEEP_DURATION = 180 * 1000000; //180 sec* us = 3 min
|
||||||
String line[12] = { "", "", "", "", "", "", "", "", "", "", "", "" }; //buffer for each line in terminal function
|
const uint8_t MAX_TERMINAL_LINES = 13;
|
||||||
|
String line[MAX_TERMINAL_LINES]; //buffer for each line in terminal function
|
||||||
|
uint8_t terminalLineCount = 0;
|
||||||
bool dim = false; //back light dim flag
|
bool dim = false; //back light dim flag
|
||||||
const uint8_t LIGHT_LEVEL = 40; //backlight control by light level Higher = darker light
|
const uint8_t LIGHT_LEVEL = 40; //backlight control by light level Higher = darker light
|
||||||
String serial_no = ""; //keep serial no.
|
String serial_no = ""; //keep serial no.
|
||||||
|
|
@ -157,20 +251,88 @@ XPT2046_TouchscreenSOFTSPI ts(XPT2046_CS);
|
||||||
|
|
||||||
|
|
||||||
/*------------------*/
|
/*------------------*/
|
||||||
//TERMINAL terminal for debugging display text
|
// Draw a single console boot entry with authentic status badges
|
||||||
void Terminal(String texts, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
void drawTerminalEntry(int lineIdx, const String &rawText) {
|
||||||
uint8_t max_line = round((h - y) / 16.0);
|
int yPos = 6 + lineIdx * 18;
|
||||||
tft.fillRect(x, y, x + w, y + h, TFT_BLACK);
|
String text = rawText;
|
||||||
tft.setTextColor(TFT_WHITE);
|
|
||||||
for (uint8_t i = 0; i < max_line; i++) {
|
// Clear that single line to prevent overlap if redrawn
|
||||||
if (i <= max_line - 2) {
|
tft.fillRect(0, yPos, 320, 18, TFT_BLACK);
|
||||||
line[i] = line[i + 1];
|
|
||||||
|
if (text.startsWith("[ OK ]")) {
|
||||||
|
tft.setTextColor(0x7BEF, TFT_BLACK);
|
||||||
|
tft.drawString("[", 6, yPos, 2);
|
||||||
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
||||||
|
tft.drawString(" OK ", 12, yPos, 2);
|
||||||
|
tft.setTextColor(0x7BEF, TFT_BLACK);
|
||||||
|
tft.drawString("]", 52, yPos, 2);
|
||||||
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft.drawString(text.substring(8).c_str(), 60, yPos, 2);
|
||||||
|
} else if (text.startsWith("[FAILED]")) {
|
||||||
|
tft.setTextColor(0x7BEF, TFT_BLACK);
|
||||||
|
tft.drawString("[", 6, yPos, 2);
|
||||||
|
tft.setTextColor(TFT_RED, TFT_BLACK);
|
||||||
|
tft.drawString("FAILED", 12, yPos, 2);
|
||||||
|
tft.setTextColor(0x7BEF, TFT_BLACK);
|
||||||
|
tft.drawString("]", 52, yPos, 2);
|
||||||
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft.drawString(text.substring(8).c_str(), 60, yPos, 2);
|
||||||
|
} else if (text.startsWith("[ INFO ]")) {
|
||||||
|
tft.setTextColor(0x7BEF, TFT_BLACK);
|
||||||
|
tft.drawString("[", 6, yPos, 2);
|
||||||
|
tft.setTextColor(TFT_CYAN, TFT_BLACK);
|
||||||
|
tft.drawString(" INFO ", 12, yPos, 2);
|
||||||
|
tft.setTextColor(0x7BEF, TFT_BLACK);
|
||||||
|
tft.drawString("]", 52, yPos, 2);
|
||||||
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft.drawString(text.substring(8).c_str(), 60, yPos, 2);
|
||||||
|
} else if (text.startsWith("[") && text.indexOf(']') != -1) {
|
||||||
|
int closeIdx = text.indexOf(']');
|
||||||
|
String tag = text.substring(0, closeIdx + 1);
|
||||||
|
String rest = text.substring(closeIdx + 1);
|
||||||
|
tft.setTextColor(0x07E0, TFT_BLACK); // Green kernel timestamp
|
||||||
|
tft.drawString(tag.c_str(), 6, yPos, 2);
|
||||||
|
int tagW = tft.textWidth(tag.c_str(), 2);
|
||||||
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft.drawString(rest.c_str(), 6 + tagW + 4, yPos, 2);
|
||||||
|
} else {
|
||||||
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
|
tft.drawString(text.c_str(), 6, yPos, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
texts = "[FAILED] " + texts;
|
||||||
|
} else if (texts.indexOf("Scanning") != -1 || texts.indexOf("Iniciando") != -1 || texts.indexOf("Conectando") != -1 || texts.indexOf("Insert") != -1) {
|
||||||
|
texts = "[ INFO ] " + texts;
|
||||||
} else {
|
} else {
|
||||||
line[i] = texts;
|
texts = " " + texts;
|
||||||
}
|
}
|
||||||
tft.drawString(line[i], x, i * 20 + y, 2);
|
}
|
||||||
} //for
|
|
||||||
bt_message = ""; //reset bt message
|
if (terminalLineCount < MAX_TERMINAL_LINES) {
|
||||||
|
line[terminalLineCount] = texts;
|
||||||
|
drawTerminalEntry(terminalLineCount, texts);
|
||||||
|
terminalLineCount++;
|
||||||
|
} else {
|
||||||
|
// Reached screen bottom: scroll up by 1 line
|
||||||
|
tft.fillScreen(TFT_BLACK);
|
||||||
|
for (uint8_t i = 0; i < MAX_TERMINAL_LINES - 1; i++) {
|
||||||
|
line[i] = line[i + 1];
|
||||||
|
drawTerminalEntry(i, line[i]);
|
||||||
|
}
|
||||||
|
line[MAX_TERMINAL_LINES - 1] = texts;
|
||||||
|
drawTerminalEntry(MAX_TERMINAL_LINES - 1, texts);
|
||||||
|
}
|
||||||
|
bt_message = "";
|
||||||
}
|
}
|
||||||
/*------------------*/
|
/*------------------*/
|
||||||
|
|
||||||
|
|
@ -185,10 +347,10 @@ void checkGenuine() { //check if genuine obd2 gauge - m
|
||||||
Serial.println(F("Not genuine Va&Cob Gauge!"));
|
Serial.println(F("Not genuine Va&Cob Gauge!"));
|
||||||
while (true) { //beep
|
while (true) { //beep
|
||||||
ledcWriteTone(buzzerChannel, 2000);
|
ledcWriteTone(buzzerChannel, 2000);
|
||||||
digitalWrite(LED_RED_PIN, LOW); //red on
|
setRearLedColor(255, 0, 0); //red on
|
||||||
delay(500);
|
delay(500);
|
||||||
ledcWriteTone(buzzerChannel, 0);
|
ledcWriteTone(buzzerChannel, 0);
|
||||||
digitalWrite(LED_RED_PIN, HIGH); //red off
|
setRearLedOff(); //red off
|
||||||
delay(500);
|
delay(500);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -198,12 +360,80 @@ void checkGenuine() { //check if genuine obd2 gauge - m
|
||||||
}
|
}
|
||||||
//----------------------------------------------
|
//----------------------------------------------
|
||||||
|
|
||||||
|
// 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
|
||||||
|
BTSerial.print(cmd + "\r");
|
||||||
|
String resp = "";
|
||||||
|
uint32_t start = millis();
|
||||||
|
while (millis() - start < timeoutMs) {
|
||||||
|
if (BTSerial.available() > 0) {
|
||||||
|
char c = (char)BTSerial.read();
|
||||||
|
if (c == '>') {
|
||||||
|
break; // Prompt character received, command finished
|
||||||
|
}
|
||||||
|
resp += c;
|
||||||
|
} else {
|
||||||
|
delay(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.trim();
|
||||||
|
#ifdef SERIAL_DEBUG
|
||||||
|
Serial.printf("ELM CMD [%s] -> [%s]\n", cmd.c_str(), resp.c_str());
|
||||||
|
#endif
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
//---- Include Header File --------------------
|
//---- Include Header File --------------------
|
||||||
//#include "image.h"
|
//#include "image.h"
|
||||||
#include "bluetooth.h"
|
#include "bluetooth.h"
|
||||||
#include "meter.h"
|
#include "meter.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
// Robust ELM327 and ECU connection routine
|
||||||
|
void initializeELM327() {
|
||||||
|
Terminal("[ INFO ] Iniciando comunicacion ELM327...");
|
||||||
|
|
||||||
|
// 1. Reset ELM327 (No ATBRD23! ATBRD breaks Bluetooth serial)
|
||||||
|
String rZ = sendATCommand("ATZ", 2500);
|
||||||
|
delay(300); // Allow ELM327 power and crystal stabilization
|
||||||
|
if (rZ.length() > 0) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 3. Test Battery Voltage via ATRV (direct from OBD pin 16)
|
||||||
|
String rVolt = sendATCommand("ATRV", 1500);
|
||||||
|
if (rVolt.length() > 0) {
|
||||||
|
Terminal("[ OK ] Bateria: " + rVolt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
String r0100 = sendATCommand("0100", 5000);
|
||||||
|
|
||||||
|
if (r0100.indexOf("41 00") != -1) {
|
||||||
|
Terminal("[ OK ] ECU OK: Ford J1850 PWM!");
|
||||||
|
ignitionOn = true;
|
||||||
|
delay(600);
|
||||||
|
} else {
|
||||||
|
Terminal("[ INFO ] Sin contacto (Poner en marcha)");
|
||||||
|
ignitionOn = false;
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
initScreen();
|
||||||
|
beepbeep();
|
||||||
|
bt_message = "";
|
||||||
|
prompt = true;
|
||||||
|
}
|
||||||
|
|
||||||
//----SET UP -----------------------------------
|
//----SET UP -----------------------------------
|
||||||
void setup() {
|
void setup() {
|
||||||
//pin configuration
|
//pin configuration
|
||||||
|
|
@ -211,12 +441,14 @@ void setup() {
|
||||||
pinMode(LDR_PIN, ANALOG); //ldr analog input read brightness
|
pinMode(LDR_PIN, ANALOG); //ldr analog input read brightness
|
||||||
pinMode(BUZZER_PIN, OUTPUT); //speaker
|
pinMode(BUZZER_PIN, OUTPUT); //speaker
|
||||||
pinMode(SELECTOR_PIN, INPUT_PULLUP); //button
|
pinMode(SELECTOR_PIN, INPUT_PULLUP); //button
|
||||||
pinMode(LED_RED_PIN, OUTPUT); //red
|
// Rear RGB LED PWM setup (Channels 4, 5, 6 - Timers 2 & 3)
|
||||||
pinMode(LED_GREEN_PIN, OUTPUT); //green
|
ledcSetup(LED_RED_CH, 5000, 8);
|
||||||
pinMode(LED_BLUE_PIN, OUTPUT); //blue
|
ledcSetup(LED_GREEN_CH, 5000, 8);
|
||||||
digitalWrite(LED_RED_PIN, LOW); //on
|
ledcSetup(LED_BLUE_CH, 5000, 8);
|
||||||
digitalWrite(LED_GREEN_PIN, HIGH); //off
|
ledcAttachPin(LED_RED_PIN, LED_RED_CH);
|
||||||
digitalWrite(LED_BLUE_PIN, HIGH); //off
|
ledcAttachPin(LED_GREEN_PIN, LED_GREEN_CH);
|
||||||
|
ledcAttachPin(LED_BLUE_PIN, LED_BLUE_CH);
|
||||||
|
setRearLedOff();
|
||||||
//pwm setup
|
//pwm setup
|
||||||
ledcSetup(buzzerChannel, 1500, 10); //buzzer 10 bit
|
ledcSetup(buzzerChannel, 1500, 10); //buzzer 10 bit
|
||||||
ledcSetup(backlightChannel, 12000, 8); //backlight 8 bit
|
ledcSetup(backlightChannel, 12000, 8); //backlight 8 bit
|
||||||
|
|
@ -259,16 +491,22 @@ void setup() {
|
||||||
|
|
||||||
//backlight ledcAttachPin must be set after tft.init()
|
//backlight ledcAttachPin must be set after tft.init()
|
||||||
ledcAttachPin(TFT_BL, backlightChannel); //attach backlight
|
ledcAttachPin(TFT_BL, backlightChannel); //attach backlight
|
||||||
for (uint8_t i = 255; i > 0; i--) { //fading effect
|
|
||||||
ledcWrite(backlightChannel, i); //full bright
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
digitalWrite(LED_RED_PIN, HIGH); //red off
|
|
||||||
tft.fillScreen(TFT_BLACK); //show start page
|
|
||||||
tft.fillRectVGradient(0, 0, 320, 26, 0x2104, TFT_BLACK);
|
|
||||||
tft.setTextColor(TFT_WHITE);
|
|
||||||
tft.drawString("OBDII GAUGE", 10, 5, 2);
|
|
||||||
ledcWrite(backlightChannel, 255); //full bright
|
ledcWrite(backlightChannel, 255); //full bright
|
||||||
|
tft.fillScreen(TFT_BLACK); // Consola de arranque de sistema
|
||||||
|
|
||||||
|
terminalLineCount = 0;
|
||||||
|
Terminal("[ 0.000000] ESP32-D0WD Xtensa LX6 Dual-Core @ 240MHz");
|
||||||
|
delay(40);
|
||||||
|
Terminal("[ 0.012400] FreeRTOS Kernel v10.4.3 (ESP-IDF v4.4)");
|
||||||
|
delay(40);
|
||||||
|
Terminal("[ 0.025800] Flash: 4MB QIO @ 80MHz | huge_app");
|
||||||
|
delay(40);
|
||||||
|
Terminal("[ 0.041200] SRAM: 520KB | Core 0/1 Online");
|
||||||
|
delay(40);
|
||||||
|
Terminal("[ OK ] Display: ILI9341 320x240 LCD Initialized");
|
||||||
|
delay(40);
|
||||||
|
Terminal("[ OK ] Input: XPT2046 Resistive Touch Calibrated");
|
||||||
|
delay(40);
|
||||||
|
|
||||||
// checkGenuine();//check guniune
|
// checkGenuine();//check guniune
|
||||||
//init variable
|
//init variable
|
||||||
|
|
@ -285,26 +523,49 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6]
|
||||||
pref.putUShort("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]);
|
warningValue[i] = pref.getString(pidConfig[i][0].c_str(), pidConfig[i][8]);
|
||||||
|
if (i == 0 && (warningValue[0] == "106" || warningValue[0] == "0" || warningValue[0] == "")) {
|
||||||
|
warningValue[0] = "110";
|
||||||
|
pref.putString(pidConfig[0][0].c_str(), "110");
|
||||||
|
}
|
||||||
String almKey = "alm_" + String(i);
|
String almKey = "alm_" + String(i);
|
||||||
alarmEnabled[i] = pref.getBool(almKey.c_str(), true);
|
alarmEnabled[i] = pref.getBool(almKey.c_str(), (i < 4));
|
||||||
}
|
}
|
||||||
buzzerVolumeLevel = pref.getUChar("b_vol", 3);
|
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;
|
||||||
alarmVolumeLevel = pref.getUChar("alm_vol", 3);
|
alarmVolumeLevel = pref.getUChar("alm_vol", 3);
|
||||||
if (alarmVolumeLevel > 5) alarmVolumeLevel = 3;
|
if (alarmVolumeLevel > 5) alarmVolumeLevel = 3;
|
||||||
alarmSoundType = pref.getUChar("alm_type", 0);
|
alarmSoundType = pref.getUChar("alm_type", 0);
|
||||||
if (alarmSoundType > 3) alarmSoundType = 0;
|
if (alarmSoundType > 5) alarmSoundType = 0;
|
||||||
|
autoSleepIndex = pref.getUChar("auto_sleep", 2);
|
||||||
|
if (autoSleepIndex > 4) autoSleepIndex = 2;
|
||||||
|
brightnessIndex = pref.getUChar("b_bright", 0);
|
||||||
|
if (brightnessIndex > 5) brightnessIndex = 0;
|
||||||
|
themeIndex = pref.getUChar("theme_idx", 0);
|
||||||
|
if (themeIndex > 5) themeIndex = 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)");
|
||||||
|
delay(40);
|
||||||
|
|
||||||
//start bluetooth
|
//start bluetooth
|
||||||
|
Terminal("[ INFO ] Initializing Bluetooth Controller...");
|
||||||
if (!BTSerial.begin("Va&Cob OBDII Gauge", true)) {
|
if (!BTSerial.begin("Va&Cob OBDII Gauge", true)) {
|
||||||
Serial.println(F("Bluetooth..error!"));
|
Serial.println(F("Bluetooth..error!"));
|
||||||
Terminal("Bluetooth..error!", 0, 48, 320, 191);
|
Terminal("[FAILED] Bluetooth controller initialization error!");
|
||||||
abort();
|
abort();
|
||||||
} else {
|
} else {
|
||||||
Serial.println(F("Bluetooth..OK"));
|
Serial.println(F("Bluetooth..OK"));
|
||||||
Terminal("Bluetooth..OK", 0, 48, 320, 191);
|
Terminal("[ OK ] Bluetooth SPP Master stack online");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SKIP_CONNECTION
|
#ifdef SKIP_CONNECTION
|
||||||
|
|
@ -321,38 +582,13 @@ recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6]
|
||||||
delay(50);
|
delay(50);
|
||||||
if (digitalRead(SELECTOR_PIN) == LOW) {
|
if (digitalRead(SELECTOR_PIN) == LOW) {
|
||||||
configMenu();
|
configMenu();
|
||||||
|
waitForTouchRelease();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Initialize ELM327
|
//Initialize ELM327
|
||||||
Terminal("Initializing...", 0, 48, 320, 191);
|
initializeELM327();
|
||||||
bt_message = "";
|
|
||||||
for (uint8_t initIndex = 0; initIndex < elm327InitCount; initIndex++) { //send init ELM327 AT command
|
|
||||||
BTSerial.print(elm327Init[initIndex] + "\r");
|
|
||||||
if (initIndex == elm327InitCount - 1) delay(2000); //wait for 0100 Searching..protocal
|
|
||||||
else delay(100); //must add delay , unless will skip BTSerial.avaialble>0
|
|
||||||
while (BTSerial.available() > 0) {
|
|
||||||
char incomingChar = BTSerial.read();
|
|
||||||
if (incomingChar == '>') { //check prompt
|
|
||||||
BTSerial.flush(); //clear buffer
|
|
||||||
#ifdef SERIAL_DEBUG
|
|
||||||
Serial.println(bt_message);
|
|
||||||
#endif
|
|
||||||
#ifdef TERMINAL
|
|
||||||
Terminal(bt_message, 0, 48, 320, 191);
|
|
||||||
#endif
|
|
||||||
if (initIndex == 1) BTSerial.print("\r"); //ATBRD23 response
|
|
||||||
bt_message = "";
|
|
||||||
} else {
|
|
||||||
bt_message.concat(incomingChar); //keep elm327 respond
|
|
||||||
}
|
|
||||||
} //while
|
|
||||||
} //for
|
|
||||||
|
|
||||||
initScreen();
|
|
||||||
beepbeep();
|
|
||||||
prompt = true;
|
|
||||||
//----------------------
|
//----------------------
|
||||||
} //setup
|
} //setup
|
||||||
|
|
||||||
|
|
@ -361,103 +597,156 @@ void loop() {
|
||||||
updateBacklightFade(); // non-blocking smooth backlight fade handler
|
updateBacklightFade(); // non-blocking smooth backlight fade handler
|
||||||
handleArcAlarmFlash(); // non-blocking arc alarm flash handler (blinks when in TFT_RED)
|
handleArcAlarmFlash(); // non-blocking arc alarm flash handler (blinks when in TFT_RED)
|
||||||
|
|
||||||
|
if (wokeFromSleep) {
|
||||||
|
wokeFromSleep = false;
|
||||||
|
lastActivityTime = millis();
|
||||||
|
resumeBluetoothConnection();
|
||||||
|
}
|
||||||
|
|
||||||
// SCAN TOUCHSCREEN & BUTTON
|
// SCAN TOUCHSCREEN & BUTTON
|
||||||
// - Single click on a quadrant: toggles between clock (arc) and number
|
// - Single tap on a quadrant in 4-quadrant view: switches to full screen for that quadrant
|
||||||
// - Double click on a quadrant: puts that instrument in full screen (or exits full screen)
|
// - Single tap anywhere in full screen view: returns to 4-quadrant view
|
||||||
// - Long press (> 1s) on screen OR holding selector button: opens configuration menu
|
// - Long press (> 1s) on screen OR holding selector button: opens configuration menu
|
||||||
uint16_t touchX = 0, touchY = 0;
|
uint16_t touchX = 0, touchY = 0;
|
||||||
bool isTouched = getTouch(&touchX, &touchY);
|
bool isTouched = getTouch(&touchX, &touchY);
|
||||||
bool btnPressed = (digitalRead(SELECTOR_PIN) == LOW);
|
bool btnPressed = (digitalRead(SELECTOR_PIN) == LOW);
|
||||||
|
|
||||||
|
if (isTouched || btnPressed) {
|
||||||
|
lastActivityTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
static bool isTouching = false;
|
static bool isTouching = false;
|
||||||
static uint32_t touchStartTime = 0;
|
static uint32_t touchStartTime = 0;
|
||||||
static uint16_t lastTouchX = 0, lastTouchY = 0;
|
static uint16_t lastTouchX = 0, lastTouchY = 0;
|
||||||
static uint8_t clickCount = 0;
|
|
||||||
static uint32_t firstClickTime = 0;
|
|
||||||
static int8_t clickQuadrant = -1;
|
|
||||||
static bool longPressHandled = false;
|
static bool longPressHandled = false;
|
||||||
|
|
||||||
if (isTouched) {
|
if (btnPressed) {
|
||||||
|
delay(50);
|
||||||
|
if (digitalRead(SELECTOR_PIN) == LOW) {
|
||||||
|
touchBeep();
|
||||||
|
configMenu();
|
||||||
|
waitForTouchRelease();
|
||||||
|
lastActivityTime = millis();
|
||||||
|
initScreen();
|
||||||
|
}
|
||||||
|
} else if (isTouched) {
|
||||||
if (!isTouching) {
|
if (!isTouching) {
|
||||||
isTouching = true;
|
isTouching = true;
|
||||||
touchStartTime = millis();
|
touchStartTime = millis();
|
||||||
lastTouchX = touchX;
|
lastTouchX = touchX;
|
||||||
lastTouchY = touchY;
|
lastTouchY = touchY;
|
||||||
longPressHandled = false;
|
longPressHandled = false;
|
||||||
casioBeep(4096, 25); // Crisp, bright Casio chirp on touch screen press!
|
touchBeep(); // Crisp acoustic feedback on touch screen press!
|
||||||
} else if (!longPressHandled && (millis() - touchStartTime > 1000)) {
|
} else {
|
||||||
// Long press (> 1s): Open Config Menu!
|
// Keep tracking coordinates while held to ensure stable quadrant detection
|
||||||
longPressHandled = true;
|
lastTouchX = touchX;
|
||||||
clickCount = 0;
|
lastTouchY = touchY;
|
||||||
casioBeep(4096, 30); // High confirmation chirp
|
|
||||||
configMenu();
|
if (!longPressHandled && (millis() - touchStartTime > 1000)) {
|
||||||
if (foundOBD2) initScreen();
|
// Long press (> 1s): Open Config Menu immediately!
|
||||||
isTouching = false;
|
longPressHandled = true;
|
||||||
|
touchBeep(); // Confirmation chirp
|
||||||
|
configMenu();
|
||||||
|
waitForTouchRelease();
|
||||||
|
lastActivityTime = millis();
|
||||||
|
initScreen();
|
||||||
|
isTouching = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Touch / button released
|
// Touch / button released
|
||||||
if (isTouching) {
|
if (isTouching) {
|
||||||
isTouching = false;
|
isTouching = false;
|
||||||
if (!longPressHandled && (millis() - touchStartTime >= 30)) {
|
if (!longPressHandled) {
|
||||||
// Valid tap detected! Determine quadrant
|
// Valid tap detected!
|
||||||
int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3);
|
if (fullScreenCell == -1) {
|
||||||
|
// Switch to full screen for tapped quadrant
|
||||||
if (clickCount == 0) {
|
int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3);
|
||||||
clickCount = 1;
|
fullScreenCell = quad;
|
||||||
firstClickTime = millis();
|
} else {
|
||||||
clickQuadrant = quad;
|
// Return to 4 quadrants view
|
||||||
} else if (clickCount == 1) {
|
fullScreenCell = -1;
|
||||||
if ((millis() - firstClickTime < 400) && (quad == clickQuadrant || fullScreenCell != -1)) {
|
|
||||||
// DOUBLE CLICK DETECTED!
|
|
||||||
clickCount = 0;
|
|
||||||
|
|
||||||
if (fullScreenCell == -1) {
|
|
||||||
fullScreenCell = quad;
|
|
||||||
} else {
|
|
||||||
fullScreenCell = -1;
|
|
||||||
}
|
|
||||||
initScreen();
|
|
||||||
} else {
|
|
||||||
clickCount = 1;
|
|
||||||
firstClickTime = millis();
|
|
||||||
clickQuadrant = quad;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
initScreen();
|
||||||
|
waitForTouchRelease(); // Clear mechanical bounce on release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle single click timeout (after 400ms without a second tap)
|
// AUTO-SLEEP BY INACTIVITY
|
||||||
if (clickCount == 1 && (millis() - firstClickTime >= 400)) {
|
// If auto-sleep is enabled, and screen is not being held, and engine is stopped (RPM <= 300)
|
||||||
clickCount = 0;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fullScreenCell != -1) {
|
#ifndef SKIP_CONNECTION
|
||||||
// In full screen: single click toggles between arc and number mode without header flicker
|
// Check Bluetooth connection state: if adapter disconnected or lost power, show "---" (no data)
|
||||||
cellIsNumeric[fullScreenCell] = !cellIsNumeric[fullScreenCell];
|
if (!BTSerial.connected() || !foundOBD2) {
|
||||||
fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell], false);
|
static uint32_t lastBtReconnect = 0;
|
||||||
} else if (clickQuadrant >= 0 && clickQuadrant < 4) {
|
currentRpm = 0.0f;
|
||||||
// In 4 quadrants: single click toggles between arc and number for this quadrant without header flicker
|
ignitionOn = false;
|
||||||
cellIsNumeric[clickQuadrant] = !cellIsNumeric[clickQuadrant];
|
for (uint8_t c = 0; c < 4; c++) {
|
||||||
uint8_t pid = pidInCell[clickQuadrant];
|
plotNoData(c);
|
||||||
if (cellIsNumeric[clickQuadrant]) {
|
}
|
||||||
numericMeter4(clickQuadrant, pid, false);
|
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 {
|
} else {
|
||||||
arcMeter4(clickQuadrant, pid, false);
|
setRearLedOff();
|
||||||
|
foundOBD2 = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
//----------------------
|
#endif
|
||||||
//BLUETOOTH read
|
|
||||||
|
//----------------------
|
||||||
|
//BLUETOOTH read
|
||||||
static uint32_t lastPidSendTime = 0;
|
static uint32_t lastPidSendTime = 0;
|
||||||
// Watchdog: If waiting for prompt for more than 6000ms (standard OBD2 maximum ECU timeout), recover!
|
// Watchdog: If waiting for prompt for more than 5000ms, recover safely without touching screen
|
||||||
if (!prompt && (millis() - lastPidSendTime > 6000)) {
|
if (!prompt && (millis() - lastPidSendTime > 5000)) {
|
||||||
#ifdef SERIAL_DEBUG
|
#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
|
#endif
|
||||||
BTSerial.flush();
|
BTSerial.flush();
|
||||||
bt_message = "";
|
bt_message = "";
|
||||||
prompt = true;
|
prompt = true;
|
||||||
skip = true;
|
skip = true; // IMPORTANT: skip updateMeter so timeout never puts zeros on screen!
|
||||||
}
|
}
|
||||||
|
|
||||||
while (BTSerial.available() > 0) {
|
while (BTSerial.available() > 0) {
|
||||||
|
|
@ -500,25 +789,57 @@ void loop() {
|
||||||
|
|
||||||
//Send another PID to elm327
|
//Send another PID to elm327
|
||||||
if (prompt) { //> ELM327 ready! -> request next PID
|
if (prompt) { //> ELM327 ready! -> request next PID
|
||||||
prompt = false; //no prompt from ELM327
|
if (!skip && bt_message.length() > 0) {
|
||||||
if (!skip) {
|
|
||||||
updateMeter(pidIndex, bt_message); //update meter screen
|
updateMeter(pidIndex, bt_message); //update meter screen
|
||||||
}
|
}
|
||||||
pidIndex++; //point to next pid
|
bt_message = "";
|
||||||
if (pidIndex >= maxpidIndex) {
|
|
||||||
pidIndex = 0; //back to pid 0
|
if (!ignitionOn) {
|
||||||
autoDim(); //auto backlight handle
|
// 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.
|
||||||
|
static uint32_t lastIgnitionProbeTime = 0;
|
||||||
|
static uint32_t lastDimCheck = 0;
|
||||||
|
if (millis() - lastDimCheck >= 500) {
|
||||||
|
lastDimCheck = millis();
|
||||||
|
autoDim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() - lastIgnitionProbeTime >= 1500) {
|
||||||
|
lastIgnitionProbeTime = millis();
|
||||||
|
prompt = false;
|
||||||
|
skip = false;
|
||||||
|
// Buscar el indice de RPM en pidList (dentro de los 4 cuadrantes)
|
||||||
|
uint8_t rpmPidNo = 2; // Default cell 2 = RPM
|
||||||
|
for (uint8_t i = 0; i < 4; i++) {
|
||||||
|
if (pidConfig[pidInCell[i]][3].toInt() == 3) {
|
||||||
|
rpmPidNo = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pidIndex = rpmPidNo;
|
||||||
|
BTSerial.print(pidList[pidIndex] + "\r");
|
||||||
|
lastPidSendTime = millis();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Operacion normal: ciclo continuo y veloz de PIDs
|
||||||
|
prompt = false; //no prompt from ELM327
|
||||||
|
pidIndex++; //point to next pid
|
||||||
|
if (pidIndex >= maxpidIndex) {
|
||||||
|
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
|
||||||
|
skip = false;
|
||||||
|
BTSerial.print(pidList[pidIndex] + "\r"); //send PID request
|
||||||
|
lastPidSendTime = millis();
|
||||||
|
} else { //skip sending request
|
||||||
|
pidCurrentSkip[pidIndex] = pidCurrentSkip[pidIndex] - 1; //decrese loop count
|
||||||
|
prompt = true;
|
||||||
|
skip = true;
|
||||||
|
} //else
|
||||||
}
|
}
|
||||||
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
|
|
||||||
lastPidSendTime = millis();
|
|
||||||
} else { //skip sending request
|
|
||||||
pidCurrentSkip[pidIndex] = pidCurrentSkip[pidIndex] - 1; //decrese loop count
|
|
||||||
prompt = true;
|
|
||||||
skip = true;
|
|
||||||
} //else
|
|
||||||
|
|
||||||
} //if prompt
|
} //if prompt
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,6 @@ extern XPT2046_TouchscreenSOFTSPI ts;
|
||||||
#define _height 240
|
#define _height 240
|
||||||
#define _width 320
|
#define _width 320
|
||||||
|
|
||||||
// Direct touch check using pressure Z
|
|
||||||
inline bool isScreenPressed() {
|
|
||||||
if (!tsSoftSpi) return false;
|
|
||||||
return ts.touched(tsSoftSpi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get mapped touchscreen coordinates in landscape (320x240)
|
// Get mapped touchscreen coordinates in landscape (320x240)
|
||||||
bool getTouch(uint16_t *x, uint16_t *y) {
|
bool getTouch(uint16_t *x, uint16_t *y) {
|
||||||
if (!tsSoftSpi) return false;
|
if (!tsSoftSpi) return false;
|
||||||
|
|
@ -29,8 +23,8 @@ bool getTouch(uint16_t *x, uint16_t *y) {
|
||||||
if (ts.touched(tsSoftSpi)) {
|
if (ts.touched(tsSoftSpi)) {
|
||||||
TS_Point p = ts.getPoint(tsSoftSpi);
|
TS_Point p = ts.getPoint(tsSoftSpi);
|
||||||
|
|
||||||
// Filter noise: require solid touch pressure and coordinates within active screen
|
// Filter noise: require valid touch pressure and coordinates within active screen
|
||||||
if (p.z < 600) return false;
|
if (p.z < 350) return false;
|
||||||
if (p.x < 150 || p.x > 3850 || p.y < 150 || p.y > 3850) 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 tx = map(p.x, 200, 3700, 0, 320);
|
||||||
|
|
@ -43,6 +37,27 @@ bool getTouch(uint16_t *x, uint16_t *y) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct touch check using pressure Z
|
||||||
|
inline bool isScreenPressed() {
|
||||||
|
if (!tsSoftSpi) return false;
|
||||||
|
return ts.touched(tsSoftSpi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until both touchscreen and selector button are completely released with 50ms continuous debounce
|
||||||
|
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)) {
|
||||||
|
releaseCount = 0;
|
||||||
|
} else {
|
||||||
|
releaseCount++;
|
||||||
|
}
|
||||||
|
delay(10);
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void touch_calibrate() {
|
void touch_calibrate() {
|
||||||
// Calibration placeholder for backward compatibility
|
// Calibration placeholder for backward compatibility
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue