@fdm-monster/server
Version:
FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.
233 lines (232 loc) • 7.57 kB
JavaScript
import { uploadFileInputSchema } from "./printer-api.interface.js";
import { BambuMqttAdapter } from "./bambu/bambu-mqtt.adapter.js";
import { statSync } from "node:fs";
//#region src/services/bambu.api.ts
const defaultLog = { adapter: "bambu-lab" };
/**
* Bambu Lab printer API implementation
* Implements the IPrinterApi interface for fdm-monster integration
*
* Note: MQTT adapter is managed by PrinterSocketStore
*
* Credentials mapping:
* - printerURL: Host IP address
* - password: Access code (8-character authentication code)
* - username: Serial number
*/
var BambuApi = class BambuApi {
logger;
client;
printerLogin;
printerSocketStore;
printerId;
constructor(bambuClient, printerLogin, printerSocketStore, loggerFactory) {
this.logger = loggerFactory(BambuApi.name);
this.client = bambuClient;
this.printerLogin = printerLogin;
this.printerSocketStore = printerSocketStore;
this.logger.debug("Constructed Bambu API client", this.logMeta());
}
/**
* Set the printer ID for accessing the MQTT adapter
*/
setPrinterId(printerId) {
this.printerId = printerId;
}
/**
* Get the MQTT adapter from the printer socket store
*/
getMqttAdapter() {
if (!this.printerId) throw new Error("Printer ID not set. Cannot access MQTT adapter.");
const adapter = this.printerSocketStore.getPrinterSocket(this.printerId);
if (!adapter) throw new Error(`MQTT adapter not found for printer ${this.printerId}`);
if (!(adapter instanceof BambuMqttAdapter)) throw new Error(`Adapter for printer ${this.printerId} is not a BambuMqttAdapter`);
return adapter;
}
/**
* Ensure FTP is connected, auto-connect if needed
*/
async ensureFtpConnected() {
if (!this.client.isConnected) {
this.logger.debug("FTP not connected, connecting automatically");
await this.client.connect(this.printerLogin);
}
}
get type() {
return 3;
}
set login(login) {
this.printerLogin = login;
}
async getVersion() {
return (await this.client.getApiVersion(this.printerLogin)).version;
}
async validateConnection() {
this.logger.debug("Validating Bambu connection", this.logMeta());
try {
await this.ensureFtpConnected();
const files = await this.client.ftp.listFiles("/");
this.logger.debug(`FTP connection successful - found ${files.length} directories`, this.logMeta());
} catch (ftpError) {
this.logger.debug(`FTP validation failed: ${ftpError}`, this.logMeta());
throw new Error(`Bambu FTP connection failed: ${ftpError}`);
}
}
async connect() {
await this.client.connect(this.printerLogin);
}
async disconnect() {
await this.client.disconnect();
}
restartServer() {
this.logger.warn("restartServer not supported by Bambu Lab printers");
throw new Error("Method not supported");
}
restartHost() {
this.logger.warn("restartHost not supported by Bambu Lab printers");
throw new Error("Method not supported");
}
restartPrinterFirmware() {
this.logger.warn("restartPrinterFirmware not supported by Bambu Lab printers");
throw new Error("Method not supported");
}
async startPrint(path) {
this.logger.log(`Starting print: ${path}`, this.logMeta());
await this.getMqttAdapter().startPrint(path);
}
async pausePrint() {
this.logger.log("Pausing print", this.logMeta());
await this.getMqttAdapter().pausePrint();
}
async resumePrint() {
this.logger.log("Resuming print", this.logMeta());
await this.getMqttAdapter().resumePrint();
}
async cancelPrint() {
this.logger.log("Canceling print", this.logMeta());
await this.getMqttAdapter().stopPrint();
}
async quickStop() {
this.logger.log("Quick stop (same as cancel for Bambu)", this.logMeta());
await this.getMqttAdapter().stopPrint();
}
async sendGcode(script) {
this.logger.log(`Sending GCode: ${script}`, this.logMeta());
await this.getMqttAdapter().sendGcode(script);
}
movePrintHead(amounts) {
this.logger.warn("movePrintHead not implemented for Bambu Lab printers");
throw new Error("Method not implemented");
}
homeAxes(axes) {
this.logger.warn("homeAxes not implemented for Bambu Lab printers");
throw new Error("Method not implemented");
}
async getFile(path) {
this.logger.debug(`Getting file info: ${path}`, this.logMeta());
await this.ensureFtpConnected();
const file = (await this.client.ftp.listFiles("/")).find((f) => f.name === path || f.name.endsWith(path));
if (!file) throw new Error(`File not found: ${path}`);
return {
path: file.name,
size: file.size,
date: file.modifiedAt ? new Date(file.modifiedAt).getTime() : null,
dir: file.isDirectory
};
}
async getFiles(recursive = false, startDir = "/") {
if (recursive) throw new Error("Recursive listing not supported for Bambu Lab printers");
this.logger.debug(`Listing files from ${startDir}`, this.logMeta());
await this.ensureFtpConnected();
const mapped = (await this.client.ftp.listFiles(startDir)).map((item) => {
return {
path: startDir === "/" ? `/${item.name}` : `${startDir}/${item.name}`,
size: item.size,
date: item.modifiedAt ? new Date(item.modifiedAt).getTime() : null,
dir: item.isDirectory
};
});
return {
dirs: mapped.filter((i) => i.dir),
files: mapped.filter((i) => !i.dir)
};
}
async downloadFile(path) {
this.logger.log(`Downloading file via FTP: ${path}`, this.logMeta());
await this.ensureFtpConnected();
const { stream, tempPath } = await this.client.ftp.downloadFileAsStream(path);
const stats = statSync(tempPath);
const filename = path.split("/").pop() || "download";
return {
data: stream,
status: 200,
statusText: "OK",
headers: {
"content-type": "application/octet-stream",
"content-length": String(stats.size),
"content-disposition": `attachment; filename="${filename}"`
},
config: { headers: {} }
};
}
getFileChunk(path, startBytes, endBytes) {
this.logger.warn("getFileChunk not implemented for Bambu Lab printers");
throw new Error("Method not implemented");
}
async uploadFile(input) {
const validated = uploadFileInputSchema.parse(input);
this.logger.log(`Uploading file: ${validated.fileName} (${validated.contentLength} bytes)`, this.logMeta());
try {
await this.ensureFtpConnected();
await this.client.ftp.uploadFile(validated.stream, validated.fileName, validated.uploadToken);
if (validated.startPrint) {
this.logger.log(`Starting print after upload: ${validated.fileName}`, this.logMeta());
await this.getMqttAdapter().startPrint(validated.fileName);
}
} catch (error) {
this.logger.error(`Upload failed: ${error.message}`, this.logMeta());
throw error;
}
}
async deleteFile(path) {
this.logger.log(`Deleting file: ${path}`, this.logMeta());
await this.ensureFtpConnected();
await this.client.ftp.deleteFile(path);
}
deleteFolder(path) {
this.logger.warn("deleteFolder not implemented for Bambu Lab printers");
throw new Error("Method not implemented");
}
getSettings() {
this.logger.warn("getSettings not implemented for Bambu Lab printers");
throw new Error("Method not implemented");
}
async getReprintState() {
const state = this.getMqttAdapter().getLastState();
if (!state) return {
reprintState: 0,
connectionState: null
};
const lastFile = state.gcode_file;
if (!lastFile) return {
reprintState: 1,
connectionState: null
};
return {
file: {
path: lastFile,
size: -1,
date: null,
dir: false
},
reprintState: 2,
connectionState: null
};
}
logMeta() {
return defaultLog;
}
};
//#endregion
export { BambuApi };
//# sourceMappingURL=bambu.api.js.map