Files
vw-eu-data-act-prometheus-e…/README.md
T
2026-08-13 18:19:32 +02:00

147 lines
7.8 KiB
Markdown

# 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, 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](#configuration-configpy)). 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 `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 |
| `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)
```bash
# 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
```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.
- Service fails immediately with `status=226/NAMESPACE` /
`Failed to set up mount namespacing`: usually a stale unit file from
before `StateDirectory=` was introduced (e.g. still using
`ReadWritePaths=.../raw_json` pointing at a directory that was never
created). Re-copy the current `vw-eu-data-act-exporter.service`, run
`sudo systemctl daemon-reload`, and restart - `StateDirectory=` makes
systemd create that directory (with correct ownership) itself, no manual
`mkdir`/`chown` needed.