diff --git a/deploy/systemd/betterframe-kiosk.service b/deploy/systemd/betterframe-kiosk.service index 82d415d..80485d5 100644 --- a/deploy/systemd/betterframe-kiosk.service +++ b/deploy/systemd/betterframe-kiosk.service @@ -30,6 +30,8 @@ Environment=XDG_SESSION_TYPE=wayland Environment=XDG_SESSION_CLASS=user Environment=GST_DEBUG=1 Environment=BETTERFRAME_SERVER=http://localhost +# Let the unprivileged kiosk process control the Pi fan PWM sysfs files. +ExecStartPre=+/bin/sh -c 'for d in /sys/class/hwmon/hwmon*; do [ -e "$d/pwm1" ] || continue; chgrp bfkiosk "$d/pwm1" "$d/pwm1_enable" 2>/dev/null || true; chmod g+w "$d/pwm1" "$d/pwm1_enable" 2>/dev/null || true; done' ExecStart=/usr/bin/cage -s -- /opt/betterframe/kiosk/betterframe-kiosk Restart=always RestartSec=2 diff --git a/kiosk/src/ui.rs b/kiosk/src/ui.rs index 06be1e6..cdbe2cb 100644 --- a/kiosk/src/ui.rs +++ b/kiosk/src/ui.rs @@ -193,7 +193,12 @@ fn activate(app: &Application) { ServerMsg::Wake => { let _ = tx_for_reload.send(WorkerMsg::Wake); } - ServerMsg::Fan(pwm) => { hwmon::set_fan(pwm); } + ServerMsg::Fan(pwm) => { + if !hwmon::set_fan(pwm) { + warn!("fan command failed"); + } + send_heartbeat_now(&server_for_reload, &key_for_reload); + } ServerMsg::SwitchLayout(id) => { let _ = tx_for_reload.send(WorkerMsg::SwitchLayout(id)); } @@ -205,9 +210,7 @@ fn activate(app: &Application) { // immediately so admin "Hardware" panel populates without waiting a // full minute after boot/pair. loop { - let displays = query_displays(); - let hw = hwmon::read(); - server::heartbeat(&server, &key, &displays, &hw); + send_heartbeat_now(&server, &key); std::thread::sleep(std::time::Duration::from_secs(60)); } }); @@ -262,6 +265,12 @@ fn mark_activity(display_id: u32) { }); } +fn send_heartbeat_now(server_url: &str, kiosk_key: &str) { + let displays = query_displays(); + let hw = hwmon::read(); + server::heartbeat(server_url, kiosk_key, &displays, &hw); +} + /// Install the once-per-second watchdog that enforces idle/sleep timeouts /// per display. Safe to call multiple times — installs at most once. fn install_idle_watchdog() { diff --git a/kiosk/src/ws_client.rs b/kiosk/src/ws_client.rs index 87db955..54c0a39 100644 --- a/kiosk/src/ws_client.rs +++ b/kiosk/src/ws_client.rs @@ -58,15 +58,17 @@ pub fn run(server_url: &str, kiosk_key: &str, tx: Sender) { } } else if text.contains("\"type\":\"fan\"") { info!("ws: fan received: {text}"); - let pwm: Option = if text.contains("\"mode\":\"auto\"") { + let Ok(msg) = serde_json::from_str::(&text) else { + warn!("ws: fan command was not valid JSON"); + continue; + }; + let pwm: Option = if msg.get("mode").and_then(|v| v.as_str()) == Some("auto") { None + } else if let Some(value) = msg.get("pwm").and_then(|v| v.as_u64()) { + Some(value.min(255) as u32) } else { - // Parse "pwm":N - let v = text.split("\"pwm\":").nth(1) - .and_then(|s| s.split(|c: char| !c.is_ascii_digit()).next()) - .and_then(|s| s.parse::().ok()); - if v.is_none() { continue; } - v + warn!("ws: fan command missing mode=auto or pwm"); + continue; }; let _ = tx.send(ServerMsg::Fan(pwm)); } else {