vw-eu-data-act-prometheus-exporter
A Prometheus exporter for vehicle data from the VW Group EU Data Act Portal (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, the JSON extracted from every downloaded
dataset - including "_no_content_found.zip" placeholders, if they carry a
JSON payload - can be persisted to disk via
config.persist_raw_json(see Configuration). ZIP files themselves are never written to disk - Fields are only updated when they are present in the current dataset - if a field is missing, the last known value is kept
- The
healthmetric 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 |
http_requests_total |
Counter | Total number of HTTP requests made to the portal (login + API calls) |
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)
# 1. Create directory + user (the "prometheus" system user is typically
# already present if Prometheus itself is installed on this host)
sudo mkdir -p /opt/vw-eu-data-act-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 prometheus:prometheus /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 JSON extracted from every downloaded dataset is written to disk (one file per VIN per scrape), including the JSON extracted from _no_content_found.zip placeholders when they carry one. ZIP files are never written to disk, only the extracted JSON - 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. Defaults to $STATE_DIRECTORY (set automatically by the systemd unit's StateDirectory=vw-eu-data-act-exporter, i.e. /var/lib/vw-eu-data-act-exporter), falling back to ./raw_json for ad-hoc/local runs outside systemd |
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
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
healthstays0permanently: checkjournalctl -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 asUNKNOWN, extend the list in the source code if needed.- Service fails immediately with
status=226/NAMESPACE/Failed to set up mount namespacing: usually a stale unit file from beforeStateDirectory=was introduced (e.g. still usingReadWritePaths=.../raw_jsonpointing at a directory that was never created). Re-copy the currentvw-eu-data-act-exporter.service, runsudo systemctl daemon-reload, and restart -StateDirectory=makes systemd create that directory (with correct ownership) itself, no manualmkdir/chownneeded.