// Everything but go2rtc comes from the library crate (src/lib.rs) rather than // being re-declared here. Declaring `mod printer;` in both places compiles the // same files twice, once per crate, and the binary copy warned about every // `pub use` re-export and every method only examples or tests call - "unused" // is true inside a binary, where nothing is externally reachable, and wrong // about the library's actual public API. One copy, no false warnings. mod go2rtc; use tracing_subscriber::EnvFilter; use continuum_proxy::config::Config; use continuum_proxy::printer::{GenericPrinter, PrinterHandle}; use continuum_proxy::{camera, completion, fleet, uplink}; #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))) .init(); dotenvy::dotenv().ok(); let config = Config::from_env()?; 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, 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; // 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>` 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)); // Four long-running tasks: the cloud uplink, the go2rtc subprocess // watchdog, the camera-config builder that tells it what to serve, and // the print-completion watcher that blinks a printer's lights when it // finishes. Every printer's MQTT session is a fifth 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). // // The completion watcher is deliberately its own task rather than part // of the uplink: it must keep working with the cloud connection down, // because the person it is for is standing next to the printer. See // `completion::run`. // // The camera builder and the watchdog are two tasks and not one because // they answer different questions - what should be served, and keeping // the thing that serves it alive. They meet at one `Notify`: the builder // rings it after writing a new config, because go2rtc only reads its // configuration at startup. // // `tokio::select!` stops as soon as any returns; all four loop forever, // so in practice this runs until the process is killed. let restart_go2rtc = std::sync::Arc::new(tokio::sync::Notify::new()); tokio::select! { _ = uplink::run(config.clone(), fleet.clone()) => {} _ = go2rtc::watchdog( config.go2rtc_bin.clone(), config.go2rtc_config.clone(), restart_go2rtc.clone(), ) => {} _ = camera::run( fleet.clone(), config.go2rtc_config.clone(), config.go2rtc_api_listen.clone(), restart_go2rtc.clone(), ) => {} _ = completion::run(fleet.clone()) => {} } Ok(()) } /// Connects every configured printer concurrently (not one at a time - /// Bambu's auto-detect fallback alone can take several seconds per /// printer if it has to time out on MQTT v5 before trying v3.1.1; /// serially, a fleet of twenty would take twenty times as long to start). /// Logs success/failure per printer instead of failing startup over one - /// a farm shouldn't refuse to start because one printer is unplugged. async fn connect_fleet(fleet: &mut [PrinterHandle]) { let results = futures_util::future::join_all(fleet.iter_mut().map(|printer| async { let name = printer.display_name(); (name, printer.connect().await) })) .await; for (name, result) in results { match result { Ok(()) => tracing::info!(printer = %name, "connected"), Err(err) => tracing::warn!(printer = %name, %err, "failed to connect"), } } }