diff --git a/server/app/routers/api_frames.py b/server/app/routers/api_frames.py index b50226a..76bb782 100644 --- a/server/app/routers/api_frames.py +++ b/server/app/routers/api_frames.py @@ -244,6 +244,14 @@ def api_status( "device": { "last_seen": frame.last_seen or None, "overdue": bool(frame.last_seen and now - frame.last_seen > overdue_gap), + # When the device is next expected to check in, per the same + # sleep duration frame_config() actually hands it (see + # device.py's /frame/config) -- not the raw overdue_gap above, + # which is deliberately generous (OVERDUE_FACTOR) to avoid + # false alarms during quiet hours rather than a best guess. + "expected_next_checkin": ( + frame.last_seen + quiet_hours.effective_refresh_interval_s(frame) if frame.last_seen else None + ), "firmware_version": frame.device_firmware_version or None, "firmware_available": frame.firmware_available_version or None, "battery": ( diff --git a/server/app/static/device_status_bar.js b/server/app/static/device_status_bar.js index d135e03..c6b296b 100644 --- a/server/app/static/device_status_bar.js +++ b/server/app/static/device_status_bar.js @@ -18,6 +18,14 @@ function renderDeviceStatusBar(device) { } const now = Date.now() / 1000; const rows = []; + if (device.expected_next_checkin) { + const remaining = device.expected_next_checkin - now; + rows.push([ + 'Expected in', + remaining > 0 ? `~${formatDuration(remaining)}` : 'Any moment', + device.overdue, + ]); + } const ago = formatDuration(Math.max(0, now - device.last_seen)); rows.push(['Last seen', `${ago} ago`, device.overdue]); if (device.firmware_version) {