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 +5 -1
- package/dist/printers/bambu.d.ts +12 -15
- package/dist/printers/bambu.js +193 -242
- package/dist/printers/printer-factory.d.ts +0 -1
- package/dist/printers/printer-factory.js +6 -4
- package/dist/types.d.ts +0 -6
- package/dist/types.js +0 -20
- package/package.json +4 -2
- package/src/printers/bambu.ts +211 -291
- package/src/printers/printer-factory.ts +7 -4
- package/src/types.ts +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-3d-printer-server",
|
|
3
|
-
"version": "1.0.
|
|
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",
|
package/src/printers/bambu.ts
CHANGED
|
@@ -1,44 +1,18 @@
|
|
|
1
|
-
import { PrinterImplementation
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
9
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
this.bambuPrinterStore = bambuPrinterStore;
|
|
62
|
-
}
|
|
32
|
+
async getPrinter(host: string, serial: string, token: string): Promise<BambuClient> {
|
|
33
|
+
const key = `${host}-${serial}`;
|
|
63
34
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
console.log(`
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
//
|
|
145
|
-
async
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
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
|
-
// ---
|
|
174
|
+
// --- print3mf ---
|
|
187
175
|
async print3mf(host: string, serial: string, token: string, options: BambuPrintOptionsInternal): Promise<any> {
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
// ---
|
|
180
|
+
// --- cancelJob ---
|
|
256
181
|
async cancelJob(host: string, port: string, apiKey: string): Promise<any> {
|
|
257
182
|
const [serial, token] = this.extractBambuCredentials(apiKey);
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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 (
|
|
198
|
+
// --- setTemperature --- (Commenting out due to type issues)
|
|
265
199
|
async setTemperature(host: string, port: string, apiKey: string, component: string, temperature: number) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
// ---
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if
|
|
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
|
-
|
|
323
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|