From b338281a97655e0a08b3a9ea5e3723ad74089429 Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Thu, 5 Mar 2026 15:59:23 +0100 Subject: [PATCH] Add PSU --- .../manifest.json | 32 ++++++++++++ src/actions/printer-status.ts | 6 +++ src/actions/psu-off.ts | 52 +++++++++++++++++++ src/actions/psu-on.ts | 52 +++++++++++++++++++ src/actions/temperature.ts | 6 +++ src/octoprint-client.ts | 45 ++++++++++++++++ src/plugin.ts | 4 ++ 7 files changed, 197 insertions(+) create mode 100644 src/actions/psu-off.ts create mode 100644 src/actions/psu-on.ts diff --git a/com.carsten-graf.octocontroll.sdPlugin/manifest.json b/com.carsten-graf.octocontroll.sdPlugin/manifest.json index a34363b..78a5b1e 100644 --- a/com.carsten-graf.octocontroll.sdPlugin/manifest.json +++ b/com.carsten-graf.octocontroll.sdPlugin/manifest.json @@ -82,6 +82,38 @@ "TitleAlignment": "middle" } ] + }, + { + "Name": "Turn PSU On", + "UUID": "com.octoprint.monitor.psu-on", + "Icon": "imgs/actions/counter/icon", + "Tooltip": "Turn the printer PSU on via OctoPrint PSUControl.", + "PropertyInspectorPath": "ui/increment-counter.html", + "Controllers": [ + "Keypad" + ], + "States": [ + { + "Image": "imgs/actions/counter/key", + "TitleAlignment": "middle" + } + ] + }, + { + "Name": "Turn PSU Off", + "UUID": "com.octoprint.monitor.psu-off", + "Icon": "imgs/actions/counter/icon", + "Tooltip": "Turn the printer PSU off via OctoPrint PSUControl.", + "PropertyInspectorPath": "ui/increment-counter.html", + "Controllers": [ + "Keypad" + ], + "States": [ + { + "Image": "imgs/actions/counter/key", + "TitleAlignment": "middle" + } + ] } ], "Category": "OctoControll", diff --git a/src/actions/printer-status.ts b/src/actions/printer-status.ts index 23b66fc..6020ea1 100644 --- a/src/actions/printer-status.ts +++ b/src/actions/printer-status.ts @@ -72,6 +72,12 @@ export class PrinterStatusAction extends SingletonAction { const client = new OctoPrintClient(settings); try { + const connection = await client.getConnectionState(); + if (connection.current.state === "Closed") { + await action.setTitle("Offline"); + return; + } + const [printer, job] = await Promise.all([ client.getPrinterState(), client.getJobState(), diff --git a/src/actions/psu-off.ts b/src/actions/psu-off.ts new file mode 100644 index 0000000..21ed9dc --- /dev/null +++ b/src/actions/psu-off.ts @@ -0,0 +1,52 @@ +import streamDeck, { action, KeyDownEvent, SingletonAction, WillAppearEvent } from "@elgato/streamdeck"; + +import { OctoPrintClient, OctoPrintSettings } from "../octoprint-client"; + +@action({ UUID: "com.octoprint.monitor.psu-off" }) +export class PsuOffAction extends SingletonAction { + override async onWillAppear(ev: WillAppearEvent): Promise { + await this.updateTitle(ev.action, ev.payload.settings as unknown as OctoPrintSettings); + } + + override async onKeyDown(ev: KeyDownEvent): Promise { + const settings = ev.payload.settings as unknown as OctoPrintSettings; + + if (!settings?.host || !settings?.apiKey) { + await ev.action.setTitle("Configure"); + return; + } + + const client = new OctoPrintClient(settings); + + try { + await ev.action.setTitle("Turning\nOff…"); + await client.turnPSUOff(); + setTimeout(() => this.updateTitle(ev.action, settings), 1500); + } catch (err) { + streamDeck.logger.error("PsuOff: failed", err); + await ev.action.setTitle("⚠️ Error"); + setTimeout(() => this.updateTitle(ev.action, settings), 2000); + } + } + + private async updateTitle(action: any, settings: OctoPrintSettings): Promise { + if (!settings?.host || !settings?.apiKey) { + await action.setTitle("Configure"); + return; + } + + try { + const client = new OctoPrintClient(settings); + const psu = await client.getPSUState(); + + if (!psu.isPSUOn) { + await action.setTitle("PSU\nOff"); + } else { + await action.setTitle("Turn\nPSU Off"); + } + } catch { + await action.setTitle("Turn\nPSU Off"); + } + } +} + diff --git a/src/actions/psu-on.ts b/src/actions/psu-on.ts new file mode 100644 index 0000000..adec4e0 --- /dev/null +++ b/src/actions/psu-on.ts @@ -0,0 +1,52 @@ +import streamDeck, { action, KeyDownEvent, SingletonAction, WillAppearEvent } from "@elgato/streamdeck"; + +import { OctoPrintClient, OctoPrintSettings } from "../octoprint-client"; + +@action({ UUID: "com.octoprint.monitor.psu-on" }) +export class PsuOnAction extends SingletonAction { + override async onWillAppear(ev: WillAppearEvent): Promise { + await this.updateTitle(ev.action, ev.payload.settings as unknown as OctoPrintSettings); + } + + override async onKeyDown(ev: KeyDownEvent): Promise { + const settings = ev.payload.settings as unknown as OctoPrintSettings; + + if (!settings?.host || !settings?.apiKey) { + await ev.action.setTitle("Configure"); + return; + } + + const client = new OctoPrintClient(settings); + + try { + await ev.action.setTitle("Turning\nOn…"); + await client.turnPSUOn(); + setTimeout(() => this.updateTitle(ev.action, settings), 1500); + } catch (err) { + streamDeck.logger.error("PsuOn: failed", err); + await ev.action.setTitle("⚠️ Error"); + setTimeout(() => this.updateTitle(ev.action, settings), 2000); + } + } + + private async updateTitle(action: any, settings: OctoPrintSettings): Promise { + if (!settings?.host || !settings?.apiKey) { + await action.setTitle("Configure"); + return; + } + + try { + const client = new OctoPrintClient(settings); + const psu = await client.getPSUState(); + + if (psu.isPSUOn) { + await action.setTitle("PSU\nOn"); + } else { + await action.setTitle("Turn\nPSU On"); + } + } catch { + await action.setTitle("Turn\nPSU On"); + } + } +} + diff --git a/src/actions/temperature.ts b/src/actions/temperature.ts index b1fd9a7..8cb2fa5 100644 --- a/src/actions/temperature.ts +++ b/src/actions/temperature.ts @@ -65,6 +65,12 @@ export class TemperatureAction extends SingletonAction { const client = new OctoPrintClient(settings); try { + const connection = await client.getConnectionState(); + if (connection.current.state === "Closed") { + await action.setTitle("Offline"); + return; + } + const printer = await client.getPrinterState(); if (!printer.temperature) { diff --git a/src/octoprint-client.ts b/src/octoprint-client.ts index 73b7af3..65a46d9 100644 --- a/src/octoprint-client.ts +++ b/src/octoprint-client.ts @@ -41,6 +41,19 @@ export interface JobState { state: string; } +export interface ConnectionState { + current: { + baudrate: number | null; + port: string | null; + printerProfile: string; + state: string; + }; +} + +export interface PsuControlState { + isPSUOn: boolean; +} + export class OctoPrintClient { private baseUrl: string; private apiKey: string; @@ -74,6 +87,14 @@ export class OctoPrintClient { return res.json() as Promise; } + async getConnectionState(): Promise { + const res = await fetch(`${this.baseUrl}/connection`, { + headers: this.headers, + }); + if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); + return res.json() as Promise; + } + async pausePrint(): Promise { await fetch(`${this.baseUrl}/job`, { method: "POST", @@ -114,6 +135,30 @@ export class OctoPrintClient { } } + async getPSUState(): Promise { + const res = await fetch(`${this.baseUrl}/plugin/psucontrol`, { + headers: this.headers, + }); + if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); + return res.json() as Promise; + } + + async turnPSUOn(): Promise { + await fetch(`${this.baseUrl}/plugin/psucontrol`, { + method: "POST", + headers: this.headers, + body: JSON.stringify({ command: "turnPSUOn" }), + }); + } + + async turnPSUOff(): Promise { + await fetch(`${this.baseUrl}/plugin/psucontrol`, { + method: "POST", + headers: this.headers, + body: JSON.stringify({ command: "turnPSUOff" }), + }); + } + async testConnection(): Promise { try { const res = await fetch(`${this.baseUrl}/version`, { headers: this.headers }); diff --git a/src/plugin.ts b/src/plugin.ts index eb5e620..be2ae5f 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -6,6 +6,8 @@ import { PrintPauseAction } from "./actions/print-pause"; import { PrintCancelAction } from "./actions/print-cancel"; import { TemperatureAction } from "./actions/temperature"; import { HomeAxesAction } from "./actions/home-axes"; +import { PsuOnAction } from "./actions/psu-on"; +import { PsuOffAction } from "./actions/psu-off"; // Register all actions streamDeck.actions.registerAction(new PrinterStatusAction() as any); @@ -13,6 +15,8 @@ streamDeck.actions.registerAction(new PrintPauseAction() as any); streamDeck.actions.registerAction(new PrintCancelAction() as any); streamDeck.actions.registerAction(new TemperatureAction() as any); streamDeck.actions.registerAction(new HomeAxesAction() as any); +streamDeck.actions.registerAction(new PsuOnAction() as any); +streamDeck.actions.registerAction(new PsuOffAction() as any); // Connect to Stream Deck streamDeck.connect();