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:
+11
-96
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user