Added CI Support and other things.
This commit is contained in:
parent
2ac65ec717
commit
90d031c5fe
45
Jenkinsfile
vendored
Normal file
45
Jenkinsfile
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'node:20-alpine'
|
||||
args '-v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
|
||||
environment {
|
||||
NODE_ENV = 'production'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Install Dependencies') {
|
||||
steps {
|
||||
sh 'npm ci'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'npm run build'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
echo 'Deploying application...'
|
||||
// Add your deployment steps here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
success {
|
||||
echo 'Pipeline completed successfully!'
|
||||
}
|
||||
failure {
|
||||
echo 'Pipeline failed!'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"server": {
|
||||
"port": 8081,
|
||||
"logLevel": "debug"
|
||||
"logLevel": "info"
|
||||
},
|
||||
"auth": {
|
||||
"enabled": true,
|
||||
|
||||
3543
package-lock.json
generated
3543
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,6 @@
|
||||
"main": "src/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
@ -22,6 +21,8 @@
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.9"
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.1.9",
|
||||
"supertest": "^6.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,13 +13,7 @@ const filamentStockSchema = new Schema(
|
||||
currentGrossWeight: { type: Number, required: true },
|
||||
currentNetWeight: { type: Number, required: true },
|
||||
filament: { type: mongoose.Schema.Types.ObjectId, ref: "Filament" },
|
||||
stockEvents: [{
|
||||
type: { type: String, required: true },
|
||||
value: { type: Number, required: true },
|
||||
subJob: { type: mongoose.Schema.Types.ObjectId, ref: "PrintSubJob", required: false },
|
||||
job: { type: mongoose.Schema.Types.ObjectId, ref: "PrintJob", required: false },
|
||||
timestamp: { type: Date, default: Date.now }
|
||||
}]
|
||||
stockEvents: [{ type: mongoose.Schema.Types.ObjectId, ref: "StockEvent" }]
|
||||
},
|
||||
{ timestamps: true },
|
||||
);
|
||||
|
||||
@ -17,6 +17,7 @@ const alertSchema = new Schema(
|
||||
{
|
||||
priority: { type: String, required: true }, // order to show
|
||||
type: { type: String, required: true }, // selectFilament, error, info, message,
|
||||
message: { type: String, required: false }
|
||||
},
|
||||
{ timestamps: true, _id: false }
|
||||
);
|
||||
|
||||
@ -13,6 +13,7 @@ const printJobSchema = new mongoose.Schema({
|
||||
createdAt: { required: true, type: Date },
|
||||
updatedAt: { required: true, type: Date },
|
||||
startedAt: { required: false, type: Date },
|
||||
finishedAt: { required: false, type: Date },
|
||||
gcodeFile: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "GCodeFile",
|
||||
|
||||
@ -36,7 +36,9 @@ const printSubJobSchema = new mongoose.Schema({
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
},
|
||||
startedAt: { required: false, type: Date },
|
||||
finishedAt: { required: false, type: Date },
|
||||
});
|
||||
|
||||
printSubJobSchema.virtual("id").get(function () {
|
||||
|
||||
25
src/database/stockevent.schema.js
Normal file
25
src/database/stockevent.schema.js
Normal file
@ -0,0 +1,25 @@
|
||||
import mongoose from "mongoose";
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const stockEventSchema = new Schema(
|
||||
{
|
||||
type: { type: String, required: true },
|
||||
value: { type: Number, required: true },
|
||||
subJob: { type: Schema.Types.ObjectId, ref: "PrintSubJob", required: false },
|
||||
job: { type: Schema.Types.ObjectId, ref: "PrintJob", required: false },
|
||||
filamentStock: { type: Schema.Types.ObjectId, ref: "FilamentStock", required: true },
|
||||
timestamp: { type: Date, default: Date.now }
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
stockEventSchema.virtual("id").get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
stockEventSchema.set("toJSON", { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const stockEventModel = mongoose.model("StockEvent", stockEventSchema);
|
||||
@ -4,6 +4,7 @@ import { printSubJobModel } from "../database/printsubjob.schema.js";
|
||||
import { gcodeFileModel } from "../database/gcodefile.schema.js"
|
||||
import { filamentStockModel } from "../database/filamentstock.schema.js";
|
||||
import { filamentModel } from "../database/filament.schema.js";
|
||||
import { stockEventModel } from "../database/stockevent.schema.js";
|
||||
import log4js from "log4js";
|
||||
import { loadConfig } from "../config.js";
|
||||
|
||||
@ -11,9 +12,52 @@ const config = loadConfig();
|
||||
const logger = log4js.getLogger("Printer Database");
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
// Debounce utility for rate limiting database updates
|
||||
class Debouncer {
|
||||
constructor(delay) {
|
||||
this.delay = delay;
|
||||
this.timeouts = new Map();
|
||||
this.pendingUpdates = new Map();
|
||||
}
|
||||
|
||||
debounce(key, fn) {
|
||||
// If this is the first call for this key, execute immediately
|
||||
if (!this.timeouts.has(key)) {
|
||||
this.timeouts.set(key, true);
|
||||
return fn().finally(() => {
|
||||
// After the first call completes, set up the delayed call
|
||||
setTimeout(() => {
|
||||
this.timeouts.delete(key);
|
||||
// If there's a pending update, execute it
|
||||
if (this.pendingUpdates.has(key)) {
|
||||
const pendingFn = this.pendingUpdates.get(key);
|
||||
this.pendingUpdates.delete(key);
|
||||
pendingFn();
|
||||
}
|
||||
}, this.delay);
|
||||
});
|
||||
} else {
|
||||
// If there's already a call in progress, store this one as pending
|
||||
return new Promise((resolve) => {
|
||||
this.pendingUpdates.set(key, async () => {
|
||||
try {
|
||||
const result = await fn();
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
resolve(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PrinterDatabase {
|
||||
constructor(socketManager) {
|
||||
this.socketManager = socketManager;
|
||||
this.debouncer = new Debouncer(2000); // 2 second delay
|
||||
this.filamentStock = null; // Store current filament stock
|
||||
this.existingEvent = null; // Store existing stock event
|
||||
logger.info("Initialized PrinterDatabase with socket manager");
|
||||
}
|
||||
|
||||
@ -36,6 +80,14 @@ export class PrinterDatabase {
|
||||
}
|
||||
|
||||
async updatePrinterState(printerId, state, online) {
|
||||
// Broadcast immediately
|
||||
logger.debug(`Broadcasting printer state update for ${printerId}:`, { state, online });
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
_id: printerId,
|
||||
state,
|
||||
});
|
||||
|
||||
return this.debouncer.debounce(`printer_${printerId}`, async () => {
|
||||
try {
|
||||
logger.debug(`Updating printer state for ${printerId}:`, { state, online });
|
||||
|
||||
@ -62,22 +114,16 @@ export class PrinterDatabase {
|
||||
previousState: updatedPrinter.state
|
||||
});
|
||||
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
id: printerId,
|
||||
state,
|
||||
});
|
||||
|
||||
return updatedPrinter;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to update printer state for ${printerId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async clearCurrentJob(printerId) {
|
||||
try {
|
||||
logger.warn(`Clearing current job for printer ${printerId}`);
|
||||
|
||||
const updatedPrinter = await printerModel.findByIdAndUpdate(
|
||||
printerId,
|
||||
{
|
||||
@ -93,8 +139,9 @@ export class PrinterDatabase {
|
||||
}
|
||||
|
||||
// Broadcast the update through websocket
|
||||
logger.debug(`Broadcasting current job clear for printer ${printerId}`);
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
id: printerId,
|
||||
_id: printerId,
|
||||
currentSubJob: null,
|
||||
currentJob: null
|
||||
});
|
||||
@ -111,7 +158,6 @@ export class PrinterDatabase {
|
||||
|
||||
async setCurrentJobForPrinting(printerId, queuedJobIds) {
|
||||
try {
|
||||
logger.error(`Setting current job for printer ${printerId} as printing starts`);
|
||||
|
||||
const printer = await printerModel.findById(printerId).populate('subJobs');
|
||||
if (!printer) {
|
||||
@ -168,8 +214,12 @@ export class PrinterDatabase {
|
||||
}
|
||||
|
||||
// Broadcast the update through websocket
|
||||
logger.debug(`Broadcasting current job update for printer ${printerId}:`, {
|
||||
subJobId: subJob.id,
|
||||
jobId: subJob.printJob
|
||||
});
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
id: printerId,
|
||||
_id: printerId,
|
||||
currentSubJob: subJob,
|
||||
currentJob: job
|
||||
});
|
||||
@ -188,19 +238,44 @@ export class PrinterDatabase {
|
||||
}
|
||||
|
||||
async updateSubJobState(subJobId, state) {
|
||||
try {
|
||||
// Broadcast immediately
|
||||
logger.debug(`Broadcasting subjob state update for ${subJobId}:`, { state });
|
||||
this.socketManager.broadcast("notify_subjob_update", {
|
||||
_id: subJobId,
|
||||
state,
|
||||
});
|
||||
|
||||
return this.debouncer.debounce(`subjob_${subJobId}`, async () => {
|
||||
try {
|
||||
if (state.type == "standby" || state.type == "error" || state.type == "offline") {
|
||||
state.type = 'failed'
|
||||
logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
} else {
|
||||
logger.debug(`Updating subjob state for ${subJobId}:`, state);
|
||||
}
|
||||
|
||||
// Set startedAt if the subjob is starting to print
|
||||
const updateData = { state };
|
||||
if (state.type === 'printing') {
|
||||
const subJob = await printSubJobModel.findById(subJobId);
|
||||
if (!subJob.startedAt) {
|
||||
updateData.startedAt = new Date();
|
||||
logger.info(`Setting startedAt for subjob ${subJobId} as printing begins`);
|
||||
}
|
||||
}
|
||||
|
||||
// Set finishedAt if the subjob is complete, failed, or cancelled
|
||||
if (['complete', 'failed', 'cancelled'].includes(state.type)) {
|
||||
const subJob = await printSubJobModel.findById(subJobId);
|
||||
if (!subJob.finishedAt) {
|
||||
updateData.finishedAt = new Date();
|
||||
logger.info(`Setting finishedAt for subjob ${subJobId} as state is ${state.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedSubJob = await printSubJobModel.findByIdAndUpdate(
|
||||
subJobId,
|
||||
{ state },
|
||||
updateData,
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
@ -213,25 +288,24 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
type: state.type,
|
||||
progress: state.progress,
|
||||
previousState: updatedSubJob.state,
|
||||
printJob: updatedSubJob.printJob
|
||||
printJob: updatedSubJob.printJob,
|
||||
startedAt: updatedSubJob.startedAt,
|
||||
finishedAt: updatedSubJob.finishedAt
|
||||
});
|
||||
|
||||
// Update parent job state
|
||||
await this.updateJobState(updatedSubJob.printJob);
|
||||
|
||||
this.socketManager.broadcast("notify_subjob_update", {
|
||||
id: subJobId,
|
||||
state,
|
||||
});
|
||||
|
||||
return updatedSubJob;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to update sub job state for ${subJobId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async updateJobState(jobId) {
|
||||
return this.debouncer.debounce(`job_${jobId}`, async () => {
|
||||
try {
|
||||
logger.debug(`Updating job state for ${jobId}`);
|
||||
|
||||
@ -269,21 +343,35 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
subJobCount: job.subJobs.length
|
||||
});
|
||||
|
||||
// Update finishedAt if all subjobs are complete and none are queued
|
||||
if ((stateCounts.complete + stateCounts.failed + stateCounts.cancelled) === job.subJobs.length && stateCounts.queued === 0 && !job.finishedAt) {
|
||||
logger.info(`Setting finishedAt for job ${jobId} as all subjobs are complete`);
|
||||
job.finishedAt = new Date();
|
||||
}
|
||||
|
||||
job.state = jobState;
|
||||
job.subJobStats = stateCounts;
|
||||
|
||||
await job.save();
|
||||
|
||||
logger.info(`Updated job ${jobId} state:`, {
|
||||
id: jobId,
|
||||
_id: jobId,
|
||||
state: jobState,
|
||||
subJobStats: stateCounts
|
||||
subJobStats: stateCounts,
|
||||
finishedAt: job.finishedAt
|
||||
});
|
||||
|
||||
this.socketManager.broadcast("notify_job_update", {
|
||||
id: jobId,
|
||||
// Broadcast immediately after calculating new state
|
||||
logger.debug(`Broadcasting job state update for ${jobId}:`, {
|
||||
state: jobState,
|
||||
subJobStats: stateCounts
|
||||
subJobStats: stateCounts,
|
||||
finishedAt: job.finishedAt
|
||||
});
|
||||
this.socketManager.broadcast("notify_job_update", {
|
||||
_id: jobId,
|
||||
state: jobState,
|
||||
subJobStats: stateCounts,
|
||||
finishedAt: job.finishedAt
|
||||
});
|
||||
|
||||
return job;
|
||||
@ -291,6 +379,7 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
logger.error(`Failed to update job state for ${jobId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
determineJobState(stateCounts) {
|
||||
@ -374,6 +463,7 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
try {
|
||||
logger.debug(`Updating display status for printer ${printerId}:`, { message });
|
||||
|
||||
logger.debug(`Broadcasting display status for printer ${printerId}:`, { message });
|
||||
this.socketManager.broadcast("notify_display_status", {
|
||||
printerId,
|
||||
message
|
||||
@ -417,25 +507,67 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
try {
|
||||
logger.debug(`Adding alert to printer ${printerId}:`, alert);
|
||||
|
||||
// First check if an alert of this type already exists
|
||||
const printer = await printerModel.findById(printerId);
|
||||
if (!printer) {
|
||||
logger.error(`Printer with ID ${printerId} not found when adding alert`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const existingAlertIndex = printer.alerts.findIndex(a => a.type === alert.type);
|
||||
if (existingAlertIndex !== -1) {
|
||||
// If we have a message to update, update the existing alert
|
||||
if (alert.message) {
|
||||
logger.debug(`Updating message for existing alert of type ${alert.type} on printer ${printerId}`);
|
||||
printer.alerts[existingAlertIndex].message = alert.message;
|
||||
const updatedPrinter = await printer.save();
|
||||
|
||||
// Broadcast the update
|
||||
logger.debug(`Broadcasting alert update for printer ${printerId}:`, {
|
||||
alerts: updatedPrinter.alerts
|
||||
});
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
_id: printerId,
|
||||
alerts: updatedPrinter.alerts
|
||||
});
|
||||
|
||||
logger.info(`Updated message for existing alert on printer ${printerId}:`, {
|
||||
type: alert.type,
|
||||
message: alert.message
|
||||
});
|
||||
|
||||
return updatedPrinter;
|
||||
}
|
||||
|
||||
logger.debug(`Alert of type ${alert.type} already exists for printer ${printerId}, skipping`);
|
||||
return printer;
|
||||
}
|
||||
|
||||
// No existing alert found, create a new one
|
||||
logger.debug(`Creating new alert for printer ${printerId}:`, {
|
||||
type: alert.type,
|
||||
priority: alert.priority,
|
||||
hasMessage: !!alert.message
|
||||
});
|
||||
|
||||
const updatedPrinter = await printerModel.findByIdAndUpdate(
|
||||
printerId,
|
||||
{ $push: { alerts: alert } },
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (!updatedPrinter) {
|
||||
logger.error(`Printer with ID ${printerId} not found when adding alert`);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.info(`Added alert to printer ${printerId}:`, {
|
||||
logger.info(`Added new alert to printer ${printerId}:`, {
|
||||
type: alert.type,
|
||||
priority: alert.priority
|
||||
priority: alert.priority,
|
||||
hasMessage: !!alert.message
|
||||
});
|
||||
|
||||
// Broadcast the alert through websocket
|
||||
logger.debug(`Broadcasting new alert for printer ${printerId}:`, {
|
||||
alerts: updatedPrinter.alerts
|
||||
});
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
id: printerId,
|
||||
_id: printerId,
|
||||
alerts: updatedPrinter.alerts
|
||||
});
|
||||
|
||||
@ -475,8 +607,12 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
});
|
||||
|
||||
// Broadcast the alert clear through websocket
|
||||
logger.debug(`Broadcasting alert removal for printer ${printerId}:`, {
|
||||
options,
|
||||
remainingAlerts: updatedPrinter.alerts.length
|
||||
});
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
id: printerId,
|
||||
_id: printerId,
|
||||
alerts: updatedPrinter.alerts
|
||||
});
|
||||
|
||||
@ -518,6 +654,37 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
}
|
||||
}
|
||||
|
||||
async clearAlerts(printerId) {
|
||||
try {
|
||||
logger.debug(`Clearing all alerts for printer ${printerId}`);
|
||||
|
||||
const updatedPrinter = await printerModel.findByIdAndUpdate(
|
||||
printerId,
|
||||
{ $set: { alerts: [] } },
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (!updatedPrinter) {
|
||||
logger.error(`Printer with ID ${printerId} not found when clearing alerts`);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.info(`Cleared all alerts for printer ${printerId}`);
|
||||
|
||||
// Broadcast the alert clear through websocket
|
||||
logger.debug(`Broadcasting all alerts clear for printer ${printerId}`);
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
_id: printerId,
|
||||
alerts: []
|
||||
});
|
||||
|
||||
return updatedPrinter;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to clear alerts for printer ${printerId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async setCurrentFilamentStock(printerId, filamentStockId) {
|
||||
try {
|
||||
logger.debug(`Setting current filament stock for printer ${printerId}:`, { filamentStockId });
|
||||
@ -541,8 +708,11 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
});
|
||||
|
||||
// Broadcast the update through websocket
|
||||
logger.debug(`Broadcasting filament stock update for printer ${printerId}:`, {
|
||||
filamentStock: updatedPrinter.currentFilamentStock
|
||||
});
|
||||
this.socketManager.broadcast("notify_printer_update", {
|
||||
id: printerId,
|
||||
_id: printerId,
|
||||
currentFilamentStock: updatedPrinter.currentFilamentStock
|
||||
});
|
||||
|
||||
@ -555,73 +725,112 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
|
||||
async updateFilamentStockWeight(filamentStockId, weight, subJobId = null, jobId = null) {
|
||||
try {
|
||||
logger.debug(`Updating filament stock weight for ${filamentStockId}:`, { weight, subJobId, jobId });
|
||||
|
||||
const filamentStock = await filamentStockModel.findById(filamentStockId);
|
||||
if (!filamentStock) {
|
||||
// Get or fetch filament stock
|
||||
if (!this.filamentStock || this.filamentStock._id.toString() !== filamentStockId) {
|
||||
this.filamentStock = await filamentStockModel.findById(filamentStockId).populate('stockEvents');
|
||||
if (!this.filamentStock) {
|
||||
logger.error(`Filament stock with ID ${filamentStockId} not found`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Calculate new weights immediately
|
||||
const totalEventWeight = this.filamentStock.stockEvents.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 = totalEventWeight;
|
||||
const newGrossWeight = totalEventWeight + (this.filamentStock.startingGrossWeight - this.filamentStock.startingNetWeight);
|
||||
const remainingPercent = newNetWeight / this.filamentStock.startingNetWeight;
|
||||
const state = {
|
||||
type: 'partiallyconsumed',
|
||||
percent: (1 - remainingPercent).toFixed(2)
|
||||
};
|
||||
|
||||
// Broadcast both updates with calculated values
|
||||
logger.debug(`Broadcasting filament stock weight update for ${filamentStockId}:`, {
|
||||
newNetWeight,
|
||||
newGrossWeight,
|
||||
state
|
||||
});
|
||||
this.socketManager.broadcast("notify_filamentstock_update", {
|
||||
_id: filamentStockId,
|
||||
currentNetWeight: newNetWeight,
|
||||
currentGrossWeight: newGrossWeight,
|
||||
updatedAt: new Date(),
|
||||
state
|
||||
});
|
||||
|
||||
if (this.existingEvent) {
|
||||
|
||||
logger.debug(`Broadcasting stock event update for ${filamentStockId}:`, {
|
||||
_id: this.existingEvent._id,
|
||||
value: weight,
|
||||
subJob: subJobId,
|
||||
job: jobId
|
||||
})
|
||||
|
||||
this.socketManager.broadcast("notify_stockevent_update", {
|
||||
_id: this.existingEvent._id,
|
||||
value: weight,
|
||||
updatedAt: new Date()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return this.debouncer.debounce('filament_stock', async () => {
|
||||
try {
|
||||
// Check if a stock event already exists for this subJobId and jobId
|
||||
const existingEventIndex = filamentStock.stockEvents.findIndex(
|
||||
event => event.subJob?.toString() === subJobId.toString() &&
|
||||
event.job?.toString() === jobId.toString()
|
||||
);
|
||||
this.existingEvent = await stockEventModel.findOne({
|
||||
filamentStock: filamentStockId,
|
||||
subJob: subJobId,
|
||||
job: jobId
|
||||
});
|
||||
|
||||
let updatedFilamentStock;
|
||||
if (existingEventIndex !== -1) {
|
||||
let stockEvent;
|
||||
if (this.existingEvent) {
|
||||
// Update existing event
|
||||
logger.debug(`Updating existing stock event for subJobId ${subJobId} and jobId ${jobId}`);
|
||||
updatedFilamentStock = await filamentStockModel.findOneAndUpdate(
|
||||
stockEvent = await stockEventModel.findByIdAndUpdate(
|
||||
this.existingEvent._id,
|
||||
{
|
||||
_id: filamentStockId,
|
||||
'stockEvents.subJob': subJobId,
|
||||
'stockEvents.job': jobId
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
'stockEvents.$.value': weight,
|
||||
'stockEvents.$.timestamp': new Date()
|
||||
}
|
||||
value: weight,
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{ new: true }
|
||||
);
|
||||
} else {
|
||||
// Create new stock event
|
||||
logger.debug(`Creating new stock event for subJobId ${subJobId} and jobId ${jobId}`);
|
||||
const stockEvent = {
|
||||
stockEvent = await stockEventModel.create({
|
||||
type: 'subJob',
|
||||
value: weight,
|
||||
subJob: subJobId,
|
||||
job: jobId,
|
||||
timestamp: new Date()
|
||||
};
|
||||
filamentStock: filamentStockId,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
updatedFilamentStock = await filamentStockModel.findByIdAndUpdate(
|
||||
// Add the new stock event to the filament stock
|
||||
await filamentStockModel.findByIdAndUpdate(
|
||||
filamentStockId,
|
||||
{
|
||||
$push: { stockEvents: stockEvent }
|
||||
},
|
||||
{ new: true }
|
||||
{ $push: { stockEvents: stockEvent._id } }
|
||||
);
|
||||
}
|
||||
|
||||
if (!updatedFilamentStock) {
|
||||
logger.error(`Failed to update filament stock ${filamentStockId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate new net weight based on all stock events
|
||||
const totalEventWeight = updatedFilamentStock.stockEvents.reduce((sum, event) => sum + event.value, 0);
|
||||
const newNetWeight = updatedFilamentStock.startingNetWeight + totalEventWeight;
|
||||
const newGrossWeight = updatedFilamentStock.startingGrossWeight + totalEventWeight;
|
||||
|
||||
|
||||
// Update the net weight
|
||||
const finalFilamentStock = await filamentStockModel.findByIdAndUpdate(
|
||||
const updatedFilamentStock = await filamentStockModel.findByIdAndUpdate(
|
||||
filamentStockId,
|
||||
{ currentNetWeight: newNetWeight, currentGrossWeight: newGrossWeight },
|
||||
{
|
||||
currentNetWeight: newNetWeight,
|
||||
currentGrossWeight: newGrossWeight,
|
||||
state
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
populate: {
|
||||
@ -640,41 +849,41 @@ logger.warn(`Updating subjob state for ${subJobId}:`, state);
|
||||
}
|
||||
);
|
||||
|
||||
// Calculate remaining percentage and update state
|
||||
const remainingPercent = newNetWeight / finalFilamentStock.startingNetWeight;
|
||||
const state = {
|
||||
type: 'partiallyconsumed',
|
||||
percent: (1 - remainingPercent).toFixed(2)
|
||||
};
|
||||
|
||||
const filamentStockWithState = await filamentStockModel.findByIdAndUpdate(
|
||||
filamentStockId,
|
||||
{ state },
|
||||
{ new: true }
|
||||
);
|
||||
// Update the cached filament stock
|
||||
this.filamentStock = updatedFilamentStock;
|
||||
|
||||
logger.info(`Updated filament stock ${filamentStockId}:`, {
|
||||
newGrossWeight: newGrossWeight,
|
||||
newNetWeight: newNetWeight,
|
||||
eventCount: finalFilamentStock.stockEvents.length,
|
||||
updatedExistingEvent: existingEventIndex !== -1,
|
||||
eventCount: updatedFilamentStock.stockEvents.length,
|
||||
updatedExistingEvent: !!this.existingEvent,
|
||||
remainingPercent: remainingPercent.toFixed(2),
|
||||
consumedPercent: state.percent,
|
||||
});
|
||||
|
||||
// Broadcast the update through websocket
|
||||
// Broadcast the final state after database update
|
||||
logger.debug(`Broadcasting final filament stock update for ${filamentStockId}:`, {
|
||||
newNetWeight,
|
||||
newGrossWeight,
|
||||
eventCount: updatedFilamentStock.stockEvents.length,
|
||||
state
|
||||
});
|
||||
this.socketManager.broadcast("notify_filamentstock_update", {
|
||||
id: filamentStockId,
|
||||
_id: filamentStockId,
|
||||
currentNetWeight: newNetWeight,
|
||||
currentGrossWeight: newGrossWeight,
|
||||
stockEvents: finalFilamentStock.stockEvents,
|
||||
state
|
||||
});
|
||||
|
||||
return filamentStockWithState;
|
||||
return updatedFilamentStock;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to update filament stock weight for ${filamentStockId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to initialize update for filament stock ${filamentStockId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,7 @@ export class PrinterClient {
|
||||
this.printerManager = printerManager;
|
||||
this.socketManager = socketManager;
|
||||
this.state = { type: 'offline '};
|
||||
this.klippyState = { type: 'offline '};
|
||||
this.klippyState = { type: 'offline'};
|
||||
this.config = printer.moonraker;
|
||||
this.version = printer.version;
|
||||
this.jsonRpc = new JsonRPC();
|
||||
@ -111,7 +111,7 @@ export class PrinterClient {
|
||||
|
||||
this.socket.on("open", () => {
|
||||
logger.info(`Connected to Moonraker (${this.name})`);
|
||||
this.online = true;
|
||||
this.isOnline = true;
|
||||
this.identifyConnection();
|
||||
});
|
||||
|
||||
@ -121,7 +121,7 @@ export class PrinterClient {
|
||||
|
||||
this.socket.on("close", () => {
|
||||
logger.info(`Disconnected from Moonraker (${this.name})`);
|
||||
this.online = false;
|
||||
this.isOnline = false;
|
||||
this.state = { type: "offline" };
|
||||
this.updatePrinterState();
|
||||
this.connectionId = null;
|
||||
@ -175,7 +175,7 @@ export class PrinterClient {
|
||||
try {
|
||||
// Get server info
|
||||
const serverResult = await this.jsonRpc.callMethod("server.info");
|
||||
this.online = true;
|
||||
this.isOnline = true;
|
||||
this.klippyState = { type: serverResult.klippy_state };
|
||||
logger.info(
|
||||
"Server:",
|
||||
@ -194,9 +194,27 @@ export class PrinterClient {
|
||||
`Updated firmware version for ${this.name} to ${klippyResult.software_version}`,
|
||||
);
|
||||
|
||||
if (klippyResult.state === "error") {
|
||||
if (klippyResult.state === "error" && klippyResult.state_message) {
|
||||
logger.error(
|
||||
`Klippy error for ${this.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert(this.id, {
|
||||
type: "klippyError",
|
||||
message: klippyResult.state_message,
|
||||
priority: 9,
|
||||
timestamp: new Date()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (klippyResult.state === "startup" && klippyResult.state_message) {
|
||||
logger.warn(
|
||||
`Klippy startup message for ${this.name}: ${klippyResult.state_message}`,
|
||||
this.database.addAlert(this.id, {
|
||||
type: "klippyStartup",
|
||||
message: klippyResult.state_message,
|
||||
priority: 8,
|
||||
timestamp: new Date()
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@ -219,7 +237,7 @@ export class PrinterClient {
|
||||
|
||||
async getPrinterState() {
|
||||
logger.info(`Getting state of (${this.name})`);
|
||||
if (!this.online) {
|
||||
if (!this.isOnline) {
|
||||
logger.error(
|
||||
`Cannot send command: Not connected to Moonraker (${this.name})`,
|
||||
);
|
||||
@ -267,9 +285,9 @@ export class PrinterClient {
|
||||
Object.assign(allSubscriptions, value);
|
||||
}
|
||||
|
||||
logger.debug("Combined subscriptions:", allSubscriptions);
|
||||
logger.debug("Combined subscriptions:", Object.keys(allSubscriptions).join(", "));
|
||||
|
||||
if (!this.online) {
|
||||
if (!this.isOnline) {
|
||||
logger.error(
|
||||
`Cannot send command: Not connected to Moonraker (${this.name})`,
|
||||
);
|
||||
@ -281,6 +299,9 @@ export class PrinterClient {
|
||||
objects: allSubscriptions,
|
||||
});
|
||||
logger.debug(`Command sent to (${this.name})`);
|
||||
logger.debug({
|
||||
objects: allSubscriptions,
|
||||
})
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`Error sending command to (${this.name}):`, error);
|
||||
@ -290,7 +311,7 @@ export class PrinterClient {
|
||||
|
||||
async sendPrinterCommand(command) {
|
||||
logger.info(`Sending ${command.method} command to (${this.name})`);
|
||||
if (!this.online) {
|
||||
if (!this.isOnline) {
|
||||
logger.error(
|
||||
`Cannot send command: Not connected to Moonraker (${this.name})`,
|
||||
);
|
||||
@ -329,7 +350,7 @@ export class PrinterClient {
|
||||
if (status.print_stats?.state) {
|
||||
const newState = status.print_stats.state;
|
||||
if (newState !== this.state.type) {
|
||||
logger.info(`printer ${this.name} state changed from ${this.state.type} to ${newState}`);
|
||||
logger.info(`Printer ${this.name} state changed from ${this.state.type} to ${newState}`);
|
||||
this.state.type = newState;
|
||||
stateChanged = true;
|
||||
}
|
||||
@ -345,14 +366,6 @@ export class PrinterClient {
|
||||
// Calculate weight in grams
|
||||
const filamentWeightG = filamentVolumeCm3 * this.currentFilamentStockDensity;
|
||||
|
||||
console.log('Filament used:', {
|
||||
length: status.print_stats.filament_used,
|
||||
lengthCm: filamentLengthCm,
|
||||
volumeCm3: filamentVolumeCm3,
|
||||
density: this.currentFilamentStockDensity,
|
||||
weightG: filamentWeightG
|
||||
});
|
||||
|
||||
if (this.currentSubJobId != null && this.currentJobId != null && this.currentFilamentStockId != null) {
|
||||
this.database.updateFilamentStockWeight(this.currentFilamentStockId, (-1 * filamentWeightG), this.currentSubJobId, this.currentJobId);
|
||||
}
|
||||
@ -398,9 +411,7 @@ this.database.updateFilamentStockWeight(this.currentFilamentStockId, (-1 * filam
|
||||
|
||||
if (stateChanged || progressChanged) {
|
||||
// Update printer state first
|
||||
await this.database.updatePrinterState(this.id, this.state, this.online);
|
||||
|
||||
logger.warn('State changed!')
|
||||
await this.updatePrinterState()
|
||||
// Set current job to null when not printing or paused
|
||||
if (!["printing", "paused"].includes(this.state.type)) {
|
||||
this.currentJobId = null;
|
||||
@ -428,7 +439,8 @@ this.database.updateFilamentStockWeight(this.currentFilamentStockId, (-1 * filam
|
||||
|
||||
async updatePrinterState() {
|
||||
try {
|
||||
await this.database.updatePrinterState(this.id, this.state, this.online);
|
||||
const state = this.klippyState.type !== 'ready' ? this.klippyState : this.state;
|
||||
await this.database.updatePrinterState(this.id, state, this.isOnline);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to update printer state:`, error);
|
||||
}
|
||||
@ -504,7 +516,7 @@ this.database.updateFilamentStockWeight(this.currentFilamentStockId, (-1 * filam
|
||||
if (!this.currentSubJobId) {
|
||||
const result = await this.database.setCurrentJobForPrinting(this.id, this.queuedJobIds);
|
||||
if (result) {
|
||||
logger.info(`Setting first queued subjob as current for printer ${this.name}`, result);
|
||||
logger.info(`Setting first queued subjob as current for printer ${this.name}: `, result.currentSubJob._id);
|
||||
this.currentSubJobId = result.currentSubJob._id;
|
||||
this.currentJobId = result.currentJob._id;
|
||||
await this.database.updateSubJobState(this.currentSubJobId, this.state);
|
||||
@ -538,10 +550,12 @@ this.database.updateFilamentStockWeight(this.currentFilamentStockId, (-1 * filam
|
||||
async handleKlippyDisconnected() {
|
||||
logger.info(`Klippy disconnected (${this.name})`);
|
||||
this.state = { type: "offline" };
|
||||
this.klippyState = { type: 'offline'};
|
||||
this.isOnline = false;
|
||||
this.isPrinting = false;
|
||||
this.isError = false;
|
||||
this.isReady = false;
|
||||
await this.database.clearAlerts(this.id);
|
||||
await this.updatePrinterState();
|
||||
await this.updatePrinterSubJobs();
|
||||
}
|
||||
@ -565,7 +579,7 @@ this.database.updateFilamentStockWeight(this.currentFilamentStockId, (-1 * filam
|
||||
|
||||
async uploadGcodeFile(fileBlob, fileName) {
|
||||
logger.info(`Uploading G-code file ${fileName} to ${this.name}`);
|
||||
if (!this.online) {
|
||||
if (!this.isOnline) {
|
||||
logger.error(
|
||||
`Cannot upload file: Not connected to Moonraker (${this.name})`,
|
||||
);
|
||||
|
||||
@ -138,7 +138,7 @@ export class SocketClient {
|
||||
|
||||
// Merge the new subscription data with existing data
|
||||
const mergedSubscription = {
|
||||
...existingSubscription.objects,
|
||||
...existingSubscription,
|
||||
...data.objects,
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user