diff --git a/README.md b/README.md index f19c002..2750909 100644 --- a/README.md +++ b/README.md @@ -7,45 +7,40 @@ A PyQt5 GUI client visualises the data in real time. ## Repository layout ``` -collect.py — collector daemon (entry point) -gui_client.py — PyQt5 GUI client -client.py — minimal CLI test client 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 -utils/ - jay_diff.py — JSON diff / merge library + 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 credentials/ — JSON config files (gitignored) -requirements.txt ``` -## Requirements +## Server -``` -pip install carconnectivity carconnectivity-connector-volkswagen PyQt5 pyqtgraph -``` - -Or use the installer which also sets up a systemd user service: +### Install (systemd user service) ```bash +cd server +chmod +x install.sh ./install.sh +# then edit ~/.config/we_monitor/credentials.json +systemctl --user start we_monitor ``` ---- - -## Collector (`collect.py`) - -Connects via CarConnectivity, polls selected domains at a configurable interval, and -appends timestamped records to a rotating JSON log. Simultaneously runs a TCP -push server so clients receive every new snapshot the moment it is collected. - -### Quick start +### Manual start ```bash -python collect.py -c credentials/my_car.json +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 @@ -54,7 +49,7 @@ Username and password can also be supplied via environment variables ```bash export WC_USER=me@example.com export WC_PASS=secret -python collect.py -c credentials/my_car.json +python -m server.main -c credentials/my_car.json ``` ### Credentials file @@ -166,27 +161,33 @@ 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.py --diff` or the +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. --- -## GUI client (`gui_client.py`) +## Client ```bash -python gui_client.py +pip install -r client/requirements.txt +``` + +### GUI client (`client/gui_client.py`) + +```bash +python client/gui_client.py ``` 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 +#### Connector tab | Control | Description | |---------|-------------| -| **Host / Port** | Address of the `collect.py` push server | +| **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 | @@ -195,18 +196,18 @@ 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 +#### 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 +#### 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 bar (per plot)** | Control | Description | |---------|-------------| @@ -216,7 +217,7 @@ has its own control bar. | **Time window spinner** | Show the last 1–24 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 +**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 @@ -227,7 +228,7 @@ has its own control bar. sampled value for every active trace, with a colored bullet and unit. * **Legend** — trace names and colors, shown inside the plot area. -#### Mouse interaction +**Mouse interaction** | Action | Effect | |--------|--------| @@ -236,13 +237,11 @@ has its own control bar. | Click + drag | Pan the view | | Right-click | pyqtgraph context menu (export, auto-range, …) | ---- - -## CLI test client (`client.py`) +### CLI test client (`client/client.py`) ```bash -python client.py --host 192.168.1.10 --port 9999 -python client.py --diff # reconstruct from diff stream +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 @@ -258,11 +257,15 @@ pretty-printed JSON with a running record counter. ## Module reference -### `collect.py` +### `server/main.py` -Entry point for the collector daemon. Parses CLI / credentials-file arguments, -establishes the CarConnectivity session, starts the push server, and runs the main -poll loop. +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` @@ -315,22 +318,7 @@ 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. -### `utils/jay_diff.py` - -Lightweight JSON diff / patch library. - -`jay_diff_full(old, new, combine_upd_add=True) → dict` -Computes a diff between two dicts. With `combine_upd_add=True` (the default -used everywhere in this project) the result uses `update_add` for all additions -and updates, plus a separate `delete` key. Returns `{}` when nothing changed. - -`jay_merge_full(old, diff) → dict` -Applies a `jay_diff_full` diff to `old`. The diff structure is self-describing: -if `diff` contains none of the known diff keys (`update`, `update_add`, `add`, -`delete`) and `old` is empty, `diff` is returned as-is (full-snapshot -passthrough for bootstrap). - -### `gui_client.py` — class overview +### `client/gui_client.py` — class overview | Class | Role | |-------|------| @@ -341,4 +329,4 @@ passthrough for bootstrap). | `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` | \ No newline at end of file +| `MainWindow` | `QMainWindow`; wires the three tabs together and owns the `TcpReader` |