Update dependencies, improve document printer management, and add workspace configuration
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit

This commit is contained in:
Tom Butcher 2026-06-15 00:53:28 +01:00
parent 3cc2cbe020
commit ffa6d84251
11 changed files with 1959 additions and 1417 deletions

View File

@ -5,7 +5,7 @@
"apiUrl": "https://dev.tombutcher.work/api", "apiUrl": "https://dev.tombutcher.work/api",
"host": { "host": {
"id": "691a1db49ce913faf0e51284", "id": "691a1db49ce913faf0e51284",
"authCode": "FvD3qnNh8FP_xJShlECfYshqQawfD5oPP4xlGOFV2vQIDPRxkAjH4rO6sIgpLucX" "authCode": "9uu3DC0si__-F9FnGWTKudle5z6yZasFMlKohnShElPekRYteh-LlZaksHOXFfOO"
} }
}, },
"production": { "production": {

View File

@ -20,45 +20,46 @@
"author": "Tom Butcher", "author": "Tom Butcher",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.13.2", "axios": "^1.16.1",
"canvas": "^3.2.0", "canvas": "^3.2.3",
"etcd3": "^1.1.2", "etcd3": "^1.1.2",
"express": "^5.1.0", "express": "^5.2.1",
"form-data": "^4.0.5",
"ipp": "^2.0.1", "ipp": "^2.0.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.3",
"keycloak-connect": "^26.1.1", "keycloak-connect": "^26.1.1",
"lodash": "^4.17.21", "lodash": "^4.18.1",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"mongoose": "^9.0.0", "mongoose": "^9.6.2",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"node-thermal-printer": "^4.5.0", "node-thermal-printer": "^4.6.0",
"pdf-to-img": "^5.0.0", "pdf-to-img": "^6.1.0",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"socket.io": "^4.8.1", "socket.io": "^4.8.3",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.3",
"ws": "^8.18.3" "ws": "^8.20.1"
}, },
"devDependencies": { "devDependencies": {
"@ant-design/icons": "^6.1.0", "@ant-design/icons": "^6.2.3",
"antd": "^5.28.0", "@electron/rebuild": "^4.0.4",
"@electron/rebuild": "^4.0.1", "@vitejs/plugin-react": "^6.0.2",
"@vitejs/plugin-react": "^5.1.1", "antd": "^5.29.2",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"electron": "^38.7.1", "electron": "^42.1.0",
"electron-builder": "^26.0.12", "electron-builder": "^26.8.1",
"jest": "^30.2.0", "jest": "^30.4.2",
"nodemon": "^3.1.11", "nodemon": "^3.1.14",
"pkg": "^5.8.1", "pkg": "^5.8.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.2.0", "react": "^19.2.6",
"react-dom": "^19.2.0", "react-dom": "^19.2.6",
"rimraf": "^6.1.2", "rimraf": "^6.1.3",
"shx": "^0.3.4", "shx": "^0.4.0",
"supertest": "^7.1.4", "supertest": "^7.2.2",
"vite": "^7.2.4", "vite": "^8.0.13",
"vite-plugin-svgo": "^2.0.0", "vite-plugin-svgo": "^2.0.0",
"vite-plugin-svgr": "^4.5.0" "vite-plugin-svgr": "^5.2.0"
}, },
"pkg": { "pkg": {
"assets": [ "assets": [

2922
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

9
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,9 @@
allowBuilds:
canvas: true
chromedriver: true
electron-winstaller: true
electron: true
esbuild: true
protobufjs: true
sharp: true
unrs-resolver: true

View File

@ -15,7 +15,7 @@ export class DocumentPrinterClient {
documentPrinter = { documentPrinter = {
connection: { interface: "cups", host: "localhost", port: 9100 }, connection: { interface: "cups", host: "localhost", port: 9100 },
}, },
documentPrinterManager documentPrinterManager,
) { ) {
this.id = documentPrinter._id; this.id = documentPrinter._id;
this.documentPrinter = documentPrinter; this.documentPrinter = documentPrinter;
@ -23,9 +23,10 @@ export class DocumentPrinterClient {
this.queue = []; this.queue = [];
this.documentPrinterManager = documentPrinterManager; this.documentPrinterManager = documentPrinterManager;
this.currentJob = null; this.currentJob = null;
this.active = documentPrinter.active == true;
this.socketClient = documentPrinterManager.socketClient; this.socketClient = documentPrinterManager.socketClient;
this.interface = documentPrinter.connection.interface || "cups"; // cups, receipt, or os this.interface = documentPrinter.connection.interface || "cups"; // cups, receipt, or os
this.state = { type: "offline" }; this.state = { type: this.active == true ? "offline" : "inactive" };
this.isOnline = documentPrinter.online || false; this.isOnline = documentPrinter.online || false;
this.shouldReconnect = true; this.shouldReconnect = true;
this.isProcessingQueue = false; this.isProcessingQueue = false;
@ -38,7 +39,7 @@ export class DocumentPrinterClient {
initializeInterface() { initializeInterface() {
logger.info( logger.info(
`Initializing ${this.interface} interface for document printer ${this.id}` `Initializing ${this.interface} interface for document printer ${this.id}`,
); );
switch (this.interface) { switch (this.interface) {
case "cups": case "cups":
@ -104,6 +105,16 @@ export class DocumentPrinterClient {
await this.reconnect(); await this.reconnect();
} }
} }
this.documentPrinter = { ...this.documentPrinter, ...data };
if (Object.hasOwn(data || {}, "active") && data.active != this.active) {
if (data.active == true) {
await this.setActive();
} else {
await this.setInactive();
}
}
} }
registerEventHandlers() { registerEventHandlers() {
@ -126,8 +137,18 @@ export class DocumentPrinterClient {
} }
async connect() { async connect() {
if (this.active == false) {
logger.info( logger.info(
`Connecting to document printer ${this.id} (${this.interface})` `Document printer ${this.id} is not active, skipping connection`,
);
this.shouldReconnect = false;
this.isOnline = false;
this.state = { type: "inactive" };
await this.updateDocumentPrinterState();
return false;
}
logger.info(
`Connecting to document printer ${this.id} (${this.interface})`,
); );
clearTimeout(this.reconnectTimeout); clearTimeout(this.reconnectTimeout);
@ -140,7 +161,7 @@ export class DocumentPrinterClient {
if (!this.printerInterface) { if (!this.printerInterface) {
logger.error( logger.error(
`Cannot connect: No interface initialized for ${this.interface}` `Cannot connect: No interface initialized for ${this.interface}`,
); );
return false; return false;
} }
@ -150,7 +171,7 @@ export class DocumentPrinterClient {
if (result.error) { if (result.error) {
logger.error( logger.error(
`Error connecting to document printer ${this.documentPrinter.name}:`, `Error connecting to document printer ${this.documentPrinter.name}:`,
result.error result.error,
); );
this.isOnline = false; this.isOnline = false;
this.state = { type: "offline", message: result.error }; this.state = { type: "offline", message: result.error };
@ -158,27 +179,37 @@ export class DocumentPrinterClient {
return false; return false;
} }
logger.info( logger.info(
`Connected to document printer ${this.documentPrinter.name} (${this.interface})` `Connected to document printer ${this.documentPrinter.name} (${this.interface})`,
); );
return true; return true;
} }
async reconnect() { async reconnect() {
if (this.active == false) {
logger.info(
`Document printer ${this.documentPrinter.name} is inactive, skipping reconnect`,
);
this.shouldReconnect = false;
this.isOnline = false;
this.state = { type: "inactive" };
await this.updateDocumentPrinterState();
return false;
}
if (this.isOnline == true) { if (this.isOnline == true) {
logger.info( logger.info(
`Disconnecting from document printer ${this.documentPrinter.name} before reconnecting...` `Disconnecting from document printer ${this.documentPrinter.name} before reconnecting...`,
); );
await this.disconnect(); await this.disconnect();
} }
logger.info( logger.info(
`Reconnecting to document printer ${this.documentPrinter.name}` `Reconnecting to document printer ${this.documentPrinter.name}`,
); );
this.shouldReconnect = true; this.shouldReconnect = true;
const connectResult = await this.connect(); const connectResult = await this.connect();
if (connectResult == false) { if (connectResult == false) {
logger.error( logger.error(
`Error reconnecting to document printer ${this.documentPrinter.name}:`, `Error reconnecting to document printer ${this.documentPrinter.name}:`,
connectResult.error connectResult.error,
); );
if (this.shouldReconnect) { if (this.shouldReconnect) {
// Attempt to reconnect after delay // Attempt to reconnect after delay
@ -190,7 +221,7 @@ export class DocumentPrinterClient {
if (initializeResult == false) { if (initializeResult == false) {
logger.error( logger.error(
`Error initializing document printer ${this.documentPrinter.name}:`, `Error initializing document printer ${this.documentPrinter.name}:`,
initializeResult.error initializeResult.error,
); );
if (this.shouldReconnect) { if (this.shouldReconnect) {
// Attempt to reconnect after delay // Attempt to reconnect after delay
@ -213,7 +244,7 @@ export class DocumentPrinterClient {
if (result.error) { if (result.error) {
logger.error( logger.error(
`Error initializing document printer ${this.documentPrinter.name}:`, `Error initializing document printer ${this.documentPrinter.name}:`,
result.error result.error,
); );
this.state = { type: "offline", message: result.error }; this.state = { type: "offline", message: result.error };
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
@ -223,7 +254,7 @@ export class DocumentPrinterClient {
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
this.eventUpdateInterval = setInterval( this.eventUpdateInterval = setInterval(
this.handleEventUpdate.bind(this), this.handleEventUpdate.bind(this),
3000 3000,
); );
return true; return true;
} }
@ -236,7 +267,7 @@ export class DocumentPrinterClient {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error retrieving status for document printer ${this.documentPrinter.name}:`, `Error retrieving status for document printer ${this.documentPrinter.name}:`,
error error,
); );
} }
} }
@ -272,19 +303,19 @@ export class DocumentPrinterClient {
async deployDocumentJob(documentJob) { async deployDocumentJob(documentJob) {
logger.info( logger.info(
`Deploying document job ${documentJob._id} to ${this.documentPrinter.name}` `Deploying document job ${documentJob._id} to ${this.documentPrinter.name}`,
); );
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot deploy job: Document printer not connected (${this.documentPrinter.name})` `Cannot deploy job: Document printer not connected (${this.documentPrinter.name})`,
); );
return { error: "Document printer not connected" }; return { error: "Document printer not connected" };
} }
if (!this.printerInterface) { if (!this.printerInterface) {
logger.error( logger.error(
`Cannot deploy job: No interface initialized (${this.documentPrinter.name})` `Cannot deploy job: No interface initialized (${this.documentPrinter.name})`,
); );
return { error: "No interface initialized" }; return { error: "No interface initialized" };
} }
@ -310,7 +341,7 @@ export class DocumentPrinterClient {
}); });
if (!documentTemplate) { if (!documentTemplate) {
logger.error( logger.error(
`Document template not found for job ${documentJob._id}` `Document template not found for job ${documentJob._id}`,
); );
return { error: "Document template not found" }; return { error: "Document template not found" };
} }
@ -325,20 +356,20 @@ export class DocumentPrinterClient {
}); });
if (!pdfObj) { if (!pdfObj) {
logger.error( logger.error(
`Failed to render document template for job ${documentJob._id}` `Failed to render document template for job ${documentJob._id}`,
); );
return { error: "Failed to render document template" }; return { error: "Failed to render document template" };
} }
const result = await this.printerInterface.deploy( const result = await this.printerInterface.deploy(
documentJob, documentJob,
pdfObj.pdf pdfObj.pdf,
); );
await this.updateJobState(documentJob._id, { await this.updateJobState(documentJob._id, {
type: "deploying", type: "deploying",
progress: 1.0, progress: 1.0,
}); });
logger.info( logger.info(
`Deployed document job ${documentJob._id} to ${this.documentPrinter.name}` `Deployed document job ${documentJob._id} to ${this.documentPrinter.name}`,
); );
await this.updateJobState(documentJob._id, { await this.updateJobState(documentJob._id, {
type: "queued", type: "queued",
@ -349,21 +380,21 @@ export class DocumentPrinterClient {
this.queue.push(documentJob._id); this.queue.push(documentJob._id);
} else { } else {
logger.warn( logger.warn(
`Job ${documentJob._id} is already in the queue for ${this.documentPrinter.name}` `Job ${documentJob._id} is already in the queue for ${this.documentPrinter.name}`,
); );
} }
this.startQueue(); this.startQueue();
return result; return result;
} else { } else {
logger.error( logger.error(
`Interface ${this.interface} does not support deployDocumentJob` `Interface ${this.interface} does not support deployDocumentJob`,
); );
return { error: "Interface does not support this operation" }; return { error: "Interface does not support this operation" };
} }
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error deploying document job to ${this.documentPrinter.name}:`, `Error deploying document job to ${this.documentPrinter.name}:`,
error error,
); );
await this.updateJobState(documentJob._id, { await this.updateJobState(documentJob._id, {
type: "error", type: "error",
@ -377,20 +408,20 @@ export class DocumentPrinterClient {
async startQueue() { async startQueue() {
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot start queue: Document printer not connected (${this.documentPrinter.name})` `Cannot start queue: Document printer not connected (${this.documentPrinter.name})`,
); );
return { error: "Document printer not connected" }; return { error: "Document printer not connected" };
} }
if (!this.printerInterface) { if (!this.printerInterface) {
logger.error( logger.error(
`Cannot start queue: No interface initialized (${this.documentPrinter.name})` `Cannot start queue: No interface initialized (${this.documentPrinter.name})`,
); );
return { error: "No interface initialized" }; return { error: "No interface initialized" };
} }
// Prevent concurrent queue processing // Prevent concurrent queue processing
if (this.isProcessingQueue) { if (this.isProcessingQueue) {
logger.debug( logger.debug(
`Queue is already being processed for ${this.documentPrinter.name}` `Queue is already being processed for ${this.documentPrinter.name}`,
); );
return { info: "Queue is already being processed" }; return { info: "Queue is already being processed" };
} }
@ -404,19 +435,19 @@ export class DocumentPrinterClient {
async runQueue() { async runQueue() {
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot print next job: Document printer not connected (${this.documentPrinter.name})` `Cannot print next job: Document printer not connected (${this.documentPrinter.name})`,
); );
return { error: "Document printer not connected" }; return { error: "Document printer not connected" };
} }
if (!this.printerInterface) { if (!this.printerInterface) {
logger.error( logger.error(
`Cannot print next job: No interface initialized (${this.documentPrinter.name})` `Cannot print next job: No interface initialized (${this.documentPrinter.name})`,
); );
return { error: "No interface initialized" }; return { error: "No interface initialized" };
} }
if (this.state.type != "standby") { if (this.state.type != "standby") {
logger.error( logger.error(
`Cannot print next job: Document printer not in standby mode (${this.documentPrinter.name})` `Cannot print next job: Document printer not in standby mode (${this.documentPrinter.name})`,
); );
return { error: "Document printer not in standby mode" }; return { error: "Document printer not in standby mode" };
} }
@ -424,7 +455,7 @@ export class DocumentPrinterClient {
// Prevent concurrent queue processing // Prevent concurrent queue processing
if (this.isProcessingQueue) { if (this.isProcessingQueue) {
logger.debug( logger.debug(
`Queue is already being processed for ${this.documentPrinter.name}` `Queue is already being processed for ${this.documentPrinter.name}`,
); );
return { info: "Queue is already being processed" }; return { info: "Queue is already being processed" };
} }
@ -439,7 +470,7 @@ export class DocumentPrinterClient {
// Re-check connection status before each job // Re-check connection status before each job
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Printer went offline while printing (${this.documentPrinter.name})` `Printer went offline while printing (${this.documentPrinter.name})`,
); );
this.state = { this.state = {
type: "offline", type: "offline",
@ -450,7 +481,7 @@ export class DocumentPrinterClient {
} }
if (!this.printerInterface) { if (!this.printerInterface) {
logger.error( logger.error(
`Printer interface lost while printing (${this.documentPrinter.name})` `Printer interface lost while printing (${this.documentPrinter.name})`,
); );
this.state = { this.state = {
type: "offline", type: "offline",
@ -472,7 +503,7 @@ export class DocumentPrinterClient {
await this.printerInterface.print(jobId); await this.printerInterface.print(jobId);
logger.info( logger.info(
`Successfully printed job ${jobId} for ${this.documentPrinter.name}` `Successfully printed job ${jobId} for ${this.documentPrinter.name}`,
); );
// Only remove job from queue after successful printing // Only remove job from queue after successful printing
this.queue.shift(); this.queue.shift();
@ -480,7 +511,7 @@ export class DocumentPrinterClient {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error printing job ${jobId} for ${this.documentPrinter.name}:`, `Error printing job ${jobId} for ${this.documentPrinter.name}:`,
error error,
); );
// Remove failed job from queue to prevent infinite retry loop // Remove failed job from queue to prevent infinite retry loop
@ -492,7 +523,7 @@ export class DocumentPrinterClient {
} }
logger.info( logger.info(
`Finished printing all jobs for ${this.documentPrinter.name}` `Finished printing all jobs for ${this.documentPrinter.name}`,
); );
this.state = { type: "standby", message: null }; this.state = { type: "standby", message: null };
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
@ -513,7 +544,7 @@ export class DocumentPrinterClient {
await this.printerInterface.disconnect(); await this.printerInterface.disconnect();
} }
this.isOnline = false; this.isOnline = false;
this.state = { type: "offline" }; this.state = { type: this.active == false ? "inactive" : "offline" };
this.isProcessingQueue = false; this.isProcessingQueue = false;
this.queue = []; // Clear queue on disconnect this.queue = []; // Clear queue on disconnect
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
@ -521,4 +552,21 @@ export class DocumentPrinterClient {
logger.info(`Successfully disconnected from ${this.documentPrinter.name}`); logger.info(`Successfully disconnected from ${this.documentPrinter.name}`);
return true; return true;
} }
async setInactive() {
this.active = false;
this.documentPrinter.active = false;
this.isOnline = false;
await this.disconnect();
this.state = { type: "inactive" };
await this.updateDocumentPrinterState();
}
async setActive() {
this.active = true;
this.documentPrinter.active = true;
this.shouldReconnect = true;
this.isOnline = false;
await this.reconnect();
}
} }

View File

@ -86,7 +86,7 @@ export class DocumentPrinterManager {
logger.debug("Handling document printer update for id:", id); logger.debug("Handling document printer update for id:", id);
const documentPrinter = this.getDocumentPrinterClient(id); const documentPrinter = this.getDocumentPrinterClient(id);
if (documentPrinter) { if (documentPrinter) {
documentPrinter.updateDocumentPrinter(data.object); await documentPrinter.updateDocumentPrinter(data.object);
} }
} }

View File

@ -64,8 +64,8 @@ const App = () => {
prev.map((printer) => prev.map((printer) =>
printer._id === newPrinter._id printer._id === newPrinter._id
? _.merge(printer, newPrinter) ? _.merge(printer, newPrinter)
: printer : printer,
) ),
); );
}); });
@ -73,7 +73,7 @@ const App = () => {
"setDocumentPrinters", "setDocumentPrinters",
(newDocumentPrinters) => { (newDocumentPrinters) => {
setDocumentPrinters(newDocumentPrinters); setDocumentPrinters(newDocumentPrinters);
} },
); );
window.electronAPI.onIPCData("setDocumentPrinter", (newDocumentPrinter) => { window.electronAPI.onIPCData("setDocumentPrinter", (newDocumentPrinter) => {
@ -81,8 +81,8 @@ const App = () => {
prev.map((documentPrinter) => prev.map((documentPrinter) =>
documentPrinter._id === newDocumentPrinter._id documentPrinter._id === newDocumentPrinter._id
? _.merge(documentPrinter, newDocumentPrinter) ? _.merge(documentPrinter, newDocumentPrinter)
: documentPrinter : documentPrinter,
) ),
); );
}); });
@ -229,7 +229,7 @@ const App = () => {
className="ant-menu-horizontal ant-menu-light electron-drag-area" className="ant-menu-horizontal ant-menu-light electron-drag-area"
style={{ style={{
lineHeight: "40px", lineHeight: "40px",
padding: "0 8px 0 75px", padding: "0 8px 0 83px",
}} }}
justify="space-between" justify="space-between"
> >
@ -246,6 +246,7 @@ const App = () => {
flexWrap: "wrap", flexWrap: "wrap",
border: 0, border: 0,
lineHeight: "38px", lineHeight: "38px",
marginTop: "1px",
}} }}
overflowedIndicator={ overflowedIndicator={
<Button type="text" icon={<MenuOutlined />} /> <Button type="text" icon={<MenuOutlined />} />

View File

@ -60,6 +60,14 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
status = "success"; status = "success";
text = "Unconsumed"; text = "Unconsumed";
break; break;
case "inactive":
status = "default";
text = "Inactive";
break;
case "connecting":
status = "warning";
text = "Connecting";
break;
case "error": case "error":
status = "error"; status = "error";
text = "Error"; text = "Error";

View File

@ -23,7 +23,11 @@ export class PrinterClient {
this.socketClient = printerManager.socketClient; this.socketClient = printerManager.socketClient;
this.database = new PrinterDatabase(this.socketClient, this.printer); this.database = new PrinterDatabase(this.socketClient, this.printer);
this.printerFileManager = new PrinterFileManager(this); this.printerFileManager = new PrinterFileManager(this);
this.state = { type: "offline", message: "Moonraker disconnected." }; this.active = printer.active !== false;
this.state =
this.active == true
? { type: "offline", message: "Moonraker disconnected." }
: { type: "inactive" };
this.klippyState = { type: "offline", message: "Klippy disconnected." }; this.klippyState = { type: "offline", message: "Klippy disconnected." };
this.config = printer.moonraker; this.config = printer.moonraker;
this.version = printer.version; this.version = printer.version;
@ -37,10 +41,11 @@ export class PrinterClient {
this.motionObject = {}; this.motionObject = {};
this.miscObject = {}; this.miscObject = {};
this.currentFilamentStock = printer.currentFilamentStock; this.currentFilamentStock = printer.currentFilamentStock;
this.currentFilament = null; this.currentFilamentSku = null;
this.currentFilamentUsed = 0; this.currentFilamentUsed = 0;
this.registerEventHandlers(); this.registerEventHandlers();
this.subscribeToActions(); this.subscribeToActions();
this.subscribeToObjectUpdates();
this.baseSubscription = { this.baseSubscription = {
print_stats: null, print_stats: null,
display_status: null, display_status: null,
@ -74,32 +79,32 @@ export class PrinterClient {
registerEventHandlers() { registerEventHandlers() {
// Register event handlers for Moonraker notifications // Register event handlers for Moonraker notifications
this.jsonRpc.registerMethod( this.jsonRpc.registerMethod(
"notify_gcode_response" "notify_gcode_response",
//this.handleGcodeResponse.bind(this), //this.handleGcodeResponse.bind(this),
); );
this.jsonRpc.registerMethod( this.jsonRpc.registerMethod(
"notify_status_update", "notify_status_update",
this.handleStatusUpdate.bind(this) this.handleStatusUpdate.bind(this),
); );
this.jsonRpc.registerMethod( this.jsonRpc.registerMethod(
"notify_klippy_disconnected", "notify_klippy_disconnected",
this.handleKlippyDisconnected.bind(this) this.handleKlippyDisconnected.bind(this),
); );
this.jsonRpc.registerMethod( this.jsonRpc.registerMethod(
"notify_klippy_ready", "notify_klippy_ready",
this.handleKlippyReady.bind(this) this.handleKlippyReady.bind(this),
); );
this.jsonRpc.registerMethod( this.jsonRpc.registerMethod(
"notify_filelist_changed", "notify_filelist_changed",
this.handleFileListChanged.bind(this) this.handleFileListChanged.bind(this),
); );
this.jsonRpc.registerMethod( this.jsonRpc.registerMethod(
"notify_metadata_update", "notify_metadata_update",
this.handleMetadataUpdate.bind(this) this.handleMetadataUpdate.bind(this),
); );
this.jsonRpc.registerMethod( this.jsonRpc.registerMethod(
"notify_power_changed", "notify_power_changed",
this.handlePowerChanged.bind(this) this.handlePowerChanged.bind(this),
); );
} }
@ -110,6 +115,31 @@ export class PrinterClient {
}); });
} }
subscribeToObjectUpdates() {
this.socketClient.subscribeToObjectUpdates({
objectType: "printer",
_id: this.id,
});
}
async updatePrinter(data) {
logger.debug(`Updating printer ${this.id} with data...`);
this.printer = { ...this.printer, ...data };
this.database.printer = this.printer;
if (data?.moonraker) {
this.config = data.moonraker;
}
if (Object.hasOwn(data || {}, "active") && data.active != this.active) {
if (data.active == true) {
await this.setActive();
} else {
await this.setInactive();
}
}
}
async _runQueueMutation(task) { async _runQueueMutation(task) {
const executeTask = async () => task(); const executeTask = async () => task();
this.queueMutationChain = this.queueMutationChain this.queueMutationChain = this.queueMutationChain
@ -119,6 +149,15 @@ export class PrinterClient {
} }
async connect() { async connect() {
if (this.active == false) {
logger.info(`Printer ${this.id} is not active, skipping connection`);
this.shouldReconnect = false;
this.isOnline = false;
this.state = { type: "inactive" };
await this.updatePrinterState();
return false;
}
const { protocol, host, port } = this.config; const { protocol, host, port } = this.config;
const wsUrl = `${protocol}://${host}:${port}/websocket`; const wsUrl = `${protocol}://${host}:${port}/websocket`;
@ -143,7 +182,10 @@ export class PrinterClient {
this.socket.addEventListener("close", () => { this.socket.addEventListener("close", () => {
logger.info(`Disconnected from Moonraker (${this.printer.name})`); logger.info(`Disconnected from Moonraker (${this.printer.name})`);
this.isOnline = false; this.isOnline = false;
this.state = { type: "offline", message: "Moonraker disconnected." }; this.state =
this.active == true
? { type: "offline", message: "Moonraker disconnected." }
: { type: "inactive" };
this.connectedAt = null; this.connectedAt = null;
this.updatePrinterState(); this.updatePrinterState();
this.connectionId = null; this.connectionId = null;
@ -182,14 +224,14 @@ export class PrinterClient {
.then(async (result) => { .then(async (result) => {
this.connectionId = result.connection_id; this.connectionId = result.connection_id;
logger.info( logger.info(
`Connection identified with ID: ${this.connectionId} (${this.printer.name})` `Connection identified with ID: ${this.connectionId} (${this.printer.name})`,
); );
await this.initialize(); await this.initialize();
}) })
.catch((error) => { .catch((error) => {
logger.error( logger.error(
`Error identifying connection (${this.printer.name}):`, `Error identifying connection (${this.printer.name}):`,
error error,
); );
}); });
} }
@ -207,7 +249,7 @@ export class PrinterClient {
await this.syncSubJobs(); await this.syncSubJobs();
this.eventUpdateInterval = setInterval( this.eventUpdateInterval = setInterval(
this.handleEventUpdate.bind(this), this.handleEventUpdate.bind(this),
500 500,
); );
} }
@ -221,20 +263,20 @@ export class PrinterClient {
logger.info( logger.info(
"Server:", "Server:",
`Moonraker ${serverResult.moonraker_version} (${this.printer.name})`, `Moonraker ${serverResult.moonraker_version} (${this.printer.name})`,
`State: ${this.klippyState.type}` `State: ${this.klippyState.type}`,
); );
try { try {
const klippyResult = await this.jsonRpc.callMethod("printer.info"); const klippyResult = await this.jsonRpc.callMethod("printer.info");
logger.info( logger.info(
`Klippy info for ${this.printer.name}: ${klippyResult.hostname}, ${klippyResult.software_version}` `Klippy info for ${this.printer.name}: ${klippyResult.hostname}, ${klippyResult.software_version}`,
); );
// Update firmware version in database // Update firmware version in database
await this.database.updatePrinterFirmware( await this.database.updatePrinterFirmware(
klippyResult.software_version klippyResult.software_version,
); );
logger.info( logger.info(
`Updated firmware version for ${this.printer.name} to ${klippyResult.software_version}` `Updated firmware version for ${this.printer.name} to ${klippyResult.software_version}`,
); );
if (klippyResult.state === "error" && klippyResult.state_message) { if (klippyResult.state === "error" && klippyResult.state_message) {
@ -245,7 +287,7 @@ export class PrinterClient {
type: "error", type: "error",
message: klippyResult.state_message, message: klippyResult.state_message,
actions: ["restartPrinter"], actions: ["restartPrinter"],
}) }),
); );
} }
@ -257,7 +299,7 @@ export class PrinterClient {
type: "error", type: "error",
message: klippyResult.state_message, message: klippyResult.state_message,
actions: ["restartPrinter"], actions: ["restartPrinter"],
}) }),
); );
} }
@ -269,19 +311,19 @@ export class PrinterClient {
message: klippyResult.state_message, message: klippyResult.state_message,
priority: 8, priority: 8,
timestamp: new Date(), timestamp: new Date(),
}) }),
); );
} }
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error getting Klippy info for ${this.printer.name}:`, `Error getting Klippy info for ${this.printer.name}:`,
error error,
); );
} }
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error getting server info for ${this.printer.name}:`, `Error getting server info for ${this.printer.name}:`,
error error,
); );
} }
} }
@ -294,7 +336,7 @@ export class PrinterClient {
for (const file of result) { for (const file of result) {
if (file.path.startsWith("farmcontrol/")) { if (file.path.startsWith("farmcontrol/")) {
this.printrFileIds.push( this.printrFileIds.push(
file.path.replace("farmcontrol/", "").replace(".gcode", "") file.path.replace("farmcontrol/", "").replace(".gcode", ""),
); );
} }
} }
@ -305,17 +347,17 @@ export class PrinterClient {
logger.info(`Getting current filament for printer: ${this.printer.name}`); logger.info(`Getting current filament for printer: ${this.printer.name}`);
if (!this.currentFilamentStock || !this.currentFilamentStock?._id) { if (!this.currentFilamentStock || !this.currentFilamentStock?._id) {
logger.warn( logger.warn(
`No current filament stock found for printer: ${this.printer.name}` `No current filament stock found for printer: ${this.printer.name}`,
); );
return null; return null;
} }
const result = await this.socketClient.getObject({ const result = await this.socketClient.getObject({
objectType: "filamentStock", objectType: "filamentStock",
_id: this.currentFilamentStock._id, _id: this.currentFilamentStock._id,
populate: ["filament"], populate: ["filamentSku"],
}); });
this.currentFilament = result.filament; this.currentFilamentSku = result.filamentSku;
return this.currentFilament; return this.currentFilamentSku;
} }
async getQueuedJobIds() { async getQueuedJobIds() {
@ -332,7 +374,7 @@ export class PrinterClient {
logger.info(`Getting state of (${this.printer.name})`); logger.info(`Getting state of (${this.printer.name})`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot send command: Not connected to Moonraker. (${this.printer.name})` `Cannot send command: Not connected to Moonraker. (${this.printer.name})`,
); );
return false; return false;
} }
@ -354,7 +396,7 @@ export class PrinterClient {
try { try {
const result = await this.jsonRpc.callMethodWithKwargs( const result = await this.jsonRpc.callMethodWithKwargs(
"printer.objects.query", "printer.objects.query",
{ objects: this.baseSubscription } { objects: this.baseSubscription },
); );
logger.debug(`Command sent to ${this.printer.name}`); logger.debug(`Command sent to ${this.printer.name}`);
if (result.status != undefined) { if (result.status != undefined) {
@ -380,12 +422,12 @@ export class PrinterClient {
logger.debug( logger.debug(
"Combined subscriptions:", "Combined subscriptions:",
Object.keys(allSubscriptions).join(", ") Object.keys(allSubscriptions).join(", "),
); );
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot send command: Not connected to Moonraker (${this.printer.name})` `Cannot send command: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -406,7 +448,7 @@ export class PrinterClient {
logger.info(`Sending ${command.method} command to (${this.printer.name})`); logger.info(`Sending ${command.method} command to (${this.printer.name})`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot send command: Not connected to Moonraker (${this.printer.name})` `Cannot send command: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -414,7 +456,7 @@ export class PrinterClient {
try { try {
const result = await this.jsonRpc.callMethodWithKwargs( const result = await this.jsonRpc.callMethodWithKwargs(
command.method, command.method,
command.params command.params,
); );
logger.debug(`Command sent to ${this.printer.name}`); logger.debug(`Command sent to ${this.printer.name}`);
if (result.status != undefined) { if (result.status != undefined) {
@ -443,7 +485,7 @@ export class PrinterClient {
const oldState = this.state.type; const oldState = this.state.type;
if (newState !== oldState) { if (newState !== oldState) {
logger.info( logger.info(
`Printer ${this.printer.name} state changed from ${this.state.type} to ${newState}` `Printer ${this.printer.name} state changed from ${this.state.type} to ${newState}`,
); );
if (oldState == "printing" && newState == "complete") { if (oldState == "printing" && newState == "complete") {
printerFinished = true; printerFinished = true;
@ -476,7 +518,7 @@ export class PrinterClient {
const newProgress = status.display_status.progress; const newProgress = status.display_status.progress;
if (newProgress !== this.state.progress) { if (newProgress !== this.state.progress) {
logger.info( logger.info(
`Printer ${this.printer.name} progress changed from ${this.state.progress} to ${newProgress}` `Printer ${this.printer.name} progress changed from ${this.state.progress} to ${newProgress}`,
); );
this.state.progress = newProgress; this.state.progress = newProgress;
progressChanged = true; progressChanged = true;
@ -639,7 +681,7 @@ export class PrinterClient {
if (newFilamentDetected !== this.filamentDetected) { if (newFilamentDetected !== this.filamentDetected) {
console.log(status["filament_switch_sensor fsensor"]); console.log(status["filament_switch_sensor fsensor"]);
logger.info( logger.info(
`Printer ${this.printer.name} filament detection changed from ${this.filamentDetected} to ${newFilamentDetected}.` `Printer ${this.printer.name} filament detection changed from ${this.filamentDetected} to ${newFilamentDetected}.`,
); );
this.filamentDetected = newFilamentDetected; this.filamentDetected = newFilamentDetected;
@ -701,11 +743,11 @@ export class PrinterClient {
if (stateChanged == true) { if (stateChanged == true) {
if (printerFinished == true && isCurrentJobSubJob == true) { if (printerFinished == true && isCurrentJobSubJob == true) {
logger.info( logger.info(
`Subjob ${this.currentSubJob._id} completed, posting part stock items for printer ${this.id}` `Subjob ${this.currentSubJob._id} completed, posting part stock items for printer ${this.id}`,
); );
await this.database.setSubJobFinishedAt( await this.database.setSubJobFinishedAt(
this.currentSubJob._id, this.currentSubJob._id,
new Date() new Date(),
); );
await this.database.postSubJobPartStockItems(this.currentSubJob._id); await this.database.postSubJobPartStockItems(this.currentSubJob._id);
} }
@ -720,7 +762,7 @@ export class PrinterClient {
this.currentSubJob.startedAt = new Date(); this.currentSubJob.startedAt = new Date();
await this.database.setSubJobStartedAt( await this.database.setSubJobStartedAt(
this.currentSubJob._id, this.currentSubJob._id,
new Date() new Date(),
); );
} }
if (this.currentJob.startedAt == null) { if (this.currentJob.startedAt == null) {
@ -738,7 +780,7 @@ export class PrinterClient {
}; };
await this.database.updateSubJobState( await this.database.updateSubJobState(
this.currentSubJob._id, this.currentSubJob._id,
subJobState subJobState,
); );
await this.database.updateJobState(this.currentJob._id); await this.database.updateJobState(this.currentJob._id);
} }
@ -780,13 +822,13 @@ export class PrinterClient {
) { ) {
logger.debug( logger.debug(
"Updating stock event value:", "Updating stock event value:",
this.currentFilamentUsed.toFixed(2) this.currentFilamentUsed.toFixed(2),
); );
this.database.updateFilamentStockWeight( this.database.updateFilamentStockWeight(
this.currentFilamentStock, this.currentFilamentStock,
this.currentFilamentUsed, this.currentFilamentUsed,
this.currentSubJob, this.currentSubJob,
this.currentJob this.currentJob,
); );
} }
} }
@ -794,11 +836,15 @@ export class PrinterClient {
async updatePrinterState() { async updatePrinterState() {
try { try {
const state = const state =
this.klippyState.type !== "ready" ? this.klippyState : this.state; this.active == false
? { type: "inactive" }
: this.klippyState.type !== "ready"
? this.klippyState
: this.state;
await this.database.updatePrinterState( await this.database.updatePrinterState(
state, state,
this.isOnline, this.isOnline,
this.connectedAt this.connectedAt,
); );
} catch (error) { } catch (error) {
logger.error(`Failed to update printer state:`, error); logger.error(`Failed to update printer state:`, error);
@ -822,11 +868,11 @@ export class PrinterClient {
const serverSubJobs = await this.database.getQueuedSubJobs(); const serverSubJobs = await this.database.getQueuedSubJobs();
const queuedSubJobs = serverSubJobs.filter((subJob) => const queuedSubJobs = serverSubJobs.filter((subJob) =>
this.queuedJobIds.includes(subJob.moonrakerJobId) this.queuedJobIds.includes(subJob.moonrakerJobId),
); );
const unqueuedSubJobs = serverSubJobs.filter( const unqueuedSubJobs = serverSubJobs.filter(
(subJob) => !this.queuedJobIds.includes(subJob.moonrakerJobId) (subJob) => !this.queuedJobIds.includes(subJob.moonrakerJobId),
); );
this.queue = queuedSubJobs; this.queue = queuedSubJobs;
@ -850,7 +896,7 @@ export class PrinterClient {
printingTypes.includes(stateType) printingTypes.includes(stateType)
) { ) {
logger.info( logger.info(
`No current subjob or job, setting first unqueued subjob as current...` `No current subjob or job, setting first unqueued subjob as current...`,
); );
const targetSubJob = unqueuedSubJobs[0]; const targetSubJob = unqueuedSubJobs[0];
const targetJob = await this.database.getJobById(targetSubJob.job); const targetJob = await this.database.getJobById(targetSubJob.job);
@ -869,7 +915,7 @@ export class PrinterClient {
}); });
this.currentSubJob.state.type = stateType; this.currentSubJob.state.type = stateType;
this.currentJob.state = await this.database.updateJobState( this.currentJob.state = await this.database.updateJobState(
this.currentJob._id this.currentJob._id,
); );
} }
} }
@ -941,7 +987,7 @@ export class PrinterClient {
handleMetadataUpdate(metadata) { handleMetadataUpdate(metadata) {
logger.info( logger.info(
`Metadata updated for ${this.printer.name}:`, `Metadata updated for ${this.printer.name}:`,
metadata.filename metadata.filename,
); );
} }
@ -953,7 +999,7 @@ export class PrinterClient {
logger.info(`Uploading G-code file ${fileName} to ${this.printer.name}`); logger.info(`Uploading G-code file ${fileName} to ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot upload file: Not connected to Moonraker (${this.printer.name})` `Cannot upload file: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -986,14 +1032,14 @@ export class PrinterClient {
headers, headers,
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round( const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total (progressEvent.loaded * 100) / progressEvent.total,
); );
logger.debug( logger.debug(
`Uploading file to ${this.printer.name}: ` + `Uploading file to ${this.printer.name}: ` +
fileName + fileName +
" " + " " +
percentCompleted + percentCompleted +
"%" "%",
); );
this.socketManager.broadcast("notify_printer_update", { this.socketManager.broadcast("notify_printer_update", {
printerId: this.id, printerId: this.id,
@ -1011,7 +1057,7 @@ export class PrinterClient {
} }
logger.info( logger.info(
`Successfully uploaded file ${fileName} to ${this.printer.name}` `Successfully uploaded file ${fileName} to ${this.printer.name}`,
); );
return true; return true;
} catch (error) { } catch (error) {
@ -1024,7 +1070,7 @@ export class PrinterClient {
// Add subJob to queue // Add subJob to queue
this.deploySubJobQueue.push(subJob); this.deploySubJobQueue.push(subJob);
logger.debug( logger.debug(
`Queued sub job ${subJob._id} for deployment. Queue size: ${this.deploySubJobQueue.length}` `Queued sub job ${subJob._id} for deployment. Queue size: ${this.deploySubJobQueue.length}`,
); );
const now = Date.now(); const now = Date.now();
@ -1065,7 +1111,7 @@ export class PrinterClient {
this.deploySubJobQueue = []; this.deploySubJobQueue = [];
logger.info( logger.info(
`Processing ${subJobsToDeploy.length} queued sub job(s) for printer ${this.id}` `Processing ${subJobsToDeploy.length} queued sub job(s) for printer ${this.id}`,
); );
// Process sub jobs in parallel with a 250ms stagger between starts // Process sub jobs in parallel with a 250ms stagger between starts
@ -1082,10 +1128,10 @@ export class PrinterClient {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error deploying sub job ${subJob._id} to printer ${this.id}:`, `Error deploying sub job ${subJob._id} to printer ${this.id}:`,
error error,
); );
} }
})() })(),
); );
await Promise.all(deployPromises); await Promise.all(deployPromises);
@ -1098,7 +1144,7 @@ export class PrinterClient {
"to printer:", "to printer:",
this.id, this.id,
"with gcode file:", "with gcode file:",
`${subJob.gcodeFile.name}` `${subJob.gcodeFile.name}`,
); );
const gcodeFile = await this.socketClient.getObject({ const gcodeFile = await this.socketClient.getObject({
objectType: "gcodeFile", objectType: "gcodeFile",
@ -1117,7 +1163,7 @@ export class PrinterClient {
progress: (progress / 100 / 2).toFixed(2), progress: (progress / 100 / 2).toFixed(2),
}); });
await this.database.updateJobState(subJob.job._id); await this.database.updateJobState(subJob.job._id);
} },
); );
if (!file) { if (!file) {
throw new Error("Error getting file"); throw new Error("Error getting file");
@ -1134,7 +1180,7 @@ export class PrinterClient {
progress: (progress / 100 / 2 + 0.5).toFixed(2), progress: (progress / 100 / 2 + 0.5).toFixed(2),
}); });
await this.database.updateJobState(subJob.job._id); await this.database.updateJobState(subJob.job._id);
} },
); );
this.printrFileIds.push(gcodeFile.file.toString()); this.printrFileIds.push(gcodeFile.file.toString());
if (!uploadResult) { if (!uploadResult) {
@ -1161,7 +1207,7 @@ export class PrinterClient {
const mostRecentQueuedJob = queuedJobs[queuedJobs.length - 1]; const mostRecentQueuedJob = queuedJobs[queuedJobs.length - 1];
const updatedSubJob = await this.database.setSubJobMoonrakerJobId( const updatedSubJob = await this.database.setSubJobMoonrakerJobId(
subJob._id, subJob._id,
mostRecentQueuedJob.job_id mostRecentQueuedJob.job_id,
); );
await this.database.updateSubJobState(subJob._id, { await this.database.updateSubJobState(subJob._id, {
type: "queued", type: "queued",
@ -1206,7 +1252,7 @@ export class PrinterClient {
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot set temperature: Not connected to Moonraker (${this.printer.name})` `Cannot set temperature: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -1217,20 +1263,20 @@ export class PrinterClient {
// Handle extruder temperature // Handle extruder temperature
if (temperature.extruder?.target !== undefined) { if (temperature.extruder?.target !== undefined) {
gcodeCommands.push( gcodeCommands.push(
`SET_HEATER_TEMPERATURE HEATER=extruder TARGET=${temperature.extruder.target}` `SET_HEATER_TEMPERATURE HEATER=extruder TARGET=${temperature.extruder.target}`,
); );
} }
// Handle bed temperature // Handle bed temperature
if (temperature.bed?.target !== undefined) { if (temperature.bed?.target !== undefined) {
gcodeCommands.push( gcodeCommands.push(
`SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=${temperature.bed.target}` `SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=${temperature.bed.target}`,
); );
} }
if (gcodeCommands.length === 0) { if (gcodeCommands.length === 0) {
logger.warn( logger.warn(
`No valid temperature targets provided for ${this.printer.name}` `No valid temperature targets provided for ${this.printer.name}`,
); );
return false; return false;
} }
@ -1245,7 +1291,7 @@ export class PrinterClient {
if (!result) { if (!result) {
logger.error( logger.error(
`Failed to set temperature with command: ${gcodeCommand}` `Failed to set temperature with command: ${gcodeCommand}`,
); );
return false; return false;
} }
@ -1258,7 +1304,7 @@ export class PrinterClient {
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error setting temperature for ${this.printer.name}:`, `Error setting temperature for ${this.printer.name}:`,
error error,
); );
return false; return false;
} }
@ -1268,7 +1314,7 @@ export class PrinterClient {
logger.info(`Restarting printer firmware for ${this.printer.name}`); logger.info(`Restarting printer firmware for ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot restart printer firmware: Not connected to Moonraker (${this.printer.name})` `Cannot restart printer firmware: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -1281,13 +1327,13 @@ export class PrinterClient {
throw new Error("Failed to restart printer firmware"); throw new Error("Failed to restart printer firmware");
} }
logger.info( logger.info(
`Successfully restarted printer firmware for ${this.printer.name}` `Successfully restarted printer firmware for ${this.printer.name}`,
); );
return true; return true;
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error restarting printer firmware for ${this.printer.name}:`, `Error restarting printer firmware for ${this.printer.name}:`,
error error,
); );
return false; return false;
} }
@ -1297,7 +1343,7 @@ export class PrinterClient {
logger.info(`Restarting printer for ${this.printer.name}`); logger.info(`Restarting printer for ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot restart printer: Not connected to Moonraker (${this.printer.name})` `Cannot restart printer: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -1320,7 +1366,7 @@ export class PrinterClient {
logger.info(`Restarting moonraker server for ${this.printer.name}`); logger.info(`Restarting moonraker server for ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot restart moonraker server: Not connected to moonraker server (${this.printer.name})` `Cannot restart moonraker server: Not connected to moonraker server (${this.printer.name})`,
); );
return false; return false;
} }
@ -1333,13 +1379,13 @@ export class PrinterClient {
throw new Error("Failed to restart moonraker server"); throw new Error("Failed to restart moonraker server");
} }
logger.info( logger.info(
`Successfully restarted moonraker server for ${this.printer.name}` `Successfully restarted moonraker server for ${this.printer.name}`,
); );
return true; return true;
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error restarting moonraker server for ${this.printer.name}:`, `Error restarting moonraker server for ${this.printer.name}:`,
error error,
); );
return false; return false;
} }
@ -1349,7 +1395,7 @@ export class PrinterClient {
logger.info(`Starting queue for ${this.printer.name}`); logger.info(`Starting queue for ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot start queue: Not connected to Moonraker (${this.printer.name})` `Cannot start queue: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -1372,7 +1418,7 @@ export class PrinterClient {
logger.info(`Pausing job for ${this.printer.name}`); logger.info(`Pausing job for ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot pause job: Not connected to Moonraker (${this.printer.name})` `Cannot pause job: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -1395,7 +1441,7 @@ export class PrinterClient {
logger.info(`Resuming job for ${this.printer.name}`); logger.info(`Resuming job for ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot resume job: Not connected to Moonraker (${this.printer.name})` `Cannot resume job: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -1418,7 +1464,7 @@ export class PrinterClient {
logger.info(`Cancelling job for ${this.printer.name}`); logger.info(`Cancelling job for ${this.printer.name}`);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
`Cannot cancel job: Not connected to Moonraker (${this.printer.name})` `Cannot cancel job: Not connected to Moonraker (${this.printer.name})`,
); );
return false; return false;
} }
@ -1477,4 +1523,26 @@ export class PrinterClient {
logger.info(`Successfully disconnected from ${this.printer.name}`); logger.info(`Successfully disconnected from ${this.printer.name}`);
return true; return true;
} }
async setInactive() {
this.active = false;
this.printer.active = false;
this.database.printer = this.printer;
this.isOnline = false;
await this.disconnect();
this.state = { type: "inactive" };
this.klippyState = { type: "inactive" };
this.connectedAt = null;
await this.updatePrinterState();
}
async setActive() {
this.active = true;
this.printer.active = true;
this.database.printer = this.printer;
this.klippyState = { type: "offline", message: "Klippy disconnected." };
this.shouldReconnect = true;
this.isOnline = false;
await this.connect();
}
} }

View File

@ -119,7 +119,7 @@ export class PrinterManager {
return; return;
case "loadFilamentStock": case "loadFilamentStock":
const loadFilamentStockResult = await printer.loadFilamentStock( const loadFilamentStockResult = await printer.loadFilamentStock(
action.data.filamentStock action.data.filamentStock,
); );
callback(loadFilamentStockResult); callback(loadFilamentStockResult);
return; return;
@ -127,6 +127,14 @@ export class PrinterManager {
callback({ error: "Unknown command." }); callback({ error: "Unknown command." });
} }
async handlePrinterUpdate(id, data) {
logger.debug("Handling printer update for id:", id);
const printer = this.getPrinterClient(id);
if (printer) {
await printer.updatePrinter(data.object);
}
}
getPrinterClient(printerId) { getPrinterClient(printerId) {
return this.printerClients.get(printerId); return this.printerClients.get(printerId);
} }
@ -138,7 +146,7 @@ export class PrinterManager {
// Close all printer connections // Close all printer connections
async closeAllConnections() { async closeAllConnections() {
logger.info( logger.info(
`Closing all printer connections... current count: ${this.printerClients.size}` `Closing all printer connections... current count: ${this.printerClients.size}`,
); );
// Take a snapshot so any mutations during disconnects don't affect iteration // Take a snapshot so any mutations during disconnects don't affect iteration
@ -150,14 +158,14 @@ export class PrinterManager {
printerClient.shouldReconnect = false; printerClient.shouldReconnect = false;
await printerClient.disconnect(); await printerClient.disconnect();
logger.info( logger.info(
`Disconnected printer client ${printerClient?.id || "unknown"}` `Disconnected printer client ${printerClient?.id || "unknown"}`,
); );
} catch (error) { } catch (error) {
logger.error( logger.error(
`Failed to disconnect printer client ${ `Failed to disconnect printer client ${
printerClient?.id || "unknown" printerClient?.id || "unknown"
}:`, }:`,
error error,
); );
} }
} }
@ -169,7 +177,7 @@ export class PrinterManager {
this.printers = []; this.printers = [];
logger.info( logger.info(
`All printer connections closed. Remaining clients: ${this.printerClients.size}` `All printer connections closed. Remaining clients: ${this.printerClients.size}`,
); );
} }
} }

View File

@ -216,6 +216,9 @@ export class SocketClient {
callback callback
); );
} }
if (data.objectType == "printer") {
this.printerManager.handlePrinterUpdate(data._id, data, callback);
}
} }
handleConnect() { handleConnect() {