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/README.md
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/mcp-3d-printer-server)
|
|
10
10
|
[](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
|
package/dist/printers/bambu.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PrinterImplementation
|
|
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?:
|
|
12
|
+
amsMapping?: {
|
|
13
|
+
[originalFilamentIndex: string]: number;
|
|
14
|
+
};
|
|
13
15
|
md5?: string;
|
|
14
16
|
}
|
|
15
17
|
export declare class BambuImplementation extends PrinterImplementation {
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
private
|
|
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:
|
|
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 {};
|
package/dist/printers/bambu.js
CHANGED
|
@@ -1,279 +1,226 @@
|
|
|
1
1
|
import { PrinterImplementation } from "../types.js";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (this.
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const key
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
102
|
-
this.
|
|
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
|
|
99
|
+
// --- getStatus ---
|
|
105
100
|
async getStatus(host, port, apiKey) {
|
|
106
101
|
const [serial, token] = this.extractBambuCredentials(apiKey);
|
|
107
102
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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:
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
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
|
-
// ---
|
|
140
|
+
// --- print3mf ---
|
|
124
141
|
async print3mf(host, serial, token, options) {
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
// ---
|
|
145
|
+
// --- cancelJob ---
|
|
187
146
|
async cancelJob(host, port, apiKey) {
|
|
188
147
|
const [serial, token] = this.extractBambuCredentials(apiKey);
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 (
|
|
162
|
+
// --- setTemperature --- (Commenting out due to type issues)
|
|
195
163
|
async setTemperature(host, port, apiKey, component, temperature) {
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
|
|
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
|
-
// ---
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
await
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
|
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,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
|
|
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
|
-
|
|
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
|
-
}
|