Skip to content

TheInGoF/IDMate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

IDmate

IDmate logo

πŸ”Œ Companion firmware: IDTelemetry β€” the ESP32-S3 stick that collects the data and sends it here over MQTT.

Self-hosted telemetry & trip log for non-Tesla EVs. Collects data via IDTelemetry (ESP32/OBD2) over MQTT with AES-256-CBC encryption and via Home Assistant webhooks. Stores everything in InfluxDB + SQLite and ships with a built-in trip log (Triplog) β€” dashboard, logbook, journeys, charge tracker, geofencing, analysis and user management.

To my knowledge it is the only open-source, self-hosted telemetry + trip log built specifically for non-Tesla EVs: it reads the car directly over OBD2/CAN (via the IDTelemetry stick) instead of relying on a vendor API the VW ID family does not provide.

Note: This is a personal hobby project built for the data-obsessed self-hoster. It is not intended for commercial use and comes with no warranties or support guarantees.


Contents

Full setup walkthrough: How-To: Setup IDmate + IDTelemetry β€” MQTT & router port-forwarding, remote access via DuckDNS/Synology, vehicle onboarding and the Home Assistant connection, all in one guide.


Why does this exist?

Massive props to TeslaMate β€” I happily ran it for years and it spoiled me. Then an ID.7 joined the family, and reality hit:

  • VW telemetry is essentially non-existent β€” no API, no public data feed, the official app is a black box.
  • And how the hell do I track my charging costs automatically so I can hand a clean monthly report to my employer?
  • And honestly: while VW is still busy figuring out a halfway-acceptable UI in their cars πŸ™ƒ, I'd rather build my own.

So I built IDmate: same spirit as TeslaMate (visualise vehicle data, track trips, monitor battery health), but starting from CAN/OBD2 via an ESP32 stick instead of a vendor API β€” plus a Home Assistant webhook for wallbox energy/cost tracking.

IDmate is not a fork of TeslaMate and contains no code from it.

↑ Contents


Who is this for?

  • Non-Tesla EV owners (VW ID.3 / ID.4 / ID.5 / ID.7 / ID. Buzz and other CAN/OBD2-readable EVs) who want the kind of insight TeslaMate gives Tesla drivers.
  • Self-hosters who want their telemetry on their own InfluxDB + SQLite β€” their data, their broker, their dashboard, no cloud.
  • Company-car / business drivers who need an automatic, exportable trip log and per-charge cost breakdown for monthly expense reports.
  • Home Assistant users who already meter their wallbox and want charging energy and cost tracked per session and per vehicle.
  • ABRP users who want live SoC and consumption from a non-Tesla via the stick's BLE ELM327 emulation.

Not the right fit if you drive a Tesla (use TeslaMate), want a plug-and-play cloud service, or don't want to run Docker and flash an ESP32.

↑ Contents


Screenshots

Vehicle Dashboard

Vehicle dashboard

Live map with heading arrow, SoC, range, charge status and geofence pins.

Trip Logbook

Trip log

Automatic trip detection from InfluxDB. Per-trip purpose, destination, reason, notes β€” with batch edit and CSV export.

Journeys

Journeys

Group multiple trips into a journey with a combined map, per-leg trip list, the charges that happened along the way and one-click GPX export.

Analysis

Analysis overview

Charts for trips, purposes, battery health and efficiency (temperature/speed correlation), scoped by a unified Grafana-style date-range picker.

Charge Tracker

Charge log

Wallbox sessions (15-min readings via Home Assistant) and external/DC charges. Tibber vs. flat-rate comparison.

Settings / Admin

Settings overview

Role-aware admin: geofences (radius + custom icons, auto-suggested from frequently visited places), charge stations & operators, vehicles, users with per-user vehicle visibility, value rename/merge, GPX/TeslaMate import and the telemetry/database tools.

Profile

Profile

Per-user profile: avatar editor (crop/zoom), language, theme and map-style preference, password change and TOTP two-factor setup with recovery codes.

↑ Contents


Quick Start

git clone https://github.com/TheInGoF/IDMate-dev.git
cd IDMate-dev
cp .env.example .env
nano .env          # fill in your values (see table below)
docker-compose up -d

Then open:

Important: Set INFLUX_TOKEN before the first start. Changing it later requires resetting the InfluxDB volume.

For custom volume paths (e.g. Synology NAS) copy docker-compose.yml to docker-compose.local.yml and adjust the volumes:

docker-compose -f docker-compose.local.yml up -d

Going further? For MQTT & router port-forwarding, remote access (DuckDNS/Synology), adding a vehicle and the Home Assistant connection, follow the full setup guide.

↑ Contents


Architecture

Architecture diagram

Source: docs/diagrams/architecture.puml β€” re-render with plantuml -tsvg docs/diagrams/*.puml.

Triplog internals

Triplog internal architecture

Triplog itself is a small Flask app split into blueprints (auth, trips, journeys, charges, admin, profile) around a thin app.py core that owns the MQTT→InfluxDB bridge, the live-state poller, CSP/CSRF hardening and CSV export. Schema changes run through versioned migrations (PRAGMA user_version), trip detection and geocoding run as background threads. Source: docs/diagrams/triplog-internals.puml.

Deployment

Infrastructure diagram

Source: docs/diagrams/infrastructure.puml

Charge Tracker Flow

Charge flow diagram

Source: docs/diagrams/charge-flow.puml

The MID meter (calibrated energy meter at the wallbox) is integrated into Home Assistant via Modbus/TCP. Home Assistant calculates 15-minute intervals and posts the readings to the Triplog Charge Tracker via REST webhook.

↑ Contents


Setup β€” Portainer

  1. Stacks > Add Stack > Web editor
  2. Name: idmate
  3. Paste the contents of docker-compose.yml into the editor.
  4. Optional: For custom volume paths (e.g. Synology NAS), replace named volumes with bind mounts (e.g. influxdb-data:/var/lib/influxdb2 β†’ /volume1/docker/idmate/influxdb/data:/var/lib/influxdb2).
  5. Environment variables > Advanced mode β€” paste the block below and adjust the values:
INFLUX_PORT=3001
INFLUX_USER=admin
INFLUX_PASS=change-me
INFLUX_ORG=home
INFLUX_BUCKET=can-scan
INFLUX_TOKEN=change-me-to-a-long-random-token
INFLUX_DEVICE=id7
TRIPLOG_PORT=3004
SECRET_KEY=
LANGUAGE=DE
MQTT_PORT=3005
MQTT_DATA_TOPIC=tele/+/data
MQTT_DOMAIN=localhost
MQTT_TLS=0
# generate with: openssl rand -hex 32  (must match ESP firmware)
MQTT_AES_KEY=
CHARGE_WEBHOOK_TOKEN=

First start: Triplog has no users on first launch β€” open http://your-host:3004/setup and the wizard will create the admin account (username, password, optional 2FA). No default credentials are shipped.

⚠️ Generating tokens, keys and secrets

Use a cryptographically secure random number generator. Never ask a chatbot or LLM 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.

Use these instead:

openssl rand -hex 32                                  # AES-256 key, SECRET_KEY
openssl rand -base64 32                               # API/webhook tokens
python3 -c "import secrets; print(secrets.token_hex(32))"

This applies to INFLUX_TOKEN, SECRET_KEY, MQTT_AES_KEY and CHARGE_WEBHOOK_TOKEN.

  • For private image repos: add a ghcr.io registry under Registries with a GitHub PAT (read:packages).
  • Deploy the stack.

All images are built via GitHub Actions and pushed to ghcr.io β€” no local docker build needed, no bind mounts required, works with the Portainer web editor.

Environment Variables

Variable Description Default
INFLUX_PORT InfluxDB external port 3001
INFLUX_USER InfluxDB admin user β€”
INFLUX_PASS InfluxDB admin password β€”
INFLUX_ORG InfluxDB organisation home
INFLUX_BUCKET InfluxDB bucket can-scan
INFLUX_TOKEN InfluxDB API token β€”
INFLUX_DEVICE Device tag in InfluxDB id7
TRIPLOG_PORT Triplog external port 3004
SECRET_KEY Flask session secret (auto-generated + persisted if empty) β€”
LANGUAGE UI language (DE or EN) DE
ENABLE_DEBUG Expose /debug, /scan-debug, /influx-delete routes 1
CHARGE_WEBHOOK_TOKEN Charge tracker webhook token (optional) (empty = no auth)
MQTT_PORT Mosquitto external port 3005
MQTT_DATA_TOPIC MQTT topic pattern for telemetry tele/+/data
MQTT_AES_KEY Hex-encoded 32-byte AES-256 key (must match ESP) β€”
MQTT_TLS Enable TLS to broker (0 or 1) 0
MQTT_DOMAIN Hostname used in Mosquitto cert config localhost

↑ Contents


Features

Triplog (Trip Log)

Standalone Python service on port 3004 β€” not an official logbook, but a personal trip log:

  • πŸ” Login/Auth β€” session-based authentication, admin role, multi-user, optional TOTP 2FA with recovery codes
  • πŸ™‹ Per-user Profile β€” avatar editor, own language/theme/map-style, password & 2FA management
  • πŸš— Vehicle Dashboard β€” current status, SoC, range, map with heading arrow and geofence pins
  • πŸš™ Multi-vehicle β€” switch the active vehicle from the header; admins set per-user vehicle visibility
  • πŸ›£οΈ Automatic Trip Detection from InfluxDB (every 5 min), including delta decompression
  • πŸ“’ Trip Logbook β€” purposes, destinations, visit reasons, notes, batch assignment, per-user trip tags
  • πŸ—ΊοΈ Journeys β€” group multiple trips, combined map, charges along the route, GPX export
  • πŸ“Š Analysis β€” charts for trips, purposes, battery health, efficiency (temperature/speed correlation)
  • πŸ“… Date-range picker β€” unified Grafana-style range selector across trips, charges and analysis
  • πŸ“ Geofencing β€” saved locations with radius, automatic destination matching, custom icons/colors
  • βš™οΈ Admin β€” rename/merge/delete values, edit locations, manage users, map style, GPX/TeslaMate import
  • 🎨 Theming β€” dark/light themes via CSS design tokens; hardened CSP (nonce'd inline scripts)
  • 🌍 Reverse Geocoding via Nominatim (start/destination addresses)
  • πŸ“€ CSV Export β€” filterable by year/purpose
  • πŸ—£οΈ i18n β€” German and English UI, switchable per user and server-wide

Charge Tracker (details)

Tracks wallbox charges (15-min intervals via Home Assistant) and external charges (DC fast chargers, etc.):

  • Webhook Receiver β€” POST /api/charge/reading accepts quarter-hour readings
  • Session Detection β€” grouping by vehicle + odometer
  • Tibber vs. Flat Rate β€” comparison of dynamic electricity costs with annual flat rate
  • CSV Import β€” historical data from Google Sheets (German number format)
  • External Charges β€” manual entry of DC/third-party charges with separate session numbers
  • Vehicle Management β€” plate, name, VIN

↑ Contents


Home Assistant Automation

For automatic charge data collection every 15 minutes:

1. REST Command in configuration.yaml:

rest_command:
  idmate_charge_reading:
    url: "http://192.168.x.y:3004/api/charge/reading"
    method: POST
    headers:
      Content-Type: application/json
      Authorization: "Bearer {{ states('input_text.idmate_token') }}"
    payload: >-
      {
        "timestamp": "{{ now().strftime('%Y-%m-%d %H:%M') }}",
        "vehicle": "{{ states('input_select.wallbox_vehicle') }}",
        "kwh": {{ states('sensor.wallbox_energy_15m') | float(0) }},
        "meter_start": {{ states('sensor.wallbox_meter_start') | float(0) }},
        "meter_end": {{ states('sensor.wallbox_meter_end') | float(0) }},
        "tibber_price": {{ states('sensor.tibber_price') | float(0) }},
        "tibber_grundgebuehr": {{ states('sensor.tibber_grundgebuehr_15m') | float(0) }},
        "odometer": {{ states('sensor.vehicle_odometer') | float(0) }}
      }

2. Automation (every 15 minutes):

automation:
  - alias: "IDmate send charge data"
    trigger:
      - platform: time_pattern
        minutes: "/15"
    condition:
      - condition: numeric_state
        entity_id: sensor.wallbox_energy_15m
        above: 0
    action:
      - service: rest_command.idmate_charge_reading

Sensor entity IDs must be adapted to your HA installation. The CHARGE_WEBHOOK_TOKEN is optional β€” without a token, no auth is required (suitable for internal networks). For external access, set a token in .env: CHARGE_WEBHOOK_TOKEN=my-secret-token.

↑ Contents


Data Source

IDTelemetry (ESP32) publishes a binary telegram to MQTT topic tele/<device>/data roughly every 10 s. The payload is AES-256-CBC encrypted (PKCS7 padding) with a per-message random IV:

[1 byte version=0x01|0x02][16 byte IV][AES-256-CBC ciphertext]

The leading version byte selects the field layout (v2 widens the cumulative-energy kw field to u32); Triplog decodes whichever version a device sends.

Plaintext layout (after decryption):

  • field_mask (u32, little-endian) β€” bit n set β‡’ field n is present in this telegram
  • packed field values in fixed order (lat/lon as int * 1e6, speed/power/voltage as scaled u16/i16, booleans encoded purely via field_mask, …)

Triplog subscribes via paho-mqtt, decrypts each message with MQTT_AES_KEY, decodes the binary fields and writes them into InfluxDB as Line Protocol. Field names are kept short (e.g. s = speed, u = HV voltage, p = power, la/lo = GPS, bt = battery temp). Mapping to readable names happens in the Triplog UI.

The AES key is shared between the ESP firmware and Triplog (MQTT_AES_KEY, hex-encoded 32 bytes). Messages with wrong keys are silently dropped.

↑ Contents


Image Tags & Updates

Container images are published to ghcr.io/theingof/idmate with the following tag matrix:

Tag When to use Stability
1.2.3 Pin to an exact release Most stable
1.2 Auto-update patches within 1.2.x Stable
1 Auto-update minors within 1.x Recommended for most
latest Always newest main Active development β€” read release notes before pulling

Multi-arch: linux/amd64 and linux/arm64 (Synology DS920+, Raspberry Pi 4/5, Apple Silicon, …).

To update:

docker compose pull && docker compose up -d

↑ Contents


Backup & Restore

State lives in three named volumes: triplog-data (SQLite + uploads), influxdb-data (time-series), mosquitto-data (MQTT persistence). Back them up while the stack runs (SQLite is read-consistent during a single tar pass, InfluxDB writes are append-only):

mkdir -p backup && cd backup
for v in triplog-data influxdb-data influxdb-config mosquitto-data mosquitto-certs; do
  docker run --rm -v idmate_${v}:/src -v "$PWD":/dst alpine \
    tar czf /dst/${v}-$(date +%F).tar.gz -C /src .
done

Restore by stopping the stack, recreating empty volumes and untarring back into them:

docker compose down
for v in triplog-data influxdb-data influxdb-config mosquitto-data mosquitto-certs; do
  docker volume create idmate_${v}
  docker run --rm -v idmate_${v}:/dst -v "$PWD":/src alpine \
    tar xzf /src/${v}-YYYY-MM-DD.tar.gz -C /dst
done
docker compose up -d

The volume prefix (idmate_) is the Compose project name β€” adapt if your stack runs under a different name (docker volume ls to verify).

↑ Contents


Project Structure

IDMate/
β”œβ”€β”€ docker-compose.yml              # Stack (named volumes, ghcr.io images)
β”œβ”€β”€ .env.example                    # Credential template
β”œβ”€β”€ .env                            # Actual credentials (gitignored)
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ diagrams/                   # PlantUML sources + rendered SVGs
β”‚   └── screenshots/                # README screenshots
β”œβ”€β”€ mosquitto/                      # MQTT broker (TLS)
└── triplog/                        # Trip log service (Python/Flask)
    β”œβ”€β”€ Dockerfile
    β”œβ”€β”€ requirements.txt            # runtime deps
    β”œβ”€β”€ requirements-dev.txt        # test/lint deps
    β”œβ”€β”€ app.py                      # app core: factory, auth, MQTT bridge, state poller, CSV, CSP
    β”œβ”€β”€ blueprints/                 # route modules split out of app.py
    β”‚   β”œβ”€β”€ auth.py                 #   login, setup wizard, 2FA
    β”‚   β”œβ”€β”€ trips.py                #   trip logbook + tags
    β”‚   β”œβ”€β”€ journeys.py             #   journey grouping + GPX
    β”‚   β”œβ”€β”€ charges.py              #   charge tracker
    β”‚   β”œβ”€β”€ admin.py                #   geofencing, users, vehicles, settings
    β”‚   └── profile.py              #   avatar, theme, language, 2FA
    β”œβ”€β”€ detector.py                 # trip detection + delta decompression
    β”œβ”€β”€ geocoder.py                 # reverse geocoding (Nominatim)
    β”œβ”€β”€ import_job.py               # CSV/backfill import worker
    β”œβ”€β”€ teslamate_import.py         # optional TeslaMate (Postgres) import
    β”œβ”€β”€ migrations.py               # versioned SQLite migrations (PRAGMA user_version)
    β”œβ”€β”€ config.py                   # configuration via environment variables
    β”œβ”€β”€ plmn.py                     # mobile-carrier (PLMN) name/colour lookup
    β”œβ”€β”€ schema.sql                  # SQLite base schema
    β”œβ”€β”€ lang/                       # de.json / en.json translation catalogs
    β”œβ”€β”€ tests/                      # pytest suite (detector + app + migrations)
    β”œβ”€β”€ static/                     # logo, CSS design tokens, JS, PWA assets
    └── templates/                  # Flask Jinja templates

↑ Contents


FAQ

Is there a TeslaMate alternative for non-Tesla EVs? That is what IDmate is. Same idea β€” self-hosted dashboard, trip log, battery-health and charge tracking β€” but it reads the car over OBD2/CAN through the IDTelemetry ESP32 stick instead of a vendor API, because the VW ID family has none.

Can I track my EV charging costs automatically for an employer expense report? Yes. The charge tracker logs every session (wallbox via Home Assistant plus external/DC charges), breaks down energy and cost per session and per vehicle, supports a dynamic (Tibber) vs. flat-rate comparison, and the trip log exports to CSV.

Does it work with a VW ID.3 / ID.4 / ID.5 / ID.7 / ID. Buzz? Yes β€” anything the IDTelemetry stick can read over CAN/OBD2. The stick targets the MEB platform; other EVs work if their OBD2/UDS PIDs are readable.

Do I need the IDTelemetry hardware? For live driving telemetry (SoC, position, power, battery temperature) yes β€” that is the data source. Charge data alone can come from Home Assistant webhooks without the stick, and SoC/odometer can be fed the same way.

Is my data sent to any cloud? No. Everything runs on your own host: Mosquitto, Triplog, InfluxDB and SQLite. MQTT payloads are AES-256-CBC encrypted, there are no default credentials, and no telemetry leaves your network.

How do I get live vehicle data into ABRP (A Better Routeplanner)? The IDTelemetry stick emulates a BLE ELM327, so ABRP pairs with it like a standard OBD2 dongle.

Why two databases? InfluxDB for time-series telemetry (efficient range queries) and SQLite for relational data (trips, vehicles, users, charge sessions).

↑ Contents


Development

Triplog is a single Flask app, split into blueprints around a thin app.py core (see Triplog internals). Schema changes are versioned migrations keyed off PRAGMA user_version β€” never hand-edit a live DB. Trip detection and reverse geocoding run as background threads; the dashboard live state is fed by a separate InfluxDB poller.

Run the test suite (detector logic, app routes, migrations) from triplog/:

python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
SECRET_KEY=test python -m pytest tests/ -q

CI (GitHub Actions) runs the same suite plus a boot smoke-test as a gate before the container images are built and pushed to ghcr.io.

This project was developed in a human-in-the-loop workflow with AI assistance. Architecture, code and configuration were created in dialogue β€” all decisions were made by the human.

↑ Contents


License

GNU Affero General Public License v3.0 β€” see LICENSE

↑ Contents


Ko-fi

Donations are voluntary and solely support the project. They do not influence the prioritisation of bugs, feature requests or support enquiries.

About

Self-hosted telemetry & trip log for non-Tesla EVs (ESP32 / OBD2 via MQTT+AES-256, InfluxDB, Flask)

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors