📊 Companion server: IDMate — the dashboard & trip log that consumes the telemetry and lets you analyse trips, charges and battery health.
Open telemetry for the VW ID family — an ESP32-based stick that turns the OBD2 port into a live data feed for SoC, battery temperature, power flow, charging behavior and location.
Born out of frustration: the VW ID.7 shows you almost nothing about what's happening under the hood. No real SoC, no battery temperature, no power draw, no charge curve — not even a simple energy consumption history. Even a base-model VW Hybrid puts more data on the dashboard than an ID.7 does. This project is a DIY box that plugs into OBD2 and turns a black-box EV into something you can actually understand — your data, your broker, your dashboard.
- Reads battery, drivetrain and climate data from the CAN bus (UDS/ISO-TP)
- Pushes telemetry to InfluxDB over LTE-M for Grafana dashboards
- Buffers ~40,000 rows (~5,000 km) in a dedicated 5 MB Flash partition — survives reboots, crashes and long radio dead zones
- Tracks location via Traccar (OsmAnd protocol)
- Emulates a BLE ELM327 so ABRP sees the car natively
- Runs a WiFi AP with a full Web-UI for live data, config and debugging
- Sleeps in the low-µA range when parked, wakes on motion
The stick reads CAN data via UDS, persists every telemetry row to a dedicated 5 MB raw Flash partition (~40,000 rows ≈ 5,000 km offline buffer), pushes encrypted telemetry over LTE-M MQTT, exposes a BLE ELM327 emulation for ABRP, and serves a local Web-UI on its WiFi AP.
- High-level structure:
docs/*.puml— rendered withplantuml, themed viadocs/_theme.puml - Step-by-step algorithms (telemetry store, MQTT escalation, sleep/wake, GPS capture):
docs/algorithms.md
The 16 MB Flash is split into:
| Partition | Size | Purpose |
|---|---|---|
| app0 | 2 MB | Firmware (Web-UI HTMLs are embedded inside) |
| coredump | 64 KB | Crash dumps for post-mortem |
| telem | 5 MB | Raw ring buffer for telemetry rows (no filesystem — atomic per-slot status byte) |
| spiffs | 8.8 MB | Runtime logs only |
HTMLs live inside the firmware binary, not in SPIFFS — they cannot be corrupted by log writes or filesystem issues. The telemetry ring buffer is crash-safe by design: each row is committed via a single status-byte flip, so a power loss mid-write loses at most one row.
When MQTT or GPRS misbehave, the firmware escalates through four levels before giving up — built to survive long radio dead zones without endless reboot loops.
⚠️ Board requirementsThe firmware is built specifically for ESP32-S3 with 16 MB flash and 8 MB PSRAM (the
N16R8chip variant). Smaller flash or no-PSRAM boards will not boot — the partition table assumes a 16 MB layout, and the embedded Web UI plus the 5 MB raw telemetry partition do not fit in less. Plain ESP32 (non-S3) boards are not supported.
| Component | Details |
|---|---|
| MCU | LILYGO T-SIM7080G-S3 (ESP32-S3, 16 MB Flash, 8 MB PSRAM) |
| CAN Transceiver | SN65HVD230 (3.3 V, active mode) |
| Modem | SIM7080G (LTE-M / NB-IoT), onboard |
| GPS + Compass | BLITZ Mini M10 (u-blox M10 multi-GNSS + QMC5883L compass) — external via UART |
| GPS (fallback) | SIM7080G integrated GNSS (used when external GPS is disabled) |
| PMU | AXP2101 (onboard, manages DC rails for modem, GPS, peripherals) |
| RTC | DS1307 + CR2032 (keeps time across power cycles) |
| IMU | MPU-6050 (accelerometer + gyroscope, motion wake-up) |
The firmware supports two GPS configurations controlled by GPS_EXT_ENABLED in config.h:
External GPS (default, recommended): The BLITZ Mini M10 runs continuously on UART2 (GPIO1/GPIO2) and maintains a fix independently of the modem. LTE stays connected via MQTT at all times — no GPS/LTE cycling needed. The M10 draws ~5–8 mA in idle and keeps tracking even during sleep transitions. Power is supplied via the PMU DC5 rail (3.3 V).
Internal GPS (fallback):
When GPS_EXT_ENABLED is set to false, the SIM7080G's built-in GNSS is used instead. Because the SIM7080G shares a single radio frontend, GPS and LTE data cannot run simultaneously. The device cycles between modes every 60 seconds:
GPS active (55 s) → GPS off → LTE on → send data → LTE off → GPS on
The external GPS eliminates this limitation entirely.
The device opens a WiFi Access Point:
- SSID:
IDTelemetry - Password:
IDTelemetry1 - URL:
http://192.168.4.1
Three pages:
- Data — live telemetry table
- Debug — gyro graph, compass, CAN tools, syslog viewer
- Config — AP credentials, SIM / APN (full variant only), MQTT broker + AES key, WiFi Upload (STA) SSID / password / endpoint URL, BLE behaviour, SPIFFS logging toggles, OTA firmware update
The Config page is the single source of runtime configuration — everything that used to live in secrets.h can be edited here and is persisted in NVS.
The setup has two main paths:
- End-user path (Web Flasher + Config page) — needs only a USB cable and a phone or laptop. No source code, no toolchain. See Step 1 below.
- Developer path (clone + PlatformIO) — for building from source or modifying. Jump to Building from source.
| Variant | Part | Description | approx. Price |
|---|---|---|---|
| both | OBD2 plug + cable | 16-pin OBD2 connector (pin 6 + 14 for CAN, pin 16 for 12 V) | ~5 € |
| both | SN65HVD230 CAN transceiver | 3.3 V CAN bus module | ~3 € |
| both | DS1307 RTC module | Real-time clock (I2C) with coin cell | ~2 € |
| both | MPU-6050 breakout | Accelerometer / gyroscope (I2C) | ~3 € |
| both | USB-C cable | For flashing and power | — |
| full | LILYGO T-SIM7080G-S3 | ESP32-S3 board with LTE-M/NB-IoT modem, AXP2101 PMU, Li-Po | ~35 € |
| full | BLITZ Mini M10 | u-blox M10 GNSS + QMC5883L compass (UART) — recommended | ~40 € |
| full | LTE-M SIM card | e.g. ThingsMobile (prepaid, no contract) | ~15 € |
| lite | ESP32-S3-DevKitC-1 (N16R8) | Plain ESP32-S3 board, 16 MB flash, 8 MB PSRAM | ~12 € |
💡 The easiest path is the Web Flasher — no toolchain, no IDE. If that's not available for your release yet, see Building from source further down.
Open the Web Flasher in Chrome or Edge, plug the stick in via USB, click Install on the matching variant (Full for LILYGO T-SIM7080G-S3, Lite for plain ESP32-S3).
Right after flashing the stick spans up its own WiFi:
- SSID:
IDTelemetry - Password:
IDTelemetry1
iOS / Android pop up a captive-portal sheet with a Setup button — click it. Otherwise open http://192.168.4.1 in any browser.
Everything you used to put into secrets.h lives in the Config page now, persisted in NVS. Fill in whatever applies to your setup:
| Section | Fields | When to fill |
|---|---|---|
| WiFi Upload (STA) | Home WiFi SSID + password, Upload URL | Always if you want a WiFi upload path (mandatory for the lite variant, optional secondary for full) |
| MQTT Broker | Host, port, topic, AES-256 key | Full variant — primary upload path over LTE-M |
| MQTT Broker → AES-256 Key | 64 hex chars | Overrides the compile-time key from secrets.h. Leave blank to keep the built-in default. |
| SIM / APN | PIN, APN, user, pass | Full variant only |
| WiFi Access Point | AP SSID + password | Optional — only if you want the stick to advertise a different AP than the default |
Click Save. APN and AP credentials require a restart to take effect; the rest is live.
- Data page shows live telemetry once CAN is up and the car is awake.
- Debug page shows the syslog stream — look for
MQTT Verbunden,STA verbunden, orLTE: RSRP=…lines to confirm each upload path. - ABRP / Torque can pair to BLE
OBDII.
If you want to modify the firmware or run a development build, follow this section instead of Step 1.
cp src/secrets.h.example src/secrets.hsecrets.h provides the compile-time defaults for everything that can be overridden via the Web UI. You only have to fill it in if you intend to flash a pre-configured stick (a single fleet of sticks that all use the same MQTT broker, AES key, etc.). For one-off setups, leave the defaults and configure everything via Step 3 above.
⚠️ Generating tokens, keys and secretsUse a cryptographically secure random number generator. Never ask an LLM (ChatGPT, Claude, Copilot, …) to "give me a random 32-byte key" — LLM output is probability-distributed, not random. The same prompt produces a small, predictable distribution of values that an attacker can enumerate offline. The convenience is not worth the silent loss of entropy.
openssl rand -hex 32 # AES-256 key openssl rand -base64 32 # API/webhook tokens python3 -c "import secrets; print(secrets.token_hex(32))"The MQTT AES key in
secrets.hmust match theMQTT_AES_KEYconfigured on the IDMate server side.
LILYGO T-SIM7080G-S3 External Modules
┌──────────────────┐
│ │ ┌─── CAN Bus (OBD2) ───┐
│ GPIO17 (CAN TX) ─┼──→ SN65HVD230 CTX ──→ OBD2 Pin 6 (CAN-H)
│ GPIO18 (CAN RX) ─┼──→ SN65HVD230 CRX ──→ OBD2 Pin 14 (CAN-L)
│ │
│ │ ┌─── I2C Bus (shared) ──┐
│ GPIO45 (SDA) ────┼──→ DS1307 + MPU-6050 + QMC5883L (SDA)
│ GPIO21 (SCL) ────┼──→ DS1307 + MPU-6050 + QMC5883L (SCL)
│ │
│ GPIO3 (INT) ────┼──← MPU-6050 INT (deep sleep wake-up)
│ │
│ │ ┌─── External GPS ──────┐
│ GPIO1 (RX) ─────┼──← BLITZ M10 TX (UART2, 115200 baud)
│ GPIO2 (TX) ─────┼──→ BLITZ M10 RX
│ │
│ 3V3 ─────────────┼──→ VCC for CAN transceiver + RTC
│ GND ─────────────┼──→ GND for all modules
└──────────────────┘
Onboard (no wiring needed):
GPIO5/4 → SIM7080G UART (modem, UART1)
GPIO41 → SIM7080G PWRKEY
GPIO42 → SIM7080G DTR
GPIO40 → SIM7080G STATUS
GPIO44 → SIM7080G FLIGHT (RF kill)
GPIO15/7 → AXP2101 I2C (PMU, Wire1)
GPIO6 → AXP2101 INT (VBUS wake-up)
Insert a nano-SIM card into the slot on the bottom of the LILYGO board.
Note: The MPU-6050 AD0 pin must be pulled to 3.3 V so it uses I2C address 0x69 (avoiding collision with the DS1307 at 0x68). The BLITZ M10 is powered by the PMU DC5 rail (3.3 V).
# Install toolchain
# - VSCode + PlatformIO IDE extension, OR
# - just `pio` from `pip install platformio`
# Build + upload the full variant (LILYGO T-SIM7080G-S3)
pio run -e s3_full --target upload
pio run -e s3_full --target uploadfs # only on first flash, initialises SPIFFS
# Or the lite variant (plain ESP32-S3)
pio run -e s3_lite --target upload
pio run -e s3_lite --target uploadfs # only on first flashPre-built binaries from the Releases page can also be flashed directly with esptool.py — see docs/flasher/README.md for the byte-offset table that ESP Web Tools uses.
| GPIO | Function | Direction | Bus |
|---|---|---|---|
| 1 | External GPS RX (← M10 TX) | IN | UART2 |
| 2 | External GPS TX (→ M10 RX) | OUT | UART2 |
| 3 | MPU-6050 INT (deep sleep wake) | IN | EXT1 |
| 4 | Modem RX (← SIM7080G TX) | IN | UART1 |
| 5 | Modem TX (→ SIM7080G RX) | OUT | UART1 |
| 6 | PMU interrupt (VBUS wake) | IN | — |
| 7 | PMU SCL | BIDIR | I2C Wire1 |
| 15 | PMU SDA | BIDIR | I2C Wire1 |
| 17 | CAN TX | OUT | TWAI |
| 18 | CAN RX | IN | TWAI |
| 21 | Sensor SCL | BIDIR | I2C Wire0 |
| 40 | Modem STATUS | IN | — |
| 41 | Modem PWRKEY | OUT | — |
| 42 | Modem DTR | OUT | — |
| 44 | Modem FLIGHT / RF kill | OUT | — |
| 45 | Sensor SDA | BIDIR | I2C Wire0 |
I2C Wire0 devices: DS1307 (0x68), MPU-6050 (0x69), QMC5883L (0x0D — onboard BLITZ M10)
Two PlatformIO environments share the same source tree:
| Env | Hardware | Includes | Status |
|---|---|---|---|
s3_full (default) |
LILYGO T-SIM7080G-S3 | LTE-M modem, external GNSS, AXP2101 PMU, Li-Po, BLE, CAN, raw 5 MB telemetry buffer | ✅ production |
s3_lite |
Plain ESP32-S3 (N16R8) with 16 MB flash | BLE ELM327, CAN, raw 1 MB telemetry buffer, WiFi-only upload | 🧪 scaffolding only — not yet runnable |
Build with pio run -e s3_full or pio run -e s3_lite. The active env selects partition table and FEATURE_* compile flags. See platformio.ini.
The lite variant is positioned as a hobby-grade option for users who already have a plain ESP32-S3 (N16R8) board and want BLE ELM327 + CAN + WiFi upload without buying the LILYGO board with modem and PMU. It is not feature-complete yet — feature flags compile cleanly, but the WiFi upload path and #ifdef gating are still being written.
⚠️ The lite variant requires an external GNSS module. The plain ESP32-S3 board has no built-in GNSS, and the lite build does not include the SIM7080G internal GNSS path. Wire a BLITZ Mini M10 (or any u-blox / NMEA-output GNSS module) to UART2 (GPIO1 RX / GPIO2 TX, 115 200 baud). Without it, the firmware boots but no location data is captured.
⚠️ Power supply for the lite variant is unresolved.The lite variant is intended to be powered via USB only — i.e. always-on power from the OBD2 power pin or a 12 V → 5 V USB converter. Do not wire it to the car's permanent +12 V tap.
When the vehicle goes to sleep (CAN bus quiet), any node still writing on CAN will trigger the wake-up alarm and eventually flag a CAN diagnostic fault. The lite variant has no AXP2101 / battery / sleep flow yet — so it must be on switched power that turns off with the ignition (OBD2 pin 16 is permanent on most VWs, but the cigarette-lighter USB ports usually switch with ignition).
The
s3_fullvariant solves this via VBUS detection + gyro motion wake — that whole layer is missing in lite.
CAN bus TX is only enabled while the configured car WiFi SSID is visible. If the car's hotspot goes out of range, TX is locked immediately. This prevents accidental bus writes when the stick is powered outside the vehicle. An alternative guard mode uses VBUS detection (external 12 V power present = car is on).
When the gyro detects no movement (car parked):
- Final telemetry batch is sent
- GPS enters idle mode (M10 keeps tracking at ~5 mA, resumes instantly)
- ESP32-S3 enters deep sleep (µA range)
- Wakes on MPU-6050 motion interrupt (GPIO3) or VBUS insertion (GPIO6)
| Library | License | Author |
|---|---|---|
| arduino-esp32 | Apache 2.0 | Espressif |
| AsyncTCP | LGPL v3 | mathieucarbou |
| ESPAsyncWebServer | LGPL v3 | mathieucarbou |
| TinyGSM | LGPL v3 | vshymanskyy |
| ArduinoJson | MIT | Benoit Blanchon |
| NimBLE-Arduino | Apache 2.0 | h2zero |
| ArduinoHttpClient | Apache 2.0 | Arduino |
| XPowersLib | MIT | lewis he |
| Space Mono & Syne | OFL | Google Fonts |
Notes:
- ELM327 AT command set: proprietary protocol by ELM Electronics. This project implements an emulation layer for ABRP compatibility.
- OBD-II PIDs: SAE J1979 / ISO 15031 standard, freely usable.
- VW-specific UDS DIDs (service 0x22): community reverse-engineering, no official documentation.
This project follows the HITL principle (Human in the Loop): concept, decisions and direction come from the human — code is AI-assisted.
GNU Affero General Public License v3.0 © 2026 Ingo F — see LICENSE
Donations are voluntary and solely support the project. They have no influence on bug prioritization, feature requests or support.


