Fix kiosk fan control state updates

This commit is contained in:
Mitchell R 2026-05-13 03:47:34 +02:00
parent d018b34955
commit 54d4dfefa8
3 changed files with 24 additions and 11 deletions

View file

@ -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

View file

@ -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() {

View file

@ -58,15 +58,17 @@ pub fn run(server_url: &str, kiosk_key: &str, tx: Sender<ServerMsg>) {
}
} else if text.contains("\"type\":\"fan\"") {
info!("ws: fan received: {text}");
let pwm: Option<u32> = if text.contains("\"mode\":\"auto\"") {
let Ok(msg) = serde_json::from_str::<serde_json::Value>(&text) else {
warn!("ws: fan command was not valid JSON");
continue;
};
let pwm: Option<u32> = 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::<u32>().ok());
if v.is_none() { continue; }
v
warn!("ws: fan command missing mode=auto or pwm");
continue;
};
let _ = tx.send(ServerMsg::Fan(pwm));
} else {