Files
octoturge 847f7cb686 Raise alerts: failed or paused prints, and AI detections
Nothing subscribed to channel:print_alerts and nothing watched a printer
fail, so an operator away from the screen found out when they came back to
a ruined plate. Alerts are now rows in printer_alerts, listed at
GET /alerts (open=true for what still needs a person) and cleared with
POST /alerts/:id/acknowledge, which is operator-only and keeps the first
acknowledger on the record.

Two sources. Telemetry: a change into error is a failed print if the printer
was printing or paused, otherwise a printer error; a print the printer
paused itself (how an AMS fault looks) is announced too. Transitions, not
levels - a printer sitting in error is one alert, not one per frame - and a
connection's first sight of a printer raises nothing, so a gateway that
reconnects does not re-announce known faults. AI: the worker's Valkey
publications are stored when confidence is at least ALERT_MIN_CONFIDENCE
(default 0.6); the worker deliberately does not threshold, so the policy
lives here. A bad message costs one message, not the listener.

e2e-authz.sh gains the scoping/ack cases; new e2e-alerts.sh drives a stand-in
gateway over the real /ws/edge/v1 and the real Valkey channel (checked to
fail when the derivation is broken).
2026-10-29 14:56:24 +00:00

37 lines
1.3 KiB
TypeScript

/**
* A stand-in gateway for scripts/e2e-alerts.sh: connects to /ws/edge/v1 with
* an edge JWT, says hello, then reports one printer through a sequence of
* states, one telemetry frame each, and waits for the backend to take them.
*
* Usage: bun scripts/fake-gateway.ts <ws-url> <edge-jwt> <printer-id> STATE[,STATE...]
* A state is a Bambu gcode_state word (IDLE, RUNNING, PAUSE, FAILED).
*/
const [url, jwt, printerId, states] = process.argv.slice(2);
if (!url || !jwt || !printerId || !states) {
console.error("usage: fake-gateway.ts <ws-url> <edge-jwt> <printer-id> STATE[,STATE...]");
process.exit(2);
}
const ws = new WebSocket(url, { headers: { authorization: `Bearer ${jwt}` } } as never);
ws.onerror = () => {
console.error("socket error");
process.exit(1);
};
ws.onopen = async () => {
ws.send(JSON.stringify({ type: "hello", gateway_id: "fake-gw" }));
for (const state of states.split(",")) {
ws.send(
JSON.stringify({
type: "telemetry",
printers: [{ printer_id: printerId, name: "Fake", state, percent: 10, print_error: 0 }],
}),
);
// Telemetry is persisted in order per socket; a pause keeps the frames
// from being coalesced into one observation.
await Bun.sleep(400);
}
await Bun.sleep(500);
ws.close();
process.exit(0);
};