Enhance connection handling in DocumentPrinterClient
All checks were successful
farmcontrol/farmcontrol-server/pipeline/head This commit looks good

- Introduced connection state management with `isConnecting` to prevent multiple concurrent connection attempts.
- Added `scheduleReconnect` method to handle reconnection logic with a delay.
- Improved error handling and logging during connection and reconnection processes.
- Updated `updateDocumentPrinterState` to optimize state updates and prevent unnecessary changes.
This commit is contained in:
Tom Butcher 2026-09-01 12:34:37 +01:00
parent bb9b472559
commit d5fe978361
2 changed files with 123 additions and 71 deletions

View File

@ -31,6 +31,8 @@ export class DocumentPrinterClient {
this.state = { type: this.active == true ? "offline" : "inactive" }; 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.isConnecting = false;
this.reconnectTimeout = null;
this.isProcessingQueue = false; this.isProcessingQueue = false;
this.eventUpdateInterval = null; this.eventUpdateInterval = null;
this.initializeInterface(); this.initializeInterface();
@ -138,7 +140,37 @@ export class DocumentPrinterClient {
}); });
} }
statesEqual(a, b) {
if (!a && !b) {
return true;
}
if (!a || !b) {
return false;
}
return (
a.type === b.type && a.message === b.message && a.progress === b.progress
);
}
scheduleReconnect(delay = 30000) {
if (!this.shouldReconnect || this.active === false) {
return;
}
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = setTimeout(() => {
this.reconnectTimeout = null;
this.reconnect();
}, delay);
}
async connect() { async connect() {
if (this.isConnecting) {
logger.debug(
`Already connecting to document printer ${this.documentPrinter.name}, skipping`,
);
return false;
}
if (this.active == false) { if (this.active == false) {
this.shouldReconnect = false; this.shouldReconnect = false;
this.isOnline = false; this.isOnline = false;
@ -146,44 +178,58 @@ export class DocumentPrinterClient {
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
return false; return false;
} }
logger.info(
`Connecting to document printer ${this.id} (${this.interface})`,
);
clearTimeout(this.reconnectTimeout); this.isConnecting = true;
// Always stop the event update interval when connecting try {
clearInterval(this.eventUpdateInterval); logger.info(
this.eventUpdateInterval = null; `Connecting to document printer ${this.id} (${this.interface})`,
this.state = { type: "connecting", message: null };
this.isOnline = false;
await this.updateDocumentPrinterState();
if (!this.printerInterface) {
logger.error(
`Cannot connect: No interface initialized for ${this.interface}`,
); );
return false;
}
const result = await this.printerInterface.connect(); clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = null;
if (result.error) { // Always stop the event update interval when connecting
logger.error( clearInterval(this.eventUpdateInterval);
`Error connecting to document printer ${this.documentPrinter.name}:`, this.eventUpdateInterval = null;
result.error, this.state = { type: "connecting", message: null };
);
this.isOnline = false; this.isOnline = false;
this.state = { type: "offline", message: result.error };
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
return false;
if (!this.printerInterface) {
logger.error(
`Cannot connect: No interface initialized for ${this.interface}`,
);
return false;
}
const result = await this.printerInterface.connect();
if (result.error) {
logger.error(
`Error connecting to document printer ${this.documentPrinter.name}:`,
result.error,
);
this.isOnline = false;
this.state = { type: "offline", message: result.error };
await this.updateDocumentPrinterState();
return false;
}
logger.info(
`Connected to document printer ${this.documentPrinter.name} (${this.interface})`,
);
return true;
} finally {
this.isConnecting = false;
} }
logger.info(
`Connected to document printer ${this.documentPrinter.name} (${this.interface})`,
);
return true;
} }
async reconnect() { async reconnect() {
if (this.isConnecting) {
logger.debug(
`Document printer ${this.documentPrinter.name} is already connecting, skipping reconnect`,
);
return false;
}
if (this.active == false) { if (this.active == false) {
logger.info( logger.info(
`Document printer ${this.documentPrinter.name} is inactive, skipping reconnect`, `Document printer ${this.documentPrinter.name} is inactive, skipping reconnect`,
@ -194,6 +240,10 @@ export class DocumentPrinterClient {
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
return false; return false;
} }
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = null;
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...`,
@ -207,25 +257,17 @@ export class DocumentPrinterClient {
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,
); );
if (this.shouldReconnect) { this.scheduleReconnect();
// Attempt to reconnect after delay
setTimeout(() => this.reconnect(), 30000);
}
return false; return false;
} }
const initializeResult = await this.initialize(); const initializeResult = await this.initialize();
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,
); );
if (this.shouldReconnect) { this.scheduleReconnect();
// Attempt to reconnect after delay
setTimeout(() => this.reconnect(), 30000);
}
return false; return false;
} }
@ -281,17 +323,33 @@ export class DocumentPrinterClient {
} }
async updateDocumentPrinterState() { async updateDocumentPrinterState() {
const updateData = {
online: this.isOnline,
state: this.state,
connectedAt: this.connectedAt ?? null,
};
const currentConnectedAt = this.documentPrinter?.connectedAt;
const connectedAtUnchanged =
(currentConnectedAt?.toString?.() ?? null) ===
(updateData.connectedAt?.toString?.() ?? null);
if (
this.documentPrinter?.online === updateData.online &&
this.statesEqual(this.documentPrinter?.state, updateData.state) &&
connectedAtUnchanged
) {
return;
}
this.documentPrinter = { ...this.documentPrinter, ...updateData };
try { try {
// Update state in database or via socket client await this.socketClient.editObject({
// This can be implemented based on your database structure
this.socketClient.editObject({
_id: this.id, _id: this.id,
objectType: "documentPrinter", objectType: "documentPrinter",
updateData: { updateData,
online: this.isOnline, auditLog: true,
state: this.state,
connectedAt: this.connectedAt,
},
}); });
} catch (error) { } catch (error) {
logger.error(`Failed to update document printer state:`, error); logger.error(`Failed to update document printer state:`, error);
@ -308,8 +366,7 @@ export class DocumentPrinterClient {
async updateJobState(jobId, state) { async updateJobState(jobId, state) {
logger.info(`Updating job state for ${jobId}`); logger.info(`Updating job state for ${jobId}`);
const notify = const notify = state?.type !== "deploying" && state?.type !== "queued";
state?.type !== "deploying" && state?.type !== "queued";
await this.socketClient.editObject({ await this.socketClient.editObject({
_id: jobId, _id: jobId,
objectType: "documentJob", objectType: "documentJob",
@ -571,6 +628,8 @@ export class DocumentPrinterClient {
async disconnect() { async disconnect() {
logger.info(`Disconnecting from ${this.documentPrinter.name}`); logger.info(`Disconnecting from ${this.documentPrinter.name}`);
this.shouldReconnect = false; this.shouldReconnect = false;
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = null;
// Always stop the event update interval when disconnecting // Always stop the event update interval when disconnecting
clearInterval(this.eventUpdateInterval); clearInterval(this.eventUpdateInterval);
this.eventUpdateInterval = null; this.eventUpdateInterval = null;
@ -583,7 +642,6 @@ export class DocumentPrinterClient {
this.queue = []; // Clear queue on disconnect this.queue = []; // Clear queue on disconnect
this.jobQuantities.clear(); this.jobQuantities.clear();
await this.updateDocumentPrinterState(); await this.updateDocumentPrinterState();
clearTimeout(this.reconnectTimeout);
logger.info(`Successfully disconnected from ${this.documentPrinter.name}`); logger.info(`Successfully disconnected from ${this.documentPrinter.name}`);
return true; return true;
} }

View File

@ -774,8 +774,6 @@ export class PrinterClient {
); );
this.filamentDetected = newFilamentDetected; this.filamentDetected = newFilamentDetected;
console.log(this.currentFilamentStock);
if (newFilamentDetected == false && this.currentFilamentStock == null) { if (newFilamentDetected == false && this.currentFilamentStock == null) {
await this.database.removeAlertsByCode("noFilamentLoaded"); await this.database.removeAlertsByCode("noFilamentLoaded");
await this.database.addAlert({ await this.database.addAlert({
@ -1175,9 +1173,12 @@ export class PrinterClient {
const remainingTime = this.deploySubJobTargetTime - now; const remainingTime = this.deploySubJobTargetTime - now;
clearTimeout(this.deploySubJobTimer); clearTimeout(this.deploySubJobTimer);
this.deploySubJobTimer = setTimeout(() => { this.deploySubJobTimer = setTimeout(
this.processDeploySubJobQueue(); () => {
}, Math.max(remainingTime, 0)); this.processDeploySubJobQueue();
},
Math.max(remainingTime, 0),
);
await this.database.updateSubJobState(subJob._id, { await this.database.updateSubJobState(subJob._id, {
type: "deploying", type: "deploying",
@ -1227,7 +1228,6 @@ export class PrinterClient {
"with gcode file:", "with gcode file:",
`${subJob.gcodeFile._id}`, `${subJob.gcodeFile._id}`,
); );
console.log("subJob", subJob);
const gcodeFile = await this.socketClient.getObject({ const gcodeFile = await this.socketClient.getObject({
objectType: "gcodeFile", objectType: "gcodeFile",
_id: subJob.gcodeFile._id, _id: subJob.gcodeFile._id,
@ -1237,8 +1237,6 @@ export class PrinterClient {
throw new Error("G-code file not found"); throw new Error("G-code file not found");
} }
console.log("gcodeFile", gcodeFile);
const fileId = (gcodeFile.file?._id || gcodeFile.file).toString(); const fileId = (gcodeFile.file?._id || gcodeFile.file).toString();
let deploymentActive = true; let deploymentActive = true;
@ -1517,9 +1515,7 @@ export class PrinterClient {
try { try {
if (speedFactor === undefined || speedFactor === null) { if (speedFactor === undefined || speedFactor === null) {
logger.warn( logger.warn(`No speed factor provided for ${this.printer.name}`);
`No speed factor provided for ${this.printer.name}`,
);
return this.printerActionResult(false, "No speed factor provided"); return this.printerActionResult(false, "No speed factor provided");
} }
@ -1550,7 +1546,10 @@ export class PrinterClient {
} }
async setExtrudeFactor({ extrudeFactor }) { async setExtrudeFactor({ extrudeFactor }) {
logger.info(`Setting extrude factor for ${this.printer.name}:`, extrudeFactor); logger.info(
`Setting extrude factor for ${this.printer.name}:`,
extrudeFactor,
);
if (!this.isOnline) { if (!this.isOnline) {
logger.error( logger.error(
@ -1564,9 +1563,7 @@ export class PrinterClient {
try { try {
if (extrudeFactor === undefined || extrudeFactor === null) { if (extrudeFactor === undefined || extrudeFactor === null) {
logger.warn( logger.warn(`No extrude factor provided for ${this.printer.name}`);
`No extrude factor provided for ${this.printer.name}`,
);
return this.printerActionResult(false, "No extrude factor provided"); return this.printerActionResult(false, "No extrude factor provided");
} }
@ -1710,10 +1707,7 @@ export class PrinterClient {
} }
try { try {
if ( if (squareCornerVelocity === undefined || squareCornerVelocity === null) {
squareCornerVelocity === undefined ||
squareCornerVelocity === null
) {
logger.warn( logger.warn(
`No square corner velocity provided for ${this.printer.name}`, `No square corner velocity provided for ${this.printer.name}`,
); );