No description
  • Python 57.3%
  • TypeScript 35.4%
  • JavaScript 3%
  • HTML 1.9%
  • CSS 1.6%
  • Other 0.8%
Find a file
Luca e160e06cec
Some checks failed
ci / version (push) Successful in 3s
ci / icd (push) Successful in 15s
ci / server (push) Successful in 40s
ci / bridge (push) Successful in 22s
ci / e2e (push) Successful in 47s
ci / app (push) Failing after 16s
ci / image (push) Successful in 40s
feat: add live demo-data simulator for UI testing
Producer under bridges/simulator pushes synthetic EV, MCU, plant, radar and lightning channels, plus optional Open-Meteo weather and ISS position, through the ICD-002 values API. Adds make demo-data/test-simulator and runs the simulator tests in CI.
2026-10-11 21:15:58 +02:00
.forgejo/workflows feat: add live demo-data simulator for UI testing 2026-10-11 21:15:58 +02:00
app Automate release versioning and guard latest tag 2026-10-09 14:52:08 +02:00
assets/brand Add Cathode Ray brand iconography 2026-10-09 14:31:45 +02:00
bridges feat: add live demo-data simulator for UI testing 2026-10-11 21:15:58 +02:00
deploy/infra chore: bump release metadata to 0.1.3 2026-10-06 19:59:34 +02:00
icd feat: migrate Cathode Ray to v2 menus and scoped APIs 2026-10-06 19:51:20 +02:00
server Automate release versioning and guard latest tag 2026-10-09 14:52:08 +02:00
.dockerignore feat: migrate Cathode Ray to v2 menus and scoped APIs 2026-10-06 19:51:20 +02:00
.editorconfig Cathode Ray: v1 system, ICDs, Forgejo CI, central docs 2026-10-05 21:24:26 +02:00
.gitattributes Cathode Ray: v1 system, ICDs, Forgejo CI, central docs 2026-10-05 21:24:26 +02:00
.gitignore feat: migrate Cathode Ray to v2 menus and scoped APIs 2026-10-06 19:51:20 +02:00
AGENTS.md feat: migrate Cathode Ray to v2 menus and scoped APIs 2026-10-06 19:51:20 +02:00
ARCHITECTURE.md feat: migrate Cathode Ray to v2 menus and scoped APIs 2026-10-06 19:51:20 +02:00
CLAUDE.md Cathode Ray: v1 system, ICDs, Forgejo CI, central docs 2026-10-05 21:24:26 +02:00
Makefile feat: add live demo-data simulator for UI testing 2026-10-11 21:15:58 +02:00
README.md feat: add live demo-data simulator for UI testing 2026-10-11 21:15:58 +02:00

Cathode Ray

Cathode Ray

Live mini dashboards for Even Realities G2 glasses. A small "Grafana for your face": print progress and temperatures from OctoPrint, server health from InfluxDB, all updating in real time.

 InfluxDB  ─┐                                    ┌─────────────── phone ───────────────┐
 OctoPrint ─┼─▶  Cathode Ray server  ══ wss ══▶  │ Even App ▶ WebView: Cathode Ray app │ ──BLE──▶ G2
 (more…)   ─┘    crt.lucavs.nl                   └─────────────────────────────────────┘
                 polls, formats, thresholds          layout, input, reconnect
Folder What Owner of
icd/ Interface contracts (ICD-001 v1/v2, ICD-002, ICD-003): docs, JSON Schemas, golden examples. The single source of truth. The contracts
server/ Python/FastAPI container. Talks to data sources, serves the glasses over WebSocket. What to show
app/ Even Hub app (Vite + TypeScript). Renders on the glasses. How it fits on 576×288
bridges/dmm121gw/ EEVblog 121GW Bluetooth reader. Pushes readings to the server. Talking to the meter
deploy/infra/ Template for the deployment folder in the infra repo. Running it on Indium

Rule of the house: a change that crosses a boundary starts in icd/ (document, schema, examples), then code. Server and app tests read the same golden examples from icd/. Agents: start with AGENTS.md.

Repositories and CI

Cathode Ray and the homelab infra are two separate repos, and neither contains the other:

Repo Contains Changes when
Cathode-Ray (this one, git.lucavs.nl/lucavanstraaten/Cathode-Ray) code, contracts, tests, CI, deployment templates you build features
infra cathode-ray/: compose, production dashboards.yaml, .env (gitignored), nginx vhost. forgejo-runner/. you deploy or reconfigure

They're connected by a versioned image, not by shared files.

CI runs on Forgejo Actions (.forgejo/workflows/ci.yml). Every push runs:

  • ICD checks
  • server, bridge and app tests
  • an end-to-end test (the app's real client against the real server)
  • a build and smoke test of the server image

Tagging v0.2.0 also pushes git.lucavs.nl/lucavanstraaten/cathode-ray-server:0.2.0 to Forgejo's own registry. Deploying means setting that version in the infra repo and running docker compose pull && docker compose up -d.

Templates:

Where each setting is edited (web page, app, glasses, git): ARCHITECTURE.md.

Multimeter on your glasses

Run the bridge on whichever laptop is next to the meter. It pushes to the server, and the glasses show the 121GW dashboard (big 7-segment digits):

cd bridges/dmm121gw && pip install -r requirements.txt
python -m dmm121gw --server https://crt.lucavs.nl --token <token>          # real meter over Bluetooth
python -m dmm121gw --server https://crt.lucavs.nl --token <token> --fake   # simulated meter

Fully local: run make dev-init, then make dev-meter PUSH_KEY=<printed producer key> (METER=real for Bluetooth). Point the app at ws://<laptop>:8080/ws/v2 and enter the printed viewer key on the phone. Details: bridges/dmm121gw/README.md.

Live UI Test Data

The producer simulator in bridges/simulator/simulator.py pushes independent channel groups through the existing values API. No server config is required. All groups use the authorized testdata prefix: EV inverter/motor/BMS, MCU debug, industrial pump/PLC, living-room mmWave occupancy, and simulated lightning. They are synthetic UI fixtures, not hardware measurements or safety information. The normal run cycles through threshold states, regen, empty rooms and no-data readings.

python3 -m venv bridges/simulator/.venv
bridges/simulator/.venv/bin/pip install -r bridges/simulator/requirements-dev.txt
# For a fresh checkout, populate bridges/simulator/.env using .env.example.
make demo-data
make demo-data DEMO_ARGS="--public"                 # add weather and ISS
make demo-data DEMO_ARGS="--sources ev,mcu --faults" # MCU drops out: stale then recovery
make demo-data DEMO_ARGS="--extra 180 --interval 0.2 --duration 60" # bounded load test
make demo-data DEMO_ARGS="--dry-run"                # inspect payloads without network

The script loads its adjacent, gitignored .env; exported environment variables take precedence. The tracked bridges/simulator/.env.example contains no key. Use --server http://127.0.0.1:8080 and an exported local producer key/prefix for a local server. The default location for weather is Utrecht; set DEMO_LATITUDE/DEMO_LONGITUDE or use CLI options.

--public polls Open-Meteo every five minutes (model-derived current weather, not a local sensor) and Where the ISS at? every 15 seconds (orbital tracking). They use a separate, unauthenticated HTTP client. Outages never produce fake public readings: their channels become stale if refreshes stop. Real lightning needs a suitable licensed feed; the demo does not scrape a lightning network.

On the glasses, open Live, or use the phone channel picker to assemble quick views from testdata.ev.*, testdata.mcu.*, etc. Labels distinguish groups. Above 100 channels the server pages Live into subviews. Each POST contains at most 50 channels, with at most ten POSTs per second; larger frames automatically take longer. Extra load is capped at 250 channels. Existing server channels also count toward the global 1,000-channel limit. Ctrl-C stops the producer without deleting data; channels become stale, then expire under the server's idle-expiry policy. --faults also intentionally raises MCU staleness alerts (best used with the default one-second interval). Run focused tests with make test-simulator PY=bridges/simulator/.venv/bin/python.

Quick demo (no infrastructure needed)

pip install -r server/requirements-dev.txt && (cd app && npm install)
make dev        # server with a simulated printer on :8080 + the app's dev server on :5173

Run make dev-init before the first make dev; it creates scoped development keys in the gitignored .local-data/ directory and prints their secrets once. Open http://localhost:5173/?server=ws://localhost:8080/ws/v2 and enter the viewer key in the form. Back opens the menu; select opens a view. Keys never go in a URL.

Production serves public docs at /docs/, password-session administration at /admin/, and scoped producer endpoints under /api/v2. The v1 endpoints are removed: deploy the new server and app together. Action execution remains deferred to phase 4.

On the real glasses

See app/README.md: sideload with a QR code while developing, or install the packed .ehpk.

Deploy on Indium

See deploy/infra/cathode-ray/README.md.

Tests

make test        # everything below except e2e and the image build, which CI also runs
Suite Count Covers
ICD 4 contracts every schema is valid; golden examples pass, invalid examples fail for the intended rule
server pytest + focused unittest v2 protocol, producer API, admin sessions, config, formatting, sources, persistence and prefix isolation
bridge 30 121GW packet decoding, reassembly from BLE fragments, min/max tracking
app vitest v2 decoder, menus/paging, reconnect/backoff, layout maths, renderer diffing and hero rendering
e2e (CI) 1 real v2 client/server: rendering, live updates and quick-view create/delete
image (CI) 1 the Docker image builds, starts and answers health

Not covered yet: InfluxDB queries against a real InfluxDB, the nginx config, and anything that needs the glasses or the meter.