bambu-printer-mcp 1.1.33 → 1.1.34
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/CONTRIBUTORS.md +3 -1
- package/README.md +4 -2
- package/dist/bambu-network-bridge.d.ts +78 -4
- package/dist/bambu-network-bridge.js +292 -47
- package/dist/bambu-network-certs.d.ts +13 -0
- package/dist/bambu-network-certs.js +152 -0
- package/dist/index.js +28 -11
- package/dist/printers/bambu.d.ts +12 -2
- package/dist/printers/bambu.js +74 -41
- package/dist/safety/printer-state.d.ts +2 -0
- package/dist/safety/printer-state.js +42 -1
- package/dist/stl/stl-manipulator.d.ts +8 -0
- package/dist/stl/stl-manipulator.js +119 -12
- package/package.json +1 -1
- package/src/bambu-network-bridge.ts +324 -55
- package/src/bambu-network-certs.ts +154 -0
- package/src/index.ts +43 -25
- package/src/printers/bambu.ts +74 -58
- package/src/safety/printer-state.ts +42 -1
- package/src/stl/stl-manipulator.ts +126 -7
package/CONTRIBUTORS.md
CHANGED
|
@@ -6,6 +6,8 @@ Thank you to everyone who builds, tests, reports problems, and shares real print
|
|
|
6
6
|
|
|
7
7
|
| Contributor | Contribution |
|
|
8
8
|
| --- | --- |
|
|
9
|
+
| [MorganFreeeman](https://github.com/MorganFreeeman) | Reports in [#67](https://github.com/DMontgomery40/bambu-printer-mcp/issues/67) that every H2D print stopped on stock HS01 nozzles. Supplies idle and mid-print firmware 01.04.00.00 nozzle reports showing a permanent `stat: 1` with `sn: "N/A"`, proposes keeping the check narrow to nozzles without an identity chip, and points out the misleading declined-confirmation advice and the missing settings-table entries. Maintainer validation uses their reports in regressions; no H2D hardware was available. |
|
|
10
|
+
| [Aaron Roberts (aaroberts)](https://github.com/aaroberts) | Diagnoses FULU's Linux host exit 101 and its working-directory library loading in [#68](https://github.com/DMontgomery40/bambu-printer-mcp/issues/68), reports both upstream as [FULU #50](https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/50) and [#51](https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/51), and shares a working configuration: the `cat` wrapper, absolute plug-in paths, the FULU GUI's session file for cloud login, and the required 02.05.02.5x plug-in. Reports cloud printing on two P1S printers with 1.1.33. The server now sets the library paths and explains exit 101. The host fixes themselves remain with FULU. |
|
|
9
11
|
| [bwees](https://github.com/bwees) | Reports and fixes missing non-preset vendor data in [OrcaSlicer #16700](https://github.com/OrcaSlicer/OrcaSlicer/issues/16700) and [#16706](https://github.com/OrcaSlicer/OrcaSlicer/pull/16706), restoring the authoritative BBL CLI matrix required by cache-based MCP slicing. |
|
|
10
12
|
| [ExPikaPaka](https://github.com/ExPikaPaka), [raistlin7447](https://github.com/raistlin7447), [HanifKoh](https://github.com/HanifKoh), and [SoftFever](https://github.com/SoftFever) | Provide and maintain OrcaSlicer's published preset-cache format, source-profile inheritance and include handling used as the contract for this independent cache reader. Credit covers upstream source and format evidence; it does not claim they tested this MCP integration. |
|
|
11
13
|
| [mygrexit](https://github.com/mygrexit) | Contributes pinned x86-64 Linux X2D native setup, emergency routing and one-shot cleanup in [#56](https://github.com/DMontgomery40/bambu-printer-mcp/pull/56), checked Orca/time-lapse G-code forms in [#57](https://github.com/DMontgomery40/bambu-printer-mcp/pull/57), and model-specific hidden-HMS notices in [#58](https://github.com/DMontgomery40/bambu-printer-mcp/pull/58). Supplies regressions, review corrections, and contributor-reported Debian/X2D read-only, heater-off and print-start evidence in [#55](https://github.com/DMontgomery40/bambu-printer-mcp/issues/55); maintainer validation is offline and mocked. Also reports and fixes hanging Linux X2D stop/heater-off calls in [#61](https://github.com/DMontgomery40/bambu-printer-mcp/pull/61), with never-connecting transport and helper-exit regressions; the release adds protection against expired commands dispatching after reconnection. In [#64](https://github.com/DMontgomery40/bambu-printer-mcp/pull/64), contributes ready-card native job storage, fresh dispatch rechecks and tests, and reports Linux X2D firmware 01.01.01.00 printing to external storage with plug-in 02.08.02.54 and a Bambuddy thumbnail; maintainer verification uses source and mocked transports. |
|
|
@@ -37,7 +39,7 @@ Thank you to everyone who builds, tests, reports problems, and shares real print
|
|
|
37
39
|
|
|
38
40
|
Thank you also to the existing contributors whose work the project builds on:
|
|
39
41
|
|
|
40
|
-
- [David Montgomery (DMontgomery40)](https://github.com/DMontgomery40) — project maintainer; contributes the printer-profile, temperature, and hardware-safety reports informing the shared slicing and print checks.
|
|
42
|
+
- [David Montgomery (DMontgomery40)](https://github.com/DMontgomery40) — project maintainer; contributes the printer-profile, temperature, and hardware-safety reports informing the shared slicing and print checks. For 1.1.34, reports the stale A1/P1 camera snapshots and that FULU bridge LAN printing needs no Developer Mode, asks which OrcaSlicer CLI option combinations crash, and supplies the P1S used for the real FULU bridge prints.
|
|
41
43
|
- [rowbotik](https://github.com/rowbotik) — printer, AMS, slicing, and operational work across the existing release history.
|
|
42
44
|
- [len-foss](https://github.com/len-foss) — project code contribution.
|
|
43
45
|
- [thebitrock](https://github.com/thebitrock) — project code contribution.
|
package/README.md
CHANGED
|
@@ -179,7 +179,7 @@ See the [changelog](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main
|
|
|
179
179
|
|
|
180
180
|
[FULU OrcaSlicer-bambulab](https://github.com/FULU-Foundation/OrcaSlicer-bambulab) is a supported slicer target (`SLICER_TYPE=orcaslicer-bambulab`; aliases include `fulu-orca` and `orca-studio`). Use its GUI to export a sliced project for direct LAN printing, or configure its CLI with matching installed profiles. From 1.1.11, FULU/Orca share the [machine-preset gate and profile safety checks](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
|
|
181
181
|
|
|
182
|
-
The optional FULU **BambuNetwork bridge** exposes `bambu_network_bridge_status`, `bambu_network_call`, and `print_3mf_bambu_network`, also reachable through `print_3mf` with `connection_mode: "bambu_network"`. Slicer selection does not enable the bridge. It needs a separately installed FULU runtime and an explicit launch command; cloud printing also needs an authenticated BambuNetwork session.
|
|
182
|
+
The optional FULU **BambuNetwork bridge** exposes `bambu_network_bridge_status`, `bambu_network_call`, and `print_3mf_bambu_network`, also reachable through `print_3mf` with `connection_mode: "bambu_network"`. Slicer selection does not enable the bridge. It needs a separately installed FULU runtime and an explicit launch command; cloud printing also needs an authenticated BambuNetwork session. On Linux, FULU's v1.0.0 host needs a small `cat` wrapper and the 02.05.02.5x plug-in; the guide covers both. LAN bridge prints open the plug-in's own LAN session with Bambu's printer certificate, which the server supplies.
|
|
183
183
|
|
|
184
184
|
**[Follow the FULU setup guide](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md)** for the direct LAN recipe, Linux/Windows/macOS bridge setup, connection probes, authentication, and troubleshooting. Bridge protocol tests and a successful handshake do not establish a successful physical print.
|
|
185
185
|
|
|
@@ -623,7 +623,7 @@ Capture a single JPEG frame from the printer's chamber camera. Read-only.
|
|
|
623
623
|
|
|
624
624
|
Two transports are wired in, picked by `bambu_model`:
|
|
625
625
|
|
|
626
|
-
- **TCP-on-6000** for **A1, A1 mini, P1S, P1P**. Native protocol per [OpenBambuAPI/video.md](https://github.com/Doridian/OpenBambuAPI/blob/main/video.md): TLS on port 6000, 80-byte auth packet (`bblp` + access token), repeating 16-byte frame header + JPEG payload.
|
|
626
|
+
- **TCP-on-6000** for **A1, A1 mini, P1S, P1P**. Native protocol per [OpenBambuAPI/video.md](https://github.com/Doridian/OpenBambuAPI/blob/main/video.md): TLS on port 6000, 80-byte auth packet (`bblp` + access token), repeating 16-byte frame header + JPEG payload. The printer first replays the last frame it buffered, which after an idle period can show the chamber as it was long ago, so the tool skips that frame and returns the next live one (about 3 s). If no live frame follows within `timeout_ms`, it fails instead of returning a possibly stale image.
|
|
627
627
|
- **RTSP** for **X1, X1 Carbon, X1E, P2S, X2D** and **H2, H2S, H2D, H2C, H2D Pro**. Shells out to ffmpeg with `rtsps://bblp:<token>@<host>:322/streaming/live/1 -frames:v 1`. The H2 series wasn't documented in OpenBambuAPI's `video.md` but its firmware uses the same RTSP endpoint as X1 (verified live against an H2S, 2026-04-27).
|
|
628
628
|
|
|
629
629
|
**Requires ffmpeg in PATH** for the RTSP path. Install with `brew install ffmpeg` on macOS. Configure a trusted custom binary with the server-side `FFMPEG_PATH` environment variable, or set `MCP_ALLOW_EXECUTABLE_ARG=1` before using the `ffmpeg_path` tool argument. The TCP-on-6000 path uses native Node TLS and does not require ffmpeg.
|
|
@@ -1115,6 +1115,8 @@ Slice an STL or 3MF file using an external slicer and return the path to the out
|
|
|
1115
1115
|
|
|
1116
1116
|
**FULU/Orca CLI safety:** from 1.1.11, these backends require the exact model/nozzle preset from the selected installation and stop on profile preparation failures. A process override cannot bypass that gate. See [configuration and validation limits](https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#fulu-and-orca-cli-safety-limit).
|
|
1117
1117
|
|
|
1118
|
+
**OrcaSlicer CLI transforms:** OrcaSlicer 2.4.2's CLI segfaults when `scale` or `rotate_x`/`rotate_y` moves an object below the bed while arranging, which is the default for STL input. For STL input with `orcaslicer` or `orcaslicer-bambulab`, the server applies `scale` and the rotations to a copy of the mesh in the CLI's order and leaves those flags out. For 3MF input it refuses `scale` above 1, `rotate_x` or `rotate_y` together with `arrange: true`. `repetitions` copies an STL with Orca's `--clone-objects`. `clone_objects` takes one total count, because the CLI counts copies per loaded file. With `arrange: false`, an STL that is not already on the plate fails to slice. A failure names the slicer's own reason.
|
|
1119
|
+
|
|
1118
1120
|
`slicer_path` and `slicer_profile` fall back to the `SLICER_PATH` and `SLICER_PROFILE` environment variables when omitted. Per-call `slicer_path` overrides require `MCP_ALLOW_EXECUTABLE_ARG=1`.
|
|
1119
1121
|
|
|
1120
1122
|
You can provide either `template_3mf_path` or `template_name` when you want to slice from a saved template. `template_name` resolves through the local template registry directory configured for the server.
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/** A parenthesized explanation of a plug-in print result, or "" when it names no known cause. */
|
|
2
|
+
export declare function describeBambuNetworkPrintError(value: number): string;
|
|
3
|
+
/** Waits from native/bambu-native-print.cpp, which prints through the same plug-in calls. */
|
|
4
|
+
export declare const LOCAL_PRINT_TIMING: Readonly<{
|
|
5
|
+
connectTimeoutMs: 30000;
|
|
6
|
+
pollMs: 250;
|
|
7
|
+
settleMs: 3000;
|
|
8
|
+
retryWaitMs: 8000;
|
|
9
|
+
}>;
|
|
10
|
+
export type LocalPrinter = {
|
|
11
|
+
devId: string;
|
|
12
|
+
devIp: string;
|
|
13
|
+
username: string;
|
|
14
|
+
password: string;
|
|
15
|
+
useSsl: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type BridgeJobUpdate = {
|
|
18
|
+
status: unknown;
|
|
19
|
+
code: unknown;
|
|
20
|
+
msg: unknown;
|
|
21
|
+
};
|
|
22
|
+
export type LocalSession = {
|
|
23
|
+
installCertificate: () => Promise<void>;
|
|
24
|
+
settle: (ms: number) => Promise<void>;
|
|
25
|
+
jobUpdates: BridgeJobUpdate[];
|
|
26
|
+
};
|
|
1
27
|
type JsonObject = Record<string, unknown>;
|
|
2
28
|
export type BambuNetworkBridgeOptions = {
|
|
3
29
|
bridgeCommand?: string;
|
|
@@ -22,17 +48,37 @@ export type BambuNetworkBridgeStatus = {
|
|
|
22
48
|
lastStderr: string;
|
|
23
49
|
recentLogs: string[];
|
|
24
50
|
runtime: {
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
/** Folder passed to the plug-in's set_cert_file. */
|
|
52
|
+
certDir: string;
|
|
53
|
+
/** Linux only: absolute plug-in library paths passed to FULU's host. */
|
|
54
|
+
pluginLibraries?: Record<string, string>;
|
|
55
|
+
/** macOS only: conventional FULU runtime discovery. */
|
|
56
|
+
macosRuntimeDir?: string;
|
|
57
|
+
macosRuntimeDirExists?: boolean;
|
|
27
58
|
macosPluginDir?: string;
|
|
28
59
|
macosPluginDirExists?: boolean;
|
|
29
|
-
macosMissingRuntimeFiles
|
|
30
|
-
macosMissingPluginFiles
|
|
60
|
+
macosMissingRuntimeFiles?: string[];
|
|
61
|
+
macosMissingPluginFiles?: string[];
|
|
31
62
|
suggestedMacCommand?: string;
|
|
32
63
|
};
|
|
33
64
|
note?: string;
|
|
34
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* The plug-in's certificate folder: BAMBU_NETWORK_CERT_DIR when set, else
|
|
68
|
+
* Bambu's pinned public certificates written to the agent's config folder,
|
|
69
|
+
* which the host must already be able to read.
|
|
70
|
+
*/
|
|
71
|
+
export declare function bambuNetworkCertDir(configDir: string, env?: NodeJS.ProcessEnv): string;
|
|
72
|
+
export declare function prepareBambuNetworkCertDir(configDir: string, env?: NodeJS.ProcessEnv): string;
|
|
35
73
|
export declare function defaultBambuNetworkConfigDir(): string;
|
|
74
|
+
/**
|
|
75
|
+
* FULU's Linux host opens its network and source libraries by bare name from
|
|
76
|
+
* its working directory instead of PJARCZAK_BAMBU_PLUGIN_DIR
|
|
77
|
+
* (FULU-Foundation/OrcaSlicer-bambulab#51), so name them in that directory
|
|
78
|
+
* unless they are already set. macOS wrappers run the host in a Linux guest
|
|
79
|
+
* that may not see host paths, so they are left alone.
|
|
80
|
+
*/
|
|
81
|
+
export declare function linuxPluginLibraryEnv(env: NodeJS.ProcessEnv, platform: NodeJS.Platform): Record<string, string>;
|
|
36
82
|
export declare class BambuNetworkBridge {
|
|
37
83
|
private child?;
|
|
38
84
|
private commandLine?;
|
|
@@ -45,6 +91,13 @@ export declare class BambuNetworkBridge {
|
|
|
45
91
|
private lastStderr;
|
|
46
92
|
private recentLogs;
|
|
47
93
|
private inferredExpectedNetworkVersion?;
|
|
94
|
+
/** Whether the host accepted the certificate folder and callbacks a LAN session needs. */
|
|
95
|
+
private localSessionReady;
|
|
96
|
+
/**
|
|
97
|
+
* The plug-in holds one LAN printer connection and the host one event queue,
|
|
98
|
+
* so LAN sessions run one at a time, as Bambu Studio connects one printer.
|
|
99
|
+
*/
|
|
100
|
+
private localSessionTail;
|
|
48
101
|
resolveBridgeCommand(override?: string): string;
|
|
49
102
|
resolveConfigDir(override?: string): string;
|
|
50
103
|
resolveCountryCode(override?: string): string;
|
|
@@ -54,6 +107,27 @@ export declare class BambuNetworkBridge {
|
|
|
54
107
|
agent: number;
|
|
55
108
|
handshake: unknown;
|
|
56
109
|
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Open the plug-in's own LAN MQTT session with a printer, in the order Bambu
|
|
112
|
+
* Studio and native/bambu-native-print.cpp use: connect, install the
|
|
113
|
+
* printer's application certificate (again on each "wait_info"), wait for
|
|
114
|
+
* the local connection, request the printer's state and let the key
|
|
115
|
+
* exchange settle. Without the certificate the plug-in uploads a job but
|
|
116
|
+
* cannot encrypt or publish project_file (-4030). The session is closed
|
|
117
|
+
* after `run`, so printer reports stop accumulating in the host's queue.
|
|
118
|
+
*/
|
|
119
|
+
withLocalSession<T>(printer: LocalPrinter, options: BambuNetworkBridgeOptions, assertActive: () => void, timing: typeof LOCAL_PRINT_TIMING, run: (session: LocalSession) => Promise<T>): Promise<T>;
|
|
120
|
+
private runLocalSession;
|
|
121
|
+
/**
|
|
122
|
+
* Run a LAN print method in a local session. A synchronous -4030 means the
|
|
123
|
+
* plug-in published nothing, so it is retried once after the certificate
|
|
124
|
+
* exchange, as the native helper does; each attempt passes the caller's
|
|
125
|
+
* fresh pre-dispatch checks.
|
|
126
|
+
*/
|
|
127
|
+
startLocalPrint(printer: LocalPrinter, method: string, payload: JsonObject, options?: BambuNetworkBridgeOptions, assertActive?: () => void, timing?: typeof LOCAL_PRINT_TIMING): Promise<{
|
|
128
|
+
result: unknown;
|
|
129
|
+
jobUpdates: BridgeJobUpdate[];
|
|
130
|
+
}>;
|
|
57
131
|
callWithAgent(method: string, payload?: JsonObject, options?: BambuNetworkBridgeOptions): Promise<unknown>;
|
|
58
132
|
stop(): Promise<void>;
|
|
59
133
|
private ensureStarted;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
6
|
+
import { BAMBU_NETWORK_CERTIFICATES } from "./bambu-network-certs.js";
|
|
5
7
|
const FRAME_MAGIC = 0x52424a50;
|
|
6
8
|
const FRAME_HEADER_SIZE = 16;
|
|
7
9
|
const FRAME_TYPE_JSON_REQUEST = 1;
|
|
@@ -9,6 +11,45 @@ const FRAME_TYPE_JSON_RESPONSE = 2;
|
|
|
9
11
|
const FRAME_TYPE_BINARY_DATA = 3;
|
|
10
12
|
const FRAME_TYPE_LOG = 4;
|
|
11
13
|
const DEFAULT_REQUEST_TIMEOUT_MS = 120000;
|
|
14
|
+
// bambu_networking.hpp ConnectStatus.
|
|
15
|
+
const LOCAL_CONNECT_STATUS = ["ok", "failed", "lost"];
|
|
16
|
+
// bambu_networking.hpp BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED.
|
|
17
|
+
const LOCAL_PRINT_PUBLISH_FAILED = -4030;
|
|
18
|
+
// bambu_networking.hpp print errors that name a recoverable cause.
|
|
19
|
+
const BAMBU_NETWORK_PRINT_ERRORS = {
|
|
20
|
+
[-4010]: "the file exceeds the 1 GB upload limit",
|
|
21
|
+
[-4020]: "FTPS upload to the printer failed",
|
|
22
|
+
[LOCAL_PRINT_PUBLISH_FAILED]: "the networking plug-in could not send the print command to the printer over MQTT",
|
|
23
|
+
};
|
|
24
|
+
/** A parenthesized explanation of a plug-in print result, or "" when it names no known cause. */
|
|
25
|
+
export function describeBambuNetworkPrintError(value) {
|
|
26
|
+
return BAMBU_NETWORK_PRINT_ERRORS[value] ? ` (${BAMBU_NETWORK_PRINT_ERRORS[value]})` : "";
|
|
27
|
+
}
|
|
28
|
+
/** Waits from native/bambu-native-print.cpp, which prints through the same plug-in calls. */
|
|
29
|
+
export const LOCAL_PRINT_TIMING = Object.freeze({ connectTimeoutMs: 30000, pollMs: 250, settleMs: 3000, retryWaitMs: 8000 });
|
|
30
|
+
/**
|
|
31
|
+
* The plug-in's certificate folder: BAMBU_NETWORK_CERT_DIR when set, else
|
|
32
|
+
* Bambu's pinned public certificates written to the agent's config folder,
|
|
33
|
+
* which the host must already be able to read.
|
|
34
|
+
*/
|
|
35
|
+
export function bambuNetworkCertDir(configDir, env = process.env) {
|
|
36
|
+
return trimEnv(env.BAMBU_NETWORK_CERT_DIR) || path.join(configDir, "cert");
|
|
37
|
+
}
|
|
38
|
+
export function prepareBambuNetworkCertDir(configDir, env = process.env) {
|
|
39
|
+
const certDir = bambuNetworkCertDir(configDir, env);
|
|
40
|
+
if (trimEnv(env.BAMBU_NETWORK_CERT_DIR))
|
|
41
|
+
return certDir;
|
|
42
|
+
fs.mkdirSync(certDir, { recursive: true });
|
|
43
|
+
for (const [name, { sha256, base64 }] of Object.entries(BAMBU_NETWORK_CERTIFICATES)) {
|
|
44
|
+
const content = Buffer.from(base64, "base64");
|
|
45
|
+
if (createHash("sha256").update(content).digest("hex") !== sha256)
|
|
46
|
+
throw new Error(`Bundled Bambu certificate ${name} does not match its pinned SHA-256.`);
|
|
47
|
+
const target = path.join(certDir, name);
|
|
48
|
+
if (!fileExists(target) || !fs.readFileSync(target).equals(content))
|
|
49
|
+
fs.writeFileSync(target, content);
|
|
50
|
+
}
|
|
51
|
+
return certDir;
|
|
52
|
+
}
|
|
12
53
|
export function defaultBambuNetworkConfigDir() {
|
|
13
54
|
return path.join(os.homedir(), ".config", "bambu-printer-mcp", "bambu-network");
|
|
14
55
|
}
|
|
@@ -40,6 +81,72 @@ function collectMissingFiles(baseDir, files) {
|
|
|
40
81
|
return files;
|
|
41
82
|
return files.filter((file) => !fileExists(path.join(baseDir, file)));
|
|
42
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* FULU's Linux host opens its network and source libraries by bare name from
|
|
86
|
+
* its working directory instead of PJARCZAK_BAMBU_PLUGIN_DIR
|
|
87
|
+
* (FULU-Foundation/OrcaSlicer-bambulab#51), so name them in that directory
|
|
88
|
+
* unless they are already set. macOS wrappers run the host in a Linux guest
|
|
89
|
+
* that may not see host paths, so they are left alone.
|
|
90
|
+
*/
|
|
91
|
+
export function linuxPluginLibraryEnv(env, platform) {
|
|
92
|
+
const pluginDir = trimEnv(env.PJARCZAK_BAMBU_PLUGIN_DIR);
|
|
93
|
+
if (platform !== "linux" || !pluginDir)
|
|
94
|
+
return {};
|
|
95
|
+
const libraries = {};
|
|
96
|
+
if (!trimEnv(env.PJARCZAK_BAMBU_NETWORK_SO))
|
|
97
|
+
libraries.PJARCZAK_BAMBU_NETWORK_SO = path.resolve(pluginDir, "libbambu_networking.so");
|
|
98
|
+
if (!trimEnv(env.PJARCZAK_BAMBU_SOURCE_SO))
|
|
99
|
+
libraries.PJARCZAK_BAMBU_SOURCE_SO = path.resolve(pluginDir, "libBambuSource.so");
|
|
100
|
+
return libraries;
|
|
101
|
+
}
|
|
102
|
+
/** FULU's Linux host exits 101 when it cannot reopen a socket stdout (FULU-Foundation/OrcaSlicer-bambulab#50). */
|
|
103
|
+
const STDOUT_SOCKET_HINT = "Exit code 101 means FULU's Linux host could not reopen its stdout, which fails when stdout is a socket, as Node.js provides " +
|
|
104
|
+
"(https://github.com/FULU-Foundation/OrcaSlicer-bambulab/issues/50). Launch the host through a wrapper that pipes its output through cat: " +
|
|
105
|
+
"https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#linux";
|
|
106
|
+
/** Conventional FULU macOS runtime locations; the wrapper command is suggested only when it exists. */
|
|
107
|
+
function macosRuntimeHints() {
|
|
108
|
+
const macosRuntimeDir = trimEnv(process.env.PJARCZAK_MAC_RUNTIME_DIR) ||
|
|
109
|
+
path.join(os.homedir(), "Library", "Application Support", "OrcaSlicer", "macos-bridge", "runtime");
|
|
110
|
+
const macosPluginDir = trimEnv(process.env.BAMBU_NETWORK_PLUGIN_DIR) ||
|
|
111
|
+
trimEnv(process.env.PJARCZAK_BAMBU_PLUGIN_DIR) ||
|
|
112
|
+
resolveFirstDirectoryWithFile([
|
|
113
|
+
path.join(os.homedir(), "Library", "Application Support", "OrcaSlicer", "plugins"),
|
|
114
|
+
"/Applications/Orca Studio.app/Contents/MacOS",
|
|
115
|
+
"/Applications/OrcaSlicer.app/Contents/MacOS",
|
|
116
|
+
"/Applications/OrcaSlicer-BMCU.app/Contents/MacOS",
|
|
117
|
+
"/Applications/Orca Studio.app/Contents/Resources",
|
|
118
|
+
"/Applications/OrcaSlicer.app/Contents/Resources",
|
|
119
|
+
"/Applications/OrcaSlicer-BMCU.app/Contents/Resources",
|
|
120
|
+
macosRuntimeDir,
|
|
121
|
+
], "pjarczak-bambu-linux-host-wrapper") ||
|
|
122
|
+
resolveFirstExistingDirectory([macosRuntimeDir]);
|
|
123
|
+
const runtimeRequiredFiles = [
|
|
124
|
+
"libbambu_networking.so",
|
|
125
|
+
"libBambuSource.so",
|
|
126
|
+
"pjarczak_bambu_linux_host",
|
|
127
|
+
"pjarczak_bambu_linux_host_abi1",
|
|
128
|
+
"pjarczak_bambu_linux_host_abi0",
|
|
129
|
+
"ca-certificates.crt",
|
|
130
|
+
"slicer_base64.cer",
|
|
131
|
+
];
|
|
132
|
+
const pluginRequiredFiles = [
|
|
133
|
+
"pjarczak-bambu-linux-host-wrapper",
|
|
134
|
+
"install_runtime_macos.sh",
|
|
135
|
+
"verify_runtime_macos.sh",
|
|
136
|
+
];
|
|
137
|
+
const wrapper = macosPluginDir ? path.join(macosPluginDir, "pjarczak-bambu-linux-host-wrapper") : undefined;
|
|
138
|
+
return {
|
|
139
|
+
macosRuntimeDir,
|
|
140
|
+
macosRuntimeDirExists: fileExists(macosRuntimeDir),
|
|
141
|
+
macosPluginDir,
|
|
142
|
+
macosPluginDirExists: macosPluginDir ? fileExists(macosPluginDir) : undefined,
|
|
143
|
+
macosMissingRuntimeFiles: collectMissingFiles(macosRuntimeDir, runtimeRequiredFiles),
|
|
144
|
+
macosMissingPluginFiles: collectMissingFiles(macosPluginDir, pluginRequiredFiles),
|
|
145
|
+
suggestedMacCommand: wrapper && fileExists(wrapper)
|
|
146
|
+
? `PJARCZAK_BAMBU_PLUGIN_DIR=${shellQuote(macosRuntimeDir)} ${shellQuote(wrapper)} ${shellQuote(path.join(macosRuntimeDir, "pjarczak_bambu_linux_host"))}`
|
|
147
|
+
: undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
43
150
|
function readFrameJson(payload) {
|
|
44
151
|
const text = payload.toString("utf8");
|
|
45
152
|
return text.length > 0 ? JSON.parse(text) : {};
|
|
@@ -57,6 +164,11 @@ function bridgeValueAsNumber(value, method) {
|
|
|
57
164
|
}
|
|
58
165
|
throw new Error(`FULU BambuNetwork bridge method ${method} did not return a numeric value.`);
|
|
59
166
|
}
|
|
167
|
+
function bridgeNumericResult(value) {
|
|
168
|
+
return typeof value === "object" && value !== null && typeof value.value === "number"
|
|
169
|
+
? value.value
|
|
170
|
+
: undefined;
|
|
171
|
+
}
|
|
60
172
|
function assertBridgeOk(value, method) {
|
|
61
173
|
if (isBridgeFailure(value)) {
|
|
62
174
|
const errorText = value.error === undefined ? "unknown bridge error" : String(value.error);
|
|
@@ -70,6 +182,13 @@ export class BambuNetworkBridge {
|
|
|
70
182
|
this.nextRequestId = 1;
|
|
71
183
|
this.lastStderr = "";
|
|
72
184
|
this.recentLogs = [];
|
|
185
|
+
/** Whether the host accepted the certificate folder and callbacks a LAN session needs. */
|
|
186
|
+
this.localSessionReady = false;
|
|
187
|
+
/**
|
|
188
|
+
* The plug-in holds one LAN printer connection and the host one event queue,
|
|
189
|
+
* so LAN sessions run one at a time, as Bambu Studio connects one printer.
|
|
190
|
+
*/
|
|
191
|
+
this.localSessionTail = Promise.resolve();
|
|
73
192
|
}
|
|
74
193
|
resolveBridgeCommand(override) {
|
|
75
194
|
return (trimEnv(override) ||
|
|
@@ -94,43 +213,20 @@ export class BambuNetworkBridge {
|
|
|
94
213
|
const command = this.resolveBridgeCommand(options.bridgeCommand);
|
|
95
214
|
const configDir = this.resolveConfigDir(options.configDir);
|
|
96
215
|
const countryCode = this.resolveCountryCode(options.countryCode);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"/Applications/OrcaSlicer-BMCU.app/Contents/Resources",
|
|
109
|
-
macosRuntimeDir,
|
|
110
|
-
], "pjarczak-bambu-linux-host-wrapper") ||
|
|
111
|
-
resolveFirstExistingDirectory([macosRuntimeDir]);
|
|
112
|
-
const runtimeRequiredFiles = [
|
|
113
|
-
"libbambu_networking.so",
|
|
114
|
-
"libBambuSource.so",
|
|
115
|
-
"pjarczak_bambu_linux_host",
|
|
116
|
-
"pjarczak_bambu_linux_host_abi1",
|
|
117
|
-
"pjarczak_bambu_linux_host_abi0",
|
|
118
|
-
"ca-certificates.crt",
|
|
119
|
-
"slicer_base64.cer",
|
|
120
|
-
];
|
|
121
|
-
const pluginRequiredFiles = [
|
|
122
|
-
"pjarczak-bambu-linux-host-wrapper",
|
|
123
|
-
"install_runtime_macos.sh",
|
|
124
|
-
"verify_runtime_macos.sh",
|
|
125
|
-
];
|
|
126
|
-
let suggestedMacCommand;
|
|
127
|
-
if (macosPluginDir) {
|
|
128
|
-
const wrapper = path.join(macosPluginDir, "pjarczak-bambu-linux-host-wrapper");
|
|
129
|
-
const host = path.join(macosRuntimeDir, "pjarczak_bambu_linux_host");
|
|
130
|
-
suggestedMacCommand =
|
|
131
|
-
`PJARCZAK_BAMBU_PLUGIN_DIR=${shellQuote(macosRuntimeDir)} ` +
|
|
132
|
-
`${shellQuote(wrapper)} ${shellQuote(host)}`;
|
|
216
|
+
const runtime = { certDir: bambuNetworkCertDir(configDir) };
|
|
217
|
+
if (process.platform === "linux") {
|
|
218
|
+
const pluginLibraries = {
|
|
219
|
+
...Object.fromEntries(["PJARCZAK_BAMBU_NETWORK_SO", "PJARCZAK_BAMBU_SOURCE_SO"].flatMap(name => {
|
|
220
|
+
const value = trimEnv(process.env[name]);
|
|
221
|
+
return value ? [[name, value]] : [];
|
|
222
|
+
})),
|
|
223
|
+
...linuxPluginLibraryEnv(process.env, process.platform),
|
|
224
|
+
};
|
|
225
|
+
if (Object.keys(pluginLibraries).length)
|
|
226
|
+
runtime.pluginLibraries = pluginLibraries;
|
|
133
227
|
}
|
|
228
|
+
if (process.platform === "darwin")
|
|
229
|
+
Object.assign(runtime, macosRuntimeHints());
|
|
134
230
|
return {
|
|
135
231
|
configured: command.length > 0,
|
|
136
232
|
command,
|
|
@@ -142,15 +238,7 @@ export class BambuNetworkBridge {
|
|
|
142
238
|
agentReady: this.agentId !== undefined,
|
|
143
239
|
lastStderr: this.lastStderr,
|
|
144
240
|
recentLogs: this.recentLogs.slice(-10),
|
|
145
|
-
runtime
|
|
146
|
-
macosRuntimeDir,
|
|
147
|
-
macosRuntimeDirExists: fileExists(macosRuntimeDir),
|
|
148
|
-
macosPluginDir,
|
|
149
|
-
macosPluginDirExists: macosPluginDir ? fileExists(macosPluginDir) : undefined,
|
|
150
|
-
macosMissingRuntimeFiles: collectMissingFiles(macosRuntimeDir, runtimeRequiredFiles),
|
|
151
|
-
macosMissingPluginFiles: collectMissingFiles(macosPluginDir, pluginRequiredFiles),
|
|
152
|
-
suggestedMacCommand,
|
|
153
|
-
},
|
|
241
|
+
runtime,
|
|
154
242
|
note: command
|
|
155
243
|
? undefined
|
|
156
244
|
: "Set BAMBU_NETWORK_BRIDGE_COMMAND to the FULU OrcaSlicer-bambulab Linux host or macOS/WSL wrapper command.",
|
|
@@ -202,11 +290,13 @@ export class BambuNetworkBridge {
|
|
|
202
290
|
const configDir = this.resolveConfigDir(options.configDir);
|
|
203
291
|
const countryCode = this.resolveCountryCode(options.countryCode);
|
|
204
292
|
const userInfo = trimEnv(options.userInfo) || trimEnv(process.env.BAMBU_NETWORK_USER_INFO) || "";
|
|
205
|
-
const
|
|
293
|
+
const certDir = bambuNetworkCertDir(configDir);
|
|
294
|
+
const key = `${command}\n${configDir}\n${countryCode}\n${userInfo}\n${certDir}`;
|
|
206
295
|
if (this.agentId !== undefined && this.agentKey === key) {
|
|
207
296
|
return { agent: this.agentId, handshake: this.handshake };
|
|
208
297
|
}
|
|
209
298
|
fs.mkdirSync(configDir, { recursive: true });
|
|
299
|
+
prepareBambuNetworkCertDir(configDir);
|
|
210
300
|
this.handshake = await this.request("bridge.handshake", {}, options);
|
|
211
301
|
assertBridgeOk(this.handshake, "bridge.handshake");
|
|
212
302
|
if (this.needsExpectedVersionRetry(this.handshake) && !trimEnv(process.env.PJARCZAK_EXPECTED_BAMBU_NETWORK_VERSION)) {
|
|
@@ -215,6 +305,13 @@ export class BambuNetworkBridge {
|
|
|
215
305
|
this.handshake = await this.request("bridge.handshake", {}, options);
|
|
216
306
|
assertBridgeOk(this.handshake, "bridge.handshake");
|
|
217
307
|
}
|
|
308
|
+
const loaded = this.handshake;
|
|
309
|
+
if (loaded?.network_loaded === false && typeof loaded.network_status === "string" && /file open failed/i.test(loaded.network_status)) {
|
|
310
|
+
throw new Error(`FULU BambuNetwork bridge could not open its networking library (${loaded.network_status}). ` +
|
|
311
|
+
"Set PJARCZAK_BAMBU_PLUGIN_DIR to the directory containing libbambu_networking.so and libBambuSource.so, " +
|
|
312
|
+
"or set PJARCZAK_BAMBU_NETWORK_SO and PJARCZAK_BAMBU_SOURCE_SO to their absolute paths: " +
|
|
313
|
+
"https://github.com/DMontgomery40/bambu-printer-mcp/blob/main/docs/FULU.md#linux");
|
|
314
|
+
}
|
|
218
315
|
const created = await this.request("net.create_agent", { log_dir: configDir, country_code: countryCode }, options);
|
|
219
316
|
assertBridgeOk(created, "net.create_agent");
|
|
220
317
|
const agent = bridgeValueAsNumber(created, "net.create_agent");
|
|
@@ -226,16 +323,162 @@ export class BambuNetworkBridge {
|
|
|
226
323
|
};
|
|
227
324
|
await withAgent("net.set_config_dir", { config_dir: configDir });
|
|
228
325
|
await withAgent("net.init_log");
|
|
326
|
+
// Bambu Studio and FULU's GUI set this before starting the agent. The
|
|
327
|
+
// plug-in verifies a LAN printer with printer.cer from this folder, and
|
|
328
|
+
// FULU's host crashes on connect_printer when no folder was ever set.
|
|
329
|
+
const certificates = await withAgent("net.set_cert_file", { folder: certDir, filename: "slicer_base64.cer" }, false);
|
|
229
330
|
await withAgent("net.set_country_code", { country_code: countryCode });
|
|
230
331
|
await withAgent("net.start");
|
|
332
|
+
// The plug-in runs callbacks through queue_on_main; FULU's host executes
|
|
333
|
+
// them on its own dispatcher and queues on_local_connect for poll_events.
|
|
334
|
+
// Cloud jobs and probes do not depend on these, so a host without them
|
|
335
|
+
// still starts; a LAN session refuses before connecting instead.
|
|
336
|
+
const callbacks = [await withAgent("net.set_queue_on_main_fn", {}, false), await withAgent("net.set_on_local_connect_fn", {}, false)];
|
|
337
|
+
const localSessionReady = ![certificates, ...callbacks].some(isBridgeFailure);
|
|
231
338
|
await withAgent("net.connect_server", {}, false);
|
|
232
339
|
if (userInfo) {
|
|
233
340
|
await withAgent("net.change_user", { user_info: userInfo });
|
|
234
341
|
}
|
|
235
342
|
this.agentId = agent;
|
|
236
343
|
this.agentKey = key;
|
|
344
|
+
this.localSessionReady = localSessionReady;
|
|
237
345
|
return { agent, handshake: this.handshake };
|
|
238
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Open the plug-in's own LAN MQTT session with a printer, in the order Bambu
|
|
349
|
+
* Studio and native/bambu-native-print.cpp use: connect, install the
|
|
350
|
+
* printer's application certificate (again on each "wait_info"), wait for
|
|
351
|
+
* the local connection, request the printer's state and let the key
|
|
352
|
+
* exchange settle. Without the certificate the plug-in uploads a job but
|
|
353
|
+
* cannot encrypt or publish project_file (-4030). The session is closed
|
|
354
|
+
* after `run`, so printer reports stop accumulating in the host's queue.
|
|
355
|
+
*/
|
|
356
|
+
async withLocalSession(printer, options, assertActive, timing, run) {
|
|
357
|
+
const previous = this.localSessionTail;
|
|
358
|
+
let release;
|
|
359
|
+
this.localSessionTail = new Promise(resolve => { release = resolve; });
|
|
360
|
+
try {
|
|
361
|
+
await previous;
|
|
362
|
+
return await this.runLocalSession(printer, options, assertActive, timing, run);
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
release();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async runLocalSession(printer, options, assertActive, timing, run) {
|
|
369
|
+
const jobUpdates = [];
|
|
370
|
+
const installCertificate = async () => {
|
|
371
|
+
await this.callWithAgent("net.install_device_cert", { dev_id: printer.devId, lan_only: true }, options);
|
|
372
|
+
await this.callWithAgent("net.update_cert", {}, options);
|
|
373
|
+
};
|
|
374
|
+
// Drain queued events: collect job reports, return this printer's
|
|
375
|
+
// local-connect statuses, and reinstall the certificate on "wait_info".
|
|
376
|
+
const pump = async () => {
|
|
377
|
+
const polled = await this.request("bridge.poll_events", { limit: 1024 }, options);
|
|
378
|
+
assertBridgeOk(polled, "bridge.poll_events");
|
|
379
|
+
const statuses = [];
|
|
380
|
+
let waitInfo = false;
|
|
381
|
+
for (const event of Array.isArray(polled?.events) ? polled.events : []) {
|
|
382
|
+
if (event?.name === "job.update_status")
|
|
383
|
+
jobUpdates.push({ status: event.payload?.status, code: event.payload?.code, msg: event.payload?.msg });
|
|
384
|
+
const eventDev = event?.payload?.dev_id;
|
|
385
|
+
if (eventDev !== undefined && eventDev !== "" && eventDev !== printer.devId)
|
|
386
|
+
continue;
|
|
387
|
+
if (event?.name === "on_local_connect")
|
|
388
|
+
statuses.push(event.payload.status);
|
|
389
|
+
if ((event?.name === "on_local_message" || event?.name === "on_message") && event.payload?.msg === "wait_info")
|
|
390
|
+
waitInfo = true;
|
|
391
|
+
}
|
|
392
|
+
if (waitInfo)
|
|
393
|
+
await installCertificate();
|
|
394
|
+
return statuses;
|
|
395
|
+
};
|
|
396
|
+
const settle = async (ms) => {
|
|
397
|
+
for (const deadline = Date.now() + ms; Date.now() < deadline;) {
|
|
398
|
+
assertActive();
|
|
399
|
+
await pump();
|
|
400
|
+
await new Promise(resolve => setTimeout(resolve, timing.pollMs));
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
await this.ensureAgent(options);
|
|
404
|
+
assertActive();
|
|
405
|
+
if (!this.localSessionReady) {
|
|
406
|
+
throw new Error("FULU BambuNetwork bridge host did not accept the certificate folder or local-connection callbacks a LAN print needs. Update the FULU runtime; no print command was sent.");
|
|
407
|
+
}
|
|
408
|
+
for (const callback of ["net.set_on_printer_connected_fn", "net.set_on_local_message_fn"]) {
|
|
409
|
+
assertBridgeOk(await this.callWithAgent(callback, {}, options), callback);
|
|
410
|
+
}
|
|
411
|
+
await this.callWithAgent("net.update_cert", {}, options);
|
|
412
|
+
await pump();
|
|
413
|
+
assertActive();
|
|
414
|
+
const requested = await this.callWithAgent("net.connect_printer", {
|
|
415
|
+
dev_id: printer.devId, dev_ip: printer.devIp, username: printer.username, password: printer.password, use_ssl: printer.useSsl,
|
|
416
|
+
}, options);
|
|
417
|
+
assertBridgeOk(requested, "net.connect_printer");
|
|
418
|
+
const connectResult = bridgeValueAsNumber(requested, "net.connect_printer");
|
|
419
|
+
if (connectResult !== 0) {
|
|
420
|
+
throw new Error(`FULU BambuNetwork bridge could not start a LAN connection to printer ${printer.devId} (net.connect_printer returned ${connectResult}). No print command was sent.`);
|
|
421
|
+
}
|
|
422
|
+
let connected = false;
|
|
423
|
+
try {
|
|
424
|
+
await installCertificate();
|
|
425
|
+
const deadline = Date.now() + timing.connectTimeoutMs;
|
|
426
|
+
while (!connected) {
|
|
427
|
+
assertActive();
|
|
428
|
+
for (const status of await pump()) {
|
|
429
|
+
if (status === 0) {
|
|
430
|
+
connected = true;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
const reason = typeof status === "number" && LOCAL_CONNECT_STATUS[status] ? LOCAL_CONNECT_STATUS[status] : `reported status ${status}`;
|
|
434
|
+
throw new Error(`FULU BambuNetwork bridge LAN connection to printer ${printer.devId} at ${printer.devIp} ${reason}. Check the printer IP, LAN access code and LAN mode. No print command was sent.`);
|
|
435
|
+
}
|
|
436
|
+
if (connected)
|
|
437
|
+
break;
|
|
438
|
+
if (Date.now() >= deadline) {
|
|
439
|
+
throw new Error(`FULU BambuNetwork bridge did not connect to printer ${printer.devId} at ${printer.devIp} over LAN within ${Math.round(timing.connectTimeoutMs / 1000)} s. No print command was sent.`);
|
|
440
|
+
}
|
|
441
|
+
await new Promise(resolve => setTimeout(resolve, timing.pollMs));
|
|
442
|
+
}
|
|
443
|
+
// Bambu Studio asks a newly connected printer for its full state and
|
|
444
|
+
// security metadata before building the encrypted project_file message.
|
|
445
|
+
for (const [message, qos] of [
|
|
446
|
+
[{ pushing: { sequence_id: "1", command: "pushall", version: 1, push_target: 1 } }, 0],
|
|
447
|
+
[{ info: { sequence_id: "2", command: "get_version" } }, 1],
|
|
448
|
+
[{ system: { sequence_id: "3", command: "get_access_code" } }, 0],
|
|
449
|
+
]) {
|
|
450
|
+
await this.callWithAgent("net.send_message_to_printer", { dev_id: printer.devId, msg: JSON.stringify(message), qos, flag: 0 }, options);
|
|
451
|
+
}
|
|
452
|
+
await settle(timing.settleMs);
|
|
453
|
+
const result = await run({ installCertificate, settle, jobUpdates });
|
|
454
|
+
await pump();
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
const cleanupFailed = (step) => (error) => console.error(`FULU BambuNetwork bridge ${step} after the LAN session failed: ${error instanceof Error ? error.message : error}`);
|
|
459
|
+
if (connected)
|
|
460
|
+
await this.callWithAgent("net.disconnect_printer", {}, options).catch(cleanupFailed("disconnect"));
|
|
461
|
+
await this.request("bridge.poll_events", { limit: 4096 }, options).catch(cleanupFailed("event cleanup"));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Run a LAN print method in a local session. A synchronous -4030 means the
|
|
466
|
+
* plug-in published nothing, so it is retried once after the certificate
|
|
467
|
+
* exchange, as the native helper does; each attempt passes the caller's
|
|
468
|
+
* fresh pre-dispatch checks.
|
|
469
|
+
*/
|
|
470
|
+
async startLocalPrint(printer, method, payload, options = {}, assertActive = () => { }, timing = LOCAL_PRINT_TIMING) {
|
|
471
|
+
return await this.withLocalSession(printer, options, assertActive, timing, async (session) => {
|
|
472
|
+
let result = await this.callWithAgent(method, payload, options);
|
|
473
|
+
if (bridgeNumericResult(result) === LOCAL_PRINT_PUBLISH_FAILED) {
|
|
474
|
+
await session.settle(timing.retryWaitMs);
|
|
475
|
+
await session.installCertificate();
|
|
476
|
+
await session.settle(timing.settleMs);
|
|
477
|
+
result = await this.callWithAgent(method, payload, options);
|
|
478
|
+
}
|
|
479
|
+
return { result, jobUpdates: session.jobUpdates };
|
|
480
|
+
});
|
|
481
|
+
}
|
|
239
482
|
async callWithAgent(method, payload = {}, options = {}) {
|
|
240
483
|
const { agent } = await this.ensureAgent(options);
|
|
241
484
|
return await this.request(method, { ...payload, agent }, options);
|
|
@@ -287,6 +530,7 @@ export class BambuNetworkBridge {
|
|
|
287
530
|
this.child = spawn(shell, shellArgs, {
|
|
288
531
|
env: {
|
|
289
532
|
...process.env,
|
|
533
|
+
...linuxPluginLibraryEnv(process.env, process.platform),
|
|
290
534
|
PJARCZAK_BAMBU_COUNTRY_CODE: this.resolveCountryCode(),
|
|
291
535
|
PJARCZAK_EXPECTED_BAMBU_NETWORK_VERSION: this.inferredExpectedNetworkVersion ||
|
|
292
536
|
process.env.PJARCZAK_EXPECTED_BAMBU_NETWORK_VERSION ||
|
|
@@ -307,7 +551,8 @@ export class BambuNetworkBridge {
|
|
|
307
551
|
if (this.child !== spawnedChild) {
|
|
308
552
|
return;
|
|
309
553
|
}
|
|
310
|
-
this.rejectAllPending(new Error(`FULU BambuNetwork bridge exited (code=${code ?? "null"}, signal=${signal ?? "null"}). ${this.lastStderr}`.trim()
|
|
554
|
+
this.rejectAllPending(new Error(`FULU BambuNetwork bridge exited (code=${code ?? "null"}, signal=${signal ?? "null"}). ${this.lastStderr}`.trim() +
|
|
555
|
+
(code === 101 ? ` ${STDOUT_SOCKET_HINT}` : "")));
|
|
311
556
|
this.child = undefined;
|
|
312
557
|
this.commandLine = undefined;
|
|
313
558
|
this.agentId = undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bambu's public certificates from Bambu Studio v02.08.02.61 resources/cert,
|
|
3
|
+
* the folder Bambu Studio and FULU's GUI pass to the networking plug-in's
|
|
4
|
+
* set_cert_file. printer.cer holds the BBL CA roots that sign each printer's
|
|
5
|
+
* LAN MQTT certificate; slicer_base64.cer is the cloud certificate. FULU's
|
|
6
|
+
* standalone Linux host runtime ships only slicer_base64.cer, so its plug-in
|
|
7
|
+
* cannot verify a LAN printer without these. Exact bytes, pinned by SHA-256
|
|
8
|
+
* (the same pins as scripts/setup-bambu-native.mjs).
|
|
9
|
+
*/
|
|
10
|
+
export declare const BAMBU_NETWORK_CERTIFICATES: Readonly<Record<"printer.cer" | "slicer_base64.cer", {
|
|
11
|
+
sha256: string;
|
|
12
|
+
base64: string;
|
|
13
|
+
}>>;
|