Fix download progress calculation in FileManager and improve error handling in PrinterDatabase. Refactor PrinterClient to manage current filament state more effectively.
Some checks failed
farmcontrol/farmcontrol-server/pipeline/head There was a failure building this commit

This commit is contained in:
Tom Butcher 2026-07-26 16:54:18 +01:00
parent b57d269f9b
commit 57e0c5a30d
3 changed files with 86 additions and 103 deletions

View File

@ -142,13 +142,13 @@ export class FileManager {
onDownloadProgress: (progressEvent) => {
console.log(progressEvent);
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
(progressEvent.loaded * 100) / (progressEvent.total || 1),
);
logger.debug(`Downloading file ${fileId}: ${percent}%`);
this.files = _.unionBy(
[{ ...fileObject, state: { type: "downloading", percent } }],
this.files,
"_id"
"_id",
);
sendIPC("setFiles", this.files);
if (this.progressCallbacks.has(fileId)) {
@ -166,7 +166,7 @@ export class FileManager {
this.files = _.unionBy(
[{ ...fileObject, state: { type: "downloaded" } }],
this.files,
"_id"
"_id",
);
if (this.progressCallbacks.has(fileId)) {
const progressCallbacks = this.progressCallbacks.get(fileId);
@ -193,7 +193,7 @@ export class FileManager {
this.files = _.unionBy(
[{ ...fileObject, state: { type: "error" } }],
this.files,
"_id"
"_id",
);
sendIPC("setFiles", this.files);
}

View File

@ -12,8 +12,6 @@ export class PrinterDatabase {
this.socketClient = socketClient;
this.printer = printer;
this.id = this.printer._id;
this.filamentStock = null; // Store current filament stock
this.existingEvent = null; // Store existing stock event
// Initialize cache with 30 second TTL
this.printerCache = new NodeCache({ stdTTL: 30 });
logger.info("Initialized PrinterDatabase with socket manager");
@ -106,7 +104,7 @@ export class PrinterDatabase {
if (!printers || printers.length === 0) {
logger.error(
`Printer with ID ${this.id} not found when getting config`
`Printer with ID ${this.id} not found when getting config`,
);
return null;
}
@ -114,7 +112,7 @@ export class PrinterDatabase {
const printer = printers[0];
logger.debug(
`Retrieved printer config for ${this.id}:`,
printer.moonraker
printer.moonraker,
);
return printer.moonraker;
} catch (error) {
@ -132,7 +130,7 @@ export class PrinterDatabase {
if (state.type === "printing" && state.progress === undefined) {
logger.debug(
`Setting default progress for printing state on printer ${this.printer.name}`
`Setting default progress for printing state on printer ${this.printer.name}`,
);
state.progress = 0;
}
@ -158,7 +156,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to update printer state for ${this.printer.name}:`,
error
error,
);
throw error;
}
@ -276,7 +274,7 @@ export class PrinterDatabase {
// If any subjob is printing, the overall state should be printing
if (stateCounts.deploying > 0) {
logger.debug(
"Job state determined as 'deploying' due to active deploying subjobs"
"Job state determined as 'deploying' due to active deploying subjobs",
);
return "deploying";
}
@ -284,14 +282,14 @@ export class PrinterDatabase {
// If any subjob is printing, the overall state should be printing
if (stateCounts.printing > 0) {
logger.debug(
"Job state determined as 'printing' due to active printing subjobs"
"Job state determined as 'printing' due to active printing subjobs",
);
return "printing";
}
if (stateCounts.failed > 0 || stateCounts.cancelled > 0) {
logger.debug(
"Job state determined as 'failed' due to failed or cancelled subjobs"
"Job state determined as 'failed' due to failed or cancelled subjobs",
);
return "failed";
}
@ -324,7 +322,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to set current job and subjob for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -342,7 +340,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to update display status for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -352,18 +350,18 @@ export class PrinterDatabase {
try {
logger.debug(
`Updating firmware version for printer ${this.id}:`,
firmwareVersion
firmwareVersion,
);
this.printer.firmware = firmwareVersion;
await this.editPrinter({ firmware: firmwareVersion });
logger.info(
`Updated firmware version for printer ${this.id}:`,
firmwareVersion
firmwareVersion,
);
} catch (error) {
logger.error(
`Failed to update firmware version for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -399,7 +397,7 @@ export class PrinterDatabase {
if (alertId) {
filteredAlerts = filteredAlerts.filter(
(alert) => alert._id !== alertId
(alert) => alert._id !== alertId,
);
}
@ -414,7 +412,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to clear alert ${alertId} for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -451,7 +449,7 @@ export class PrinterDatabase {
if (!updatedPrinter) {
logger.error(
`Printer with ID ${this.id} not found when clearing alerts`
`Printer with ID ${this.id} not found when clearing alerts`,
);
return null;
}
@ -477,14 +475,14 @@ export class PrinterDatabase {
logger.info(
`Updated current filament stock for printer ${this.id}:`,
filamentStock
filamentStock,
);
return updatedPrinter.currentFilamentStock;
} catch (error) {
logger.error(
`Failed to set current filament stock for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -494,12 +492,12 @@ export class PrinterDatabase {
filamentStock,
weight,
subJob = null,
job = null
job = null,
) {
const subJobId = subJob._id || subJob.id;
const jobId = job._id || job.id;
console.log("WEIGHT", weight);
try {
// Get or fetch filament stock
const filamentStockStockEvents = await this.socketClient.listObjects({
objectType: "stockEvent",
filter: {
@ -509,43 +507,20 @@ export class PrinterDatabase {
populate: ["owner"],
});
// Calculate new weights immediately
const totalEventWeight =
filamentStockStockEvents.reduce((sum, event) => {
// Skip the existing event if it exists
if (
this.existingEvent &&
event._id.toString() === this.existingEvent._id.toString()
) {
return sum;
}
return sum + event.value;
}, 0) + weight;
const newNetWeight = filamentStock.startingWeight.net + totalEventWeight;
const newGrossWeight =
filamentStock.startingWeight.gross + totalEventWeight;
const remainingPercent = newNetWeight / filamentStock.startingWeight.net;
const state = {
type: newNetWeight <= 0 ? "depleted" : "used",
progress: (1 - remainingPercent).toFixed(2),
};
// Check if a stock event already exists for this subJobId and jobId
const stockEvents = filamentStockStockEvents.filter(
(event) =>
event.owner._id.toString() === subJobId.toString() &&
event.ownerType === "subJob"
event.ownerType === "subJob",
);
let stockEvent;
if (stockEvents && stockEvents.length > 0) {
// Update existing event
this.existingEvent = stockEvents[0];
const existingEvent = stockEvents[0];
logger.trace(
`Updating existing stock event for subJobId ${subJobId} and jobId ${jobId}`
`Updating existing stock event for subJobId ${subJobId} and jobId ${jobId}`,
);
stockEvent = await this.socketClient.editObject({
_id: this.existingEvent._id,
_id: existingEvent._id,
objectType: "stockEvent",
updateData: {
value: weight,
@ -554,9 +529,8 @@ export class PrinterDatabase {
auditLog: false,
});
} else {
// Create new stock event
logger.debug(
`Creating new stock event for subJobId ${subJobId} and jobId ${jobId}`
`Creating new stock event for subJobId ${subJobId} and jobId ${jobId}`,
);
stockEvent = await this.socketClient.newObject({
objectType: "stockEvent",
@ -572,33 +546,19 @@ export class PrinterDatabase {
});
}
const updatedFilamentStock = await this.socketClient.editObject({
_id: filamentStock._id,
objectType: "filamentStock",
updateData: {
currentWeight: { net: newNetWeight, gross: newGrossWeight },
state,
logger.trace(
`Updated stock event for filament stock ${filamentStock._id}:`,
{
value: weight,
updatedExistingEvent: stockEvents.length > 0,
},
auditLog: false,
});
);
// Update the cached filament stock
this.filamentStock = updatedFilamentStock;
logger.trace(`Updated filament stock ${filamentStock._id}:`, {
newGrossWeight: newGrossWeight,
newNetWeight: newNetWeight,
eventCount: filamentStockStockEvents.length,
updatedExistingEvent: !!this.existingEvent,
remainingPercent: remainingPercent.toFixed(2),
consumedPercent: state.percent,
});
return updatedFilamentStock;
return stockEvent;
} catch (error) {
logger.error(
`Failed to update filament stock weight for ${filamentStock._id}:`,
error
`Failed to update stock event for filament stock ${filamentStock._id}:`,
error,
);
throw error;
}
@ -607,7 +567,7 @@ export class PrinterDatabase {
async queueSubJob(subJob, moonrakerJobId) {
try {
logger.info(
`Queueing subjob ${subJob._id} for printer ${this.id} with Moonraker ID ${moonrakerJobId}`
`Queueing subjob ${subJob._id} for printer ${this.id} with Moonraker ID ${moonrakerJobId}`,
);
// 1. Update SubJob with Moonraker ID and State
@ -640,7 +600,7 @@ export class PrinterDatabase {
try {
logger.debug(
`Setting queued subjobs for printer ${this.id}:`,
subJobs.map((subJob) => subJob._id).join(", ")
subJobs.map((subJob) => subJob._id).join(", "),
);
const updatedPrinter = await this.editPrinter({
queue: subJobs,
@ -649,7 +609,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to set queued subjobs for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -663,7 +623,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to get queued subjobs for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -680,7 +640,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to get job by ID ${jobId} for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -689,7 +649,7 @@ export class PrinterDatabase {
async postSubJobPartStockItems(subJobId) {
try {
logger.info(
`Posting subjob part stock items for subjob ${subJobId} for printer ${this.id}`
`Posting subjob part stock items for subjob ${subJobId} for printer ${this.id}`,
);
logger.debug(`Getting subjob ${subJobId} for printer ${this.id}`);
const subJob = await this.socketClient.getObject({
@ -698,7 +658,7 @@ export class PrinterDatabase {
});
logger.debug(`Subjob ${subJobId} for printer ${this.id}:`, subJob);
logger.debug(
`Getting gcode file for subjob ${subJobId} for printer ${this.id}`
`Getting gcode file for subjob ${subJobId} for printer ${this.id}`,
);
const gcodeFile = await this.socketClient.getObject({
objectType: "gcodeFile",
@ -710,7 +670,7 @@ export class PrinterDatabase {
logger.info(
`Posting part stock item for part ${
partItem.part?.name || partItem.part._id
} for subjob ${subJobId} for printer ${this.id}`
} for subjob ${subJobId} for printer ${this.id}`,
);
const quantity = partItem.quantity;
@ -734,7 +694,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to post subjob part stock items for subjob ${subJobId} for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -743,7 +703,7 @@ export class PrinterDatabase {
async setSubJobStartedAt(subJobId, date) {
try {
logger.debug(
`Setting started at for subjob ${subJobId} for printer ${this.id}`
`Setting started at for subjob ${subJobId} for printer ${this.id}`,
);
const updatedSubJob = await this.socketClient.editObject({
_id: subJobId,
@ -754,7 +714,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to set started at for subjob ${subJobId} for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -763,7 +723,7 @@ export class PrinterDatabase {
async setSubJobFinishedAt(subJobId, date) {
try {
logger.debug(
`Setting finished at for subjob ${subJobId} for printer ${this.id}`
`Setting finished at for subjob ${subJobId} for printer ${this.id}`,
);
const updatedSubJob = await this.socketClient.editObject({
_id: subJobId,
@ -774,7 +734,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to set finished at for subjob ${subJobId} for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -783,7 +743,7 @@ export class PrinterDatabase {
async setJobStartedAt(jobId, date) {
try {
logger.debug(
`Setting started at for job ${jobId} for printer ${this.id}`
`Setting started at for job ${jobId} for printer ${this.id}`,
);
const updatedJob = await this.socketClient.editObject({
_id: jobId,
@ -794,7 +754,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to set started at for job ${jobId} for printer ${this.id}:`,
error
error,
);
throw error;
}
@ -803,7 +763,7 @@ export class PrinterDatabase {
async setJobFinishedAt(jobId, date) {
try {
logger.debug(
`Setting finished at for job ${jobId} for printer ${this.id}`
`Setting finished at for job ${jobId} for printer ${this.id}`,
);
const updatedJob = await this.socketClient.editObject({
_id: jobId,
@ -814,7 +774,7 @@ export class PrinterDatabase {
} catch (error) {
logger.error(
`Failed to set finished at for job ${jobId} for printer ${this.id}:`,
error
error,
);
throw error;
}

View File

@ -41,6 +41,7 @@ export class PrinterClient {
this.motionObject = {};
this.miscObject = {};
this.currentFilamentStock = printer.currentFilamentStock;
this.currentFilament = null;
this.currentFilamentSku = null;
this.currentFilamentUsed = 0;
this.registerEventHandlers();
@ -244,6 +245,7 @@ export class PrinterClient {
await this.getInfo();
await this.getFiles();
await this.getCurrentFilament();
await this.loadCurrentFilament();
await this.updateSubscriptions();
await this.getPrinterState();
await this.syncSubJobs();
@ -286,7 +288,7 @@ export class PrinterClient {
_id: "klippyError",
type: "error",
message: klippyResult.state_message,
actions: ["restartPrinter"],
actions: ["restart", "restartFirmware"],
}),
);
}
@ -298,7 +300,7 @@ export class PrinterClient {
_id: "klippyError",
type: "error",
message: klippyResult.state_message,
actions: ["restartPrinter"],
actions: ["restart", "restartFirmware"],
}),
);
}
@ -360,6 +362,23 @@ export class PrinterClient {
return this.currentFilamentSku;
}
async loadCurrentFilament() {
if (!this.currentFilamentStock?.filament?._id) {
this.currentFilament = null;
return null;
}
this.currentFilament = await this.socketClient.getObject({
objectType: "filament",
_id: this.currentFilamentStock.filament._id,
});
return this.currentFilament;
}
async setCurrentFilamentStock(filamentStock) {
this.currentFilamentStock = filamentStock;
await this.loadCurrentFilament();
}
async getQueuedJobIds() {
logger.info(`Getting queued jobs info for (${this.printer.name})`);
const result = await this.sendPrinterCommand({
@ -511,6 +530,7 @@ export class PrinterClient {
// Calculate weight in grams
const filamentWeightG =
filamentVolumeCm3 * this.currentFilament?.density || 0;
console.log("FILAMENT WEIGHT", this.currentFilament);
this.currentFilamentUsed = -1 * filamentWeightG;
}
@ -701,7 +721,7 @@ export class PrinterClient {
newFilamentDetected == true &&
this.currentFilamentStock == null
) {
this.currentFilamentStock = null;
await this.setCurrentFilamentStock(null);
await this.database.setCurrentFilamentStock(null);
await this.database.removeAlert("noFilamentSelected");
await this.database.addAlert({
@ -731,7 +751,7 @@ export class PrinterClient {
actions: ["loadFilamentStock"],
canDismiss: false,
});
this.currentFilamentStock = null;
await this.setCurrentFilamentStock(null);
await this.database.setCurrentFilamentStock(null);
}
}
@ -1144,19 +1164,21 @@ export class PrinterClient {
"to printer:",
this.id,
"with gcode file:",
`${subJob.gcodeFile.name}`,
`${subJob.gcodeFile._id}`,
);
console.log("subJob", subJob);
const gcodeFile = await this.socketClient.getObject({
objectType: "gcodeFile",
_id: subJob.gcodeFile,
_id: subJob.gcodeFile._id,
});
if (!gcodeFile) {
throw new Error("G-code file not found");
}
console.log("gcodeFile", gcodeFile);
const file = await this.socketClient.fileManager.getFile(
gcodeFile.file,
gcodeFile.file?._id || gcodeFile.file,
async (progress) => {
await this.database.updateSubJobState(subJob._id, {
type: "deploying",
@ -1495,10 +1517,11 @@ export class PrinterClient {
async loadFilamentStock(filamentStock) {
if (filamentStock == null) {
await this.database.setCurrentFilamentStock(null);
await this.setCurrentFilamentStock(null);
return true;
}
await this.database.setCurrentFilamentStock(filamentStock);
this.currentFilamentStock = filamentStock;
await this.setCurrentFilamentStock(filamentStock);
await this.getCurrentFilament();
await this.database.removeAlert("noFilamentSelected");
await this.database.removeAlert("noFilamentLoaded");