mcp-3d-printer-server 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,10 @@
9
9
  [![Downloads](https://img.shields.io/npm/dm/mcp-3d-printer-server.svg)](https://www.npmjs.com/package/mcp-3d-printer-server)
10
10
  [![GitHub stars](https://img.shields.io/github/stars/dmontgomery40/mcp-3d-printer-server.svg?style=social&label=Star)](https://github.com/yourusername/mcp-3d-printer-server)
11
11
 
12
+ <a href="https://glama.ai/mcp/servers/7f6v2enbgk">
13
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/7f6v2enbgk/badge" alt="3D Printer Server MCP server" />
14
+ </a>
15
+
12
16
  <details>
13
17
  <summary><strong>✨ What's New / Significant Updates (as of last session)</strong></summary>
14
18
 
@@ -795,4 +799,4 @@ Due to the nature of the Bambu Lab printer API, there are some limitations:
795
799
 
796
800
  ## License
797
801
 
798
- GPL-2.0
802
+ GPL-2.0
@@ -1,4 +1,4 @@
1
- import { PrinterImplementation, BambuPrinterStore } from "../types.js";
1
+ import { PrinterImplementation } from "../types.js";
2
2
  interface BambuPrintOptionsInternal {
3
3
  projectName: string;
4
4
  filePath: string;
@@ -9,34 +9,31 @@ interface BambuPrintOptionsInternal {
9
9
  vibrationCalibration?: boolean;
10
10
  layerInspect?: boolean;
11
11
  timelapse?: boolean;
12
- amsMapping?: number[];
12
+ amsMapping?: {
13
+ [originalFilamentIndex: string]: number;
14
+ };
13
15
  md5?: string;
14
16
  }
15
17
  export declare class BambuImplementation extends PrinterImplementation {
16
- private bambuPrinterStore;
17
- private mqttClients;
18
- private mqttConnectionPromises;
19
- constructor(apiClient: any, bambuPrinterStore: BambuPrinterStore);
20
- private getBambuPrinterForFTP;
21
- private getMqttClient;
22
- disconnectAllMqtt(): Promise<void>;
18
+ private printerStore;
19
+ constructor(apiClient: any);
20
+ private getPrinter;
23
21
  getStatus(host: string, port: string, apiKey: string): Promise<any>;
24
22
  print3mf(host: string, serial: string, token: string, options: BambuPrintOptionsInternal): Promise<any>;
25
23
  cancelJob(host: string, port: string, apiKey: string): Promise<any>;
26
24
  setTemperature(host: string, port: string, apiKey: string, component: string, temperature: number): Promise<void>;
27
25
  getFiles(host: string, port: string, apiKey: string): Promise<{
28
- files: string[];
26
+ files: never[];
27
+ note: string;
29
28
  }>;
30
29
  getFile(host: string, port: string, apiKey: string, filename: string): Promise<{
31
30
  name: string;
32
31
  exists: boolean;
32
+ note: string;
33
33
  }>;
34
- uploadFile(host: string, port: string, apiKey: string, filePath: string, filename: string, print: boolean): Promise<{
35
- status: string;
36
- message: string;
37
- }>;
34
+ uploadFile(host: string, port: string, apiKey: string, filePath: string, filename: string, print: boolean): Promise<void>;
38
35
  startJob(host: string, port: string, apiKey: string, filename: string): Promise<void>;
39
- private publishMqttCommand;
40
36
  private extractBambuCredentials;
37
+ disconnectAll(): Promise<void>;
41
38
  }
42
39
  export {};
@@ -1,279 +1,226 @@
1
1
  import { PrinterImplementation } from "../types.js";
2
- import * as mqtt from 'mqtt'; // Import MQTT library
3
- import * as path from 'path';
4
- export class BambuImplementation extends PrinterImplementation {
5
- constructor(apiClient, bambuPrinterStore) {
6
- super(apiClient);
7
- this.mqttClients = {}; // Store connected MQTT clients
8
- this.mqttConnectionPromises = {}; // Track connection attempts
9
- this.bambuPrinterStore = bambuPrinterStore;
10
- }
11
- // Get BambuPrinter instance for FTP
12
- getBambuPrinterForFTP(host, serial, token) {
13
- // This still uses bambu-js just for the FTP part
14
- return this.bambuPrinterStore.get(host, serial, token);
2
+ import { BambuClient, UpdateStateCommand,
3
+ // State, // Removed as using string literal
4
+ // TempUpdatePartType // Removed as likely unusable type
5
+ } from "bambu-node"; // Use bambu-node library
6
+ // Store for bambu-node printer instances
7
+ class BambuClientStore {
8
+ constructor() {
9
+ this.printers = new Map();
10
+ this.initialConnectionPromises = new Map();
15
11
  }
16
- // Get or establish MQTT connection
17
- async getMqttClient(host, serial, token) {
18
- const clientKey = `${host}-${serial}`;
19
- if (this.mqttClients[clientKey] && this.mqttClients[clientKey].connected) {
20
- return this.mqttClients[clientKey];
12
+ async getPrinter(host, serial, token) {
13
+ const key = `${host}-${serial}`;
14
+ // If already connected/connecting, return existing instance or wait for connection
15
+ if (this.printers.has(key)) {
16
+ console.log(`Returning existing BambuClient instance for ${key}`);
17
+ return this.printers.get(key);
21
18
  }
22
- // If a connection attempt is already in progress, wait for it
23
- if (this.mqttConnectionPromises[clientKey] !== undefined) {
24
- console.log(`Waiting for existing MQTT connection attempt to ${clientKey}...`);
25
- return this.mqttConnectionPromises[clientKey];
19
+ if (this.initialConnectionPromises.has(key)) {
20
+ console.log(`Waiting for existing initial connection attempt for ${key}...`);
21
+ // Don't await here; if promise exists, listeners will handle map update
22
+ // await this.initialConnectionPromises.get(key);
23
+ // Throw error immediately if we know a connection is pending but not resolved yet
24
+ // Or rely on the promise rejection propagating if it fails
25
+ await this.initialConnectionPromises.get(key); // Await necessary to ensure connection completes or fails
26
+ if (this.printers.has(key)) {
27
+ return this.printers.get(key);
28
+ }
29
+ else {
30
+ throw new Error(`Existing initial connection attempt for ${key} failed or timed out.`);
31
+ }
26
32
  }
27
- console.log(`Attempting to establish new MQTT connection to ${host} for ${serial}...`);
28
- const connectPromise = new Promise((resolve, reject) => {
29
- // Use port 8883 and TLS
30
- const mqttOptions = {
31
- port: 8883,
32
- host: host,
33
- protocol: 'mqtts',
34
- username: 'bblp', // As per OpenBambuAPI docs
35
- password: token,
36
- clientId: `mcp_client_${serial}_${Date.now()}`,
37
- rejectUnauthorized: false, // Might be needed for self-signed certs? Test carefully.
38
- // ca: [fs.readFileSync('path/to/ca_cert.pem')], // Add CA cert if needed
39
- connectTimeout: 10000, // 10 seconds
40
- reconnectPeriod: 5000 // Try reconnecting every 5 seconds
41
- };
42
- const client = mqtt.connect(mqttOptions);
43
- client.on('connect', () => {
44
- console.log(`MQTT connected successfully to ${host} for ${serial}`);
45
- this.mqttClients[clientKey] = client;
46
- // Subscribe to necessary topics (e.g., responses/status)
47
- client.subscribe(`device/${serial}/report`, (err) => {
48
- if (err) {
49
- console.error(`Failed to subscribe to report topic for ${serial}:`, err);
50
- }
51
- });
52
- delete this.mqttConnectionPromises[clientKey]; // Clear promise tracker on success
53
- resolve(client);
54
- });
55
- client.on('error', (err) => {
56
- console.error(`MQTT connection error for ${serial} on ${host}:`, err);
57
- delete this.mqttClients[clientKey]; // Remove potentially bad client
58
- delete this.mqttConnectionPromises[clientKey]; // Clear promise tracker on error
59
- reject(err);
60
- });
61
- client.on('close', () => {
62
- console.log(`MQTT connection closed for ${serial} on ${host}`);
63
- delete this.mqttClients[clientKey]; // Remove client on close
64
- });
65
- // Handle status messages from the report topic
66
- client.on('message', (topic, message) => {
67
- try {
68
- const data = JSON.parse(message.toString());
69
- // Processing logic for status updates would go here
70
- // For now, we just acknowledge receiving it (optional logging)
71
- // console.log(`MQTT message received on ${topic}:`, data?.print?.subtask_name ?? data?.print?.command ?? 'Unknown message type');
72
- }
73
- catch (e) {
74
- console.error('Failed to parse MQTT message:', e);
75
- }
76
- });
33
+ // Create new instance and attempt connection
34
+ console.log(`Creating new BambuClient instance for ${key}`);
35
+ const printer = new BambuClient({
36
+ host: host,
37
+ serialNumber: serial,
38
+ accessToken: token,
77
39
  });
78
- this.mqttConnectionPromises[clientKey] = connectPromise; // Store the promise
79
- return connectPromise;
40
+ // Setup event listeners for state management
41
+ printer.on('client:connect', () => {
42
+ console.log(`BambuClient connected for ${key}`);
43
+ this.printers.set(key, printer);
44
+ this.initialConnectionPromises.delete(key);
45
+ });
46
+ printer.on('client:error', (err) => {
47
+ console.error(`BambuClient connection error for ${key}:`, err);
48
+ this.printers.delete(key);
49
+ this.initialConnectionPromises.delete(key);
50
+ });
51
+ printer.on('client:disconnect', () => {
52
+ console.log(`BambuClient connection closed for ${key}`);
53
+ this.printers.delete(key);
54
+ this.initialConnectionPromises.delete(key);
55
+ });
56
+ printer.on('printer:dataUpdate', (data) => {
57
+ // Optional: log or update internal state based on data
58
+ // console.log(`BambuClient data update for ${key}`);
59
+ });
60
+ // Store promise and initiate connection
61
+ console.log(`Attempting initial connection for BambuClient ${key}...`);
62
+ const connectPromise = printer.connect().then(() => { });
63
+ this.initialConnectionPromises.set(key, connectPromise);
64
+ try {
65
+ await connectPromise;
66
+ console.log(`Initial connection successful for ${key}.`);
67
+ // Redundant set, already handled by 'client:connect' listener
68
+ // this.printers.set(key, printer);
69
+ return printer;
70
+ }
71
+ catch (err) {
72
+ console.error(`Initial connection failed for ${key}:`, err);
73
+ this.initialConnectionPromises.delete(key); // Clean up promise map on failure
74
+ throw err; // Rethrow the error
75
+ }
80
76
  }
81
- // Disconnect MQTT clients on shutdown
82
- async disconnectAllMqtt() {
83
- console.log("Disconnecting all MQTT clients...");
84
- for (const key in this.mqttClients) {
85
- const client = this.mqttClients[key];
86
- if (client) {
87
- await new Promise((resolve, reject) => {
88
- client.end(false, {}, (err) => {
89
- if (err) {
90
- console.error(`Error ending MQTT client ${key}:`, err);
91
- reject(err);
92
- }
93
- else {
94
- console.log(`MQTT client ${key} disconnected.`);
95
- resolve();
96
- }
97
- });
98
- });
99
- }
77
+ async disconnectAll() {
78
+ console.log("Disconnecting all BambuClient instances...");
79
+ const disconnectPromises = [];
80
+ for (const [key, printer] of this.printers.entries()) {
81
+ disconnectPromises.push(printer.disconnect()
82
+ .then(() => console.log(`Disconnected ${key}`))
83
+ .catch(err => console.error(`Error disconnecting ${key}:`, err)));
100
84
  }
101
- this.mqttClients = {};
102
- this.mqttConnectionPromises = {};
85
+ await Promise.allSettled(disconnectPromises);
86
+ this.printers.clear();
87
+ this.initialConnectionPromises.clear();
88
+ }
89
+ }
90
+ export class BambuImplementation extends PrinterImplementation {
91
+ constructor(apiClient /* Not used */) {
92
+ super(apiClient);
93
+ this.printerStore = new BambuClientStore();
94
+ }
95
+ // Helper to get connected printer instance
96
+ async getPrinter(host, serial, token) {
97
+ return this.printerStore.getPrinter(host, serial, token);
103
98
  }
104
- // --- getStatus (Limited Implementation) ---
99
+ // --- getStatus ---
105
100
  async getStatus(host, port, apiKey) {
106
101
  const [serial, token] = this.extractBambuCredentials(apiKey);
107
102
  try {
108
- // Ensure MQTT connection is attempted/established
109
- const client = await this.getMqttClient(host, serial, token);
110
- // Return minimal status indicating connection success
111
- // Full status requires implementing state storage based on /report topic messages
103
+ const printer = await this.getPrinter(host, serial, token);
104
+ // Wait a moment for status data to populate if needed
105
+ if (!printer.data || Object.keys(printer.data).length === 0) {
106
+ await new Promise(resolve => setTimeout(resolve, 2000));
107
+ }
108
+ const data = printer.data;
112
109
  return {
113
- status: client.connected ? "connected" : "connecting",
114
- serial: serial,
115
- note: "Full status details require MQTT state tracking implementation."
110
+ status: data.gcode_state || "UNKNOWN",
111
+ connected: true,
112
+ temperatures: {
113
+ nozzle: {
114
+ actual: data.nozzle_temper || 0,
115
+ target: data.nozzle_target_temper || 0
116
+ },
117
+ bed: {
118
+ actual: data.bed_temper || 0,
119
+ target: data.bed_target_temper || 0
120
+ },
121
+ chamber: data.chamber_temper || data.frame_temper || 0
122
+ },
123
+ print: {
124
+ filename: data.subtask_name || "None",
125
+ progress: data.mc_percent || 0,
126
+ timeRemaining: data.mc_remaining_time || 0,
127
+ currentLayer: data.layer_num || 0,
128
+ totalLayers: data.total_layer_num || 0
129
+ },
130
+ ams: data.ams || null,
131
+ model: data.model || "Unknown",
132
+ raw: data // Include raw data for debugging
116
133
  };
117
134
  }
118
135
  catch (error) {
119
- console.error(`Failed to get MQTT client/status for ${serial}:`, error);
120
- return { status: "error", error: error.message };
136
+ console.error(`Failed to get BambuClient status for ${serial}:`, error);
137
+ return { status: "error", connected: false, error: error.message };
121
138
  }
122
139
  }
123
- // --- Refactored print3mf using direct MQTT ---
140
+ // --- print3mf ---
124
141
  async print3mf(host, serial, token, options) {
125
- // Still use bambu-js for FTP upload
126
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
127
- const remoteFilename = path.basename(options.filePath);
128
- // Ensure path uses forward slashes for printer consistency
129
- const remotePath = `gcodes/${remoteFilename}`.replace(/\\/g, '/');
130
- // 1. Connect FTP if necessary (via bambu-js helper)
131
- if (!printerFTP.isConnected) {
132
- console.log(`Connecting Bambu FTP ${host} (${serial}) on port 990 (assumed) before uploading...`);
133
- try {
134
- await printerFTP.connect(); // Connects MQTT and FTP
135
- console.log(`Connected Bambu FTP successfully.`);
136
- }
137
- catch (ftpConnectError) {
138
- console.error(`Bambu FTP Connection Error:`, ftpConnectError);
139
- throw new Error(`Failed to connect to Bambu FTP: ${ftpConnectError.message}`);
140
- }
141
- // await printerFTP.awaitInitialState(10000); // Might not be needed just for FTP
142
- }
143
- // 2. Upload the file via FTP
144
- console.log(`Uploading ${options.filePath} to ${remotePath} via FTP...`);
145
- try {
146
- await printerFTP.manipulateFiles(async (context) => {
147
- await context.sendFile(options.filePath, remotePath);
148
- });
149
- console.log(`File ${remoteFilename} uploaded successfully.`);
150
- }
151
- catch (uploadError) {
152
- console.error(`FTP Upload Error:`, uploadError);
153
- throw new Error(`Failed to upload 3MF file via FTP: ${uploadError.message}`);
154
- }
155
- // 3. Get MQTT Client
156
- const mqttClient = await this.getMqttClient(host, serial, token);
157
- // 4. Construct the MQTT Print Command Payload based on OpenBambuAPI
158
- const commandPayload = {
159
- sequence_id: Date.now().toString(),
160
- command: "project_file",
161
- param: remotePath, // Path on SD card
162
- project_name: options.projectName,
163
- use_ams: options.amsMapping && options.amsMapping.length > 0 ? true : (options.useAMS ?? false),
164
- plate_idx: options.plateIndex ?? 0,
165
- bed_levelling: options.bedLeveling ?? true,
166
- flow_cali: options.flowCalibration ?? false,
167
- vibration_cali: options.vibrationCalibration ?? false,
168
- layer_inspect: options.layerInspect ?? false,
169
- timelapse: options.timelapse ?? false,
170
- ams_mapping: options.amsMapping,
171
- md5: options.md5
172
- };
173
- // Remove ams_mapping if not provided or empty
174
- if (!commandPayload.ams_mapping || commandPayload.ams_mapping.length === 0) {
175
- delete commandPayload.ams_mapping;
176
- // Also ensure use_ams is false if no mapping exists
177
- commandPayload.use_ams = false;
178
- }
179
- // Remove md5 if not provided
180
- if (!commandPayload.md5) {
181
- delete commandPayload.md5;
182
- }
183
- // 5. Publish the command
184
- return this.publishMqttCommand(mqttClient, serial, 'print', commandPayload);
142
+ console.error("print3mf error: bambu-node library does not directly support the required FTPS file upload for .3mf files.");
143
+ throw new Error("Printing .3mf files is not supported with the current bambu-node library integration due to missing FTPS capability.");
185
144
  }
186
- // --- Refactored cancelJob using MQTT ---
145
+ // --- cancelJob ---
187
146
  async cancelJob(host, port, apiKey) {
188
147
  const [serial, token] = this.extractBambuCredentials(apiKey);
189
- const mqttClient = await this.getMqttClient(host, serial, token);
190
- // Command structure from OpenBambuAPI (assuming 'stop_print' is correct)
191
- const payload = { sequence_id: Date.now().toString(), command: "stop_print" };
192
- return this.publishMqttCommand(mqttClient, serial, 'print', payload);
148
+ const printer = await this.getPrinter(host, serial, token);
149
+ console.log(`Attempting to cancel print via bambu-node...`);
150
+ try {
151
+ // Use UpdateStateCommand with string literal state
152
+ const command = new UpdateStateCommand({ state: 'stop' });
153
+ await printer.executeCommand(command);
154
+ console.log(`Cancel print command successful via bambu-node.`);
155
+ return { status: "success", message: "Cancel command sent successfully." };
156
+ }
157
+ catch (cancelError) {
158
+ console.error(`Error sending cancel command via bambu-node:`, cancelError);
159
+ throw new Error(`Failed to cancel print: ${cancelError.message}`);
160
+ }
193
161
  }
194
- // --- setTemperature (still not directly possible via MQTT command) ---
162
+ // --- setTemperature --- (Commenting out due to type issues)
195
163
  async setTemperature(host, port, apiKey, component, temperature) {
196
- console.warn("Setting temperatures directly is not supported via known Bambu MQTT commands. Use G-code.");
197
- // We could potentially implement a tool to send custom G-code?
198
- throw new Error("Direct temperature setting via MQTT is not supported.");
164
+ // const [serial, token] = this.extractBambuCredentials(apiKey);
165
+ // const printer = await this.getPrinter(host, serial, token);
166
+ // console.log(`Attempting to set temperature for ${component} to ${temperature}°C via bambu-node...`);
167
+ // try {
168
+ // let command;
169
+ // const partArg = component.toLowerCase();
170
+ // if (partArg === 'extruder' || partArg === 'nozzle') {
171
+ // // Use string literal for part. Cast temp due to strict lib types.
172
+ // console.warn("Casting temperature to 'any' for UpdateTempCommand.");
173
+ // command = new UpdateTempCommand({ part: 'nozzle', temperature: temperature as any });
174
+ // } else if (partArg === 'bed') {
175
+ // // Use string literal for part. Cast temp due to strict lib types.
176
+ // console.warn("Casting temperature to 'any' for UpdateTempCommand.");
177
+ // command = new UpdateTempCommand({ part: 'bed', temperature: temperature as any });
178
+ // } else {
179
+ // throw new Error(`Unsupported temperature component: ${component}. Use 'extruder' or 'bed'.`);
180
+ // }
181
+ // await printer.executeCommand(command);
182
+ // console.log(`Set temperature command successful for ${component}.`);
183
+ // return { status: "success", message: `Temperature set command for ${component} sent.` };
184
+ // } catch (tempError) {
185
+ // console.error(`Error sending set temperature command via bambu-node:`, tempError);
186
+ // throw new Error(`Failed to set temperature: ${(tempError as Error).message}`);
187
+ // }
188
+ console.warn("setTemperature is currently disabled due to type inconsistencies in the bambu-node library.");
189
+ throw new Error("setTemperature is disabled.");
199
190
  }
200
- // --- Other methods (getFiles, getFile, uploadFile, startJob) need review/refactoring ---
201
- // For now, keep using bambu-js FTP for file ops, but MQTT for commands
191
+ // --- getFiles --- (Library doesn't seem to expose direct file listing)
202
192
  async getFiles(host, port, apiKey) {
203
- const [serial, token] = this.extractBambuCredentials(apiKey);
204
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
205
- if (!printerFTP.isConnected)
206
- await printerFTP.connect();
207
- const fileList = [];
208
- await printerFTP.manipulateFiles(async (context) => {
209
- const files = await context.readDir("gcodes");
210
- fileList.push(...files);
211
- });
212
- return { files: fileList };
193
+ console.warn("File listing is not directly supported by bambu-node. Returning empty list.");
194
+ return { files: [], note: "Not supported by bambu-node library" };
213
195
  }
196
+ // --- getFile --- (Library doesn't seem to expose direct file metadata/download)
214
197
  async getFile(host, port, apiKey, filename) {
215
- // Still relies on checking existence via FTP
216
- const [serial, token] = this.extractBambuCredentials(apiKey);
217
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
218
- if (!printerFTP.isConnected)
219
- await printerFTP.connect();
220
- let fileExists = false;
221
- await printerFTP.manipulateFiles(async (context) => {
222
- const files = await context.readDir("gcodes");
223
- fileExists = files.includes(filename);
224
- });
225
- if (!fileExists)
226
- throw new Error(`File not found: ${filename}`);
227
- return { name: filename, exists: true };
198
+ console.warn("Getting individual file metadata/content is not directly supported by bambu-node.");
199
+ return { name: filename, exists: false, note: "Not supported by bambu-node library" };
228
200
  }
201
+ // --- uploadFile (Handled by print method if supported) ---
229
202
  async uploadFile(host, port, apiKey, filePath, filename, print) {
230
- // This method just uploads now, doesn't handle printing
231
- const [serial, token] = this.extractBambuCredentials(apiKey);
232
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
233
- const remotePath = `gcodes/${filename}`.replace(/\\/g, '/');
234
- if (!printerFTP.isConnected)
235
- await printerFTP.connect();
236
- console.log(`Uploading ${filePath} to ${remotePath} via FTP...`);
237
- try {
238
- await printerFTP.manipulateFiles(async (context) => {
239
- await context.sendFile(filePath, remotePath);
240
- });
241
- console.log(`File ${filename} uploaded successfully.`);
242
- if (print) {
243
- console.warn("'print=true' ignored in uploadFile for Bambu. Use print_3mf tool instead.");
244
- }
245
- return { status: "success", message: `File ${filename} uploaded successfully` };
203
+ console.warn("Use the 'print_3mf' tool. Direct upload without print is not the primary use case.");
204
+ if (print) {
205
+ // Cannot directly call print3mf as it's not supported by this library
206
+ throw new Error("Cannot initiate print via uploadFile as print_3mf is not supported by bambu-node.");
207
+ // const [serial, token] = this.extractBambuCredentials(apiKey);
208
+ // await this.print3mf(host, serial, token, {
209
+ // filePath: filePath,
210
+ // projectName: path.basename(filePath, path.extname(filePath))
211
+ // });
212
+ // return { status: "success", message: "Upload and print initiated via print_3mf." };
246
213
  }
247
- catch (uploadError) {
248
- console.error(`FTP Upload Error:`, uploadError);
249
- throw new Error(`Failed to upload file via FTP: ${uploadError.message}`);
214
+ else {
215
+ throw new Error("Uploading without printing is not supported. Use print_3mf (if supported).");
250
216
  }
251
217
  }
218
+ // --- startJob (Use print method via print_3mf if supported) ---
252
219
  async startJob(host, port, apiKey, filename) {
253
- // This should likely be deprecated or only work for G-code files via a different MQTT command?
254
- console.warn("startJob is not recommended for Bambu .3mf files. Use print_3mf tool.");
255
- throw new Error("startJob is not implemented for Bambu printers using MQTT commands yet.");
220
+ console.warn("startJob is deprecated for Bambu. Use the 'print_3mf' tool (if supported).");
221
+ throw new Error("startJob is deprecated for Bambu printers. Use print_3mf (if supported).");
256
222
  }
257
- // --- Helper to publish MQTT commands ---
258
- async publishMqttCommand(client, serial, commandType, payload) {
259
- const topic = `device/${serial}/request`;
260
- const message = JSON.stringify({ [commandType]: payload });
261
- console.log(`Publishing MQTT to ${topic}: ${message}`);
262
- return new Promise((resolve, reject) => {
263
- client.publish(topic, message, { qos: 1 }, (err) => {
264
- if (err) {
265
- console.error(`MQTT Publish Error to ${topic}:`, err);
266
- reject(new Error(`Failed to publish MQTT command: ${err.message}`));
267
- }
268
- else {
269
- console.log(`MQTT command published successfully to ${topic}`);
270
- // Note: We don't wait for a response here. Status updates come via /report topic.
271
- resolve({ status: "success", message: "Command sent successfully via MQTT." });
272
- }
273
- });
274
- });
275
- }
276
- // Helper method to extract Bambu-specific credentials from apiKey
223
+ // --- Helper to extract Bambu credentials ---
277
224
  extractBambuCredentials(apiKey) {
278
225
  const parts = apiKey.split(':');
279
226
  if (parts.length !== 2) {
@@ -281,4 +228,8 @@ export class BambuImplementation extends PrinterImplementation {
281
228
  }
282
229
  return [parts[0], parts[1]];
283
230
  }
231
+ // Method required by PrinterFactory, disconnects managed printers
232
+ async disconnectAll() {
233
+ await this.printerStore.disconnectAll();
234
+ }
284
235
  }
@@ -1,7 +1,6 @@
1
1
  import { PrinterImplementation } from "../types.js";
2
2
  export declare class PrinterFactory {
3
3
  private implementations;
4
- private bambuPrinterStore;
5
4
  private apiClient;
6
5
  constructor();
7
6
  getImplementation(type: string): PrinterImplementation;
@@ -1,4 +1,3 @@
1
- import { BambuPrinterStore } from "../types.js";
2
1
  import { OctoPrintImplementation } from "./octoprint.js";
3
2
  import { KlipperImplementation } from "./klipper.js";
4
3
  import { DuetImplementation } from "./duet.js";
@@ -10,13 +9,12 @@ import axios from "axios";
10
9
  export class PrinterFactory {
11
10
  constructor() {
12
11
  this.implementations = new Map();
13
- this.bambuPrinterStore = new BambuPrinterStore();
14
12
  this.apiClient = axios.create({ timeout: 10000 });
15
13
  this.implementations.set("octoprint", new OctoPrintImplementation(this.apiClient));
16
14
  this.implementations.set("klipper", new KlipperImplementation(this.apiClient));
17
15
  this.implementations.set("duet", new DuetImplementation(this.apiClient));
18
16
  this.implementations.set("repetier", new RepetierImplementation(this.apiClient));
19
- this.implementations.set("bambu", new BambuImplementation(this.apiClient, this.bambuPrinterStore));
17
+ this.implementations.set("bambu", new BambuImplementation(this.apiClient));
20
18
  this.implementations.set("prusa", new PrusaImplementation(this.apiClient));
21
19
  this.implementations.set("creality", new CrealityImplementation(this.apiClient));
22
20
  }
@@ -28,6 +26,10 @@ export class PrinterFactory {
28
26
  return implementation;
29
27
  }
30
28
  async disconnectAll() {
31
- await this.bambuPrinterStore.disconnectAll();
29
+ // Disconnect all printers if needed
30
+ const bambuImpl = this.implementations.get("bambu");
31
+ if (bambuImpl) {
32
+ await bambuImpl.disconnectAll();
33
+ }
32
34
  }
33
35
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { AxiosInstance } from "axios";
2
- import { BambuPrinter } from "bambu-js";
3
2
  export type BambuFTP = {
4
3
  readDir: (path: string) => Promise<string[]>;
5
4
  sendFile: (sourcePath: string, destinationPath: string, progressCallback?: (progress: number) => void) => Promise<void>;
@@ -16,11 +15,6 @@ export declare abstract class PrinterImplementation {
16
15
  abstract cancelJob(host: string, port: string, apiKey: string): Promise<any>;
17
16
  abstract setTemperature(host: string, port: string, apiKey: string, component: string, temperature: number): Promise<any>;
18
17
  }
19
- export declare class BambuPrinterStore {
20
- private printers;
21
- get(host: string, serial: string, token: string): InstanceType<typeof BambuPrinter>;
22
- disconnectAll(): Promise<void>;
23
- }
24
18
  export interface SectionBounds {
25
19
  minX: number;
26
20
  minY: number;
package/dist/types.js CHANGED
@@ -1,26 +1,6 @@
1
- import { BambuPrinter } from "bambu-js";
2
1
  // Base class for printer implementations
3
2
  export class PrinterImplementation {
4
3
  constructor(apiClient) {
5
4
  this.apiClient = apiClient;
6
5
  }
7
6
  }
8
- // Store for Bambu printers
9
- export class BambuPrinterStore {
10
- constructor() {
11
- this.printers = new Map();
12
- }
13
- get(host, serial, token) {
14
- const key = `${host}-${serial}`;
15
- if (!this.printers.has(key)) {
16
- const printer = new BambuPrinter(host, serial, token);
17
- this.printers.set(key, printer);
18
- }
19
- return this.printers.get(key);
20
- }
21
- async disconnectAll() {
22
- for (const printer of this.printers.values()) {
23
- await printer.disconnect();
24
- }
25
- }
26
- }