Commit Graph
30 Commits
Author SHA1 Message Date
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
jensandClaude Sonnet 4.6 b75c4fe411 server: guard against None snapshots crashing the accept loop
If extract_all() returns None (Dashboard init failure), appending it to
store["records"] would cause _accept_loop to crash with AttributeError on
the next client connection.  Since only OSError was caught, the thread
died permanently — no further clients could join push_clients, so live
broadcasts reached nobody.

- collect.py: skip None snapshots instead of appending them
- network.py: filter None records in _accept_loop; catch all exceptions
  during history send (not just OSError) so the thread survives
- storage_helpers.py: filter None records when loading from disk

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 00:42:31 +02:00
jensandClaude Sonnet 4.6 dfd5ef7f56 server: remove diff switch, always send diffs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 22:09:51 +02:00
jensandClaude Sonnet 4.6 31d9ce2311 data_model: fix apply_procedural paths for extract_all snapshot format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 21:26:47 +02:00
jensandClaude Sonnet 4.6 1c02869bd3 gui_client: keep collapsed nodes collapsed on record updates
collect: capture apply_procedural return value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 21:23:28 +02:00
jensandClaude Sonnet 4.6 6e340a9582 data_model: add extract_all() using Dashboard instruments as primary snapshot
Replaces the domain-based collect_snapshot with extract_all(), which iterates
all supported instruments via vw_dashboard.Dashboard. Each entry is stored as
{value, unit, str_state} grouped by component type. Units are validated against
a whitelist and normalised (d→days, °C→degC); unrecognised units are dropped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 20:55:07 +02:00
jensandClaude Sonnet 4.6 f9dd96269e collect: revert last_broadcast to snapshot, not payload
Diffs should always be computed against the last full snapshot,
not the last broadcast payload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 11:57:15 +02:00
jensandClaude Sonnet 4.6 561c610b92 collect: fix last_broadcast tracking and diff condition
Always compute the payload (diff or full snapshot) regardless of
last_broadcast state, and store the broadcast payload — not the raw
snapshot — as last_broadcast so subsequent diffs are based on what
was actually sent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 11:46:54 +02:00
jensandClaude Sonnet 4.6 4e2a22d35e installers: move to project root, share a single .venv
client/install.sh → install_client.sh
server/install.sh → install_server.sh

Both scripts now resolve REPO_DIR as their own directory (repo root)
and use a shared .venv at the repo root instead of per-subdirectory
virtual environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 09:30:07 +02:00
jensandClaude Sonnet 4.6 8ff9463e76 server: install volkswagencarnet from GitHub + apply auth patch
requirements.txt now references the upstream GitHub repo pinned to
commit c30dc37 (the last upstream commit before our fix), so the source
is portable and not machine-specific.

install.sh applies cariad-hybrid-auth-fix.patch to the installed
site-packages after pip runs, using `patch -N` so it is idempotent on
repeat installs.  The patch file is checked in to the repo so install.sh
can always find it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:03:44 +02:00
jensandClaude Sonnet 4.6 bc424310b5 server: replace carconnectivity with volkswagencarnet
Switches the VW API backend from carconnectivity + connector-volkswagen
to volkswagencarnet (local fork with the cariad-hybrid-auth-fix patch
applied), fixing authentication against the current CARIAD BFF endpoints.

- requirements.txt: point at local volkswagencarnet git repo via
  git+file:// URL (editable path no longer needed now the patch is
  committed there)
- we_connect.py: rewrite around the async volkswagencarnet Connection
  class; a persistent asyncio event loop held in _Session lets the rest
  of the code stay synchronous
- data_model.py: remap all extractors to volkswagencarnet Vehicle
  properties and Paths constants; output shape preserved so
  apply_procedural and the GUI client continue to work unchanged
- collect.py: swap carconnectivity error import, drop
  TemporaryAuthenticationError (no equivalent), fix vehicle.vin.value →
  vehicle.vin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:50:57 +02:00
jensandClaude Sonnet 4.6 63e807bdb0 data_model: split windows out of doors into own domain
- Add extract_windows() and register 'windows' in ALL_DOMAINS
- Remove windows extraction from extract_doors; no more double keys
- extract_windows also captures the overall windows.open_state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 21:53:26 +02:00
jensandClaude Sonnet 4.6 4dcf2ee6aa data_model: fix extract_doors — filter None values, fix structure
- Don't store attributes whose .value is None; avoids spurious "None"
  strings in the snapshot (lock_state, open_state were unset)
- Flatten the structure: overall lock_state/open_state now live directly
  under 'doors' instead of the double-nested 'doors.doors.overallState'
- Give vehicle.windows its own try/except, independent of vehicle.doors
- Capture doors.open_state (overall open state) which was missing before

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 21:40:47 +02:00
jensandClaude Sonnet 4.6 0b68bcdef8 server/main.py: use absolute imports for collect and data_model
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:28:40 +02:00
jensandClaude Sonnet 4.6 ff6a43746a Add client/install.sh; fix server/install.sh paths after move
server/install.sh was broken after moving into server/: SCRIPT_DIR now
resolves to the server/ subdirectory, so requirements path, log dir, and
systemd WorkingDirectory all pointed at wrong locations. Introduce REPO_DIR
(parent of SCRIPT_DIR) and use it throughout.

client/install.sh creates a venv at client/.venv, installs
client/requirements.txt, and writes a client/run.sh launcher.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:48:34 +02:00
jensandClaude Sonnet 4.6 ca459c5ce9 Move install.sh to server/; clarify it is server-only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:33:41 +02:00
jensandClaude Sonnet 4.6 5230b304a2 Move collect.py into server/; split main() into server/main.py
run() stays in server/collect.py with relative imports.
main() (CLI arg parsing, credential loading) moves to server/main.py.
install.sh updated to use `python -m server.main` (required for relative
imports to resolve correctly when invoked as a module).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:30:39 +02:00
jensandClaude Sonnet 4.6 423e6d3027 Split into server/ and client/ with separate requirements.txt
Move client.py and gui_client.py into client/, split root requirements.txt
into server/requirements.txt and client/requirements.txt, update install.sh
to reference server/requirements.txt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:26:19 +02:00
jensandClaude Sonnet 4.6 2f6556116e Replace utils/jay_diff.py with jaydiff package from jayfield/jaypy
Remove the local copy of jay_diff and its tests/data; switch all four
import sites to `jaydiff.diff.diff_full` / `jaydiff.merge.merge_full`
from the shared Gitea package (http://192.168.22.90:3001/jayfield/jaypy).
Add the package as a VCS dependency in requirements.txt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 13:56:45 +02:00
jensandClaude Sonnet 4.6 4492692786 Update clients, docs, and install for CarConnectivity migration
- gui_client: update _TRACE_LABELS paths to new flat snapshot structure
- README: update library name, pip command, domain list, credentials
  template, output example, and module reference
- install.sh: update credentials template and systemd service description
- client.py: update docstring
- data_model: Kelvin→Celsius conversion for battery temps (CarConnectivity
  returns Kelvin); round range_estimated_full to 1 decimal
- jay_diff: fix merge when old value is not a dict; skip empty-dict
  branches in delete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:43:41 +02:00
jensandClaude Sonnet 4.6 bf33737654 data_model: redesign domain extractors for CarConnectivity object model
Flatten the snapshot structure so each domain maps directly to its
fields rather than mirroring the old WeConnect status-object nesting.
New domains: electric_drive (range, battery/SOC/temps, odometer),
connectivity, vehicle, position, doors — replacing the old readiness,
parking, access, and measurements domains.  Update apply_procedural
paths and collect.py defaults accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 09:01:15 +02:00
jensandClaude Sonnet 4.6 f050de7001 collect: migrate from weconnect to CarConnectivity + VW connector
Replace the weconnect library with carconnectivity and
carconnectivity-connector-volkswagen.  The connector is configured via
a dict passed to CarConnectivity(); startup()/fetch_all() replace
login()/update().  All domain extractors in data_model.py are rewritten
for the new object model (vehicle.charging.*, vehicle.get_electric_drive(),
vehicle.climatization.*, etc.).  Remaining time fields are now derived
from estimated_date_reached DateAttributes.  Battery temperatures are
read directly in °C (no Kelvin conversion).  Auth error handling drops
the manual re-login since the connector manages token refresh internally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 08:02:34 +02:00
jensandClaude Sonnet 4.6 cdc01aaa76 data_model: implement range_at_100 procedural field
Compute estimated full-charge range from totalRange and current SOC.
Guard against None values (_get returns None for missing keys) and
SOC = 0 to avoid TypeError / ZeroDivisionError on incomplete records.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:49:12 +02:00
jensandClaude Sonnet 4.6 2756c6a49b Add procedural field post-processing to snapshots
server/data_model.py:
- apply_procedural(record) — stub function; add computed fields to the
  proc dict inside and they appear under record['procedural'] in every
  record stored and broadcast.
- _get(data, *keys) — safe nested dict accessor for use inside
  apply_procedural.

collect.py:
- New snapshots: apply_procedural() called after collect_snapshot(),
  before appending to the store and broadcasting.
- Preloaded 24-h history: guarded apply (skip if 'procedural' already
  present) applied after load_last_24h_records().
- Current-day store loaded from disk: same guarded apply after
  load_store(), so legacy records sent to clients are enriched on the fly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:35:10 +02:00
jensandClaude Sonnet 4.6 ca5b284e9a Remove _diff marker: use self-describing jay_diff protocol
Server (network.py): send every record in diff mode starting from
prev={}, including the first — fixes history drop on new client connect.

Collect (collect.py): remove payload["_diff"] = True from live broadcast.

GUI client (gui_client.py): always call jay_merge_full in diff mode;
drop the _diff key check that gated reconstruction.

CLI client (client.py): fix `args.diff or True` guard that made --diff
irrelevant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:08:33 +02:00
jensandClaude Sonnet 4.6 56ed4ac265 Add opt-in diff mode for network transmission (--diff / Diff mode checkbox)
Server (collect.py --diff):
- History burst: first record sent full, subsequent as jay_diff_full diffs
- Live broadcast: each snapshot diffed against the previous one
- Diff payloads tagged with "_diff": true

Client (client.py --diff):
- Reconstructs full state via jay_merge_full before printing

GUI (Connector tab "Diff mode" checkbox):
- TcpReader.connect_to() accepts diff_mode; maintains _state dict
- Merges incoming diffs before emitting to Dashboard/Plot
- Checkbox state persisted to plot_settings.json

Both sides default to off; must be enabled consistently on server and client.
utils/__init__.py added so utils.jay_diff is importable as a package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:34:34 +02:00
jensandClaude Sonnet 4.6 b57e2a9cc2 collect: send last 24 h of history on client connect (not same-day only)
Replace load_today_records with load_last_24h_records which computes a
rolling 24 h cutoff, globs log files for both the current and previous
calendar day, and filters records by timestamp — so clients always
receive a full 24 h window regardless of when midnight falls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:20:41 +02:00
jensandClaude Sonnet 4.6 04578781af Eagerly pre-load today's disk records into push_store_ref at startup
Previously load_today() was only called if in-memory records were empty,
which was never true once the first WeConnect poll had completed.
Now disk records are loaded unconditionally at startup and merged with
live in-memory records (dedup by ts) when a client connects.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:39:22 +02:00
jensandClaude Sonnet 4.6 9d2d742368 Fall back to disk when in-memory store is empty on client connect
storage_helpers: load_today_records() scans logs/ for all files
matching today's UTC date and VIN, merges and sorts their records.

network: _accept_loop receives an optional load_today callable;
if store_ref["current"] has no records it calls load_today() to
replay the day's history from disk before adding the client.

collect: passes a load_today lambda (None when -o is used).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:21:17 +02:00
jensandClaude Sonnet 4.6 7774f4842e Move server modules into server/ package
data_model, log_config, network, storage_helpers, we_connect are now
under server/. collect.py stays in the project root and imports via
the server package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:16:46 +02:00