jensandClaude Sonnet 4.6 6fb9e624e4 collect: fix stale vehicle ref after token re-login; increase update verbosity
After a full re-login (doLogin), the library rebuilds its vehicle list with
new objects. The old vehicle reference in the collect loop was never updated
again, causing data to silently freeze. Re-select the vehicle from wc.vehicles
after every update() call to stay on the current object.

Also: update() now returns and logs its bool result; the record log line shows
which top-level keys changed each cycle (or "none" when the backend returned
identical data).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 12:31:30 +02:00

we_monitor

Periodically polls a VW vehicle via the carconnectivity library, stores timestamped records as JSON, and pushes live data to connected TCP clients. A PyQt5 GUI client visualises the data in real time.

Repository layout

server/
  main.py               — collector entry point (CLI + credential loading)
  collect.py            — run() main loop
  data_model.py         — domain extractors, collect_snapshot()
  network.py            — TCP push server (accept loop + broadcast)
  storage_helpers.py    — load / save JSON store, midnight rotation helpers
  we_connect.py         — CarConnectivity login and vehicle selection
  log_config.py         — logging setup
  requirements.txt      — server dependencies
  install.sh            — systemd user service installer (server only)
client/
  gui_client.py         — PyQt5 GUI client
  client.py             — minimal CLI test client
  requirements.txt      — client dependencies
  install.sh            — client installer (creates venv + run.sh launcher)
credentials/            — JSON config files (gitignored)

Server

Install (systemd user service)

cd server
chmod +x install.sh
./install.sh
# then edit ~/.config/we_monitor/credentials.json
systemctl --user start we_monitor

Manual start

pip install -r server/requirements.txt
python -m server.main -c credentials/my_car.json

Username and password can also be supplied via environment variables (override the credentials file):

export WC_USER=me@example.com
export WC_PASS=secret
python -m server.main -c credentials/my_car.json

Credentials file

All connection and collection settings live in a single JSON file:

{
  "credentials": {
    "username": "me@example.com",
    "password": "secret"
  },
  "vin":        "WVWZZZE1ZMP000000",
  "domains":    ["charging", "electric_drive", "connectivity"],
  "interval_s": 300,
  "host":       "0.0.0.0",
  "port":       9999,
  "log_dir":    "./logs",
  "diff":       false
}
Key Default Description
credentials.username MyVolkswagen email
credentials.password MyVolkswagen password
vin first vehicle Target VIN
domains ["charging","electric_drive","connectivity"] Domains to collect (list or "all")
interval_s 300 Collection interval in seconds
host "0.0.0.0" Push server bind address
port 9999 Push server TCP port
log_dir "./logs" Directory for rotating JSON log files
diff false Enable diff-mode compression on the push server

CLI reference

CLI flags override the corresponding credentials-file value when given.

Flag Overrides Description
-c FILE Credentials / settings file
-u / -p credentials.username/password Username / password (also WC_USER / WC_PASS)
-d DOMAINS domains Domains to collect (comma-separated or all)
-i SECONDS interval_s Collection interval in seconds
--host HOST host Push server bind address
--port PORT port Push server TCP port
--log-dir DIR log_dir Directory for rotating log files
--max-records N Keep only the last N records per file (default: unlimited)
--diff diff Enable diff-mode on the push server
--dry Skip CarConnectivity login; run push server only (useful for GUI testing)
-v Verbose / debug logging
--list-domains Print available domains and exit

Available domains: charging, climatisation, electric_drive, connectivity, vehicle, position, doors

Log rotation

A new file is started automatically after midnight UTC; previous files are kept intact. On startup and on each new client connection the collector sends the last 24 hours of records, so clients always receive a full rolling window regardless of when midnight falls.

Output format

Log files are named YYYY_MM_DD_HH_MM_SS_<VIN>.json and contain:

{
  "meta": { "vin": "...", "created_at": "...", "interval_seconds": 300 },
  "records": [
    {
      "ts": "2025-05-25T10:00:00+00:00",
      "charging": {
        "state": "readyForCharging",
        "power": { "value": 11.0, "unit": "kW" }
      },
      "electric_drive": {
        "range":   { "value": 310, "unit": "km" },
        "battery": { "soc": { "value": 80, "unit": "%" } }
      }
    }
  ]
}

Physical values are always {"value": …, "unit": …} objects.


Push server protocol

The server listens on the host:port from the credentials file (default 0.0.0.0:9999).

  • On connect: sends all records from the last 24 hours, sorted by timestamp.
  • On each new poll: sends the new snapshot immediately.

All messages are newline-delimited JSON (one record per line, \n terminated).

Diff mode (--diff)

When diff mode is enabled the history burst and every live snapshot are compressed using jay_diff_full before transmission. The wire format is self-describing — diffs carry update_add / delete keys, full snapshots do not — so no separate framing is needed.

The first message sent to a new client is always jay_diff_full({}, record[0]), i.e. an update_add containing every field of the first record. Subsequent messages contain only the fields that changed.

Enable --diff on the server and on the client (client/client.py --diff or the Diff mode checkbox in the GUI Connector tab). Both sides must agree — a mismatch produces garbled output.


Client

Install

cd client
chmod +x install.sh
./install.sh

This creates client/.venv, writes a client/run.sh launcher, and installs ~/.local/bin/we_monitor_gui so the GUI is available system-wide.

Manual start

pip install -r client/requirements.txt
python client/gui_client.py

GUI client (client/gui_client.py)

we_monitor_gui       # after install.sh

Dark-theme PyQt5 application. Three tabs: Connector, Dashboard, Plot. Connection settings, trace selections, and time windows are persisted to ~/.config/we_monitor/plot_settings.json and restored on the next launch.

Connector tab

Control Description
Host / Port Address of the push server
Diff mode checkbox Must match the server's --diff setting
Connect / Disconnect Opens or closes the TCP connection
Status Shows Connected, Disconnected, or the error message

Clicking Connect saves the current host, port, and diff-mode setting. All plot data is cleared on each connect so the fresh server history is never mixed with stale local data.

Dashboard tab

Live tree view of the most recent snapshot. The hierarchy mirrors the JSON structure: domain → status object → field, with value and unit shown inline. Expand / collapse state is preserved across updates.

Plot tab

Eight plots arranged in a 4 × 2 scrollable grid. Each plot is independent and has its own control bar.

Control bar (per plot)

Control Description
Signal dropdown Lists every numeric {value, unit} field received so far
+ button Adds the selected signal as a new trace (max 4 per plot)
Colored chips (name ×) One chip per active trace; click × to remove
Time window spinner Show the last 124 hours of data (per plot)
Date label (right-aligned) Shows the calendar date(s) of the visible window — single date or YYYY-MM-DD YYYY-MM-DD when the view spans midnight

Plot area

  • X axis — time of day in HH:MM:SS format; no locale-dependent date prefix.
  • Y axis — auto-zooms to fit the visible data on the first draw after connect or after a trace is added / removed. Manual zoom is preserved between data updates.
  • Crosshair — dashed vertical line tracks the mouse cursor.
  • Floating tooltip — appears next to the cursor and shows the nearest sampled value for every active trace, with a colored bullet and unit.
  • Legend — trace names and colors, shown inside the plot area.

Mouse interaction

Action Effect
Scroll wheel Zoom X axis in / out, centred on cursor
Shift + scroll wheel Zoom Y axis in / out, centred on cursor
Click + drag Pan the view
Right-click pyqtgraph context menu (export, auto-range, …)

CLI test client (client/client.py)

python client/client.py --host 192.168.1.10 --port 9999
python client/client.py --diff   # reconstruct from diff stream

Connects to the push server and prints every incoming snapshot as pretty-printed JSON with a running record counter.

Flag Description
--host HOST Server host (default: 127.0.0.1)
--port PORT Server port (default: 9999)
--diff Reconstruct full snapshots from diff stream

Module reference

server/main.py

Entry point for the collector daemon. Parses CLI / credentials-file arguments and calls run().

server/collect.py

run(args) — establishes the CarConnectivity session, starts the push server, and runs the main poll loop.

server/data_model.py

collect_snapshot(vehicle, domains) → dict
Iterates over the requested domains on a CarConnectivity vehicle object and extracts fields into a flat dict keyed by domain. Physical values are normalised to {"value": …, "unit": …}. Returns a record with a top-level "ts" ISO-8601 timestamp.

ALL_DOMAINS
Tuple of all domain names the collector knows about.

server/network.py

start_push_server(host, port) → (clients, lock, store_ref)
Binds a TCP socket, starts the accept-loop thread (daemon), and returns the shared state needed by broadcast.

broadcast(snapshot, clients, lock)
Serialises snapshot to a JSON line and sends it to every connected client. Dead connections are removed silently.

store_ref dict keys:

Key Type Description
"current" dict The currently-open JSON store
"preloaded" list 24-h history records loaded at startup
"diff_mode" bool Whether to compress the history burst as diffs

server/storage_helpers.py

load_store(path, vin, interval) → dict
Loads an existing store file or creates a fresh one.

save_store(path, store, max_records)
Writes the store to disk, optionally trimming to the last max_records entries.

auto_out_path(log_dir, vin) → Path
Returns a timestamped file path for a new store.

load_last_24h_records(log_dir, vin) → list
Scans the log directory for files belonging to vin, loads any records from the last 24 hours, and returns them sorted by timestamp.

server/we_connect.py

connect(username, password) → CarConnectivity
Authenticates and returns a connected CarConnectivity instance.

select_vehicle(wc, vin) → (vehicle, error)
Returns the target vehicle (first available if vin is empty) or an error string.

client/gui_client.py — class overview

Class Role
TcpReader Background thread; reads newline-delimited JSON from the socket, reconstructs full snapshots in diff mode via jay_merge_full, and emits each snapshot via the message Qt signal
ConnectorTab Host / port / diff-mode form; emits connect_requested and disconnect_requested signals; persists settings
DashboardTab Live QTreeWidget showing the latest snapshot; preserves expand state across updates
PlotTab Eight independent time-series plots in a scrollable grid
TimeAxisItem pg.AxisItem subclass; formats x-axis tick labels as HH:MM:SS
PlotViewBox pg.ViewBox subclass; routes plain scroll to X zoom and Shift+scroll to Y zoom
PlotTooltip Frameless child QWidget per plot; shows nearest sampled values for all active traces at the cursor position
MainWindow QMainWindow; wires the three tabs together and owns the TcpReader
S
Description
Periodically polls a VW WeConnect vehicle via the weconnect library, stores timestamped records as JSON, and pushes live data to connected TCP clients. A PyQt5 GUI client visualises the data in real time.
Readme
742 KiB
Languages
Python 89.2%
Shell 10.8%