This commit is contained in:
Carsten Graf
2026-03-05 15:59:23 +01:00
parent d5353b3c2b
commit b338281a97
7 changed files with 197 additions and 0 deletions

View File

@@ -82,6 +82,38 @@
"TitleAlignment": "middle" "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", "Category": "OctoControll",

View File

@@ -72,6 +72,12 @@ export class PrinterStatusAction extends SingletonAction {
const client = new OctoPrintClient(settings); const client = new OctoPrintClient(settings);
try { try {
const connection = await client.getConnectionState();
if (connection.current.state === "Closed") {
await action.setTitle("Offline");
return;
}
const [printer, job] = await Promise.all([ const [printer, job] = await Promise.all([
client.getPrinterState(), client.getPrinterState(),
client.getJobState(), client.getJobState(),

52
src/actions/psu-off.ts Normal file
View File

@@ -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<void> {
await this.updateTitle(ev.action, ev.payload.settings as unknown as OctoPrintSettings);
}
override async onKeyDown(ev: KeyDownEvent): Promise<void> {
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<void> {
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");
}
}
}

52
src/actions/psu-on.ts Normal file
View File

@@ -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<void> {
await this.updateTitle(ev.action, ev.payload.settings as unknown as OctoPrintSettings);
}
override async onKeyDown(ev: KeyDownEvent): Promise<void> {
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<void> {
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");
}
}
}

View File

@@ -65,6 +65,12 @@ export class TemperatureAction extends SingletonAction {
const client = new OctoPrintClient(settings); const client = new OctoPrintClient(settings);
try { try {
const connection = await client.getConnectionState();
if (connection.current.state === "Closed") {
await action.setTitle("Offline");
return;
}
const printer = await client.getPrinterState(); const printer = await client.getPrinterState();
if (!printer.temperature) { if (!printer.temperature) {

View File

@@ -41,6 +41,19 @@ export interface JobState {
state: string; state: string;
} }
export interface ConnectionState {
current: {
baudrate: number | null;
port: string | null;
printerProfile: string;
state: string;
};
}
export interface PsuControlState {
isPSUOn: boolean;
}
export class OctoPrintClient { export class OctoPrintClient {
private baseUrl: string; private baseUrl: string;
private apiKey: string; private apiKey: string;
@@ -74,6 +87,14 @@ export class OctoPrintClient {
return res.json() as Promise<JobState>; return res.json() as Promise<JobState>;
} }
async getConnectionState(): Promise<ConnectionState> {
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<ConnectionState>;
}
async pausePrint(): Promise<void> { async pausePrint(): Promise<void> {
await fetch(`${this.baseUrl}/job`, { await fetch(`${this.baseUrl}/job`, {
method: "POST", method: "POST",
@@ -114,6 +135,30 @@ export class OctoPrintClient {
} }
} }
async getPSUState(): Promise<PsuControlState> {
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<PsuControlState>;
}
async turnPSUOn(): Promise<void> {
await fetch(`${this.baseUrl}/plugin/psucontrol`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({ command: "turnPSUOn" }),
});
}
async turnPSUOff(): Promise<void> {
await fetch(`${this.baseUrl}/plugin/psucontrol`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({ command: "turnPSUOff" }),
});
}
async testConnection(): Promise<boolean> { async testConnection(): Promise<boolean> {
try { try {
const res = await fetch(`${this.baseUrl}/version`, { headers: this.headers }); const res = await fetch(`${this.baseUrl}/version`, { headers: this.headers });

View File

@@ -6,6 +6,8 @@ import { PrintPauseAction } from "./actions/print-pause";
import { PrintCancelAction } from "./actions/print-cancel"; import { PrintCancelAction } from "./actions/print-cancel";
import { TemperatureAction } from "./actions/temperature"; import { TemperatureAction } from "./actions/temperature";
import { HomeAxesAction } from "./actions/home-axes"; import { HomeAxesAction } from "./actions/home-axes";
import { PsuOnAction } from "./actions/psu-on";
import { PsuOffAction } from "./actions/psu-off";
// Register all actions // Register all actions
streamDeck.actions.registerAction(new PrinterStatusAction() as any); 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 PrintCancelAction() as any);
streamDeck.actions.registerAction(new TemperatureAction() as any); streamDeck.actions.registerAction(new TemperatureAction() as any);
streamDeck.actions.registerAction(new HomeAxesAction() 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 // Connect to Stream Deck
streamDeck.connect(); streamDeck.connect();