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. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+16
-14
@@ -21,25 +21,27 @@ async fn main() -> anyhow::Result<()> {
|
||||
tracing::info!(gateway_id = %config.gateway_id, farm_id = %config.farm_id, "starting continuum-proxy");
|
||||
|
||||
// Loaded and connected here, once, at startup - nothing yet re-reads
|
||||
// printers.toml on a running daemon (adding/removing a printer means
|
||||
// a restart), and nothing yet lets the uplink dispatch a command to
|
||||
// one of these once connected - see PrinterHandle's own doc comment
|
||||
// for what's built and what's still a stub per vendor. Keeping
|
||||
// `fleet` alive as a local variable for the rest of `main` (which
|
||||
// never returns, below) is what keeps every connection open; it's
|
||||
// not otherwise touched yet.
|
||||
// printers.toml on a running daemon, so adding or removing a printer
|
||||
// still means a restart.
|
||||
let mut fleet = fleet::load(std::path::Path::new(&config.printers_path))?;
|
||||
connect_fleet(&mut fleet).await;
|
||||
|
||||
// Three things run at once for now: the cloud uplink, the go2rtc
|
||||
// camera-restreaming process, and (implicitly, via the connections
|
||||
// opened above) every configured printer's live session. More will
|
||||
// join them later - printer discovery, the plate changer - as you
|
||||
// build past src/printer/'s stubs. `tokio::select!` runs both of the
|
||||
// tasks below and stops as soon as either one returns; both loop
|
||||
// The fleet is now shared with the uplink rather than just parked here
|
||||
// keeping connections alive: the uplink reads each printer's status to
|
||||
// send telemetry up, and looks printers up by id to dispatch commands
|
||||
// down. `Arc<Mutex<..>>` is how two tasks share one mutable thing -
|
||||
// see `uplink::SharedFleet` for why that type and what it costs.
|
||||
let fleet: uplink::SharedFleet = std::sync::Arc::new(tokio::sync::Mutex::new(fleet));
|
||||
|
||||
// Two long-running tasks: the cloud uplink and the go2rtc
|
||||
// camera-restreaming watchdog. Every printer's MQTT session is a third
|
||||
// thing running concurrently, but implicitly - each one has its own
|
||||
// background poll task spawned by `connect()`, kept alive by the fleet
|
||||
// above. More will join these later (printer discovery, the plate
|
||||
// changer). `tokio::select!` stops as soon as either returns; both loop
|
||||
// forever, so in practice this runs until the process is killed.
|
||||
tokio::select! {
|
||||
_ = uplink::run(config.clone()) => {}
|
||||
_ = uplink::run(config.clone(), fleet.clone()) => {}
|
||||
_ = go2rtc::watchdog(config.go2rtc_bin.clone(), config.go2rtc_config.clone()) => {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user