@vextrix3d/machine-agent 0.0.0-stage → 2.0.0
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/LICENSE +74 -0
- package/README.md +540 -2
- package/certs/bambu-ca.pem +218 -0
- package/dist/bootstrap.js +230 -0
- package/dist/camera-binaries.js +185 -0
- package/dist/camera-gateway.js +201 -0
- package/dist/camera-jpeg-bridge.js +90 -0
- package/dist/camera-jpeg-connection.js +124 -0
- package/dist/camera-relay.js +556 -0
- package/dist/camera-stream-bridge.js +58 -0
- package/dist/camera-stream-connection.js +84 -0
- package/dist/camera-tunnel.js +80 -0
- package/dist/clients/ApiRequestError.js +7 -0
- package/dist/clients/BambuMqttClient.js +108 -0
- package/dist/clients/MoonrakerClient.js +249 -0
- package/dist/clients/Vextrix3dApiClient.js +121 -0
- package/dist/print-attempt-synchronizer.js +76 -0
- package/dist/printer-gone.js +26 -0
- package/dist/printer-state-reporter.js +93 -0
- package/dist/types/bambu.types.js +10 -0
- package/dist/types/index.js +2 -0
- package/dist/types/moonraker.types.js +16 -0
- package/dist/utils.js +233 -0
- package/package.json +69 -4
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Bambu's CAs - BBL CA, and BBL CA2 in RSA and ECC, each of those also as BBL CA signed it -
|
|
2
|
+
# as OpenBambuAPI collects them:
|
|
3
|
+
# https://github.com/Doridian/OpenBambuAPI/blob/main/examples/ca_cert.pem
|
|
4
|
+
-----BEGIN CERTIFICATE-----
|
|
5
|
+
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
|
|
34
|
+
haGNe/KFNvJBMOIuIUzknRRmiQ==
|
|
35
|
+
-----END CERTIFICATE-----
|
|
36
|
+
-----BEGIN CERTIFICATE-----
|
|
37
|
+
MIIB8zCCAZmgAwIBAgIUe61jGQ4RzIC8k+sNuqbI/CaNqPIwCgYIKoZIzj0EAwIw
|
|
38
|
+
RjELMAkGA1UEBhMCQ04xITAfBgNVBAoMGEJCTCBUZWNobm9sb2dpZXMgQ28uIEx0
|
|
39
|
+
ZDEUMBIGA1UEAwwLQkJMIENBMiBFQ0MwIBcNMjUwNjE3MDEzODM1WhgPMjA1MDA2
|
|
40
|
+
MTcwMTM4MzVaMEYxCzAJBgNVBAYTAkNOMSEwHwYDVQQKDBhCQkwgVGVjaG5vbG9n
|
|
41
|
+
aWVzIENvLiBMdGQxFDASBgNVBAMMC0JCTCBDQTIgRUNDMFkwEwYHKoZIzj0CAQYI
|
|
42
|
+
KoZIzj0DAQcDQgAEpKTF7wRSty4DXpGJzgCPwRh8ghLlxUC3qJbyEgLqTvJgbiwY
|
|
43
|
+
APPHK7kVbVmerkqhHOT4QeWRlTG3dOQGLA2VpaNjMGEwHQYDVR0OBBYEFKuRpsjY
|
|
44
|
+
REOyIKH7HwOE6jhGBd6NMB8GA1UdIwQYMBaAFKuRpsjYREOyIKH7HwOE6jhGBd6N
|
|
45
|
+
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMCA0gA
|
|
46
|
+
MEUCIErBiUm3VdtP3rz4kb8aLpI5p+BzL7M9vElBGWWJxpHMAiEA3r5tJWVGwuxi
|
|
47
|
+
YCrB1c40KYFRFyahGrhOJZAj/YhRdnU=
|
|
48
|
+
-----END CERTIFICATE-----
|
|
49
|
+
-----BEGIN CERTIFICATE-----
|
|
50
|
+
MIIEeTCCA2GgAwIBAgIUOq+lNIaC2xsswkFqj5JPyVBl45cwDQYJKoZIhvcNAQEL
|
|
51
|
+
BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u
|
|
52
|
+
LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yNTA2MTcwMjAxMjdaFw0zNTA2MTUw
|
|
53
|
+
MjAxMjdaMEYxCzAJBgNVBAYTAkNOMSEwHwYDVQQKDBhCQkwgVGVjaG5vbG9naWVz
|
|
54
|
+
IENvLiBMdGQxFDASBgNVBAMMC0JCTCBDQTIgUlNBMIICIjANBgkqhkiG9w0BAQEF
|
|
55
|
+
AAOCAg8AMIICCgKCAgEAo4550G4c42gTKzQqixwKT089RizIdZpyOcGA679rPaOd
|
|
56
|
+
WsMqVwnYPP2FpMqXKkjFbedE+SpGloi2NKCuiPNVRbq9PHOOZwTs7YLobOwf53FJ
|
|
57
|
+
uO6vRFpzFfX1tlc9zlFqJvZnYO9NgHpMysidocWcgrDN/SIDywgPB5CVbYg3Vvzu
|
|
58
|
+
a9fwZx9e5KT9xd5IpTqdTrWS47jQOVKLhdQCbJFIlMrblOwLBAx+fHokwqh6tkI6
|
|
59
|
+
Ktuyyjw8Dysebi1ndWjKtZ2mW47r8xZ/J+z3EZqcyJMY6MRtx/zb1jBFuHtkjrb5
|
|
60
|
+
Kv1DMzSKlkaNJIbvC+Mk+hI97W+SjLSRuIdC7+oJUzWaSzgu9cjXCVfmq8t4IL/3
|
|
61
|
+
5hP69PK95LgLectIrP96CYAT/aVMG19FrFW0QWEyfT+kzG4jkumfPbHqY2nNkEN0
|
|
62
|
+
+tjj3h4WdzrWgQEojK/lhfcRFVkts74+aZoMpQP+vmL17CKmSzXk5o/eK21xgxJd
|
|
63
|
+
zMbdztfTpibiXk0abfOpN+1VR+3NYa+bROAKNyGaReEGsyW2bjcjNx515Vqzj3SV
|
|
64
|
+
xhMSp5vfF9E4A1jE99M/l9jQDM6RzkT0lMccGAd5tUSdNvDlrqtQaQiKv/ZsXPgX
|
|
65
|
+
LTWfOpvaLNEgwdMgZMuhjpkwvAZyoYfeF9kyydjDh7bvrX//cz/VopAUlxUtQtMC
|
|
66
|
+
AwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
|
|
67
|
+
BBYEFNVJgQad1sNTN0jxVkwbJ/XM1an1MB8GA1UdIwQYMBaAFI80QmjcZ06PxCKe
|
|
68
|
+
xXxJ5avdRL4eMA0GCSqGSIb3DQEBCwUAA4IBAQAvS8tyfagaGsFf9YncA2ko/Na5
|
|
69
|
+
9BVF+8TlUo+32oznwIVpS1AhSgLP6rNVekXNFKbuP5htudLQ17ZRBJI/UMVyYEDq
|
|
70
|
+
IN7xv7Zj+zJwF6W6haYrjb2Vk8igw1XvNULZfvVNNKIkvJUiVqEslWrC+k74crk/
|
|
71
|
+
Wv8ChVf+zqvfIN6LV3esaGRL02J3AprQGb7DDhR1EefQMScDkNpGJMUmvCmfknrl
|
|
72
|
+
iK8qgvQN1SWO7JRf6fNKHsN1ZQvyP0pgLWxpT3V0/0/WttqX3cMGuJF+jVUzm/Nh
|
|
73
|
+
xYhFewG8vc3KzTjnwQApMA6CW554FOJWFyOD2jn5yJLT3Vue+aYDQRp4bKMx
|
|
74
|
+
-----END CERTIFICATE-----
|
|
75
|
+
-----BEGIN CERTIFICATE-----
|
|
76
|
+
MIICrjCCAZagAwIBAgIUOq+lNIaC2xsswkFqj5JPyVBl45gwDQYJKoZIhvcNAQEL
|
|
77
|
+
BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u
|
|
78
|
+
LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yNTA2MTcwMjAxNDdaFw0zNTA2MTUw
|
|
79
|
+
MjAxNDdaMEYxCzAJBgNVBAYTAkNOMSEwHwYDVQQKDBhCQkwgVGVjaG5vbG9naWVz
|
|
80
|
+
IENvLiBMdGQxFDASBgNVBAMMC0JCTCBDQTIgRUNDMFkwEwYHKoZIzj0CAQYIKoZI
|
|
81
|
+
zj0DAQcDQgAEpKTF7wRSty4DXpGJzgCPwRh8ghLlxUC3qJbyEgLqTvJgbiwYAPPH
|
|
82
|
+
K7kVbVmerkqhHOT4QeWRlTG3dOQGLA2VpaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAO
|
|
83
|
+
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFKuRpsjYREOyIKH7HwOE6jhGBd6NMB8G
|
|
84
|
+
A1UdIwQYMBaAFI80QmjcZ06PxCKexXxJ5avdRL4eMA0GCSqGSIb3DQEBCwUAA4IB
|
|
85
|
+
AQCg6PjUSSZV+4bvejcVMvgXmKzfD95osWn0ctnoMBxPDa+m+Gg+BcLT2IlFAe3E
|
|
86
|
+
KYMvu4T295WQc92rjKYqW6cirFppng9uEFW2mZLimxaSmutsTftE3sbMVMJ/SLYN
|
|
87
|
+
PV7TFv6mcBSIFWXwmBOIpbh4BUcVfONTvdSfIqfyAVxsq4xzc2nc6hPBpAm21Ayj
|
|
88
|
+
ToC1ev/TbDJ8VllFZiEVmWWlIP3aNzAm8S2mOpxPB2WnanaZHSrvXLFhstyzwrjD
|
|
89
|
+
yO1/isOZ7wtr7rcuTJdEvvvCimOZlkfRhaDoTew9tQ0E2FVpzzSinw02qmQ1xIE9
|
|
90
|
+
5/H5ZzJSPkpeAHWEPnKkxg0v
|
|
91
|
+
-----END CERTIFICATE-----
|
|
92
|
+
-----BEGIN CERTIFICATE-----
|
|
93
|
+
MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL
|
|
94
|
+
BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u
|
|
95
|
+
LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw
|
|
96
|
+
MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz
|
|
97
|
+
IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
|
98
|
+
DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H
|
|
99
|
+
oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC
|
|
100
|
+
eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk
|
|
101
|
+
p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy
|
|
102
|
+
/QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW
|
|
103
|
+
1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP
|
|
104
|
+
NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr
|
|
105
|
+
3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG
|
|
106
|
+
fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu
|
|
107
|
+
3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA
|
|
108
|
+
BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6
|
|
109
|
+
itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm
|
|
110
|
+
Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2
|
|
111
|
+
Blbjg3obpHo9
|
|
112
|
+
-----END CERTIFICATE-----
|
|
113
|
+
|
|
114
|
+
# The device CAs some models' certificates are issued through, each issued by BBL CA2 RSA, as the
|
|
115
|
+
# Home Assistant integration ships them:
|
|
116
|
+
# https://github.com/greghesp/ha-bambulab/tree/main/custom_components/bambu_lab/pybambu/certs
|
|
117
|
+
|
|
118
|
+
# BBL Device CA N7-V2 - some P2S units
|
|
119
|
+
-----BEGIN CERTIFICATE-----
|
|
120
|
+
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
|
|
149
|
+
v09+U4m1fOSvF0We4MPCYt+z/E/br+uZ1pIBcg==
|
|
150
|
+
-----END CERTIFICATE-----
|
|
151
|
+
|
|
152
|
+
# BBL Device CA O1C2-V2 - H2C
|
|
153
|
+
-----BEGIN CERTIFICATE-----
|
|
154
|
+
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
|
|
184
|
+
-----END CERTIFICATE-----
|
|
185
|
+
|
|
186
|
+
# BBL Device CA N6-V2 - X2C
|
|
187
|
+
-----BEGIN CERTIFICATE-----
|
|
188
|
+
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
|
|
217
|
+
kvkHW8vmTffkFHMWC/YoXRXklNlv79Z4+Rywrw==
|
|
218
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { loadEnvFile } from 'node:process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { readJpegCamera } from './camera-jpeg-connection.js';
|
|
7
|
+
import { hasCameraStream, readCameraStream, resolveCameraRelay } from './camera-relay.js';
|
|
8
|
+
import { BambuMqttClient } from './clients/BambuMqttClient.js';
|
|
9
|
+
import { MoonrakerClient } from './clients/MoonrakerClient.js';
|
|
10
|
+
import { PrinterConnectionType, Vextrix3dApiClient, } from './clients/Vextrix3dApiClient.js';
|
|
11
|
+
import { createPrintAttemptSynchronizer } from './print-attempt-synchronizer.js';
|
|
12
|
+
import { createPrinterGone } from './printer-gone.js';
|
|
13
|
+
import { createPrinterStateReporter } from './printer-state-reporter.js';
|
|
14
|
+
import { assert, buildMoonrakerAgentState, createBambuStateResolver, formatSubtaskName, once, readBambuTemperature, requireEnvVar, } from './utils.js';
|
|
15
|
+
const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
const workDir = process.cwd();
|
|
17
|
+
const { version: AGENT_VERSION } = JSON.parse(readFileSync(resolve(packageDir, 'package.json'), 'utf8'));
|
|
18
|
+
if (process.argv.includes('--version')) {
|
|
19
|
+
console.log(AGENT_VERSION);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
const envFile = process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.local';
|
|
23
|
+
const envPath = resolve(workDir, envFile);
|
|
24
|
+
if (!existsSync(envPath)) {
|
|
25
|
+
console.error(`No ${envFile} in ${workDir}: run the agent in the printer's folder`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
loadEnvFile(envPath);
|
|
29
|
+
console.info(`Loaded environment from ${envPath}`);
|
|
30
|
+
const fetchesCameraBinaries = process.env.VEXTRIX3D_CAMERA_BINARIES?.trim() !== 'manual';
|
|
31
|
+
const apiClient = new Vextrix3dApiClient({
|
|
32
|
+
functionsUrl: requireEnvVar('VEXTRIX3D_FUNCTIONS_URL'),
|
|
33
|
+
apiKey: requireEnvVar('VEXTRIX3D_MACHINE_AGENT_API_KEY'),
|
|
34
|
+
agentVersion: AGENT_VERSION,
|
|
35
|
+
});
|
|
36
|
+
const parseWifiSignalDbm = (value) => {
|
|
37
|
+
if (typeof value !== 'string') {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const parsed = Number.parseInt(value, 10);
|
|
41
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
42
|
+
};
|
|
43
|
+
const connectToBambuPrinter = (printer, connection, apiClient, printerGone) => {
|
|
44
|
+
const { ip, serialNumber, accessCode } = connection;
|
|
45
|
+
const jpegCamera = readJpegCamera(serialNumber, ip);
|
|
46
|
+
const cameraRelay = resolveCameraRelay({
|
|
47
|
+
workDir,
|
|
48
|
+
caFile: resolve(packageDir, 'certs', 'bambu-ca.pem'),
|
|
49
|
+
printerId: printer.id,
|
|
50
|
+
printerLabel: printer.label,
|
|
51
|
+
accessCode,
|
|
52
|
+
serialNumber,
|
|
53
|
+
jpegCamera,
|
|
54
|
+
fetchesBinaries: fetchesCameraBinaries,
|
|
55
|
+
getCameraAccess: () => apiClient.getCamera(),
|
|
56
|
+
printerGone,
|
|
57
|
+
});
|
|
58
|
+
const stateReporter = createPrinterStateReporter({
|
|
59
|
+
printerLabel: printer.label,
|
|
60
|
+
reportPrinterState: async (state) => {
|
|
61
|
+
const answer = await apiClient.reportPrinterState(state);
|
|
62
|
+
cameraRelay?.noteRelayKeyFingerprint(answer.cameraRelayKeyFingerprint);
|
|
63
|
+
return answer;
|
|
64
|
+
},
|
|
65
|
+
printerGone,
|
|
66
|
+
});
|
|
67
|
+
const printAttemptSynchronizer = createPrintAttemptSynchronizer({
|
|
68
|
+
printerLabel: printer.label,
|
|
69
|
+
apiClient,
|
|
70
|
+
printerGone,
|
|
71
|
+
});
|
|
72
|
+
const resolveState = createBambuStateResolver();
|
|
73
|
+
const mqttClient = new BambuMqttClient({
|
|
74
|
+
ip,
|
|
75
|
+
serialNumber,
|
|
76
|
+
accessCode,
|
|
77
|
+
onConnect: () => {
|
|
78
|
+
console.log(`Connected to "${printer.label}" (${serialNumber})`);
|
|
79
|
+
void printAttemptSynchronizer.initialise();
|
|
80
|
+
},
|
|
81
|
+
onStatus: print => {
|
|
82
|
+
const state = resolveState(print);
|
|
83
|
+
const cameraStream = readCameraStream(print.ipcam);
|
|
84
|
+
stateReporter.report({
|
|
85
|
+
state,
|
|
86
|
+
subtaskName: formatSubtaskName(print.subtask_name),
|
|
87
|
+
progressPct: print.mc_percent ?? null,
|
|
88
|
+
remainingMinutes: print.mc_remaining_time ?? null,
|
|
89
|
+
nozzleTemperature: print.nozzle_temper ?? null,
|
|
90
|
+
bedTemperature: print.bed_temper ?? null,
|
|
91
|
+
chamberTemperature: readBambuTemperature(print.device?.ctc?.info?.temp),
|
|
92
|
+
wifiSignalDbm: parseWifiSignalDbm(print.wifi_signal),
|
|
93
|
+
hasCameraStream: cameraStream === undefined ? undefined : hasCameraStream(cameraStream, jpegCamera),
|
|
94
|
+
});
|
|
95
|
+
printAttemptSynchronizer.handleState(state);
|
|
96
|
+
cameraRelay?.update(cameraStream);
|
|
97
|
+
},
|
|
98
|
+
onReconnect: () => {
|
|
99
|
+
console.log(`Reconnecting to "${printer.label}"...`);
|
|
100
|
+
},
|
|
101
|
+
onClose: () => {
|
|
102
|
+
console.log(`Connection to "${printer.label}" closed`);
|
|
103
|
+
},
|
|
104
|
+
onError: error => {
|
|
105
|
+
console.error(`MQTT error for "${printer.label}":`, error);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
stop: once(async () => {
|
|
110
|
+
await cameraRelay?.stop();
|
|
111
|
+
await stateReporter.flush();
|
|
112
|
+
stateReporter.stop();
|
|
113
|
+
await mqttClient.stop();
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
const connectToMoonrakerPrinter = (printer, connection, apiClient, printerGone) => {
|
|
118
|
+
const { ip, port, apiKey } = connection;
|
|
119
|
+
const stateReporter = createPrinterStateReporter({
|
|
120
|
+
printerLabel: printer.label,
|
|
121
|
+
reportPrinterState: state => apiClient.reportPrinterState(state),
|
|
122
|
+
printerGone,
|
|
123
|
+
});
|
|
124
|
+
const printAttemptSynchronizer = createPrintAttemptSynchronizer({
|
|
125
|
+
printerLabel: printer.label,
|
|
126
|
+
apiClient,
|
|
127
|
+
printerGone,
|
|
128
|
+
});
|
|
129
|
+
let estimatedFilename = null;
|
|
130
|
+
let estimatedTimeS = null;
|
|
131
|
+
const loadEstimatedTime = (filename) => {
|
|
132
|
+
if (filename === estimatedFilename) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
estimatedFilename = filename;
|
|
136
|
+
estimatedTimeS = null;
|
|
137
|
+
if (!filename) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
client
|
|
141
|
+
.getFileEstimatedTime(filename)
|
|
142
|
+
.then(seconds => {
|
|
143
|
+
if (estimatedFilename === filename) {
|
|
144
|
+
estimatedTimeS = seconds;
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.catch(error => {
|
|
148
|
+
console.error(`Failed to read the estimate of "${filename}" on "${printer.label}":`, error);
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
const client = new MoonrakerClient({
|
|
152
|
+
ip,
|
|
153
|
+
port,
|
|
154
|
+
apiKey,
|
|
155
|
+
onConnect: () => {
|
|
156
|
+
console.log(`Connected to "${printer.label}" (${ip}:${port})`);
|
|
157
|
+
void printAttemptSynchronizer.initialise();
|
|
158
|
+
},
|
|
159
|
+
onStatus: status => {
|
|
160
|
+
const filename = status.print_stats?.filename;
|
|
161
|
+
loadEstimatedTime(typeof filename === 'string' ? filename : '');
|
|
162
|
+
const agentState = buildMoonrakerAgentState(status, estimatedTimeS);
|
|
163
|
+
stateReporter.report(agentState);
|
|
164
|
+
printAttemptSynchronizer.handleState(agentState.state);
|
|
165
|
+
},
|
|
166
|
+
onReconnect: () => {
|
|
167
|
+
console.log(`Reconnecting to "${printer.label}"...`);
|
|
168
|
+
},
|
|
169
|
+
onClose: () => {
|
|
170
|
+
console.log(`Connection to "${printer.label}" closed`);
|
|
171
|
+
},
|
|
172
|
+
onError: error => {
|
|
173
|
+
console.error(`Moonraker error for "${printer.label}":`, error);
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
stop: once(async () => {
|
|
178
|
+
await stateReporter.flush();
|
|
179
|
+
stateReporter.stop();
|
|
180
|
+
await client.stop();
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const connectToPrinter = (printer, apiClient, printerGone) => {
|
|
185
|
+
const { connection } = printer;
|
|
186
|
+
switch (connection.type) {
|
|
187
|
+
case PrinterConnectionType.BAMBU_MQTT:
|
|
188
|
+
return connectToBambuPrinter(printer, connection, apiClient, printerGone);
|
|
189
|
+
case PrinterConnectionType.MOONRAKER:
|
|
190
|
+
return connectToMoonrakerPrinter(printer, connection, apiClient, printerGone);
|
|
191
|
+
default:
|
|
192
|
+
throw new Error(`Unsupported printer connection type: ${connection.type}`);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
let printerConnection = null;
|
|
196
|
+
const printerGone = createPrinterGone({
|
|
197
|
+
stop: () => printerConnection?.stop(),
|
|
198
|
+
});
|
|
199
|
+
const stop = once(async () => {
|
|
200
|
+
console.log('Stopping machine agent');
|
|
201
|
+
try {
|
|
202
|
+
await printerConnection?.stop();
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error('Failed to stop cleanly:', error);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
process.once('SIGINT', () => {
|
|
212
|
+
void stop();
|
|
213
|
+
});
|
|
214
|
+
process.once('SIGTERM', () => {
|
|
215
|
+
void stop();
|
|
216
|
+
});
|
|
217
|
+
const bootstrap = async () => {
|
|
218
|
+
console.log(`Starting machine agent ${AGENT_VERSION}`);
|
|
219
|
+
const printer = await apiClient.getPrinterInfo();
|
|
220
|
+
console.log(`Loaded printer "${printer.label}" (${printer.id})`);
|
|
221
|
+
assert(printer.isEnabled, `Printer "${printer.label}" is disabled`);
|
|
222
|
+
printerConnection = connectToPrinter(printer, apiClient, printerGone);
|
|
223
|
+
};
|
|
224
|
+
bootstrap().catch(error => {
|
|
225
|
+
if (printerGone.notice(error)) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
console.error('Failed to start:', error);
|
|
229
|
+
process.exitCode = 1;
|
|
230
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { Readable, Transform } from 'node:stream';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { describeError, sha256File } from './utils.js';
|
|
9
|
+
const GO2RTC_VERSION = 'v1.9.14';
|
|
10
|
+
const CLOUDFLARED_VERSION = '2026.10.7';
|
|
11
|
+
const pinGo2rtc = (file, sha256) => ({
|
|
12
|
+
name: 'go2rtc',
|
|
13
|
+
version: GO2RTC_VERSION,
|
|
14
|
+
file,
|
|
15
|
+
url: `https://github.com/AlexxIT/go2rtc/releases/download/${GO2RTC_VERSION}/${file}`,
|
|
16
|
+
sha256,
|
|
17
|
+
packing: file.endsWith('.zip') ? 'zip' : 'raw',
|
|
18
|
+
});
|
|
19
|
+
const pinCloudflared = (file, sha256) => ({
|
|
20
|
+
name: 'cloudflared',
|
|
21
|
+
version: CLOUDFLARED_VERSION,
|
|
22
|
+
file,
|
|
23
|
+
url: `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/${file}`,
|
|
24
|
+
sha256,
|
|
25
|
+
packing: file.endsWith('.tgz') ? 'tgz' : 'raw',
|
|
26
|
+
});
|
|
27
|
+
const LINUX_ARM_CLOUDFLARED_PIN = pinCloudflared('cloudflared-linux-arm', '74f690f6707182839b63e0465726c7776dd63f0a174390b911d5d71f35cab94e');
|
|
28
|
+
const CAMERA_BINARY_PINS = {
|
|
29
|
+
'linux-arm64': [
|
|
30
|
+
pinGo2rtc('go2rtc_linux_arm64', '359fabade8a7a51e81a55fe6df6b0ef81764a5e1d63179577534eaaa71904b50'),
|
|
31
|
+
pinCloudflared('cloudflared-linux-arm64', '3df43aedc9ff291d1735b5cb576cb97e0b157488796db2e32b28aecabb445775'),
|
|
32
|
+
],
|
|
33
|
+
'linux-arm': [
|
|
34
|
+
pinGo2rtc('go2rtc_linux_arm', '4d7e1639af5a2722a28e864468fd8099b3c1682565446c798bf9e3b38fde12e4'),
|
|
35
|
+
LINUX_ARM_CLOUDFLARED_PIN,
|
|
36
|
+
],
|
|
37
|
+
'linux-x64': [
|
|
38
|
+
pinGo2rtc('go2rtc_linux_amd64', '32d616af226bd731678ffde328b94cfb94e30339bfefc469cfb76323144615a6'),
|
|
39
|
+
pinCloudflared('cloudflared-linux-amd64', '1f99fb518b83dec2ba8d870a7cc991d82a77e6196295a3d2754ba17ab9f91872'),
|
|
40
|
+
],
|
|
41
|
+
'darwin-arm64': [
|
|
42
|
+
pinGo2rtc('go2rtc_mac_arm64.zip', '919b78adc759d6b3883d1e1b2ac915ac0985bb903ff1897b4d228527bd64690c'),
|
|
43
|
+
pinCloudflared('cloudflared-darwin-arm64.tgz', '3586ccf397e98e5f437e60e7c307ca6ac463e110580503af8bb8e6f9ebf831bd'),
|
|
44
|
+
],
|
|
45
|
+
'darwin-x64': [
|
|
46
|
+
pinGo2rtc('go2rtc_mac_amd64.zip', '9b0b9a27a4dc3a5b8b93376e7e8fc2787c6af624a512842622be84aec0171c7a'),
|
|
47
|
+
pinCloudflared('cloudflared-darwin-amd64.tgz', '02f3edfd87684dd5b705607688e4eb4b323f6e777b81e7b3a85af556aa356848'),
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
const LINUX_ARMV6_PINS = [
|
|
51
|
+
pinGo2rtc('go2rtc_linux_armv6', '4dc20370556b29f3a90f4c7a09dcd95472c8f74cca56d4d1fb91f32bdd15174c'),
|
|
52
|
+
LINUX_ARM_CLOUDFLARED_PIN,
|
|
53
|
+
];
|
|
54
|
+
export const findCameraBinaryPins = (platform, arch, armVersion) => {
|
|
55
|
+
if (platform === 'linux' && arch === 'arm') {
|
|
56
|
+
if (armVersion === '6') {
|
|
57
|
+
return LINUX_ARMV6_PINS;
|
|
58
|
+
}
|
|
59
|
+
if (armVersion !== '7') {
|
|
60
|
+
console.log(`[camera] Node reports ARM version ${armVersion || 'none'} - fetching go2rtc and cloudflared for ARMv7`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const key = `${platform}-${arch}`;
|
|
64
|
+
return Object.hasOwn(CAMERA_BINARY_PINS, key) ? CAMERA_BINARY_PINS[key] : null;
|
|
65
|
+
};
|
|
66
|
+
const execFileAsync = promisify(execFile);
|
|
67
|
+
export const unpackArchiveEntry = async (archivePath, name, dir) => {
|
|
68
|
+
try {
|
|
69
|
+
await execFileAsync('tar', ['-xf', archivePath, '-C', dir, name]);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const reason = error.stderr?.trim().split('\n')[0];
|
|
73
|
+
throw new Error(`tar could not unpack ${name}${reason ? ` (${reason})` : ''}`, {
|
|
74
|
+
cause: error,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export const resolveCameraBinarySourcePath = (binaryPath) => `${binaryPath}.pin.json`;
|
|
79
|
+
const isPinnedBuild = async (path, pin) => {
|
|
80
|
+
try {
|
|
81
|
+
if (pin.packing === 'raw') {
|
|
82
|
+
return (await sha256File(path)) === pin.sha256;
|
|
83
|
+
}
|
|
84
|
+
const source = JSON.parse(readFileSync(resolveCameraBinarySourcePath(path), 'utf8'));
|
|
85
|
+
return (source.archiveSha256 === pin.sha256 &&
|
|
86
|
+
typeof source.sha256 === 'string' &&
|
|
87
|
+
(await sha256File(path)) === source.sha256);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const defaultDependencies = {
|
|
94
|
+
fetch: (url, init) => fetch(url, init),
|
|
95
|
+
timeoutMs: 10 * 60_000,
|
|
96
|
+
unpack: unpackArchiveEntry,
|
|
97
|
+
};
|
|
98
|
+
const download = async (url, path, { fetch, timeoutMs }) => {
|
|
99
|
+
const response = await fetch(url, {
|
|
100
|
+
redirect: 'follow',
|
|
101
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok || !response.body) {
|
|
104
|
+
throw new Error(`GitHub answered ${response.status} ${response.statusText}`.trim());
|
|
105
|
+
}
|
|
106
|
+
const hash = createHash('sha256');
|
|
107
|
+
await pipeline(Readable.from(response.body), new Transform({
|
|
108
|
+
transform(chunk, _encoding, callback) {
|
|
109
|
+
hash.update(chunk);
|
|
110
|
+
callback(null, chunk);
|
|
111
|
+
},
|
|
112
|
+
}), createWriteStream(path, {
|
|
113
|
+
mode: 0o600,
|
|
114
|
+
}));
|
|
115
|
+
return hash.digest('hex');
|
|
116
|
+
};
|
|
117
|
+
const ensureCameraBinary = async (cameraDir, pin, dependencies) => {
|
|
118
|
+
const path = join(cameraDir, pin.name);
|
|
119
|
+
if (await isPinnedBuild(path, pin)) {
|
|
120
|
+
if ((statSync(path).mode & 0o111) === 0) {
|
|
121
|
+
chmodSync(path, 0o755);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const hadFile = existsSync(path);
|
|
126
|
+
const downloadPath = `${path}.download`;
|
|
127
|
+
const unpackedPath = `${path}.unpacked`;
|
|
128
|
+
const removeLeftovers = () => {
|
|
129
|
+
rmSync(downloadPath, {
|
|
130
|
+
force: true,
|
|
131
|
+
});
|
|
132
|
+
rmSync(unpackedPath, {
|
|
133
|
+
recursive: true,
|
|
134
|
+
force: true,
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
mkdirSync(cameraDir, {
|
|
138
|
+
recursive: true,
|
|
139
|
+
});
|
|
140
|
+
removeLeftovers();
|
|
141
|
+
try {
|
|
142
|
+
const sha256 = await download(pin.url, downloadPath, dependencies);
|
|
143
|
+
if (sha256 !== pin.sha256) {
|
|
144
|
+
throw new Error(`its SHA-256 is ${sha256}, not the pinned ${pin.sha256} - deleted unrun`);
|
|
145
|
+
}
|
|
146
|
+
if (pin.packing === 'raw') {
|
|
147
|
+
chmodSync(downloadPath, 0o755);
|
|
148
|
+
renameSync(downloadPath, path);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
mkdirSync(unpackedPath);
|
|
152
|
+
await dependencies.unpack(downloadPath, pin.name, unpackedPath);
|
|
153
|
+
const unpackedBinaryPath = join(unpackedPath, pin.name);
|
|
154
|
+
const source = {
|
|
155
|
+
file: pin.file,
|
|
156
|
+
version: pin.version,
|
|
157
|
+
archiveSha256: pin.sha256,
|
|
158
|
+
sha256: await sha256File(unpackedBinaryPath),
|
|
159
|
+
};
|
|
160
|
+
chmodSync(unpackedBinaryPath, 0o755);
|
|
161
|
+
renameSync(unpackedBinaryPath, path);
|
|
162
|
+
writeFileSync(resolveCameraBinarySourcePath(path), `${JSON.stringify(source, null, 2)}\n`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
removeLeftovers();
|
|
167
|
+
}
|
|
168
|
+
console.log(`[camera] ${hadFile
|
|
169
|
+
? `Replaced camera/${pin.name} with ${pin.name} ${pin.version}`
|
|
170
|
+
: `Fetched ${pin.name} ${pin.version} into camera/${pin.name}`} (${pin.file}, its SHA-256 checked)`);
|
|
171
|
+
};
|
|
172
|
+
export const ensureCameraBinaries = async (cameraDir, pins, dependencies = defaultDependencies) => {
|
|
173
|
+
let isReady = true;
|
|
174
|
+
for (const pin of pins) {
|
|
175
|
+
try {
|
|
176
|
+
await ensureCameraBinary(cameraDir, pin, dependencies);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
isReady = false;
|
|
180
|
+
const path = join(cameraDir, pin.name);
|
|
181
|
+
console.error(`[camera] Could not fetch ${pin.name} ${pin.version} (${pin.file}): ${describeError(error)}; ${existsSync(path) ? `the camera/${pin.name} there is used meanwhile` : 'trying again later'}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return isReady;
|
|
185
|
+
};
|