Simplify boilerplate for learning: drop real protocol code, minimal main.rs

Removed adapters/ (rumqttc MQTT, suppaftp FTPS, reqwest HTTP), plate_changer/
(tokio-serial), discovery/, and cache.rs (rusqlite) — src/printer/ already
covers 'talk to a printer' as simple stubs, so keeping a second, more complex
version alongside it was redundant. main.rs goes from 6 concurrent tasks to
2 (uplink + go2rtc). uplink/ drops channels, jitter, and Send-bound futures.

Verified with cargo build + cargo run --example printer_polymorphism.
This commit is contained in:
2026-08-28 18:22:38 +00:00
parent 5705f36c01
commit d0a17ee50e
16 changed files with 80 additions and 799 deletions
+11 -96
View File
@@ -1,118 +1,33 @@
mod adapters;
mod cache;
mod config;
mod discovery;
mod go2rtc;
mod plate_changer;
mod printer;
mod uplink;
use std::time::Duration;
use tokio::sync::mpsc;
use tracing::{info, warn};
use tracing_subscriber::EnvFilter;
use crate::cache::EdgeCache;
use crate::config::Config;
use crate::uplink::ClientMessage;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
.json()
.init();
dotenvy_load();
dotenvy::dotenv().ok();
let config = Config::from_env()?;
info!(gateway_id = %config.gateway_id, farm_id = %config.farm_id, "starting continuum-proxy");
let cache = EdgeCache::open(&config.sqlite_path)?;
// Channels wiring the printer adapters -> uplink, and uplink -> task
// handlers for cloud-issued commands.
let (telemetry_tx, telemetry_rx) = mpsc::channel::<ClientMessage>(256);
let (task_tx, mut task_rx) = mpsc::channel::<uplink::ServerMessage>(64);
let (discovered_tx, mut discovered_rx) = mpsc::channel::<discovery::DiscoveredPrinter>(32);
let uplink_handle = tokio::spawn(uplink::run(config.clone(), telemetry_rx, task_tx));
let go2rtc_handle = tokio::spawn(go2rtc::watchdog(config.go2rtc_bin.clone(), config.go2rtc_config.clone()));
let discovery_handle = tokio::spawn(discovery::run(
Duration::from_secs(config.discovery_interval_secs),
discovered_tx,
));
// Cloud-issued task dispatcher: pause/resume/plate-change/etc. commands
// arriving over the uplink get routed to the right adapter here.
let task_dispatcher = tokio::spawn(async move {
while let Some(msg) = task_rx.recv().await {
match msg {
uplink::ServerMessage::Task { task_id, kind, payload } => {
info!(task_id, kind, ?payload, "received task from cloud");
// TODO: route to adapters::bambu / prusalink / moonraker or plate_changer
// based on `kind`, then send a ClientMessage::TaskResult back upstream.
}
uplink::ServerMessage::HelloAck { session_id } => {
info!(session_id, "uplink session established");
}
uplink::ServerMessage::HeartbeatAck { seq } => {
tracing::debug!(seq, "heartbeat acked");
}
}
}
});
// Newly-discovered printers get their telemetry adapters spawned on the fly.
let telemetry_tx_for_discovery = telemetry_tx.clone();
let discovery_dispatcher = tokio::spawn(async move {
while let Some(printer) = discovered_rx.recv().await {
info!(host = %printer.host, "spawning adapter for discovered printer");
let _ = &telemetry_tx_for_discovery;
// TODO: match printer.vendor and spawn adapters::bambu::run_telemetry / moonraker::run / prusalink::poll_loop
}
});
// Periodically flush anything the SQLite cache buffered while the uplink was down.
let cache_flusher = tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(10));
loop {
ticker.tick().await;
match cache.drain_telemetry(100) {
Ok(rows) if !rows.is_empty() => {
let ids: Vec<i64> = rows.iter().map(|(id, _, _)| *id).collect();
for (_, printer_id, payload) in &rows {
let payload: serde_json::Value = serde_json::from_str(payload).unwrap_or_default();
let _ = telemetry_tx
.send(ClientMessage::Telemetry { printer_id: printer_id.clone(), payload })
.await;
}
if let Err(err) = cache.ack_telemetry(&ids) {
warn!(?err, "failed to ack drained telemetry rows");
}
}
Ok(_) => {}
Err(err) => warn!(?err, "failed to drain edge cache"),
}
}
});
tracing::info!(gateway_id = %config.gateway_id, farm_id = %config.farm_id, "starting continuum-proxy");
// Two things run at once for now: the cloud uplink, and the go2rtc
// camera-restreaming process. More will join them later — printer
// discovery, the plate changer — as you build past src/printer/'s
// stubs. `tokio::select!` runs both and stops as soon as either one
// returns; both loop forever below, so in practice this runs until the
// process is killed.
tokio::select! {
res = uplink_handle => warn!(?res, "uplink task exited"),
res = go2rtc_handle => warn!(?res, "go2rtc watchdog exited"),
res = discovery_handle => warn!(?res, "discovery task exited"),
res = task_dispatcher => warn!(?res, "task dispatcher exited"),
res = discovery_dispatcher => warn!(?res, "discovery dispatcher exited"),
res = cache_flusher => warn!(?res, "cache flusher exited"),
_ = uplink::run(config.clone()) => {}
_ = go2rtc::watchdog(config.go2rtc_bin.clone(), config.go2rtc_config.clone()) => {}
}
Ok(())
}
/// Loads `.env` if present; a missing file is fine (env vars may already be
/// set by the process supervisor), so the error is deliberately ignored.
fn dotenvy_load() {
let _ = dotenvy::dotenv();
}