Route telemetry up and commands down over the cloud uplink
The proxy did not actually proxy. `fleet` was loaded and connected in main.rs and then never touched again, while the uplink said hello and sent heartbeats into the void - two halves of the program running side by side with nothing between them. Both directions are wired now. Up: every 5s, one vendor-neutral `PrinterTelemetry` snapshot per printer that has reported, batched into a single `Telemetry` message. The snapshot is a translation rather than a forwarded `print.push_status`, because it has to describe a Prusa too, and because Bambu's wire format has traps (0-15 fan gears, ints-or-floats temperatures) that should not reach the rest of the system. Down: a `Command` is looked up by our own printer id, executed, and answered with a `CommandResult`. `PrinterCommand` is deliberately small and deliberately has no "start a print" variant - `start_uploaded_file` has never been run against hardware, and the first thing to exercise it should be a human watching the machine, not a message from the internet. The fleet becomes `Arc<Mutex<Vec<PrinterHandle>>>` so main and the uplink can share it. One lock for the whole Vec is coarse - a slow command to one printer stalls telemetry for all of them - which is fine at a handful of printers and is the shape to revisit (a lock per printer, or an actor task each) before a farm gets large. Two things found by running this against the live P1P and H2C: - A v1 Bambu reached the cloud as a row of nulls with one real bed temperature in it, while the v2 H2C beside it was fully populated. v1 only sends a *complete* status when asked, and `pushall`'s own doc comment said to call it periodically - nothing ever did. Each telemetry tick now collects, then requests the next refresh, so v1 telemetry runs one tick behind instead of staying stuck on fragments. The quirk lives in `PrinterHandle::refresh_status` so the uplink does not have to know it. - The third instance of the silent-swallow parse bug, at the old uplink/mod.rs:63: `if let Ok(server_msg) = serde_json::from_str(..)` with no else. Same shape that hid broken `print.push_status` parsing for weeks, except here it would mean commands vanishing with the cloud never being told. Now a `match` with a loud `Err` arm. Verified end to end against both printers with a throwaway WebSocket server: H2C `RUNNING 88% layer 48/50 13min nozzle 230/230 bed 35/35`, P1P `IDLE 0% nozzle 23.5/0 bed 22.5/0`, both fan commands `ok=true`, zero warnings over 24s.
This commit is contained in:
@@ -6,6 +6,56 @@ use serde::{Deserialize, Serialize};
|
||||
pub enum ClientMessage {
|
||||
Hello { gateway_id: String, farm_id: String, token: String },
|
||||
Heartbeat { seq: u64 },
|
||||
/// One snapshot per printer, on a timer. Sent as a batch rather than a
|
||||
/// message per printer: a farm has one uplink and many printers, and the
|
||||
/// cloud wants a coherent picture of the farm at an instant more than it
|
||||
/// wants the lowest possible latency per machine.
|
||||
Telemetry { printers: Vec<PrinterTelemetry> },
|
||||
/// The reply to a `ServerMessage::Command`. Correlated by `command_id`,
|
||||
/// which the *cloud* generates - unlike Bambu's `sequence_id`, we're
|
||||
/// both ends of this protocol, so the side that needs the correlation
|
||||
/// gets to choose it.
|
||||
CommandResult { command_id: String, ok: bool, error: Option<String> },
|
||||
}
|
||||
|
||||
/// A flattened snapshot of one printer, in the terms an operator console
|
||||
/// actually displays - deliberately not Bambu's `print.push_status` shape.
|
||||
///
|
||||
/// Two reasons for the translation rather than forwarding the raw report:
|
||||
/// this has to describe a Prusa or a Klipper printer too, and Bambu's wire
|
||||
/// format has traps that should not reach the rest of the system (fan
|
||||
/// speeds are 0-15 gears, temperatures arrive as either integers or floats,
|
||||
/// and v2 reuses one `sequence_id` forever - see `PrintReport`).
|
||||
///
|
||||
/// Every field is optional because an unreachable or just-connected printer
|
||||
/// genuinely has nothing to report, and "no data yet" is different from
|
||||
/// "zero".
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct PrinterTelemetry {
|
||||
/// Our own id from printers.toml, not the vendor serial - this is what
|
||||
/// the cloud's `printers` table keys on.
|
||||
pub printer_id: String,
|
||||
pub name: String,
|
||||
/// `"IDLE"`, `"RUNNING"`, `"PAUSE"`, `"FINISH"`, `"FAILED"` - passed
|
||||
/// through as the printer's own string rather than narrowed to an enum,
|
||||
/// so an unrecognised state reaches the cloud as itself instead of being
|
||||
/// dropped on the floor here.
|
||||
pub state: Option<String>,
|
||||
pub percent: Option<i32>,
|
||||
pub layer: Option<i32>,
|
||||
pub total_layers: Option<i32>,
|
||||
pub remaining_minutes: Option<i32>,
|
||||
pub job_name: Option<String>,
|
||||
pub nozzle_temp: Option<f32>,
|
||||
pub nozzle_target: Option<f32>,
|
||||
pub bed_temp: Option<f32>,
|
||||
pub bed_target: Option<f32>,
|
||||
/// Non-zero means the printer is reporting a fault.
|
||||
pub print_error: Option<i32>,
|
||||
/// How many Health-Management-System entries the printer currently has
|
||||
/// active. Just the count for now - decoding the `{attr, code}` pairs
|
||||
/// into messages needs Bambu's HMS tables, which we don't have yet.
|
||||
pub active_alerts: Option<usize>,
|
||||
}
|
||||
|
||||
/// What `continuum-backend` sends back over the same connection.
|
||||
@@ -14,4 +64,25 @@ pub enum ClientMessage {
|
||||
pub enum ServerMessage {
|
||||
HelloAck { session_id: String },
|
||||
HeartbeatAck { seq: u64 },
|
||||
/// Do something to one printer. `printer_id` is our own id from
|
||||
/// printers.toml.
|
||||
Command { command_id: String, printer_id: String, command: PrinterCommand },
|
||||
}
|
||||
|
||||
/// The closed set of things the cloud is allowed to ask a printer to do.
|
||||
///
|
||||
/// Deliberately small, and deliberately missing the obvious one: there is no
|
||||
/// "start a print" variant. `BambuPrinter::start_uploaded_file` exists but
|
||||
/// has never been run against real hardware, and the first thing to exercise
|
||||
/// it should be a human watching the machine - not a message arriving from
|
||||
/// the internet. Add it here once it's been confirmed by hand.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(tag = "action", rename_all = "snake_case")]
|
||||
pub enum PrinterCommand {
|
||||
/// 0-100. Each vendor converts to whatever its own mechanism wants (for
|
||||
/// Bambu, an `M106` PWM value over MQTT).
|
||||
SetFanSpeed { percent: u8 },
|
||||
/// A single raw gcode line. The most powerful variant by far, and the
|
||||
/// one to be most careful about ever exposing to a UI.
|
||||
SendGcodeLine { line: String },
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user