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,6 +1,6 @@
1
1
  {
2
2
  "name": "project-foundry",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Project Foundry CLI & Local Node Daemon Manager",
5
5
  "bin": {
6
6
  "project-foundry": "bin/foundry.js",
@@ -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: state === 'RUNNING' || state === 'PREPARE' ? 'PRINTING' : (state === 'PAUSE' ? 'PAUSED' : 'IDLE'),
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) {