# vw-eu-data-act-prometheus-exporter A Prometheus exporter for vehicle data from the [VW Group EU Data Act Portal](https://eu-data-act.drivesomethinggreater.com/) (Volkswagen; not supported for Audi/Skoda/SEAT/CUPRA without adjustments). For each configured VIN, a dedicated background thread logs into the portal, downloads the latest available dataset, and derives Prometheus metrics from it - following the classic multi-target-exporter pattern via a `vin` label on a shared `/metrics` endpoint (no Docker, plain systemd deployment). ## Features - Multiple vehicles (VINs) under one VW account, each with its own scrape-interval timing in the background - **No persistent state by default**: no ZIPs, no JSON, no values are written to disk - everything lives in process memory and is empty again after a restart. Optionally, raw JSON datasets can be persisted to disk via `config.persist_raw_json` (see [Configuration](#configuration-configpy)) - Fields are only updated when they are present in the current dataset - if a field is missing, the last known value is kept - The `health` metric cleanly distinguishes between "portal ok, but no new data (yet)" (not a failure) and actual failures (login, HTTP, timeout, or parse errors) ## Metrics All metrics carry the `vin` label; all names carry the configurable prefix (default: `vw_eu_data_act_exporter_`). | Metric | Type | Description | |---|---|---| | `mileage_km` | Gauge | Odometer reading | | `hvsoc_percent` | Gauge | HV battery state of charge in % | | `driver_present` | Gauge (bool) | Driver detected in vehicle | | `cruising_range_km` | Gauge | Remaining range in km | | `hvbattery_temperature_max_celsius` | Gauge | Max. HV battery temperature | | `hvbattery_temperature_min_celsius` | Gauge | Min. HV battery temperature | | `charging_state` | Enum | `chargingStatus.currentChargeState` | | `charge_power_kw` | Gauge | Current charging power in kW | | `plug_connection_state` | Enum | Plug connection status | | `target_soc_percent` | Gauge | Charge target in % | | `position_longitude` / `position_latitude` | Gauge | Last known position (only set when **both** coordinates were present in the same dataset) | | `position_created_timestamp_seconds` | Gauge | Unix timestamp of the last known position | | `locked` | Gauge (bool) | Combined lock status of all doors, trunk and hood (only `unlocked` if at least one component actively reports `UNLOCKED`) | | `is_parked` | Gauge (bool) | Vehicle parked | | `parking_brake_engaged` | Gauge (bool) | Parking brake engaged | | `driving_mode` | Enum | Active driving mode | | `next_service_type` | Enum | Next due service type | | `service_due_in_days` | Gauge | Remaining days until the next service | | `last_vehicle_signal_timestamp_seconds` | Gauge | Timestamp of the last vehicle signal (`carCapturedUTCTimestamp`) | | `uptime_seconds` | Gauge | Uptime of the exporter process | | `last_successful_scrape_timestamp_seconds` | Gauge | Last scrape with **new** data | | `health` | Gauge (bool) | `0` initially and after a failure, `1` once new data has been received at least once | **Note on enum metrics:** The state space (e.g. possible values of `drivingMode` or `chargingStatus.currentChargeState`) is compiled best-effort from the sample dataset and comparable projects, and each one includes an `UNKNOWN` fallback. If an unknown raw value shows up, it is exported as `UNKNOWN` and additionally logged as `WARNING` - the state list in `vw-eu-data-act-exporter.py` (the `*_STATES` constants) can then be extended. ## Installation (systemd) ```bash # 1. Create directory + user sudo mkdir -p /opt/vw-eu-data-act-exporter sudo useradd --system --no-create-home --shell /usr/sbin/nologin vw-exporter # 2. Copy files sudo cp vw-eu-data-act-exporter.py config.py requirements.txt /opt/vw-eu-data-act-exporter/ # 3. Virtualenv + dependencies sudo python3 -m venv /opt/vw-eu-data-act-exporter/venv sudo /opt/vw-eu-data-act-exporter/venv/bin/pip install -r /opt/vw-eu-data-act-exporter/requirements.txt # 4. Adjust config.py (VW account, VINs, interval) sudo nano /opt/vw-eu-data-act-exporter/config.py # 5. Permissions (config.py contains the VW account password in plain text) sudo chown -R vw-exporter:vw-exporter /opt/vw-eu-data-act-exporter sudo chmod 600 /opt/vw-eu-data-act-exporter/config.py # 6. Install systemd unit sudo cp vw-eu-data-act-exporter.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now vw-eu-data-act-exporter sudo systemctl status vw-eu-data-act-exporter ``` ## Configuration (`config.py`) | Field | Meaning | |---|---| | `hostName` / `serverPort` | Bind address of the `/metrics` endpoint | | `exporter_prefix` | Prefix for all metric names (only `[a-zA-Z0-9_:]`, e.g. `vw_eu_data_act_exporter_`) | | `vw_account` | E-mail + password of the VW account under which **all** configured VINs are registered | | `vins` | List of vehicle VINs to scrape | | `scrape_interval_minutes` | Poll interval per vehicle. The portal only delivers new datasets for "continuous" data requests roughly every ~15 min anyway - a shorter interval will not yield fresher data, but will increase the login frequency against the account | | `persist_raw_json` | `False` by default. When set to `True`, the raw JSON of every downloaded dataset is written to disk (one file per VIN per scrape) - useful while tuning the field mapping in `vw-eu-data-act-exporter.py`. Not needed for normal operation | | `persist_raw_json_dir` | Directory the raw JSON files are written to when `persist_raw_json` is enabled (default: `./raw_json`, relative to the working directory) | Prerequisite in the portal (one-time, in a browser): connect vehicle -> "Get customised data" -> enable continuous, 15-minute frequency (see the docstring in `vw-eu-data-act-exporter.py`). ## Prometheus integration ```yaml scrape_configs: - job_name: vw-eu-data-act-exporter static_configs: - targets: ["127.0.0.1:9109"] ``` Since the exporter itself polls in the background (it does not hit the portal live on every Prometheus scrape), the Prometheus scrape interval can be independent of, and significantly shorter than, `scrape_interval_minutes` - it will simply repeatedly return the same cached value. ## Troubleshooting - `health` stays `0` permanently: check `journalctl -u vw-eu-data-act-exporter -f` - usually a login failure (wrong password) or no datasets available in the portal yet (see prerequisite above). - A single metric is missing entirely: the corresponding field has never been present in any dataset fetched so far (e.g. `position_longitude`/`_latitude`, if both coordinates were never delivered at the same time). - `WARNING ... Unknown enum value`: a new, not-yet-listed value for an enum metric - the state is exported as `UNKNOWN`, extend the list in the source code if needed.