project-foundry 1.0.4 → 1.0.5
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,4 +1,71 @@
|
|
|
1
1
|
import mqtt from 'mqtt';
|
|
2
|
+
import { connect as connectTls } from 'node:tls';
|
|
3
|
+
|
|
4
|
+
function buildBambuCameraAuthPacket(accessCode) {
|
|
5
|
+
const packet = Buffer.alloc(80);
|
|
6
|
+
packet.writeUInt32LE(0x40, 0);
|
|
7
|
+
packet.writeUInt32LE(0x3000, 4);
|
|
8
|
+
packet.write('bblp', 16, 31, 'utf8');
|
|
9
|
+
packet.write(accessCode, 48, 31, 'utf8');
|
|
10
|
+
return packet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function captureBambuJpegTls(connection) {
|
|
14
|
+
const host = (connection.host || '').trim().replace(/^\w+:\/\//, '').replace(/\/$/, '').split('/')[0];
|
|
15
|
+
const accessCode = connection.accessCode;
|
|
16
|
+
if (!host || !accessCode) throw new Error('Câmera Bambu requer IP e código de acesso LAN');
|
|
17
|
+
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
let pending = Buffer.alloc(0);
|
|
20
|
+
let expectedSize;
|
|
21
|
+
let settled = false;
|
|
22
|
+
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
finish(new Error('A câmera Bambu não respondeu dentro do tempo limite (12s)'));
|
|
25
|
+
}, 12000);
|
|
26
|
+
|
|
27
|
+
const socket = connectTls({ host, port: 6000, rejectUnauthorized: false });
|
|
28
|
+
|
|
29
|
+
const finish = (error, frame) => {
|
|
30
|
+
if (settled) return;
|
|
31
|
+
settled = true;
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
try { socket.destroy(); } catch {}
|
|
34
|
+
if (error) reject(error);
|
|
35
|
+
else if (frame) resolve(frame);
|
|
36
|
+
else reject(new Error('A câmera Bambu encerrou sem entregar imagem'));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
socket.once('secureConnect', () => {
|
|
40
|
+
try {
|
|
41
|
+
socket.write(buildBambuCameraAuthPacket(accessCode));
|
|
42
|
+
} catch (err) {
|
|
43
|
+
finish(err);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
socket.on('data', (chunk) => {
|
|
48
|
+
pending = Buffer.concat([pending, chunk]);
|
|
49
|
+
if (expectedSize === undefined && pending.length >= 16) {
|
|
50
|
+
expectedSize = pending.readUInt32LE(0);
|
|
51
|
+
if (expectedSize <= 0 || expectedSize > 12 * 1024 * 1024) {
|
|
52
|
+
finish(new Error('A câmera Bambu retornou um tamanho de quadro inválido'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (expectedSize !== undefined && pending.length >= expectedSize + 16) {
|
|
57
|
+
const frame = pending.subarray(16, expectedSize + 16);
|
|
58
|
+
if (frame[0] !== 0xff || frame[1] !== 0xd8) {
|
|
59
|
+
finish(new Error('A câmera Bambu não retornou uma imagem JPEG válida'));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
finish(null, frame);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
socket.on('error', (err) => finish(err));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
2
69
|
|
|
3
70
|
export async function executeLocalPrinterAction({ action, printerId, connectionConfig, payload }) {
|
|
4
71
|
if (!connectionConfig) {
|
|
@@ -11,7 +78,7 @@ export async function executeLocalPrinterAction({ action, printerId, connectionC
|
|
|
11
78
|
const headers = apiKey ? { 'X-Api-Key': apiKey } : {};
|
|
12
79
|
|
|
13
80
|
// =====================================================
|
|
14
|
-
// 1. Verificação de Conexão
|
|
81
|
+
// 1. Verificação de Conexão e Telemetria
|
|
15
82
|
// =====================================================
|
|
16
83
|
if (action === 'connect') {
|
|
17
84
|
// KLIPPER / MOONRAKER
|
|
@@ -118,9 +185,10 @@ export async function executeLocalPrinterAction({ action, printerId, connectionC
|
|
|
118
185
|
client.end(true);
|
|
119
186
|
const state = String(data.print.gcode_state || 'IDLE').toUpperCase();
|
|
120
187
|
const percent = Number(data.print.mc_percent || 0);
|
|
188
|
+
const isPrinting = state === 'RUNNING' || state === 'PREPARE';
|
|
121
189
|
resolve({
|
|
122
190
|
online: true,
|
|
123
|
-
status:
|
|
191
|
+
status: isPrinting ? 'PRINTING' : (state === 'PAUSE' ? 'PAUSED' : 'IDLE'),
|
|
124
192
|
message: `Bambu LAN conectada (${state})`,
|
|
125
193
|
progress: percent,
|
|
126
194
|
temperatures: {
|
|
@@ -128,6 +196,7 @@ export async function executeLocalPrinterAction({ action, printerId, connectionC
|
|
|
128
196
|
hotendTarget: data.print.nozzle_target_temper,
|
|
129
197
|
bed: data.print.bed_temper,
|
|
130
198
|
bedTarget: data.print.bed_target_temper,
|
|
199
|
+
chamber: data.print.chamber_temper,
|
|
131
200
|
},
|
|
132
201
|
});
|
|
133
202
|
}
|
|
@@ -161,9 +230,14 @@ export async function executeLocalPrinterAction({ action, printerId, connectionC
|
|
|
161
230
|
}
|
|
162
231
|
|
|
163
232
|
// =====================================================
|
|
164
|
-
// 2. Snapshot de Câmera Local
|
|
233
|
+
// 2. Snapshot de Câmera Local (Bambu / Klipper / OctoPrint)
|
|
165
234
|
// =====================================================
|
|
166
235
|
if (action === 'camera/snapshot') {
|
|
236
|
+
if (type === 'bambu') {
|
|
237
|
+
const jpegBuffer = await captureBambuJpegTls(connectionConfig);
|
|
238
|
+
return { base64: Buffer.from(jpegBuffer).toString('base64') };
|
|
239
|
+
}
|
|
240
|
+
|
|
167
241
|
const camUrl = connectionConfig.cameraSnapshotUrl || connectionConfig.cameraUrl || `${baseUrl}/webcam/?action=snapshot`;
|
|
168
242
|
const camRes = await fetch(camUrl, { signal: AbortSignal.timeout(10000) });
|
|
169
243
|
if (!camRes.ok) {
|