diff --git a/jsonrpc.go b/jsonrpc.go index 5a662530d..049a67601 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -880,12 +880,16 @@ func rpcSetATXPowerAction(action string) error { type ATXState struct { Power bool `json:"power"` HDD bool `json:"hdd"` + // Responsive reports whether the ATX extension board has sent a status + // line recently, so the UI can flag an unpowered or disconnected board. + Responsive bool `json:"responsive"` } func rpcGetATXState() (ATXState, error) { state := ATXState{ - Power: ledPWRState.Load(), - HDD: ledHDDState.Load(), + Power: ledPWRState.Load(), + HDD: ledHDDState.Load(), + Responsive: atxBoardResponsive(), } return state, nil } diff --git a/mqtt.go b/mqtt.go index 9c9e2f199..019b19d6a 100644 --- a/mqtt.go +++ b/mqtt.go @@ -204,8 +204,9 @@ func (m *MQTTManager) onConnect(client mqtt.Client) { // the current state of all switches and sensors right away. if config.ActiveExtension == "atx-power" { m.publishATXState(ATXState{ - Power: ledPWRState.Load(), - HDD: ledHDDState.Load(), + Power: ledPWRState.Load(), + HDD: ledHDDState.Load(), + Responsive: atxBoardResponsive(), }) } if config.ActiveExtension == "dc-power" { diff --git a/mqtt_publish.go b/mqtt_publish.go index fc2818c40..c9f6e5185 100644 --- a/mqtt_publish.go +++ b/mqtt_publish.go @@ -563,8 +563,9 @@ func (m *MQTTManager) startPeriodicStatusUpdates(interval time.Duration) { // Publish current ATX state only if ATX extension is active if config.ActiveExtension == "atx-power" { m.publishATXState(ATXState{ - Power: ledPWRState.Load(), - HDD: ledHDDState.Load(), + Power: ledPWRState.Load(), + HDD: ledHDDState.Load(), + Responsive: atxBoardResponsive(), }) } diff --git a/serial.go b/serial.go index 6189c1e03..8214e4cfc 100644 --- a/serial.go +++ b/serial.go @@ -23,6 +23,7 @@ var consoleBroker *ConsoleBroker func mountATXControl() error { _ = port.SetMode(defaultMode) + atxLastLineTime.Store(0) go runATXControl() return nil @@ -38,8 +39,24 @@ var ( ledPWRState atomic.Bool btnRSTState bool btnPWRState bool + + // atxLastLineTime holds the unix time (in seconds) of the last status + // line received from the ATX extension board. Zero means no line has + // been received since the extension was mounted. + atxLastLineTime atomic.Int64 ) +// atxResponsiveTimeout is how long after the last received status line the +// ATX extension board is still considered responsive. +const atxResponsiveTimeout = 10 * time.Second + +// atxBoardResponsive reports whether the ATX extension board has sent a +// status line within atxResponsiveTimeout. +func atxBoardResponsive() bool { + lastLine := atxLastLineTime.Load() + return lastLine != 0 && time.Since(time.Unix(lastLine, 0)) < atxResponsiveTimeout +} + func runATXControl() { scopedLogger := serialLogger.With().Str("service", "atx_control").Logger() @@ -51,6 +68,9 @@ func runATXControl() { return } + // The board is talking to us, even if the line turns out invalid. + atxLastLineTime.Store(time.Now().Unix()) + // Each line should be 4 binary digits + newline if len(line) != 5 { scopedLogger.Warn().Int("length", len(line)).Msg("Invalid line length") @@ -64,8 +84,9 @@ func runATXControl() { newBtnPWRState := line[3] == '1' atxState := ATXState{ - Power: newLedPWRState, - HDD: newLedHDDState, + Power: newLedPWRState, + HDD: newLedHDDState, + Responsive: true, // a status line was just received } if currentSession != nil { diff --git a/ui/localization/messages/cy.json b/ui/localization/messages/cy.json index cb22b38b1..d9fc10032 100644 --- a/ui/localization/messages/cy.json +++ b/ui/localization/messages/cy.json @@ -150,6 +150,7 @@ "appearance_theme_system": "System", "appearance_title": "Ymddangosiad", "attach": "Atodi", + "atx_power_control_board_not_responding": "Dim ymateb gan y bwrdd estyniad ATX. Gwiriwch y cebl RJ-12 a phŵer y bwrdd.", "atx_power_control_get_state_error": "Methwyd cael cyflwr pŵer ATX: {error}", "atx_power_control_hdd_led": "LED HDD", "atx_power_control_hold_hint": "Dal am 3 eiliad i orfodi diffodd", diff --git a/ui/localization/messages/da.json b/ui/localization/messages/da.json index 075a91f5e..4bbe07588 100644 --- a/ui/localization/messages/da.json +++ b/ui/localization/messages/da.json @@ -155,6 +155,7 @@ "appearance_theme_system": "System", "appearance_title": "Udseende", "attach": "Vedhæft", + "atx_power_control_board_not_responding": "Intet svar fra ATX-udvidelseskortet. Kontrollér RJ-12-kablet og kortets strømforsyning.", "atx_power_control_get_state_error": "Kunne ikke hente ATX-strømtilstand: {error}", "atx_power_control_hdd_led": "HDD-LED", "atx_power_control_hold_hint": "Hold nede i 3 sek. for at tvinge slukning", diff --git a/ui/localization/messages/de.json b/ui/localization/messages/de.json index 0b89ef173..c4bcefc16 100644 --- a/ui/localization/messages/de.json +++ b/ui/localization/messages/de.json @@ -155,6 +155,7 @@ "appearance_theme_system": "System", "appearance_title": "Darstellung", "attach": "Anhängen", + "atx_power_control_board_not_responding": "Keine Antwort von der ATX-Erweiterungsplatine. Prüfen Sie das RJ-12-Kabel und die Stromversorgung der Platine.", "atx_power_control_get_state_error": "ATX-Stromversorgungsstatus konnte nicht abgerufen werden: {error}", "atx_power_control_hdd_led": "Festplatten-LED", "atx_power_control_hold_hint": "3 Sekunden halten zum Ausschalten erzwingen", diff --git a/ui/localization/messages/en.json b/ui/localization/messages/en.json index 63be6e8d9..be749dc2c 100644 --- a/ui/localization/messages/en.json +++ b/ui/localization/messages/en.json @@ -155,6 +155,7 @@ "appearance_theme_system": "System", "appearance_title": "Appearance", "attach": "Attach", + "atx_power_control_board_not_responding": "No response from the ATX extension board. Check the RJ-12 cable and board power.", "atx_power_control_get_state_error": "Failed to get ATX power state: {error}", "atx_power_control_hdd_led": "HDD LED", "atx_power_control_hold_hint": "Hold for 3s to force off", diff --git a/ui/localization/messages/es.json b/ui/localization/messages/es.json index fa9aa1b69..5d098289f 100644 --- a/ui/localization/messages/es.json +++ b/ui/localization/messages/es.json @@ -155,6 +155,7 @@ "appearance_theme_system": "Sistema", "appearance_title": "Apariencia", "attach": "Adjuntar", + "atx_power_control_board_not_responding": "No hay respuesta de la placa de extensión ATX. Compruebe el cable RJ-12 y la alimentación de la placa.", "atx_power_control_get_state_error": "No se pudo obtener el estado de energía ATX: {error}", "atx_power_control_hdd_led": "LED del disco duro", "atx_power_control_hold_hint": "Mantenga presionado 3 s para forzar el apagado", diff --git a/ui/localization/messages/fr.json b/ui/localization/messages/fr.json index 226a66f28..6b513d339 100644 --- a/ui/localization/messages/fr.json +++ b/ui/localization/messages/fr.json @@ -155,6 +155,7 @@ "appearance_theme_system": "Système", "appearance_title": "Apparence", "attach": "Attacher", + "atx_power_control_board_not_responding": "Aucune réponse de la carte d'extension ATX. Vérifiez le câble RJ-12 et l'alimentation de la carte.", "atx_power_control_get_state_error": "Échec de l'obtention de l'état d'alimentation ATX : {error}", "atx_power_control_hdd_led": "Voyant du disque dur", "atx_power_control_hold_hint": "Maintenir 3 s pour forcer l'arrêt", diff --git a/ui/localization/messages/it.json b/ui/localization/messages/it.json index 2c52f2d57..b324e4daa 100644 --- a/ui/localization/messages/it.json +++ b/ui/localization/messages/it.json @@ -155,6 +155,7 @@ "appearance_theme_system": "Sistema", "appearance_title": "Aspetto", "attach": "Allega", + "atx_power_control_board_not_responding": "Nessuna risposta dalla scheda di estensione ATX. Controlla il cavo RJ-12 e l'alimentazione della scheda.", "atx_power_control_get_state_error": "Impossibile ottenere lo stato di alimentazione ATX: {error}", "atx_power_control_hdd_led": "LED dell'HDD", "atx_power_control_hold_hint": "Tenere premuto 3 s per lo spegnimento forzato", diff --git a/ui/localization/messages/ja.json b/ui/localization/messages/ja.json index 629d14133..dceed065d 100644 --- a/ui/localization/messages/ja.json +++ b/ui/localization/messages/ja.json @@ -155,6 +155,7 @@ "appearance_theme_system": "システム", "appearance_title": "外観", "attach": "接続", + "atx_power_control_board_not_responding": "ATX拡張ボードから応答がありません。RJ-12ケーブルとボードの電源を確認してください。", "atx_power_control_get_state_error": "ATX電源状態の取得に失敗しました: {error}", "atx_power_control_hdd_led": "HDD LED", "atx_power_control_hold_hint": "3秒長押しで強制オフ", diff --git a/ui/localization/messages/nb.json b/ui/localization/messages/nb.json index c7791e12c..6f2faceff 100644 --- a/ui/localization/messages/nb.json +++ b/ui/localization/messages/nb.json @@ -155,6 +155,7 @@ "appearance_theme_system": "System", "appearance_title": "Utseende", "attach": "Legg ved", + "atx_power_control_board_not_responding": "Ingen respons fra ATX-utvidelseskortet. Kontroller RJ-12-kabelen og strømforsyningen til kortet.", "atx_power_control_get_state_error": "Klarte ikke å hente ATX-strømstatus: {error}", "atx_power_control_hdd_led": "HDD-LED", "atx_power_control_hold_hint": "Hold inne 3 s for å tvinge avslåing", diff --git a/ui/localization/messages/pt.json b/ui/localization/messages/pt.json index 16157e922..71741db86 100644 --- a/ui/localization/messages/pt.json +++ b/ui/localization/messages/pt.json @@ -155,6 +155,7 @@ "appearance_theme_system": "Sistema", "appearance_title": "Aparência", "attach": "Anexar", + "atx_power_control_board_not_responding": "Sem resposta da placa de extensão ATX. Verifique o cabo RJ-12 e a alimentação da placa.", "atx_power_control_get_state_error": "Falha ao obter estado de energia ATX: {error}", "atx_power_control_hdd_led": "LED do HDD", "atx_power_control_hold_hint": "Manter premido 3 s para forçar o desligamento", diff --git a/ui/localization/messages/ru.json b/ui/localization/messages/ru.json index a14b709cd..c83852c84 100644 --- a/ui/localization/messages/ru.json +++ b/ui/localization/messages/ru.json @@ -155,6 +155,7 @@ "appearance_theme_system": "Системная", "appearance_title": "Внешний вид", "attach": "Подключить", + "atx_power_control_board_not_responding": "Нет ответа от платы расширения ATX. Проверьте кабель RJ-12 и питание платы.", "atx_power_control_get_state_error": "Не удалось получить состояние ATX-питания: {error}", "atx_power_control_hdd_led": "Индикатор HDD", "atx_power_control_hold_hint": "Удерживайте 3 с для принудительного выключения", diff --git a/ui/localization/messages/sv.json b/ui/localization/messages/sv.json index 2560ca511..2fe4a6326 100644 --- a/ui/localization/messages/sv.json +++ b/ui/localization/messages/sv.json @@ -155,6 +155,7 @@ "appearance_theme_system": "System", "appearance_title": "Utseende", "attach": "Bifoga", + "atx_power_control_board_not_responding": "Inget svar från ATX-expansionskortet. Kontrollera RJ-12-kabeln och kortets strömförsörjning.", "atx_power_control_get_state_error": "Misslyckades med att hämta ATX-strömstatus: {error}", "atx_power_control_hdd_led": "Hårddisk-LED", "atx_power_control_hold_hint": "Håll in 3 s för att tvinga avstängning", diff --git a/ui/localization/messages/zh-tw.json b/ui/localization/messages/zh-tw.json index cdc15781f..6fe875d85 100644 --- a/ui/localization/messages/zh-tw.json +++ b/ui/localization/messages/zh-tw.json @@ -155,6 +155,7 @@ "appearance_theme_system": "系統", "appearance_title": "外觀", "attach": "放至下方", + "atx_power_control_board_not_responding": "ATX 擴充板沒有回應。請檢查 RJ-12 線材與擴充板的電源。", "atx_power_control_get_state_error": "取得 ATX 電源狀態失敗:{error}", "atx_power_control_hdd_led": "HDD 指示燈", "atx_power_control_hold_hint": "按住 3 秒強制關機", diff --git a/ui/localization/messages/zh.json b/ui/localization/messages/zh.json index ce4604bd7..c25021c89 100644 --- a/ui/localization/messages/zh.json +++ b/ui/localization/messages/zh.json @@ -155,6 +155,7 @@ "appearance_theme_system": "跟随系统", "appearance_title": "外观", "attach": "挂载", + "atx_power_control_board_not_responding": "ATX 扩展板无响应。请检查 RJ-12 线缆和扩展板的电源。", "atx_power_control_get_state_error": "获取 ATX 电源状态失败:{error}", "atx_power_control_hdd_led": "硬盘指示灯", "atx_power_control_hold_hint": "按住 3 秒强制关机", diff --git a/ui/src/components/extensions/ATXPowerControl.tsx b/ui/src/components/extensions/ATXPowerControl.tsx index 2b644935a..4cc397b7b 100644 --- a/ui/src/components/extensions/ATXPowerControl.tsx +++ b/ui/src/components/extensions/ATXPowerControl.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { LuHardDrive, LuPower, LuRotateCcw } from "react-icons/lu"; +import { LuHardDrive, LuPower, LuRotateCcw, LuTriangleAlert } from "react-icons/lu"; import { m } from "@localizations/messages.js"; import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc"; @@ -10,10 +10,12 @@ import { SettingsPageHeader } from "@components/SettingsPageheader"; import notifications from "@/notifications"; const LONG_PRESS_DURATION = 3000; // 3 seconds for long press +const STATE_POLL_INTERVAL = 5000; // Poll board responsiveness every 5 seconds interface ATXState { power: boolean; hdd: boolean; + responsive?: boolean; } export function ATXPowerControl() { @@ -29,17 +31,26 @@ export function ATXPowerControl() { } }); - // Request initial state + // Request initial state and keep polling while the control is open, + // so an unresponsive extension board is detected and surfaced. useEffect(() => { - send("getATXState", {}, (resp: JsonRpcResponse) => { - if ("error" in resp) { - notifications.error( - m.atx_power_control_get_state_error({ error: resp.error.data || m.unknown_error() }), - ); - return; - } - setAtxState(resp.result as ATXState); - }); + const requestState = (notifyOnError: boolean) => { + send("getATXState", {}, (resp: JsonRpcResponse) => { + if ("error" in resp) { + if (notifyOnError) { + notifications.error( + m.atx_power_control_get_state_error({ error: resp.error.data || m.unknown_error() }), + ); + } + return; + } + setAtxState(resp.result as ATXState); + }); + }; + + requestState(true); + const pollTimer = setInterval(() => requestState(false), STATE_POLL_INTERVAL); + return () => clearInterval(pollTimer); }, [send]); const handlePowerPress = (pressed: boolean) => { @@ -115,6 +126,13 @@ export function ATXPowerControl() { ) : (
+ {/* Board responsiveness warning */} + {atxState.responsive === false && ( +
+ + {m.atx_power_control_board_not_responding()} +
+ )} {/* Control Buttons */}
-

{m.atx_power_control_hold_hint()}

+

+ {m.atx_power_control_hold_hint()} +


{/* Status Indicators */}