Commit Graph
89 Commits
Author SHA1 Message Date
jensandClaude Sonnet 4.6 73f6b85c05 gui_client: highlight changed rows on each new record
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 21:15:59 +02:00
jensandClaude Sonnet 4.6 74379e55d6 gui_client: add "Human Readable State" column populated from str_state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 21:03:54 +02:00
jensandClaude Sonnet 4.6 78b6108658 gui_client: remove diff mode switch, always use diff mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 21:01:16 +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 df944cdefb install_server: install all deps via requirements.txt, patch after
Reverts the split-install approach. volkswagencarnet stays in
server/requirements.txt; the patch is applied in a single step
after pip install completes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 09:47:12 +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 26904d9afc gui_client: fix dashboard dropping plain-value fields
DashboardTab.update() had no else-branch for non-dict, non-physical
values, so string fields like charging.state/type, connectivity.state,
vehicle.state and numeric fields like position.lat/lon were silently
skipped.  Mirrors the existing else-branch in _add_fields().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:27:52 +02:00
jensandClaude Sonnet 4.6 99b34f0b5e gitignore: add .venv/ and client/run.sh
run.sh is generated by install.sh with machine-specific absolute paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 19:00:23 +02:00
jensandClaude Sonnet 4.6 53004e5a2d Update README: we_monitor_gui system command after client install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:57:15 +02:00
jensandClaude Sonnet 4.6 49bc272461 client/install.sh: install we_monitor_gui command to ~/.local/bin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:55:14 +02:00
jensandClaude Sonnet 4.6 b5c7e66c94 Update README: add client/install.sh and run.sh to layout and Client section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:53:09 +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 5b1de48b75 Update README for server/client split
Reflect new layout (server/, client/), separate requirements.txt files,
python -m server.main entry point, and server/install.sh location.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:35:03 +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 cc2c52a827 Expand abbreviated trace labels for clarity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 16:30:58 +02:00
jens eea4bdc3ba Merge branch 'lib_carconnectivity'
Migrate we_monitor from weconnect to CarConnectivity/VW connector,
fix two jay_diff correctness bugs, and replace utils/jay_diff.py
with the shared jaydiff package from jayfield/jaypy.
2026-05-27 14:01:51 +02:00
jensandClaude Sonnet 4.6 205e6fd5f3 Remove README.jaydiff.md and now-empty utils/ and tests/ packages
jay_diff docs, tests, and data live in jayfield/jaypy; the __init__.py
files for the empty local packages are no longer needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 14:00:32 +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 12e4620c41 Add README.jaydiff.md — jay_diff library documentation
Covers API reference, diff/delete-delta encoding semantics, round-trip
guarantee, wire format usage, test overview, and bug history from the
recent aliasing crash and empty-dict pruning fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:29:51 +02:00
jensandClaude Sonnet 4.6 c8de234423 jay_diff: fix two correctness bugs + add real-data round-trip tests
Bug 1 (crash): jay_merge_delete iterated _new.items() while _old (which
aliases _new sub-dicts due to jay_diff_rev's side-effect writes) was being
mutated.  Fixed by snapshotting with list(_new.items()) before iteration.

Bug 2 (wrong round-trip): When new has an empty dict {} at a location where
old had a non-empty dict, jay_merge_delete incorrectly pruned the now-empty
parent key.  Root cause: jay_diff_del stored the full old sub-dict in the
delete delta for both "key absent from new" and "key present but empty in
new", making them indistinguishable at merge time.

Fix: jay_diff_del is now a standalone recursive function that stores None
for dict keys entirely absent from new (sentinel for "delete entire key")
vs a sub-dict of specific sub-keys for partial deletions.  jay_merge_delete
no longer prunes empty dicts; None (or any non-dict value) in the delta is
the explicit signal to remove the parent key.

Also adds:
- tests/data/  — copy of 25 real JSON log files for integration testing
- tests/test_jay_diff_real_data.py — 2028 parametrised round-trip tests
  over 675 real vehicle snapshots; exposed and confirmed both bugs above

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:25:35 +02:00
jensandClaude Sonnet 4.6 5ef7f13118 tests: add test suite for jay_diff; fix two bugs found
Move the inline assertions from jay_diff.py into tests/test_jay_diff.py
as proper pytest functions, grouped by function under test.  Add corner
cases for numeric/bool/None/list values, completely different structures,
empty containers, deep nesting, type changes (dict↔scalar), and
parametrised round-trip tests (diff→merge produces new).

Two bugs fixed during the exercise:
- jay_diff_rev: guard against _old being a non-dict (type change from
  dict to scalar caused TypeError on item assignment)
- jay_merge_delete / jay_merge_update: already fixed in previous commit;
  covered by new tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:54:57 +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 ef7b99e7e8 collect: re-login on AuthentificationError after weconnect exhausts its own retry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 20:29:08 +02:00
jensandClaude Sonnet 4.6 cd8367e5d0 Dashboard: fix display of flat phys fields (procedural domain)
The outer loop in DashboardTab.update assumed a 3-level hierarchy
(domain → object → fields).  Procedural fields sit one level higher
(domain → field directly).  Check _is_phys() first and render value /
unit into the correct columns instead of descending into the dict and
showing 'value' and 'unit' as separate rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:52:32 +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 e6c9f7d687 Plot tooltip: show cursor timestamp (HH:MM:SS) above trace values
Add a small dimmed timestamp label at the top of PlotTooltip.
set_values() now accepts ts_str as first argument; _on_mouse_move
formats the cursor x-position as HH:MM:SS and passes it through.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:05:13 +02:00
jensandClaude Sonnet 4.6 55719010da Docs: full README rewrite + gui_client.py module docstring update
README now covers:
- Updated diff-mode section (self-describing protocol, no _diff marker)
- Full GUI section with Connector/Dashboard/Plot tab descriptions
- Plot mouse interactions table (scroll, Shift+scroll, drag)
- Module reference for all server/, utils/, and gui_client classes
- --dry flag and all credential-file keys documented

gui_client.py module docstring updated to list tabs and mouse interactions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:01:42 +02:00
jensandClaude Sonnet 4.6 d0e4ec374c Plot tab: axis-selective wheel zoom (X default, Shift+wheel for Y)
Add PlotViewBox subclassing pg.ViewBox. Overrides wheelEvent to route
plain scroll to the X axis and Shift+scroll to the Y axis, both centred
on the cursor position. Passed as viewBox= to every PlotWidget so the
behaviour applies uniformly across all 8 plots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:56:02 +02:00
jensandClaude Sonnet 4.6 ad3cbf4859 Plot tab: auto-zoom Y axis on first draw after connect / trace change
Track a _needs_autozoom flag per plot. Set True at startup, on
reconnect (clear_data), and when a trace is added or removed. In
_redraw, when the flag is set and visible data exists, fit the Y range
to the data (with 10% margin) then clear the flag — subsequent live
updates leave the user's manual Y zoom undisturbed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:50:14 +02:00
jensandClaude Sonnet 4.6 849473acc0 Plot tab: floating tooltip showing trace values at crosshair position
Add PlotTooltip — a frameless child widget that appears next to the
mouse cursor whenever it moves over a plot. It lists each active trace
with a colored bullet, short name, and the nearest y-value (unit
appended). The tooltip is repositioned on every mouse-move event and
clamped to stay within the plot widget bounds. Hidden on mouse-exit and
on clear_data (reconnect).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:42:32 +02:00
jensandClaude Sonnet 4.6 5e2aa3f17d Plot tab: HH:MM:SS x-axis ticks, date label in control bar
Replace pg.DateAxisItem with a custom TimeAxisItem that formats ticks
as HH:MM:SS, eliminating locale-dependent day/date prefixes on the axis.

Add a per-plot date label (right-aligned in the control bar) that shows
the date range of the visible window — single date when the view stays
within one day, "YYYY-MM-DD – YYYY-MM-DD" when it spans midnight.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:36:57 +02:00
jensandClaude Sonnet 4.6 620d22d2e4 jay_merge_full: handle non-jaydiff input (full snapshot passthrough)
When _diff contains none of the jaydiff keys (update, update_add, add,
delete) and _old is empty, return _diff directly. This lets callers pass
a raw snapshot into jay_merge_full without needing a separate code path
for the initial-state bootstrap case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:16:33 +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 a4ce702ee4 install.sh: support update mode; auto-restart if service was active
- Detect fresh install vs update via systemctl is-enabled
- On update: pip install --upgrade picks up new dependency versions
- If service was active before running the script, restart it automatically
- Summary message adapts to install vs update context

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:50:06 +02:00
jensandClaude Sonnet 4.6 5944b0a7a4 collect: add diff to credentials file; --diff overrides it
Change --diff from store_true to default=None so CLI presence is
distinguishable from the JSON default. If not given on CLI, reads
creds_data.get("diff", False). Updated install.sh template and README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:09:54 +02:00
jensandClaude Sonnet 4.6 c33236020d install.sh: update credentials template to current format
Switch from flat username/password keys to nested credentials block
and add interval_s, host, port, log_dir with their defaults.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:58:11 +02:00
jensandClaude Sonnet 4.6 9951ef69e4 README: update CLI reference and add diff mode section
- CLI table now shows which credentials file key each flag overrides
- Added --diff, --dry, --log-dir, -i/--interval, --host, --port
- New "Diff mode" subsection under Push server protocol

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:56:53 +02:00
jensandClaude Sonnet 4.6 d53202822e collect: expose interval, host, port, log-dir as optional CLI overrides
-i/--interval, --host, --port, --log-dir can now be passed on the
command line; each overrides the corresponding credentials file value
when given. Defaults remain unchanged (300 s, 0.0.0.0, 9999, ./logs).

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