Commit Graph
74 Commits
Author SHA1 Message Date
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
jensandClaude Sonnet 4.6 2adebf82ca gui_client: fix diff mode display corruption via deepcopy on emit
jay_merge_full/jay_merge_update mutates _state nested dicts in place.
The previously emitted dict(_state) was a shallow copy sharing those
nested dicts, so background-thread merges retroactively corrupted
already-emitted snapshots: timestamps were correct (strings are
immutable) but Y values all converged to the latest record's values.

Fix: emit copy.deepcopy(_state) so each snapshot is fully independent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:45:09 +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 d4cbe110d6 gui_client: debounce plot redraws to speed up initial record import
Replace direct _redraw() calls in PlotTab.update() with a 100 ms
single-shot QTimer. During a burst of incoming records (e.g. 500
history records on connect) only the data is accumulated; a single
redraw fires 100 ms after the last record arrives instead of once
per record (~4000 pyqtgraph redraws avoided).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:07:50 +02:00
jensandClaude Sonnet 4.6 bb1e087867 collect: add --dry flag to run push server without WeConnect
When --dry is set, WeConnect login and data collection are skipped.
The push server still starts and serves preloaded 24 h history to
connecting clients — useful for GUI testing without a live vehicle.
Credentials are not required in dry-run mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:59:58 +02:00
jensandClaude Sonnet 4.6 19399ca73f README: update for credentials-file config and recent GUI changes
- Credentials file is now the single source for vin, interval_s, host,
  port, log_dir; document new format with nested credentials block
- CLI reference trimmed to the remaining flags only
- Push server section: 24 h rolling window instead of same-day history
- GUI section: connection persistence, clear-on-reconnect, per-plot time
  window spinner, x-axis fit when data < window size

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:54:17 +02:00
jensandClaude Sonnet 4.6 bedf5ed646 collect: move vin, interval, host, port, log_dir to credentials file
Remove --vin, --interval/-i, --output/-o, --host, --port from argparse.
All are now read from the credentials JSON (interval_s, host, port,
log_dir with sensible defaults). CLI flags -u/-p/-d still override the
file; log_dir replaces the old --output/auto-logs logic — logs_dir is
always used, no fixed output path option.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:52:06 +02:00
jensandClaude Sonnet 4.6 89810ce95b gui_client: clear plot data on connect/reconnect
Add PlotTab.clear_data() which resets _series and _units, then redraws.
Called in MainWindow._connect() before opening the socket so stale data
from a previous session never mingles with fresh history from the server.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:36:26 +02:00
jensandClaude Sonnet 4.6 efc21763a3 gui_client: fit x-axis to actual data when less than window size
Track the earliest timestamp across plotted traces (starting from None,
not cutoff) so setXRange uses the first real data point as the left
bound — no empty gap when data doesn't fill the full time window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:31:40 +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 9052323baa gui_client: persist connection settings and fix plot history depth
- Save host/port to plot_settings.json on connect; restore on startup
- Raise MAX_PLOT_POINTS from 200 to 2880 so 24 h of data is retained
  even at 30 s polling intervals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 09:04:04 +02:00
jensandClaude Sonnet 4.6 7d9d928bb1 Move time window to per-plot spinbox in control bar
Replaces the single global toolbar spinbox with one QSpinBox per plot,
placed between the '+' button and the trace chips. Each plot has an
independent 1–24 h window; values are persisted in plot_settings.json
under 'time_windows'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 23:55:00 +02:00
jensandClaude Sonnet 4.6 02814782bf Add global time window control to Plot tab (1–24 h)
A spinbox above the plot grid sets the visible x range for all 8 plots.
Data outside the window is excluded from each redraw; x axis is fixed to
[now - window, now]. Setting is persisted in plot_settings.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 23:40:41 +02:00
jensandClaude Sonnet 4.6 dfdf6a550e install.sh: create venv and install dependencies
Creates .venv/ with python3 -m venv if not present, then installs/
updates requirements.txt into it before setting up the systemd service.
The service ExecStart always uses the venv Python.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 23:22:31 +02:00
jensandClaude Sonnet 4.6 cf6f092cbb Add install.sh: systemd user service setup
Creates ~/.config/we_monitor/credentials.json template (mode 600),
writes ~/.config/systemd/user/we_monitor.service using the project
venv Python, enables linger for boot-time start, and enables the
service. Does not start until credentials are edited.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:59:49 +02:00
jensandClaude Sonnet 4.6 a01ae714c8 Add README.md and conversation_recap.md
README covers installation, collector CLI reference, push server protocol,
GUI client usage, and output format.  conversation_recap documents the full
development history: features, bugs fixed, and design decisions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:42:00 +02:00
jensandClaude Sonnet 4.6 9d7561ba2f Apply nmea_client dark theme across all tabs and plots
Color palette taken directly from nmea_client/gui/plot_widget.py:
- Background #1a1a2e / #12121e, borders #2a2a4a/#3a3a5e,
  text #aaaacc/#ddddee, connected #4CAF50, error #F44336
- Trace colors: Material palette (blue, red, green, orange, …)
- Plot widget background overridden from "white" to #12121e
- pyqtgraph foreground set to #aaaacc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:34:53 +02:00
jensandClaude Sonnet 4.6 8797c774e9 Plot tab: abbreviated names in combo, 8 plots with scroll, remove toolbar
- Trace selection combos show short labels (_trace_label) with full key
  stored as item data; width auto-adjusts to content (min 120 px)
- Extended plot grid from 4 to 8 (4×2) wrapped in QScrollArea
- Removed reset-zoom toolbar and cursor readout area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:11:01 +02:00
jensandClaude Sonnet 4.6 8f1dc34991 Fix erratic axis scaling when hovering over plots
InfiniteLine was included in auto-range calculations, causing the view
to rescale on every cursor move. Add it with ignoreBounds=True in both
the initial setup and after each redraw.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:57:23 +02:00
jensandClaude Sonnet 4.6 9f0bdec9f1 Redesign plot tab: per-plot add/remove traces, persist settings, readable axes
- Replace static combo grid with a control bar above each plot:
  add-trace dropdown + "+" button; active traces shown as colored chips with
  "×" to remove (max 4 per plot)
- Save trace selection to ~/.config/we_monitor/plot_settings.json on every
  change; restore automatically once matching signals arrive from the server
- Remove tiny tick-font overrides so axis labels are readable at default size
- Show received record count in the status bar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:51:39 +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 d94df87443 Print running dataset count after each received record in client.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:25:34 +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 b38f8d052e Replace matplotlib with pyqtgraph in Plot tab
- pg.PlotWidget (2×2 grid) with DateAxisItem for proper time axis
- Built-in pan (left drag), zoom (right drag / scroll wheel), Reset zoom button
- pg.InfiniteLine cursor with pg.SignalProxy for smooth 30 fps tracking
- Cursor value label shows nearest-point values colour-coded per trace
- Legend auto-updated via pi.legend.clear() + named plot() calls
- matplotlib removed from requirements.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:08:58 +02:00
jensandClaude Sonnet 4.6 2bbfcc7084 Scale plot tab to fill available window space at fullscreen
Removed the QScrollArea wrapper and fixed figure-height calculation.
FigureCanvasQTAgg now fills the tab with an Expanding size policy so
all 8 subplots fit in the visible area at 1920x1200 without scrolling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:34:16 +02:00
jensandClaude Sonnet 4.6 1b0a8670e5 Fix spurious disconnect after first message in GUI client
create_connection(timeout=5) left a 5-second read timeout on the
socket. Any collection interval longer than 5 s caused recv() to
raise socket.timeout, which was caught as OSError and reported as
a lost connection. Switch to blocking mode with settimeout(None)
immediately after the connect handshake.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:25:33 +02:00
jensandClaude Sonnet 4.6 74a33cb74e Add PyQt5 and matplotlib to requirements; gitignore __pycache__
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:19:30 +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
jensandClaude Sonnet 4.6 f0e0c9c6ae Add PyQt5 GUI client with Connector, Dashboard, and Plot tabs
- Connector tab: host/port fields, connect/disconnect button, status
- Dashboard tab: live QTreeWidget of the full snapshot hierarchy;
  physical values show value and unit in separate columns;
  expand state preserved across refreshes
- Plot tab: matplotlib time-series subplots (2 columns) for every
  physical {value, unit} field, scrollable, auto-layout on new keys
- TcpReader runs in a daemon thread and delivers messages via
  Qt signals so the UI never blocks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:06:50 +02:00
jensandClaude Sonnet 4.6 3ab7795248 Push last snapshot immediately to newly connected clients
The server keeps the most recent snapshot in memory (under the same
lock as the client list). When a client connects it receives that
snapshot right away, then continues to receive live updates on each
collection cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:57:36 +02:00
jensandClaude Sonnet 4.6 53dcaf537e Extract store helpers into storage_helpers.py
auto_out_path, load_store, save_store moved out of data_model.py into
their own module. data_model.py now contains only value helpers and
domain extractors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:50:00 +02:00
jensandClaude Sonnet 4.6 097bc5ad51 Move domain extractors and collect_snapshot into data_model.py
we_connect.py now only handles WeConnect login and vehicle selection.
All extract_*() functions, ALL_DOMAINS, and collect_snapshot() live
in data_model.py alongside the store and value helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:47:25 +02:00
jensandClaude Sonnet 4.6 bc016cd7cb Refactor collect.py into focused modules
- data_model.py  : _str, _phys, load_store, save_store, auto_out_path
- we_connect.py  : domain extractors, ALL_DOMAINS, connect,
                   select_vehicle, collect_snapshot
- network.py     : TCP push server (start_push_server, broadcast)
- log_config.py  : logging setup
- collect.py     : run() loop and CLI, wired to the above modules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:41:54 +02:00
jensandClaude Sonnet 4.6 3dae080520 Add client.py: test client for the TCP push server
Connects to collect.py's push server and prints each incoming
snapshot as pretty-printed JSON, separated by a dashed line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:27:57 +02:00
jensandClaude Sonnet 4.6 81ebcd8e0f Add TCP push server: broadcast new snapshots to connected clients
Each connected TCP client receives every snapshot as a newline-
delimited JSON line the moment it is collected (no polling needed).
The server binds on --host/--port (default 0.0.0.0:9999).
Client connections are accepted in a daemon thread; dead connections
are pruned on the next broadcast.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:26:05 +02:00
jensandClaude Sonnet 4.6 eaec158f2f Add midnight UTC log rotation for auto-named output files
When -o is not set, the collector detects a UTC date change at the
top of each collection cycle and opens a fresh timestamped file under
logs/.  Explicit -o paths are never rotated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:11:44 +02:00
jensandClaude Sonnet 4.6 e56637f132 Fix default log file extension to .json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:08:45 +02:00
jensandClaude Sonnet 4.6 725b480cb4 Default output to logs/YYYY_MM_DD_HH_MM_SS_<VIN>.log
When -o is omitted, the output file is auto-named from the run's
start timestamp and the vehicle VIN and placed under ./logs/,
which is created on demand.  logs/ is gitignored.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:07:31 +02:00
jensandClaude Sonnet 4.6 dcd5910b3d Refactor physical values to {value, unit} objects in JSON output
All fields with a physical unit are now emitted as
{"value": <number>, "unit": "<unit>"} instead of encoding the unit
in the field name.  Field names are renamed accordingly (e.g.
chargePower_kW → chargePower).  Battery temperature field names are
also fixed to reflect the already-applied K→°C conversion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 19:01:44 +02:00
jensandClaude Sonnet 4.6 02e8d71569 Initial commit: WeConnect data collector
Adds collect.py with CLI flags for credentials (-u/-p), credential
JSON file (-c), VIN, domains (-d), interval, output file, and
--list-domains.  Credentials file supports domains as array or string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 18:40:01 +02:00