Down from 29 warnings to 2, in the spirit of 4ab95ff.
The doc warnings were all one mistake made repeatedly: a paragraph wrapped so
that a line *began* with `- ` or `+ `, which rustdoc reads as a markdown list
marker and the following lines as unindented continuations. The prose always
meant an em-dash. Fixed by moving the dash to the end of the previous line and
re-wrapping; not one word changed, which is the point - these comments record
things confirmed against real printers and are worth more than the warning.
The genuine lists just had their continuations de-indented and stayed lists.
The code ones are `match x { V(v) => .., _ => {} }` to `if let`, and nested
`if let Some(x) = .. { if cond { .. } }` to a let-chain. None had an `else`,
so each is the same control flow written once instead of twice.
Two warnings are left deliberately, because fixing them is not cosmetic:
`AmsTray` (544 bytes against 32) and `BambuReport` (984 against 120) both have
one variant far larger than the rest. The fix clippy wants is a `Box`, which
changes how every reader of those enums is written and puts an allocation on
the push_status path that currently has none. Worth measuring before doing,
not worth doing to silence a warning.
51 tests still pass.
179 lines
7.9 KiB
Rust
179 lines
7.9 KiB
Rust
//! Run with: cargo run --example live_reports
|
|
//!
|
|
//! **Read-only. Safe to run against a printer that is mid-print.**
|
|
//!
|
|
//! Connects to every Bambu printer in printers.toml, asks each one what
|
|
//! firmware it's running, then watches its `print.push_status` stream for a
|
|
//! while. Everything it sends either reads state or asks the printer to
|
|
//! re-send what it already knows:
|
|
//!
|
|
//! - `get_version` - read-only.
|
|
//! - `pushall` - "re-send the full status object". A request for data,
|
|
//! not a state change. v1 only; a no-op on v2.
|
|
//! - `print_status` - reads this process's own in-memory cache, no traffic.
|
|
//!
|
|
//! It deliberately sends **no** `gcode_line`, **no** `set_fan_speed`, **no**
|
|
//! `calibration` (that one starts a physical calibration sequence and would
|
|
//! wreck a running print), no upload, and nothing that starts or stops a
|
|
//! job. Keep it that way - the whole point of this example is that it can
|
|
//! be pointed at a working printer without a second thought. Use
|
|
//! `bambu_idle_checks` for anything that writes.
|
|
//!
|
|
//! The raw payloads are the real output. `PrintReport` models only a small
|
|
//! fraction of what a real `push_status` carries (see its doc comment), so
|
|
//! this turns on the `trace`-level raw-payload log in `bambu_mqtt.rs` and
|
|
//! lets you capture the untouched JSON:
|
|
//!
|
|
//! ```sh
|
|
//! cargo run --example live_reports 2> capture.jsonl
|
|
//! ```
|
|
//!
|
|
//! Then grow `PrintReport` and `continuum-schemas`' `push_status.schema.json`
|
|
//! from what's actually in there, rather than from guesses.
|
|
|
|
use std::time::Duration;
|
|
|
|
use continuum_proxy::fleet;
|
|
use continuum_proxy::printer::{GenericPrinter, PrinterHandle};
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
/// How long to sit and watch each printer's stream before moving on.
|
|
const WATCH_FOR: Duration = Duration::from_secs(25);
|
|
/// How often to print a one-line summary of the cached report.
|
|
const POLL_EVERY: Duration = Duration::from_secs(2);
|
|
/// A printer that's switched off shouldn't hold the whole run up - the TLS
|
|
/// handshake inside `connect` has its own 5s timeout, but the MQTT half
|
|
/// doesn't, so this bounds the lot.
|
|
const CONNECT_TIMEOUT: Duration = Duration::from_secs(20);
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
// Default to trace for the raw-payload target specifically, so a plain
|
|
// run captures payloads without needing RUST_LOG set by hand. An
|
|
// explicit RUST_LOG still wins.
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
|
EnvFilter::new("info,continuum_proxy::printer::bambu_mqtt=trace")
|
|
}))
|
|
.with_writer(std::io::stderr)
|
|
.init();
|
|
|
|
let mut printers = fleet::load(std::path::Path::new("printers.toml"))?;
|
|
|
|
// Optional: a printer id to look at just that one. No argument means the
|
|
// whole fleet. Read-only either way, so there's nothing to guard here.
|
|
let only: Option<String> = std::env::args().nth(1);
|
|
|
|
for printer in &mut printers {
|
|
if let Some(id) = &only
|
|
&& &printer.base().id != id
|
|
{
|
|
continue;
|
|
}
|
|
let PrinterHandle::Bambu(bambu) = printer else {
|
|
println!("skipping {} - not a Bambu printer", printer.display_name());
|
|
continue;
|
|
};
|
|
|
|
println!("\n=== {} ===", bambu.display_name());
|
|
|
|
match tokio::time::timeout(CONNECT_TIMEOUT, bambu.connect()).await {
|
|
Ok(Ok(())) => {}
|
|
Ok(Err(err)) => {
|
|
println!(" connect failed: {err}");
|
|
continue;
|
|
}
|
|
Err(_) => {
|
|
println!(" connect timed out after {CONNECT_TIMEOUT:?} - printer off or unreachable?");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
match bambu.get_version().await {
|
|
Ok(version) => {
|
|
println!(" get_version OK: {} modules", version.module.len());
|
|
for module in &version.module {
|
|
println!(" {:<12} sw {:<14} hw {}", module.name, module.sw_ver, module.hw_ver);
|
|
}
|
|
}
|
|
Err(err) => println!(" get_version failed: {err}"),
|
|
}
|
|
|
|
// v1 needs this to get a full object at all; it's a no-op on v2 (see
|
|
// `pushall`'s doc comment). Harmless either way.
|
|
if let Err(err) = bambu.pushall().await {
|
|
println!(" pushall failed: {err}");
|
|
}
|
|
|
|
println!(" watching print.push_status for {WATCH_FOR:?}...");
|
|
let deadline = tokio::time::Instant::now() + WATCH_FOR;
|
|
// Keyed on the *contents*, not on `sequence_id`. v2 leaves
|
|
// `sequence_id` pinned at one value while the report genuinely
|
|
// changes underneath it (confirmed live - see `PrintStatusCache`),
|
|
// so keying on it reports one update for a whole print.
|
|
let mut last_seen: Option<String> = None;
|
|
let mut updates = 0u32;
|
|
|
|
while tokio::time::Instant::now() < deadline {
|
|
tokio::time::sleep(POLL_EVERY).await;
|
|
|
|
match bambu.print_status().await {
|
|
Some(report) => {
|
|
let seq = report.envelope.sequence_id.clone();
|
|
let fingerprint = format!(
|
|
"{:?}|{:?}|{:?}|{:?}|{:?}|{:?}",
|
|
report.gcode_state,
|
|
report.mc_percent,
|
|
report.layer_num,
|
|
report.mc_remaining_time,
|
|
report.nozzle_temper,
|
|
report.bed_temper
|
|
);
|
|
let changed = last_seen.as_ref() != Some(&fingerprint);
|
|
if changed {
|
|
updates += 1;
|
|
last_seen = Some(fingerprint);
|
|
}
|
|
println!(
|
|
" seq={seq:<6} {:<8} {:>3}% layer {}/{} {} min left nozzle {:.1}/{:.1}C bed {:.1}/{:.1}C{}",
|
|
report.gcode_state.as_deref().unwrap_or("?"),
|
|
report.mc_percent.unwrap_or(0),
|
|
report.layer_num.unwrap_or(0),
|
|
report.total_layer_num.unwrap_or(0),
|
|
report.mc_remaining_time.unwrap_or(0),
|
|
report.nozzle_temper.unwrap_or(0.0),
|
|
report.nozzle_target_temper.unwrap_or(0.0),
|
|
report.bed_temper.unwrap_or(0.0),
|
|
report.bed_target_temper.unwrap_or(0.0),
|
|
if changed { "" } else { " (unchanged)" }
|
|
);
|
|
if let Some(job) = report.subtask_name.as_deref().filter(|s| !s.is_empty()) {
|
|
println!(" job: {job}");
|
|
}
|
|
if let Some(hms) = report.hms.as_ref().filter(|h| !h.is_empty()) {
|
|
println!(" hms: {} active entr(y/ies)", hms.len());
|
|
}
|
|
println!(
|
|
" fans: cooling={} heatbreak={} big1={} big2={} wifi={}",
|
|
report.cooling_fan_speed.as_deref().unwrap_or("?"),
|
|
report.heatbreak_fan_speed.as_deref().unwrap_or("?"),
|
|
report.big_fan1_speed.as_deref().unwrap_or("?"),
|
|
report.big_fan2_speed.as_deref().unwrap_or("?"),
|
|
report.wifi_signal.as_deref().unwrap_or("?")
|
|
);
|
|
}
|
|
// Reaching here now means genuinely nothing arrived. It used
|
|
// to also mean "something arrived and failed to parse",
|
|
// which is why a mid-print H2C once looked completely
|
|
// silent - see `handle_incoming_report`.
|
|
None => println!(" no push_status received yet"),
|
|
}
|
|
}
|
|
|
|
println!(" {updates} distinct push_status update(s) in {WATCH_FOR:?}");
|
|
}
|
|
|
|
println!("\ndone - raw payloads went to stderr (redirect it to capture them)");
|
|
Ok(())
|
|
}
|