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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-3d-printer-server",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "MCP server for connecting Claude with 3D printer management systems",
5
5
  "main": "dist/index.js",
6
6
  "repository": "https://github.com/DMontgomery40/mcp-3D-printer-server",
@@ -9,7 +9,8 @@
9
9
  "start": "node dist/index.js",
10
10
  "dev": "nodemon src/index.ts --exec \"npm run build && npm start\"",
11
11
  "build": "tsc",
12
- "test": "npm run build && node dist/test/stl-manipulator-test.js"
12
+ "test": "npm run build && node dist/test/stl-manipulator-test.js",
13
+ "inspect": "npm run build && npx @modelcontextprotocol/inspector dist/index.js"
13
14
  },
14
15
  "bin": {
15
16
  "mcp-3d-printer-server": "./dist/index.js"
@@ -38,6 +39,7 @@
38
39
  "@modelcontextprotocol/sdk": "^1.6.0",
39
40
  "axios": "^1.6.2",
40
41
  "bambu-js": "^2.2.2",
42
+ "bambu-node": "^3.22.21",
41
43
  "dotenv": "^16.3.1",
42
44
  "form-data": "^4.0.0",
43
45
  "jszip": "^3.10.1",
@@ -1,44 +1,18 @@
1
- import { PrinterImplementation, BambuPrinterStore, BambuFTP } from "../types.js";
2
- import { BambuPrinter } from "bambu-js"; // Keep for FTP manipulation
3
- import * as mqtt from 'mqtt'; // Import MQTT library
4
- import * as fs from 'fs/promises';
1
+ import { PrinterImplementation } from "../types.js";
2
+ import {
3
+ BambuClient,
4
+ UpdateStateCommand,
5
+ UpdateTempCommand,
6
+ // State, // Removed as using string literal
7
+ // TempUpdatePartType // Removed as likely unusable type
8
+ } from "bambu-node"; // Use bambu-node library
5
9
  import * as path from 'path';
6
- import * as tls from 'tls'; // Needed for TLS connection
7
10
 
8
- // Define interface for MQTT client store if needed
9
- interface MqttClientStore {
10
- [key: string]: mqtt.MqttClient;
11
- }
12
-
13
- // Define interface for print options matching OpenBambuAPI spec
14
- interface BambuPrintCommandOptions {
15
- sequence_id: string;
16
- command: "project_file";
17
- param: string; // Path on printer, e.g., gcodes/filename.3mf
18
- project_name: string; // Name of the project (often derived from filename)
19
- project_id?: string; // Default "0" for local
20
- profile_id?: string; // Default "0"
21
- task_id?: string; // Default "0"
22
- subtask_id?: string; // Default "0"
23
- subtask_name?: string; // Default ""
24
- url?: string; // Optional: file:///mnt/sdcard/gcodes/filename.3mf (can override param?)
25
- md5?: string; // MD5 hash of the file (optional but recommended)
26
- timelapse?: boolean;
27
- bed_type?: string; // Default "auto"
28
- bed_levelling?: boolean;
29
- flow_cali?: boolean;
30
- vibration_cali?: boolean;
31
- layer_inspect?: boolean;
32
- ams_mapping?: number[]; // Array of AMS slot indices (e.g., [0, 1, 2, 3]) - Needs verification how this maps to filaments
33
- use_ams?: boolean;
34
- plate_idx?: number; // Add missing property
35
- }
36
-
37
- // Define interface for print options including overrides passed from index.ts
11
+ // Define interface for print options matching bambu-node expectations
12
+ // Note: Check ProjectFileCommand in bambu-node for exact structure
38
13
  interface BambuPrintOptionsInternal {
39
14
  projectName: string;
40
15
  filePath: string; // Path to the local .3mf file
41
- // Base options from OpenBambuAPI spec
42
16
  useAMS?: boolean;
43
17
  plateIndex?: number;
44
18
  bedLeveling?: boolean;
@@ -46,306 +20,247 @@ interface BambuPrintOptionsInternal {
46
20
  vibrationCalibration?: boolean;
47
21
  layerInspect?: boolean;
48
22
  timelapse?: boolean;
49
- // Overrides / parsed data
50
- amsMapping?: number[]; // Explicit mapping (from user override or parsed 3MF)
23
+ amsMapping?: { [originalFilamentIndex: string]: number }; // Map original filament index (string) to AMS slot number
51
24
  md5?: string; // Optional MD5 hash of the file
52
25
  }
53
26
 
54
- export class BambuImplementation extends PrinterImplementation {
55
- private bambuPrinterStore: BambuPrinterStore;
56
- private mqttClients: MqttClientStore = {}; // Store connected MQTT clients
57
- private mqttConnectionPromises: { [key: string]: Promise<mqtt.MqttClient> } = {}; // Track connection attempts
27
+ // Store for bambu-node printer instances
28
+ class BambuClientStore {
29
+ private printers: Map<string, BambuClient> = new Map();
30
+ private initialConnectionPromises: Map<string, Promise<void>> = new Map();
58
31
 
59
- constructor(apiClient: any, bambuPrinterStore: BambuPrinterStore) {
60
- super(apiClient);
61
- this.bambuPrinterStore = bambuPrinterStore;
62
- }
32
+ async getPrinter(host: string, serial: string, token: string): Promise<BambuClient> {
33
+ const key = `${host}-${serial}`;
63
34
 
64
- // Get BambuPrinter instance for FTP
65
- private getBambuPrinterForFTP(host: string, serial: string, token: string): InstanceType<typeof BambuPrinter> {
66
- // This still uses bambu-js just for the FTP part
67
- return this.bambuPrinterStore.get(host, serial, token);
68
- }
35
+ // If already connected/connecting, return existing instance or wait for connection
36
+ if (this.printers.has(key)) {
37
+ console.log(`Returning existing BambuClient instance for ${key}`);
38
+ return this.printers.get(key)!;
39
+ }
40
+ if (this.initialConnectionPromises.has(key)) {
41
+ console.log(`Waiting for existing initial connection attempt for ${key}...`);
42
+ // Don't await here; if promise exists, listeners will handle map update
43
+ // await this.initialConnectionPromises.get(key);
44
+ // Throw error immediately if we know a connection is pending but not resolved yet
45
+ // Or rely on the promise rejection propagating if it fails
46
+ await this.initialConnectionPromises.get(key); // Await necessary to ensure connection completes or fails
47
+ if (this.printers.has(key)) {
48
+ return this.printers.get(key)!;
49
+ } else {
50
+ throw new Error(`Existing initial connection attempt for ${key} failed or timed out.`);
51
+ }
52
+ }
69
53
 
70
- // Get or establish MQTT connection
71
- private async getMqttClient(host: string, serial: string, token: string): Promise<mqtt.MqttClient> {
72
- const clientKey = `${host}-${serial}`;
73
-
74
- if (this.mqttClients[clientKey] && this.mqttClients[clientKey].connected) {
75
- return this.mqttClients[clientKey];
76
- }
54
+ // Create new instance and attempt connection
55
+ console.log(`Creating new BambuClient instance for ${key}`);
56
+ const printer = new BambuClient({
57
+ host: host,
58
+ serialNumber: serial,
59
+ accessToken: token,
60
+ });
77
61
 
78
- // If a connection attempt is already in progress, wait for it
79
- if (this.mqttConnectionPromises[clientKey] !== undefined) {
80
- console.log(`Waiting for existing MQTT connection attempt to ${clientKey}...`);
81
- return this.mqttConnectionPromises[clientKey];
82
- }
62
+ // Setup event listeners for state management
63
+ printer.on('client:connect', () => {
64
+ console.log(`BambuClient connected for ${key}`);
65
+ this.printers.set(key, printer);
66
+ this.initialConnectionPromises.delete(key);
67
+ });
68
+ printer.on('client:error', (err: Error) => {
69
+ console.error(`BambuClient connection error for ${key}:`, err);
70
+ this.printers.delete(key);
71
+ this.initialConnectionPromises.delete(key);
72
+ });
73
+ printer.on('client:disconnect', () => {
74
+ console.log(`BambuClient connection closed for ${key}`);
75
+ this.printers.delete(key);
76
+ this.initialConnectionPromises.delete(key);
77
+ });
78
+ printer.on('printer:dataUpdate', (data) => {
79
+ // Optional: log or update internal state based on data
80
+ // console.log(`BambuClient data update for ${key}`);
81
+ });
83
82
 
84
- console.log(`Attempting to establish new MQTT connection to ${host} for ${serial}...`);
85
- const connectPromise = new Promise<mqtt.MqttClient>((resolve, reject) => {
86
- // Use port 8883 and TLS
87
- const mqttOptions: mqtt.IClientOptions = {
88
- port: 8883,
89
- host: host,
90
- protocol: 'mqtts',
91
- username: 'bblp', // As per OpenBambuAPI docs
92
- password: token,
93
- clientId: `mcp_client_${serial}_${Date.now()}`,
94
- rejectUnauthorized: false, // Might be needed for self-signed certs? Test carefully.
95
- // ca: [fs.readFileSync('path/to/ca_cert.pem')], // Add CA cert if needed
96
- connectTimeout: 10000, // 10 seconds
97
- reconnectPeriod: 5000 // Try reconnecting every 5 seconds
98
- };
83
+ // Store promise and initiate connection
84
+ console.log(`Attempting initial connection for BambuClient ${key}...`);
85
+ const connectPromise = printer.connect().then(() => {});
86
+ this.initialConnectionPromises.set(key, connectPromise);
99
87
 
100
- const client = mqtt.connect(mqttOptions);
88
+ try {
89
+ await connectPromise;
90
+ console.log(`Initial connection successful for ${key}.`);
91
+ // Redundant set, already handled by 'client:connect' listener
92
+ // this.printers.set(key, printer);
93
+ return printer;
94
+ } catch (err) {
95
+ console.error(`Initial connection failed for ${key}:`, err);
96
+ this.initialConnectionPromises.delete(key); // Clean up promise map on failure
97
+ throw err; // Rethrow the error
98
+ }
99
+ }
101
100
 
102
- client.on('connect', () => {
103
- console.log(`MQTT connected successfully to ${host} for ${serial}`);
104
- this.mqttClients[clientKey] = client;
105
- // Subscribe to necessary topics (e.g., responses/status)
106
- client.subscribe(`device/${serial}/report`, (err) => {
107
- if (err) {
108
- console.error(`Failed to subscribe to report topic for ${serial}:`, err);
109
- }
110
- });
111
- delete this.mqttConnectionPromises[clientKey]; // Clear promise tracker on success
112
- resolve(client);
113
- });
101
+ async disconnectAll(): Promise<void> {
102
+ console.log("Disconnecting all BambuClient instances...");
103
+ const disconnectPromises: Promise<void>[] = [];
104
+ for (const [key, printer] of this.printers.entries()) {
105
+ disconnectPromises.push(
106
+ printer.disconnect()
107
+ .then(() => console.log(`Disconnected ${key}`))
108
+ .catch(err => console.error(`Error disconnecting ${key}:`, err))
109
+ );
110
+ }
111
+ await Promise.allSettled(disconnectPromises);
112
+ this.printers.clear();
113
+ this.initialConnectionPromises.clear();
114
+ }
115
+ }
114
116
 
115
- client.on('error', (err) => {
116
- console.error(`MQTT connection error for ${serial} on ${host}:`, err);
117
- delete this.mqttClients[clientKey]; // Remove potentially bad client
118
- delete this.mqttConnectionPromises[clientKey]; // Clear promise tracker on error
119
- reject(err);
120
- });
117
+ export class BambuImplementation extends PrinterImplementation {
118
+ private printerStore: BambuClientStore; // Use correct store name
121
119
 
122
- client.on('close', () => {
123
- console.log(`MQTT connection closed for ${serial} on ${host}`);
124
- delete this.mqttClients[clientKey]; // Remove client on close
125
- });
126
-
127
- // Handle status messages from the report topic
128
- client.on('message', (topic, message) => {
129
- try {
130
- const data = JSON.parse(message.toString());
131
- // Processing logic for status updates would go here
132
- // For now, we just acknowledge receiving it (optional logging)
133
- // console.log(`MQTT message received on ${topic}:`, data?.print?.subtask_name ?? data?.print?.command ?? 'Unknown message type');
134
- } catch(e) {
135
- console.error('Failed to parse MQTT message:', e);
136
- }
137
- });
138
- });
139
-
140
- this.mqttConnectionPromises[clientKey] = connectPromise; // Store the promise
141
- return connectPromise;
120
+ constructor(apiClient: any /* Not used */) {
121
+ super(apiClient);
122
+ this.printerStore = new BambuClientStore();
142
123
  }
143
124
 
144
- // Disconnect MQTT clients on shutdown
145
- async disconnectAllMqtt(): Promise<void> {
146
- console.log("Disconnecting all MQTT clients...");
147
- for (const key in this.mqttClients) {
148
- const client = this.mqttClients[key];
149
- if (client) {
150
- await new Promise<void>((resolve, reject) => {
151
- client.end(false, {}, (err) => {
152
- if (err) {
153
- console.error(`Error ending MQTT client ${key}:`, err);
154
- reject(err);
155
- } else {
156
- console.log(`MQTT client ${key} disconnected.`);
157
- resolve();
158
- }
159
- });
160
- });
161
- }
162
- }
163
- this.mqttClients = {};
164
- this.mqttConnectionPromises = {};
165
- }
125
+ // Helper to get connected printer instance
126
+ private async getPrinter(host: string, serial: string, token: string): Promise<BambuClient> {
127
+ return this.printerStore.getPrinter(host, serial, token);
128
+ }
166
129
 
167
- // --- getStatus (Limited Implementation) ---
130
+ // --- getStatus ---
168
131
  async getStatus(host: string, port: string, apiKey: string): Promise<any> {
169
132
  const [serial, token] = this.extractBambuCredentials(apiKey);
170
133
  try {
171
- // Ensure MQTT connection is attempted/established
172
- const client = await this.getMqttClient(host, serial, token);
173
- // Return minimal status indicating connection success
174
- // Full status requires implementing state storage based on /report topic messages
175
- return {
176
- status: client.connected ? "connected" : "connecting",
177
- serial: serial,
178
- note: "Full status details require MQTT state tracking implementation."
179
- };
134
+ const printer = await this.getPrinter(host, serial, token);
135
+
136
+ // Wait a moment for status data to populate if needed
137
+ if (!printer.data || Object.keys(printer.data).length === 0) {
138
+ await new Promise(resolve => setTimeout(resolve, 2000));
139
+ }
140
+
141
+ const data = printer.data;
142
+
143
+ return {
144
+ status: data.gcode_state || "UNKNOWN",
145
+ connected: true,
146
+ temperatures: {
147
+ nozzle: {
148
+ actual: data.nozzle_temper || 0,
149
+ target: data.nozzle_target_temper || 0
150
+ },
151
+ bed: {
152
+ actual: data.bed_temper || 0,
153
+ target: data.bed_target_temper || 0
154
+ },
155
+ chamber: data.chamber_temper || data.frame_temper || 0
156
+ },
157
+ print: {
158
+ filename: data.subtask_name || "None",
159
+ progress: data.mc_percent || 0,
160
+ timeRemaining: data.mc_remaining_time || 0,
161
+ currentLayer: data.layer_num || 0,
162
+ totalLayers: data.total_layer_num || 0
163
+ },
164
+ ams: data.ams || null,
165
+ model: data.model || "Unknown",
166
+ raw: data // Include raw data for debugging
167
+ };
180
168
  } catch (error) {
181
- console.error(`Failed to get MQTT client/status for ${serial}:`, error);
182
- return { status: "error", error: (error as Error).message };
169
+ console.error(`Failed to get BambuClient status for ${serial}:`, error);
170
+ return { status: "error", connected: false, error: (error as Error).message };
183
171
  }
184
172
  }
185
173
 
186
- // --- Refactored print3mf using direct MQTT ---
174
+ // --- print3mf ---
187
175
  async print3mf(host: string, serial: string, token: string, options: BambuPrintOptionsInternal): Promise<any> {
188
-
189
- // Still use bambu-js for FTP upload
190
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
191
- const remoteFilename = path.basename(options.filePath);
192
- // Ensure path uses forward slashes for printer consistency
193
- const remotePath = `gcodes/${remoteFilename}`.replace(/\\/g, '/');
194
-
195
- // 1. Connect FTP if necessary (via bambu-js helper)
196
- if (!printerFTP.isConnected) {
197
- console.log(`Connecting Bambu FTP ${host} (${serial}) on port 990 (assumed) before uploading...`);
198
- try {
199
- await printerFTP.connect(); // Connects MQTT and FTP
200
- console.log(`Connected Bambu FTP successfully.`);
201
- } catch (ftpConnectError) {
202
- console.error(`Bambu FTP Connection Error:`, ftpConnectError);
203
- throw new Error(`Failed to connect to Bambu FTP: ${(ftpConnectError as Error).message}`);
204
- }
205
- // await printerFTP.awaitInitialState(10000); // Might not be needed just for FTP
206
- }
207
-
208
- // 2. Upload the file via FTP
209
- console.log(`Uploading ${options.filePath} to ${remotePath} via FTP...`);
210
- try {
211
- await printerFTP.manipulateFiles(async (context: BambuFTP) => {
212
- await context.sendFile(options.filePath, remotePath);
213
- });
214
- console.log(`File ${remoteFilename} uploaded successfully.`);
215
- } catch (uploadError) {
216
- console.error(`FTP Upload Error:`, uploadError);
217
- throw new Error(`Failed to upload 3MF file via FTP: ${(uploadError as Error).message}`);
218
- }
219
-
220
- // 3. Get MQTT Client
221
- const mqttClient = await this.getMqttClient(host, serial, token);
222
-
223
- // 4. Construct the MQTT Print Command Payload based on OpenBambuAPI
224
- const commandPayload: BambuPrintCommandOptions = {
225
- sequence_id: Date.now().toString(),
226
- command: "project_file",
227
- param: remotePath, // Path on SD card
228
- project_name: options.projectName,
229
- use_ams: options.amsMapping && options.amsMapping.length > 0 ? true : (options.useAMS ?? false),
230
- plate_idx: options.plateIndex ?? 0,
231
- bed_levelling: options.bedLeveling ?? true,
232
- flow_cali: options.flowCalibration ?? false,
233
- vibration_cali: options.vibrationCalibration ?? false,
234
- layer_inspect: options.layerInspect ?? false,
235
- timelapse: options.timelapse ?? false,
236
- ams_mapping: options.amsMapping,
237
- md5: options.md5
238
- };
239
-
240
- // Remove ams_mapping if not provided or empty
241
- if (!commandPayload.ams_mapping || commandPayload.ams_mapping.length === 0) {
242
- delete commandPayload.ams_mapping;
243
- // Also ensure use_ams is false if no mapping exists
244
- commandPayload.use_ams = false;
245
- }
246
- // Remove md5 if not provided
247
- if (!commandPayload.md5) {
248
- delete commandPayload.md5;
249
- }
250
-
251
- // 5. Publish the command
252
- return this.publishMqttCommand(mqttClient, serial, 'print', commandPayload);
176
+ console.error("print3mf error: bambu-node library does not directly support the required FTPS file upload for .3mf files.");
177
+ throw new Error("Printing .3mf files is not supported with the current bambu-node library integration due to missing FTPS capability.");
253
178
  }
254
179
 
255
- // --- Refactored cancelJob using MQTT ---
180
+ // --- cancelJob ---
256
181
  async cancelJob(host: string, port: string, apiKey: string): Promise<any> {
257
182
  const [serial, token] = this.extractBambuCredentials(apiKey);
258
- const mqttClient = await this.getMqttClient(host, serial, token);
259
- // Command structure from OpenBambuAPI (assuming 'stop_print' is correct)
260
- const payload = { sequence_id: Date.now().toString(), command: "stop_print" };
261
- return this.publishMqttCommand(mqttClient, serial, 'print', payload);
183
+ const printer = await this.getPrinter(host, serial, token);
184
+
185
+ console.log(`Attempting to cancel print via bambu-node...`);
186
+ try {
187
+ // Use UpdateStateCommand with string literal state
188
+ const command = new UpdateStateCommand({ state: 'stop' });
189
+ await printer.executeCommand(command);
190
+ console.log(`Cancel print command successful via bambu-node.`);
191
+ return { status: "success", message: "Cancel command sent successfully." };
192
+ } catch (cancelError) {
193
+ console.error(`Error sending cancel command via bambu-node:`, cancelError);
194
+ throw new Error(`Failed to cancel print: ${(cancelError as Error).message}`);
195
+ }
262
196
  }
263
197
 
264
- // --- setTemperature (still not directly possible via MQTT command) ---
198
+ // --- setTemperature --- (Commenting out due to type issues)
265
199
  async setTemperature(host: string, port: string, apiKey: string, component: string, temperature: number) {
266
- console.warn("Setting temperatures directly is not supported via known Bambu MQTT commands. Use G-code.");
267
- // We could potentially implement a tool to send custom G-code?
268
- throw new Error("Direct temperature setting via MQTT is not supported.");
200
+ // const [serial, token] = this.extractBambuCredentials(apiKey);
201
+ // const printer = await this.getPrinter(host, serial, token);
202
+ // console.log(`Attempting to set temperature for ${component} to ${temperature}°C via bambu-node...`);
203
+ // try {
204
+ // let command;
205
+ // const partArg = component.toLowerCase();
206
+ // if (partArg === 'extruder' || partArg === 'nozzle') {
207
+ // // Use string literal for part. Cast temp due to strict lib types.
208
+ // console.warn("Casting temperature to 'any' for UpdateTempCommand.");
209
+ // command = new UpdateTempCommand({ part: 'nozzle', temperature: temperature as any });
210
+ // } else if (partArg === 'bed') {
211
+ // // Use string literal for part. Cast temp due to strict lib types.
212
+ // console.warn("Casting temperature to 'any' for UpdateTempCommand.");
213
+ // command = new UpdateTempCommand({ part: 'bed', temperature: temperature as any });
214
+ // } else {
215
+ // throw new Error(`Unsupported temperature component: ${component}. Use 'extruder' or 'bed'.`);
216
+ // }
217
+ // await printer.executeCommand(command);
218
+ // console.log(`Set temperature command successful for ${component}.`);
219
+ // return { status: "success", message: `Temperature set command for ${component} sent.` };
220
+ // } catch (tempError) {
221
+ // console.error(`Error sending set temperature command via bambu-node:`, tempError);
222
+ // throw new Error(`Failed to set temperature: ${(tempError as Error).message}`);
223
+ // }
224
+ console.warn("setTemperature is currently disabled due to type inconsistencies in the bambu-node library.");
225
+ throw new Error("setTemperature is disabled.");
269
226
  }
270
227
 
271
- // --- Other methods (getFiles, getFile, uploadFile, startJob) need review/refactoring ---
272
- // For now, keep using bambu-js FTP for file ops, but MQTT for commands
228
+ // --- getFiles --- (Library doesn't seem to expose direct file listing)
273
229
  async getFiles(host: string, port: string, apiKey: string) {
274
- const [serial, token] = this.extractBambuCredentials(apiKey);
275
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
276
- if (!printerFTP.isConnected) await printerFTP.connect();
277
- const fileList: string[] = [];
278
- await printerFTP.manipulateFiles(async (context: BambuFTP) => {
279
- const files = await context.readDir("gcodes");
280
- fileList.push(...files);
281
- });
282
- return { files: fileList };
230
+ console.warn("File listing is not directly supported by bambu-node. Returning empty list.");
231
+ return { files: [], note: "Not supported by bambu-node library" };
283
232
  }
284
233
 
234
+ // --- getFile --- (Library doesn't seem to expose direct file metadata/download)
285
235
  async getFile(host: string, port: string, apiKey: string, filename: string) {
286
- // Still relies on checking existence via FTP
287
- const [serial, token] = this.extractBambuCredentials(apiKey);
288
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
289
- if (!printerFTP.isConnected) await printerFTP.connect();
290
- let fileExists = false;
291
- await printerFTP.manipulateFiles(async (context: BambuFTP) => {
292
- const files = await context.readDir("gcodes");
293
- fileExists = files.includes(filename);
294
- });
295
- if (!fileExists) throw new Error(`File not found: ${filename}`);
296
- return { name: filename, exists: true };
236
+ console.warn("Getting individual file metadata/content is not directly supported by bambu-node.");
237
+ return { name: filename, exists: false, note: "Not supported by bambu-node library" };
297
238
  }
298
239
 
240
+ // --- uploadFile (Handled by print method if supported) ---
299
241
  async uploadFile(host: string, port: string, apiKey: string, filePath: string, filename: string, print: boolean) {
300
- // This method just uploads now, doesn't handle printing
301
- const [serial, token] = this.extractBambuCredentials(apiKey);
302
- const printerFTP = this.getBambuPrinterForFTP(host, serial, token);
303
- const remotePath = `gcodes/${filename}`.replace(/\\/g, '/');
304
- if (!printerFTP.isConnected) await printerFTP.connect();
305
- console.log(`Uploading ${filePath} to ${remotePath} via FTP...`);
306
- try {
307
- await printerFTP.manipulateFiles(async (context: BambuFTP) => {
308
- await context.sendFile(filePath, remotePath);
309
- });
310
- console.log(`File ${filename} uploaded successfully.`);
311
- if (print) {
312
- console.warn("'print=true' ignored in uploadFile for Bambu. Use print_3mf tool instead.");
313
- }
314
- return { status: "success", message: `File ${filename} uploaded successfully` };
315
- } catch (uploadError) {
316
- console.error(`FTP Upload Error:`, uploadError);
317
- throw new Error(`Failed to upload file via FTP: ${(uploadError as Error).message}`);
242
+ console.warn("Use the 'print_3mf' tool. Direct upload without print is not the primary use case.");
243
+ if (print) {
244
+ // Cannot directly call print3mf as it's not supported by this library
245
+ throw new Error("Cannot initiate print via uploadFile as print_3mf is not supported by bambu-node.");
246
+ // const [serial, token] = this.extractBambuCredentials(apiKey);
247
+ // await this.print3mf(host, serial, token, {
248
+ // filePath: filePath,
249
+ // projectName: path.basename(filePath, path.extname(filePath))
250
+ // });
251
+ // return { status: "success", message: "Upload and print initiated via print_3mf." };
252
+ } else {
253
+ throw new Error("Uploading without printing is not supported. Use print_3mf (if supported).");
318
254
  }
319
255
  }
320
256
 
257
+ // --- startJob (Use print method via print_3mf if supported) ---
321
258
  async startJob(host: string, port: string, apiKey: string, filename: string) {
322
- // This should likely be deprecated or only work for G-code files via a different MQTT command?
323
- console.warn("startJob is not recommended for Bambu .3mf files. Use print_3mf tool.");
324
- throw new Error("startJob is not implemented for Bambu printers using MQTT commands yet.");
259
+ console.warn("startJob is deprecated for Bambu. Use the 'print_3mf' tool (if supported).");
260
+ throw new Error("startJob is deprecated for Bambu printers. Use print_3mf (if supported).");
325
261
  }
326
262
 
327
- // --- Helper to publish MQTT commands ---
328
- private async publishMqttCommand(client: mqtt.MqttClient, serial: string, commandType: string, payload: any): Promise<any> {
329
- const topic = `device/${serial}/request`;
330
- const message = JSON.stringify({ [commandType]: payload });
331
-
332
- console.log(`Publishing MQTT to ${topic}: ${message}`);
333
-
334
- return new Promise((resolve, reject) => {
335
- client.publish(topic, message, { qos: 1 }, (err) => {
336
- if (err) {
337
- console.error(`MQTT Publish Error to ${topic}:`, err);
338
- reject(new Error(`Failed to publish MQTT command: ${err.message}`));
339
- } else {
340
- console.log(`MQTT command published successfully to ${topic}`);
341
- // Note: We don't wait for a response here. Status updates come via /report topic.
342
- resolve({ status: "success", message: "Command sent successfully via MQTT." });
343
- }
344
- });
345
- });
346
- }
347
-
348
- // Helper method to extract Bambu-specific credentials from apiKey
263
+ // --- Helper to extract Bambu credentials ---
349
264
  private extractBambuCredentials(apiKey: string): [string, string] {
350
265
  const parts = apiKey.split(':');
351
266
  if (parts.length !== 2) {
@@ -353,4 +268,9 @@ export class BambuImplementation extends PrinterImplementation {
353
268
  }
354
269
  return [parts[0], parts[1]];
355
270
  }
271
+
272
+ // Method required by PrinterFactory, disconnects managed printers
273
+ async disconnectAll(): Promise<void> {
274
+ await this.printerStore.disconnectAll();
275
+ }
356
276
  }
@@ -1,4 +1,4 @@
1
- import { PrinterImplementation, BambuPrinterStore } from "../types.js";
1
+ import { PrinterImplementation } from "../types.js";
2
2
  import { OctoPrintImplementation } from "./octoprint.js";
3
3
  import { KlipperImplementation } from "./klipper.js";
4
4
  import { DuetImplementation } from "./duet.js";
@@ -10,7 +10,6 @@ import axios from "axios";
10
10
 
11
11
  export class PrinterFactory {
12
12
  private implementations: Map<string, PrinterImplementation> = new Map();
13
- private bambuPrinterStore = new BambuPrinterStore();
14
13
  private apiClient = axios.create({ timeout: 10000 });
15
14
 
16
15
  constructor() {
@@ -18,7 +17,7 @@ export class PrinterFactory {
18
17
  this.implementations.set("klipper", new KlipperImplementation(this.apiClient));
19
18
  this.implementations.set("duet", new DuetImplementation(this.apiClient));
20
19
  this.implementations.set("repetier", new RepetierImplementation(this.apiClient));
21
- this.implementations.set("bambu", new BambuImplementation(this.apiClient, this.bambuPrinterStore));
20
+ this.implementations.set("bambu", new BambuImplementation(this.apiClient));
22
21
  this.implementations.set("prusa", new PrusaImplementation(this.apiClient));
23
22
  this.implementations.set("creality", new CrealityImplementation(this.apiClient));
24
23
  }
@@ -32,6 +31,10 @@ export class PrinterFactory {
32
31
  }
33
32
 
34
33
  async disconnectAll(): Promise<void> {
35
- await this.bambuPrinterStore.disconnectAll();
34
+ // Disconnect all printers if needed
35
+ const bambuImpl = this.implementations.get("bambu") as BambuImplementation;
36
+ if (bambuImpl) {
37
+ await bambuImpl.disconnectAll();
38
+ }
36
39
  }
37
40
  }