From d8f670fcbae0f17137f45095db20ddfcff5d41a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20M=C3=BCller?= Date: Tue, 23 Sep 2025 16:49:45 +0200 Subject: [PATCH] Add backend to send custom commands --- jsonrpc.go | 14 ++++++ serial.go | 27 +++++++++++ .../components/extensions/SerialButtons.tsx | 47 ++++++++++--------- 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/jsonrpc.go b/jsonrpc.go index e448f952..a55a1c71 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -758,6 +758,8 @@ func rpcSetActiveExtension(extensionId string) error { _ = unmountATXControl() case "dc-power": _ = unmountDCControl() + case "serial-buttons": + _ = unmountSerialButtons() } config.ActiveExtension = extensionId if err := SaveConfig(); err != nil { @@ -768,6 +770,8 @@ func rpcSetActiveExtension(extensionId string) error { _ = mountATXControl() case "dc-power": _ = mountDCControl() + case "serial-buttons": + _ = mountSerialButtons() } return nil } @@ -802,6 +806,15 @@ func rpcGetATXState() (ATXState, error) { return state, nil } +func rpcSendCustomCommand(command string) error { + logger.Info().Str("Command", command).Msg("JSONRPC: Sending custom serial command") + err := sendCustomCommand(command) + if err != nil { + return fmt.Errorf("failed to set DC power state: %w", err) + } + return nil +} + type SerialSettings struct { BaudRate string `json:"baudRate"` DataBits string `json:"dataBits"` @@ -1296,6 +1309,7 @@ var rpcHandlers = map[string]RPCHandler{ "setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}}, "getATXState": {Func: rpcGetATXState}, "setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}}, + "sendCustomCommand": {Func: rpcSendCustomCommand, Params: []string{"command"}}, "getSerialSettings": {Func: rpcGetSerialSettings}, "setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}}, "getSerialButtonConfig": {Func: rpcGetSerialButtonConfig}, diff --git a/serial.go b/serial.go index 5439d135..85165f9b 100644 --- a/serial.go +++ b/serial.go @@ -251,6 +251,33 @@ func setDCRestoreState(state int) error { return nil } +func mountSerialButtons() error { + _ = port.SetMode(defaultMode) + + return nil +} + +func unmountSerialButtons() error { + _ = reopenSerialPort() + return nil +} + +func sendCustomCommand(command string) error { + scopedLogger := serialLogger.With().Str("service", "custom-buttons").Logger() + scopedLogger.Info().Str("Command", command).Msg("Sending custom command.") + _, err := port.Write([]byte("\n")) + if err != nil { + scopedLogger.Warn().Err(err).Msg("Failed to send serial output \\n") + return err + } + _, err = port.Write([]byte(command)) + if err != nil { + scopedLogger.Warn().Err(err).Str("line", command).Msg("Failed to send serial output") + return err + } + return nil +} + var defaultMode = &serial.Mode{ BaudRate: 115200, DataBits: 8, diff --git a/ui/src/components/extensions/SerialButtons.tsx b/ui/src/components/extensions/SerialButtons.tsx index a4f01898..aa47428d 100644 --- a/ui/src/components/extensions/SerialButtons.tsx +++ b/ui/src/components/extensions/SerialButtons.tsx @@ -76,15 +76,10 @@ export function SerialButtons() { return; } - const cfg = resp.result as ButtonConfig; - console.log("loaded button config: "); - console.log(cfg); setButtonConfig(resp.result as ButtonConfig); }); - console.log("loaded loaded settings through effect."); - - }, [send]); + }); const handleSerialSettingChange = (setting: keyof SerialSettings, value: string) => { const newSettings = { ...serialSettings, [setting]: value }; @@ -104,21 +99,27 @@ export function SerialButtons() { notifications.error(`Failed to update button config: ${resp.error.data || "Unknown error"}`); return; } - // setButtonConfig(newButtonConfig); + setButtonConfig(newButtonConfig); }); }; - /** build final string to send: - * if the user's button command already contains a terminator, we don't append the selected terminator safely - */ - const buildOutgoing = (raw: string): string => { - const t = buttonConfig.terminator ?? ""; - return raw.endsWith("\r") || raw.endsWith("\n") ? raw : raw + t; - }; + const onClickButton = (btn: QuickButton) => { + + /** build final string to send: + * if the user's button command already contains a terminator, we don't append the selected terminator safely + */ + const raw = btn.command; + const t = buttonConfig.terminator ?? ""; + const command = raw.endsWith("\r") || raw.endsWith("\n") ? raw : raw + t; + + send("sendCustomCommand", { command }, (resp: JsonRpcResponse) => { + if ("error" in resp) { + notifications.error( + `Failed to send ATX power action: ${resp.error.data || "Unknown error"}`, + ); + } + }); - const onClickButton = async (btn: QuickButton) => { - buildOutgoing(btn.command); - // Try to send via backend method }; /** CRUD helpers */ @@ -134,11 +135,11 @@ export function SerialButtons() { setDraftCmd(btn.command); }; - // const removeBtn = async (id: string) => { - // const next = { ...buttonConfig, buttons: buttonConfig.buttons.filter(b => b.id !== id).map((b, i) => ({ ...b, sort: i })) }; - // // await setButtonConfig(next); - // setEditorOpen(null); - // }; + const removeBtn = (id: string) => { + const nextButtons = buttonConfig.buttons.filter(b => b.id !== id).map((b, i) => ({ ...b, sort: i })) ; + handleSerialButtonConfigChange("buttons", stableSort(nextButtons) ); + setEditorOpen(null); + }; const saveDraft = () => { const label = draftLabel.trim() || "Unnamed"; @@ -327,7 +328,7 @@ export function SerialButtons() { theme="danger" LeadingIcon={LuTrash2} text="Delete" - // onClick={() => removeBtn(editorOpen!.id)} + onClick={() => removeBtn(editorOpen.id!)} aria-label={`Delete ${draftLabel}`} />)}