Commit graph

91 commits

Author SHA1 Message Date
Mitchell R
dc96e6c08c fix: gpio lines binding must be mut for read_event 2026-05-13 01:23:09 +02:00
Mitchell R
975cc184b3 feat: multi-display + snapshot + health + GPIO + nodered embed
Multi-display:
- Bundle ships displays[] each with own layouts + idle/sleep
- Rust kiosk creates one ApplicationWindow per gdk monitor
- Per-display state (layout, idle, sleep) via HashMap
- WARM_CAMERAS pool shared across displays
- Backward-compat top-level display/layouts still emitted

System Health (/admin/health):
- Online status, CPU temp (color-coded), fan RPM/PWM
- Bundle version mismatch detection
- 30s auto-refresh

Camera snapshot/test:
- shared/snapshot.ts: ffmpeg/gst-launch fallback, 5s timeout
- /admin/entities/:id/snapshot returns JPEG
- EntityEditPage shows live preview with Refresh

GPIO (Pi buttons/sensors):
- kiosk_gpio_bindings table + CRUD admin UI
- Bundle ships gpio_bindings[]
- kiosk/src/gpio.rs with gpiod crate, worker thread per pin
- Edge events POST to /api/kiosk/event with source_type=gpio

Layout switch fixes:
- GET aliases added so direct URL hits work
- New /admin/displays/:displayId/layout/:layoutId for multi-display
- DisplayEditPage gets "Switch Layout Now" section

Node-RED embed:
- /admin/nodered renders iframe at /nrdp/
- Sandbox attrs allow scripts/forms/popups
- Sidebar link now opens embedded view
2026-05-13 01:18:22 +02:00
Mitchell R
1c0fe02fcf feat: layout switch push + idle/sleep timer + offline bundle cache
Layout switch push:
- POST /admin/kiosks/:id/layout/:layoutId — coordinator sends
  {type:"layout-switch", layout_id} via WS
- Kiosk renders specified layout from cached bundle
- KioskEditPage adds Switch Layout dropdown + button

Idle/sleep timer:
- thread_local LAST_ACTIVITY + IS_ASLEEP + CURRENT_LAYOUT_ID
- mark_activity() on render/switch/wake; wakes if asleep
- glib timeout_add_local every 1s checks elapsed:
  - elapsed >= idle_timeout AND not on default + resets_idle_timer
    → switch to default layout
  - elapsed >= sleep_timeout AND !asleep → cec::standby()
- Display idle/sleep timeouts from bundle.display

Offline cache:
- server::save_bundle → ~/.betterframe-kiosk/bundle.json
- server::load_cached_bundle on offline boot
- fetch_bundle no longer panics; returns Option
- 30s retry loop until server reachable
- Reload-bundle gracefully handles fetch failures
2026-05-13 01:00:11 +02:00
Mitchell R
1e09582379 feat: per-cell content fit (cover|contain|fill), default cover
- Migration adds layout_cells.fit column (default 'cover')
- LayoutCell type + mapper + repo accept/persist fit
- Bundle ships fit per cell
- Admin cell edit form: Fit dropdown with industry-default Cover
- Rust kiosk applies ContentFit::Cover|Contain|Fill per cell.fit

Cover = fill cell, crop overflow (industry default — Nx Witness etc)
Contain = letterbox, no crop
Fill = stretch, distort
2026-05-11 13:52:22 +02:00
Mitchell R
9679ae7eb1 ui: default kiosk background to black 2026-05-11 11:50:45 +02:00
Mitchell R
51c58e7abf feat: Pi fan control + temp monitoring + stream swap on layout change
Kiosk:
- hwmon.rs reads /sys/class/thermal + /sys/class/hwmon for CPU temp,
  fan RPM, fan PWM
- Heartbeat reports cpu_temp_c, fan_rpm, fan_pwm
- WS message "fan" with {pwm: N} or {mode: "auto"} sets pwm1_enable+pwm1
- Picture content_fit Cover → Contain (no more cropping/overlay cuts)
- ensure_warm tears down + rebuilds pipeline when desired stream
  changes (M↔S swap on layout change)

Server:
- Migration v0.8: add cpu_temp_c, fan_rpm, fan_pwm to kiosks
- Heartbeat persists hwmon fields
- KioskEditPage shows CPU/fan/PWM + Auto/Off/50%/Full buttons
- POST /admin/kiosks/:id/fan dispatches via coordinator WS
2026-05-11 11:47:07 +02:00
Mitchell R
5a67c80caa fix: fire CEC AND DPMS unconditionally for power commands
Pi5 cec-ctl returns ok even when monitor ignores CEC. Doing both
covers TVs (CEC) and monitors (DPMS) without detection logic.
Both idempotent.
2026-05-11 11:18:31 +02:00
Mitchell R
0cd508a2ec chore: remove unused legacy stream_uri method 2026-05-11 11:06:24 +02:00
Mitchell R
29b7e30844 feat: auto stream selection + M/S badge overlay
Kiosk pick_stream() implements CLAUDE.md heuristic:
- cell area >= 20% of grid → main stream
- cell area < 20% → sub stream
- explicit "main"/"sub" selector still honored

Badge overlay shows which stream is rendering:
- 'M' when camera has multi-stream and we picked main
- 'S' when we picked sub
- nothing when single-stream

Small label, top-left corner, semi-transparent black background.

Reduces buffer drops on multi-camera grids — small cells now use
low-res sub streams instead of all decoding 4K main.
2026-05-11 11:05:38 +02:00
Mitchell R
820e0a5945
fix(proxy): split Node-RED route surfaces
Route backend, kiosk ingest, kiosk dashboards, and public Node-RED HTTP-in separately. Keep Node-RED editor under admin auth and attach kiosk auth when kiosk loads protected dashboard URLs.
2026-05-11 10:44:45 +02:00
Mitchell R
96d7cc45ba
fix(deploy): require proxied local services
Bind native backend services and Node-RED to loopback so Angie remains the public auth boundary. Keep Docker on an internal compose network and stop kiosk fallback to a layout when display default is none.
2026-05-11 09:51:00 +02:00
Mitchell R
026325ccd0
feat(layout): add branded none cells
Migrate empty layout cells to an explicit none state so kiosk renders the BetterFrame placeholder instead of blank HTML.
2026-05-11 09:38:50 +02:00
Mitchell R
e38c92f753
fix(power): add monitor fallback checks 2026-05-11 08:55:42 +02:00
Mitchell R
00b304c39f feat: stream warmth — keep cameras warm across layout swaps
Previously every reload-bundle killed and restarted all pipelines.
Now:
- WARM_CAMERAS map: camera_id → (pipeline, paintable)
- On reload: stop only pipelines for cameras no longer needed
- Needed = cells with content_type=camera + layout.preload_camera_ids
- Reuse existing pipeline+paintable, attach to new Picture widget
- Preloaded cameras keep decoding even when not visible

Achieves the "zero perceived latency" layout swap goal from CLAUDE.md
when cameras overlap between layouts.
2026-05-10 22:51:28 +02:00
Mitchell R
c0704be343 feat: DPMS fallback via wlr-randr for non-CEC desktop monitors 2026-05-10 22:46:30 +02:00
Mitchell R
cbb1683c5d feat: deployment artifacts + CEC relay + auth-check endpoint
Deployment (deploy/):
- systemd units for server (system) and kiosk (user session)
- Angie/nginx proxy config — routes admin, api, ws, node-red
- Dockerfile + docker-compose for containerized deployment
- deploy/README.md with install instructions

Auth:
- /api/admin/_check endpoint for proxy auth_request subrequest
- Returns 200 if admin session valid, 401/403 otherwise
- Sets X-BetterFrame-User header for upstream

CEC (Pi5 HDMI control):
- kiosk/src/cec.rs wraps cec-ctl subprocess
- Standby/wake/active-source commands
- WS message types "standby" / "wake" dispatched to CEC
- Admin UI: Wake/Standby buttons on kiosk edit page
- Server sendToKiosk via coordinator
2026-05-10 22:45:56 +02:00
Mitchell R
766bf8dee0 feat: WebKit for web/html cells + display auto-discovery via heartbeat
Rust kiosk:
- web cells now use webkit6 WebView (load_uri)
- html cells use WebView.load_html (full HTML rendering)
- query_displays() reads /sys/class/drm/ for connected HDMI/DP outputs
- Heartbeat reports display geometry every 60s

Server:
- /api/kiosk/heartbeat accepts displays array
- Syncs kiosk-reported displays to display records
- Updates dimensions when changed, creates new displays for new ports
2026-05-10 22:39:53 +02:00
Mitchell R
3a8fd70528 fix: stop old pipelines before re-render to prevent buffer overload 2026-05-10 22:21:02 +02:00
Mitchell R
16ab165b06 feat: live updates via WebSocket — server pushes, kiosk reloads
Server side:
- service-coordinator-ws: full WS implementation using ws package
- Auth via ?token=<kiosk_key> query param
- Coordinator registry for cross-plugin notification
- Admin mutations call notifyKiosks() → server pushes reload-bundle
- 30s ping/pong heartbeat

Kiosk side:
- Rust ws_client with tokio runtime + tokio-tungstenite
- Auto-reconnect with exponential backoff (1s → 60s cap)
- On reload-bundle: re-fetches bundle, re-renders layout
- Pong replies to server pings

Also fix: auto-suffix kiosk name on UNIQUE collision (re-pair with
same hostname no longer fails).
2026-05-10 22:15:58 +02:00
Mitchell R
533412a826 refactor: Nx-Witness layout builder + drop regions/is_default
- Cells own position directly (row/col/row_span/col_span)
- Drop regions JSON from layouts (cells ARE the regions)
- Drop is_default from layouts (display.default_layout_id owns)
- Drop grid_cols/grid_rows from layouts (computed from cells)
- Layout new form: name, description, priority, resets_idle_timer only
- Layout edit: visual grid builder, + buttons on cell edges,
  click cell to assign content
- Bundle cells now carry position directly
- Rust kiosk attaches widgets using cell position
- Migration v0.4: backfills cell positions from old region map
2026-05-10 21:55:19 +02:00
Mitchell R
7fbda3c2b3 refactor: merge templates into layouts, displays from kiosks
- Eliminated layout_templates as separate entity — regions/grid now
  live directly on layouts
- Displays created from kiosk pairing (not standalone), each display
  has kiosk_id FK
- Removed Templates from sidebar nav and all template routes/pages
- Layout creation uses preset buttons (fullscreen, 2x2, 1+3, 3x3)
  that set regions directly on the layout
- Setup no longer creates default display/layout (deferred to pairing)
- Pairing creates HDMI-0 display for new kiosk
- Bundle reads regions from layout directly, no template lookup
- Rust kiosk updated to match new bundle format
- DB migration adds regions/grid_cols/grid_rows to layouts, kiosk_id
  to displays, copies existing template data
2026-05-10 21:39:09 +02:00
Mitchell R
72d8ad717f fix: import PluginFeatureExtManual for set_rank 2026-05-10 20:59:23 +02:00
Mitchell R
61ab099f87 fix: demote Pi5 hw H265 decoder — sw fallback for non-standard resolutions 2026-05-10 20:52:25 +02:00
Mitchell R
246febcc80 fix: filter rtspsrc pads — only link video to decodebin, skip audio 2026-05-10 20:51:29 +02:00
Mitchell R
877596fc15 fix: add comprehensive pipeline logging for debugging 2026-05-10 20:49:00 +02:00
Mitchell R
30eb85e6b1 fix: use property_from_str for queue leaky enum 2026-05-10 20:14:58 +02:00
Mitchell R
527a62d2e5 fix: drop HANDLES_COMMAND_LINE flag 2026-05-10 20:13:01 +02:00
Mitchell R
f6dec4bf39 fix: import ApplicationExt for set_flags 2026-05-10 20:12:14 +02:00
Mitchell R
e7237d077f fix: suppress GTK file-open warning, read server URL from env 2026-05-10 20:11:31 +02:00
Mitchell R
76af07de61 fix: use std::sync::mpsc + timeout_add_local for thread→UI messaging 2026-05-10 20:10:23 +02:00
Mitchell R
df231344a8 fix: use glib channel for thread→UI communication (Send safety) 2026-05-10 20:09:06 +02:00
Mitchell R
c4315917d8 fix: resolve all Rust compile errors in kiosk app 2026-05-10 20:04:43 +02:00
Mitchell R
371c023c81
feat: Rust kiosk app — GTK4 + GStreamer multi-camera display
- Server discovery (localhost → betterframe.local → cloud)
- Pairing flow with fullscreen code display
- Bundle fetch and layout rendering
- GTK4 Grid layout matching template regions
- GStreamer pipelines per camera cell via gtk4paintablesink
- Heartbeat loop in background thread
- Placeholder widgets for web/html cells
2026-05-10 04:18:40 +02:00
Mitchell R
f5a2645ffc
fix: force sw decode for H265 — Pi5 hw decoder rejects 960x1080 2026-05-10 04:11:43 +02:00
Mitchell R
8422fdce90
feat: auto-detect codec — try H264, H265, then uridecodebin fallback 2026-05-10 04:11:18 +02:00
Mitchell R
888dc4a4e5
fix: use uridecodebin with video caps filter for codec-agnostic RTSP 2026-05-10 04:10:33 +02:00
Mitchell R
2588611501
fix: depay h264 before decodebin to avoid audio pad linking failure 2026-05-10 04:09:01 +02:00
Mitchell R
a88e3f5c72
fix: use waylandsink with fullscreen for RTSP display 2026-05-10 03:58:40 +02:00
Mitchell R
6dfed8548c
fix: strip null byte from /proc/device-tree/model 2026-05-10 03:17:48 +02:00
Mitchell R
d9a2947001
fix: single curl call for pairing claim to prevent credential wipe 2026-05-10 03:17:35 +02:00
Mitchell R
44e4b7f3af
feat: add shell kiosk prototype for end-to-end testing 2026-05-10 03:13:52 +02:00