diff --git a/Cargo.toml b/Cargo.toml index 7fcbf8f..2e32c34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "continuum-proxy" version = "0.1.0" edition = "2024" -description = "Continuum edge gateway daemon — runs on-site, bridges printers to the cloud control plane" +description = "Continuum edge gateway daemon - runs on-site, bridges printers to the cloud control plane" license = "UNLICENSED" [lib] @@ -50,7 +50,7 @@ dotenvy = "0.15" # Real printer protocol clients (MQTT for Bambu, HTTP for PrusaLink/ # Moonraker, FTPS for gcode transfer) go here once you're ready to build -# past the stubs in src/printer/ — see that module's doc comment. +# past the stubs in src/printer/ - see that module's doc comment. continuum-rust-types = { path = "../continuum-schemas/packages/rust-types" } # FOR NON-LOCAL DEV # continuum-rust-types = { git = "https://git.octoturge.com/Continuum/continuum-schemas.git" } diff --git a/README.md b/README.md index c412ebf..0b85c47 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ cloud control plane (`continuum-backend`). ## This is a learning-stage boilerplate -This repo is deliberately minimal right now — real printer protocol clients +This repo is deliberately minimal right now - real printer protocol clients (Bambu MQTT+FTPS, PrusaLink REST, Klipper/Moonraker WebSocket) are **not** implemented yet. Each vendor is a stub that just prints what it would do (`src/printer/bambu.rs`, `prusa.rs`, `klipper.rs`). The idea is to learn @@ -39,27 +39,27 @@ examples/ test_bambu_certs.rs Loads printers.toml, does a real TLS handshake to each Bambu printer ``` -`src/printer/` is where the "inheritance" question lives — see that +`src/printer/` is where the "inheritance" question lives - see that module's doc comment for the trait+enum pattern this project uses instead of class inheritance, and run `printer_polymorphism` to see it work. -`printers.toml` (gitignored — copy from `printers.example.toml`) holds real -per-printer connection details: host, access code, and — since not every -Bambu printer trusts the same certificate (see `certs/README.md`) — an +`printers.toml` (gitignored - copy from `printers.example.toml`) holds real +per-printer connection details: host, access code, and - since not every +Bambu printer trusts the same certificate (see `certs/README.md`) - an optional per-printer CA override. `src/fleet.rs` loads it; nothing in -`main.rs` uses it yet, but `test_bambu_certs` does, as a real (if narrow — +`main.rs` uses it yet, but `test_bambu_certs` does, as a real (if narrow - just the TLS handshake, no MQTT) way to check a printer's certificate without needing the full MQTT client built yet. ## What's not here yet (on purpose) -- Real MQTT/FTPS/HTTP/WebSocket printer clients — `src/printer/*.rs` has a +- Real MQTT/FTPS/HTTP/WebSocket printer clients - `src/printer/*.rs` has a `println!` where each of these will go. - Local SQLite buffering for telemetry across connectivity gaps. - LAN printer discovery (SSDP/mDNS). - The mechanical plate-changer interface (serial/GPIO). Add these back in one at a time as you get comfortable with the Rust -underneath them — each is its own small lesson (async I/O, a new crate's +underneath them - each is its own small lesson (async I/O, a new crate's API, error handling for a real protocol) rather than something to absorb all at once. diff --git a/certs/README.md b/certs/README.md index 592ce68..569b329 100644 --- a/certs/README.md +++ b/certs/README.md @@ -4,24 +4,24 @@ Bambu Lab's shared root CA ("BBL CA2 RSA") used to verify a printer's LAN-mode MQTTS (port 8883) certificate. Current-generation Bambu printers present a -certificate signed by (a chain rooted at) this CA — trust this one file +certificate signed by (a chain rooted at) this CA - trust this one file instead of fetching/pinning a certificate per printer. - Source: https://github.com/bambulab/BambuStudio/blob/master/resources/cert/printer.cer - Fetched: 2026-08-28 - SHA-256 fingerprint: `E9:8F:19:57:8B:3F:12:4A:CE:6B:8A:24:7F:FE:DA:52:DC:99:C8:9F:D4:E7:D2:0C:82:82:99:77:B7:F3:35:02` - Verified with `openssl x509 -noout -text`: self-signed (`Issuer == Subject`), - `Basic Constraints: CA:TRUE`, `Key Usage: Certificate Sign, CRL Sign` — a + `Basic Constraints: CA:TRUE`, `Key Usage: Certificate Sign, CRL Sign` - a genuine root CA, not a per-device leaf certificate. Valid until 2050. The "CA2" name implies there was a CA1 generation before it, and at least -one current model — P1P — doesn't chain to this root either: BambuStudio's +one current model - P1P - doesn't chain to this root either: BambuStudio's `resources/cert/` only has this one file (plus an unrelated `*.bambulab.com` leaf cert for their cloud API), so there's no second bundled file to grab for P1P. That means it's a genuine per-device certificate, the same "download it from the printer" case as the older units. `CONTINUUM_BAMBU_CA_CERT_OVERRIDES` (see `.env.example`) is a -per-printer-id map for exactly this — there's no single fleet-wide +per-printer-id map for exactly this - there's no single fleet-wide override, because different printers can legitimately need different certificates at the same time. diff --git a/examples/auto_connect_bambu.rs b/examples/auto_connect_bambu.rs index a9ef5b0..f2684d6 100644 --- a/examples/auto_connect_bambu.rs +++ b/examples/auto_connect_bambu.rs @@ -4,7 +4,7 @@ //! fetch_bambu_cert combined: for each Bambu printer in printers.toml, //! makes sure it has a working certificate, auto-pinning one via //! trust-on-first-connect if the bundled CA doesn't verify. Safe to run -//! repeatedly — a printer that's already pinned (certs/pinned/.pem) just gets re-verified against its pin, no network trust //! decision is made again. diff --git a/examples/bambu_commands_demo.rs b/examples/bambu_commands_demo.rs index b9fcd28..5c14e4a 100644 --- a/examples/bambu_commands_demo.rs +++ b/examples/bambu_commands_demo.rs @@ -1,7 +1,7 @@ //! Run with: cargo run --example bambu_commands_demo //! //! Round-trips both commands against the exact JSON from Bambu's real -//! protocol — proves the plain hand-written struct + serde approach +//! protocol - proves the plain hand-written struct + serde approach //! matches byte for byte, same as the proto-based version did, with none //! of the build.rs ceremony. @@ -34,7 +34,7 @@ fn main() { println!("matches Bambu's shape: {}\n", actual == expected); // --- print report: arrives under "print", NOT "pushing" (pushall - // triggers this stream, it isn't a direct reply to it) — includes the + // triggers this stream, it isn't a direct reply to it) - includes the // full ams/xcam example, with one empty tray and one loaded tray, to // exercise the untagged Empty/Loaded split. let real_print_report = r#"{ diff --git a/examples/fetch_bambu_cert.rs b/examples/fetch_bambu_cert.rs index 5726088..6ec5392 100644 --- a/examples/fetch_bambu_cert.rs +++ b/examples/fetch_bambu_cert.rs @@ -3,11 +3,11 @@ //! "Trust on first connect": connects to one printer from printers.toml //! with certificate verification disabled, captures whatever certificate //! it presents, and saves it as PEM. Meant for a printer like P1P that -//! doesn't chain to the bundled CA — after running this, point that +//! doesn't chain to the bundled CA - after running this, point that //! printer's `ca_cert_path` in printers.toml at the saved file and //! `test_bambu_certs` should report it as verified from then on. //! -//! This trusts whoever answers on the network *right now* — only run it on +//! This trusts whoever answers on the network *right now* - only run it on //! a network you trust, ideally right after unboxing the printer. use continuum_proxy::fleet; diff --git a/examples/printer_polymorphism.rs b/examples/printer_polymorphism.rs index 62acdb7..c3e524f 100644 --- a/examples/printer_polymorphism.rs +++ b/examples/printer_polymorphism.rs @@ -4,7 +4,7 @@ //! - `connect()` is called the same way regardless of vendor (polymorphism), //! but each variant's `impl GenericPrinter` runs completely different //! code (override). -//! - `dispatch_gcode()` only exists on the two Bambu structs (extension) — +//! - `dispatch_gcode()` only exists on the two Bambu structs (extension) - //! you have to `match` the enum back down to the concrete type to reach //! it, which is the trade-off for not having implicit downcasting. @@ -35,7 +35,7 @@ async fn main() { PrinterHandle::PrusaLink(PrusaLinkPrinter::new("p3", "MK4", "192.168.1.51", "prusa-api-key")), PrinterHandle::PrusaSerial(PrusaSerialPrinter::new("p4", "MK3S+", 0)), PrinterHandle::Klipper(KlipperPrinter::new("p5", "Voron 2.4", "192.168.1.52")), - // No host configured — this one will hit the Err path. + // No host configured - this one will hit the Err path. PrinterHandle::Klipper(KlipperPrinter::new("p6", "Broken Voron", "")), ]; @@ -52,7 +52,7 @@ async fn main() { // Vendor-only extension: reachable only after matching the concrete // variant back out of the enum. BambuV1Printer and BambuV2Printer are // separate types now (each *has* a BambuGenericPrinter rather than - // being the same struct), so — unlike before the split — this needs + // being the same struct), so - unlike before the split - this needs // two arms instead of one `A(x) | B(x)` pattern: an or-pattern requires // every alternative to bind the same type, and these no longer do. for printer in &fleet { @@ -64,9 +64,9 @@ async fn main() { } // A second trait method, same uniform call site as connect(): every - // vendor sends "50%" a completely different way — an MQTT gcode + // vendor sends "50%" a completely different way - an MQTT gcode // command, Moonraker's native API, gcode over HTTP, gcode over serial - // — but the caller doesn't need to know or care which. + // - but the caller doesn't need to know or care which. println!(); for printer in &mut fleet { let _ = printer.set_fan_speed(50).await; diff --git a/examples/test_bambu_certs.rs b/examples/test_bambu_certs.rs index 1ca135f..4ecf506 100644 --- a/examples/test_bambu_certs.rs +++ b/examples/test_bambu_certs.rs @@ -2,7 +2,7 @@ //! //! Loads printers.toml (copy printers.example.toml to get started, fill in //! your real printers) and attempts a raw TLS handshake to each Bambu -//! printer's MQTTS port (8883) — no MQTT protocol involved, just "does the +//! printer's MQTTS port (8883) - no MQTT protocol involved, just "does the //! certificate verify". Prusa/Klipper entries are skipped; they don't have //! this trust question. @@ -28,7 +28,7 @@ fn main() -> anyhow::Result<()> { fn report(name: &str, result: anyhow::Result<()>) { match result { - Ok(()) => println!("{name}: OK — certificate verified"), - Err(err) => println!("{name}: FAILED — {err}"), + Ok(()) => println!("{name}: OK - certificate verified"), + Err(err) => println!("{name}: FAILED - {err}"), } } diff --git a/printers.example.toml b/printers.example.toml index 5c4ae8e..b31b863 100644 --- a/printers.example.toml +++ b/printers.example.toml @@ -9,10 +9,10 @@ vendor = "bambu_v1" # bambu_v1 | bambu_v2 | prusa_link | prus host = "192.168.1.50" access_code = "REPLACE_WITH_ACCESS_CODE" sn = "REPLACE_WITH_SERIAL_NUMBER" # printer screen > Settings > Device (used for MQTT topics later) -# P1P doesn't chain to the bundled CA — point this at a cert you've +# P1P doesn't chain to the bundled CA - point this at a cert you've # downloaded from the printer itself. Leave unset to use the bundled CA. # `cargo run --example fetch_bambu_cert -- p1p ./certs/p1p.pem` can fetch it -# for you (trust-on-first-connect — see that example's doc comment). +# for you (trust-on-first-connect - see that example's doc comment). ca_cert_path = "./certs/p1p.pem" [[printer]] @@ -22,7 +22,7 @@ vendor = "bambu_v2" host = "192.168.1.51" access_code = "REPLACE_WITH_ACCESS_CODE" sn = "REPLACE_WITH_SERIAL_NUMBER" -# No ca_cert_path — current-generation printers verify against the bundled +# No ca_cert_path - current-generation printers verify against the bundled # CA (certs/bambu_ca2.pem) fine. # [[printer]] diff --git a/src/config.rs b/src/config.rs index 7428e8d..bd2fb81 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ pub struct Config { /// Per-printer CA certificate overrides, keyed by printer ID. Most /// Bambu printers chain to the bundled shared CA (`certs/bambu_ca2.pem`) - /// and don't need an entry here — but some models (P1P, at least) ship + /// and don't need an entry here - but some models (P1P, at least) ship /// their own certificate instead of one signed by that shared CA, with /// no single file covering all of them. Look up a printer's own entry /// (if any) when constructing it; there's no fleet-wide "the" override. @@ -42,7 +42,7 @@ impl Config { } /// Parses `"printer_id=path,printer_id2=path2"` into a lookup map. Empty -/// input (the common case — no overrides needed) yields an empty map. +/// input (the common case - no overrides needed) yields an empty map. fn parse_cert_overrides(raw: &str) -> HashMap { raw.split(',') .filter_map(|entry| entry.split_once('=')) diff --git a/src/fleet.rs b/src/fleet.rs index b0c7669..a1d9726 100644 --- a/src/fleet.rs +++ b/src/fleet.rs @@ -1,4 +1,4 @@ -//! Loads real printer connection details from a TOML file — see +//! Loads real printer connection details from a TOML file - see //! `printers.example.toml` for the shape. The actual file (`printers.toml` //! by default) is gitignored: it holds real hostnames and access codes for //! your printers, which don't belong in version control. @@ -23,13 +23,13 @@ struct PrinterEntry { vendor: String, host: Option, access_code: Option, - /// The printer's serial number — Bambu's real MQTT topics are addressed + /// The printer's serial number - Bambu's real MQTT topics are addressed /// by serial (`device/{sn}/report`), not by our own `id`. sn: Option, api_key: Option, com_port: Option, /// Path to a printer-specific CA certificate. Leave unset to use the - /// bundled Bambu CA — only needed for a printer that doesn't chain to + /// bundled Bambu CA - only needed for a printer that doesn't chain to /// it (P1P, at least; see certs/README.md). ca_cert_path: Option, #[serde(default = "default_true")] diff --git a/src/main.rs b/src/main.rs index 8f3ea00..e1cbc3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,8 +19,8 @@ async fn main() -> anyhow::Result<()> { 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 + // 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. diff --git a/src/printer/bambu.rs b/src/printer/bambu.rs index 71ba0b2..cc14fe2 100644 --- a/src/printer/bambu.rs +++ b/src/printer/bambu.rs @@ -4,19 +4,19 @@ use std::time::Duration; use super::{GenericPrinter, PrinterBase, PrinterError}; -/// Bambu's shared LAN-mode root CA ("BBL CA2 RSA") — see certs/README.md +/// Bambu's shared LAN-mode root CA ("BBL CA2 RSA") - see certs/README.md /// for how this was obtained and verified. `include_bytes!` embeds it into /// the compiled binary at build time, so there's no file to ship alongside /// the executable. const BUNDLED_CA: &[u8] = include_bytes!("../../certs/bambu_ca2.pem"); /// Which certificate to trust when connecting to a Bambu printer's MQTTS -/// port (8883 — Bambu's LAN mode is TLS-only, there's no unencrypted +/// port (8883 - Bambu's LAN mode is TLS-only, there's no unencrypted /// fallback). pub enum BambuTls { /// Bambu's shared root CA. Correct for current-generation printers. BundledCa, - /// A specific certificate instead — for a printer whose firmware + /// A specific certificate instead - for a printer whose firmware /// doesn't chain to the shared CA (P1P, at least). Custom(Vec), /// Skip certificate verification entirely. The least safe option, only @@ -26,7 +26,7 @@ pub enum BambuTls { impl BambuTls { /// Resolves the trust mode for *one* printer. Call this per printer, - /// passing `config.bambu_ca_cert_overrides.get(printer_id)` — there's + /// passing `config.bambu_ca_cert_overrides.get(printer_id)` - there's /// no single override for the whole fleet, because not every Bambu /// model chains to the same CA. `require_valid_cert = false` always /// means `Insecure`, regardless of whether that printer also has an @@ -41,7 +41,7 @@ impl BambuTls { } } - /// The CA certificate bytes to hand to the TLS layer — empty when + /// The CA certificate bytes to hand to the TLS layer - empty when /// `Insecure`, since there's nothing to verify against. pub fn ca_bytes(&self) -> &[u8] { match self { @@ -57,12 +57,12 @@ impl BambuTls { let mut builder = native_tls::TlsConnector::builder(); // Bambu printer certificates don't carry their LAN IP as a Subject - // Alternative Name — it's DHCP-assigned, so baking it in at + // Alternative Name - it's DHCP-assigned, so baking it in at // manufacture time wouldn't make sense. Hostname verification has // to be off regardless of trust mode: what matters is whether the // certificate chains to a CA we trust, not whether its SAN matches // the specific IP we happened to dial today. Chain verification - // itself stays fully enforced below for BundledCa/Custom — this + // itself stays fully enforced below for BundledCa/Custom - this // narrows *what* gets checked, it doesn't turn checking off. builder.danger_accept_invalid_hostnames(true); @@ -81,7 +81,7 @@ fn resolve_addr(host: &str) -> anyhow::Result { } /// Minimal DER -> PEM encoder (base64, 64-char lines, standard -/// header/footer) — just enough to save a fetched certificate in the same +/// header/footer) - just enough to save a fetched certificate in the same /// format as the bundled one. fn der_to_pem(der: &[u8]) -> Vec { use base64::Engine; @@ -103,7 +103,7 @@ fn pem_to_der(pem: &[u8]) -> anyhow::Result> { } /// Fields and behavior every Bambu printer shares, regardless of protocol -/// generation — the layer between `GenericPrinter` and the version-specific +/// generation - the layer between `GenericPrinter` and the version-specific /// `BambuV1Printer`/`BambuV2Printer` leaves below. Those two *have* one of /// these (composition) rather than a deeper inheritance chain; because /// Rust's field/method privacy is scoped to the *module* (this whole file), @@ -131,22 +131,22 @@ impl BambuGenericPrinter { &self.base } - /// Not part of the `GenericPrinter` trait — Prusa/Klipper have no + /// Not part of the `GenericPrinter` trait - Prusa/Klipper have no /// equivalent, since FTPS gcode transfer is a Bambu-specific thing. pub fn dispatch_gcode(&self, file_name: &str) { println!("[{}] uploading {file_name} over FTPS", self.base.name); } - /// A real (blocking, one-shot) TLS handshake to port 8883 — no MQTT + /// A real (blocking, one-shot) TLS handshake to port 8883 - no MQTT /// protocol, just "does the certificate verify" against this printer's /// configured trust mode. /// /// `BundledCa` verifies normally (Bambu's shared CA is a proper, - /// well-formed root — OpenSSL's usual chain validation handles it + /// well-formed root - OpenSSL's usual chain validation handles it /// fine). `Custom` does something different: *fingerprint pinning*, /// not chain validation. A device's own leaf certificate often isn't a /// well-formed CA (no `CA:TRUE`), and the device may present it - /// alongside a separate self-signed root you never captured — asking + /// alongside a separate self-signed root you never captured - asking /// OpenSSL's normal path-building to accept an arbitrary leaf as a /// trust anchor is unreliable for exactly that reason (this is a real /// bug that shipped and failed against a real P1P: `add_root_certificate` @@ -154,7 +154,7 @@ impl BambuGenericPrinter { /// certificate chain` / `unable to get local issuer certificate` on /// the very next connection). So instead: connect with verification /// off, then compare the certificate actually presented against the - /// exact bytes pinned, byte for byte. A mismatch is a hard failure — + /// exact bytes pinned, byte for byte. A mismatch is a hard failure - /// no PKI judgment call, just "is this the same certificate as before." pub fn test_tls_handshake(&self) -> anyhow::Result<()> { let addr = resolve_addr(&self.base.host)?; @@ -169,7 +169,7 @@ impl BambuGenericPrinter { if presented != pem_to_der(pinned_pem)? { anyhow::bail!( - "{} presented a certificate different from the one pinned — could be a legitimate \ + "{} presented a certificate different from the one pinned - could be a legitimate \ certificate rotation, could be something worse; re-run fetch_bambu_cert deliberately \ if you're sure it's the former", self.base.name @@ -187,7 +187,7 @@ impl BambuGenericPrinter { /// presents, and returns it as PEM bytes. /// /// This is meaningfully weaker than verifying against a CA you already - /// trust — anyone on the network at the exact moment you run this gets + /// trust - anyone on the network at the exact moment you run this gets /// trusted forever after. That's why it's a method you call once, by /// hand (see `examples/fetch_bambu_cert.rs`), rather than something /// `connect()` falls back to silently. Save the result to a file and @@ -205,15 +205,15 @@ impl BambuGenericPrinter { } /// Makes sure this printer has a *working* trust configuration, - /// auto-pinning a certificate via trust-on-first-connect if needed — + /// auto-pinning a certificate via trust-on-first-connect if needed - /// the "smooth, no manual steps" version of the fetch_bambu_cert /// workflow, built the way SSH handles host keys: /// /// - A pinned certificate already on disk (`cert_dir/.pem`) is used - /// directly. No new trust decision gets made on every run — that + /// directly. No new trust decision gets made on every run - that /// already happened once, this just re-verifies against it. Keyed by /// *serial number*, not `id`: `id` is just an arbitrary label you - /// picked in printers.toml — rename a printer's `id` and the pin + /// picked in printers.toml - rename a printer's `id` and the pin /// should still find it, because a pin certifies "this physical /// printer", and the serial number is the one thing about it that /// can't change. @@ -224,10 +224,10 @@ impl BambuGenericPrinter { /// produces a working connection, and saves it to `cert_dir/.pem` /// for every run after this one. This is the one moment a MITM /// active on your network *right now* could plant a certificate that - /// gets trusted from then on — the same tradeoff SSH accepts on a + /// gets trusted from then on - the same tradeoff SSH accepts on a /// first connection. /// - A printer with an *explicit* pin (you set `ca_cert_path` yourself - /// in printers.toml) never auto-pins over it — a verification + /// in printers.toml) never auto-pins over it - a verification /// failure there is a real error, not something to paper over, /// since that's exactly the signal pinning exists to give you (the /// cert rotated, or something worse). @@ -252,7 +252,7 @@ impl BambuGenericPrinter { } } - /// Shared by both `BambuV1Printer` and `BambuV2Printer` — same MQTT + /// Shared by both `BambuV1Printer` and `BambuV2Printer` - same MQTT /// gcode-injection mechanism regardless of protocol generation. Real /// version would publish /// `{"print":{"command":"gcode_line","param":"M106 P1 S"}}` to @@ -263,7 +263,7 @@ impl BambuGenericPrinter { Ok(()) } - /// Shared "connect" logic both V1 and V2 delegate to — the real + /// Shared "connect" logic both V1 and V2 delegate to - the real /// version would open the MQTTS session here. Still a stub, but no /// longer duplicated across two structs. async fn connect_impl(&mut self) -> Result<(), PrinterError> { @@ -278,7 +278,7 @@ impl BambuGenericPrinter { } /// Older Bambu MQTT report schema (P1P, P1S, original X1 firmware). -/// No V1-only fields yet — add them here as you learn what differs. +/// No V1-only fields yet - add them here as you learn what differs. pub struct BambuV1Printer { generic: BambuGenericPrinter, } @@ -320,7 +320,7 @@ impl GenericPrinter for BambuV1Printer { } /// Current-generation Bambu MQTT report schema (X1 Carbon/X1E, H2 series). -/// No V2-only fields yet (e.g. AMS slot state) — add them here as you build +/// No V2-only fields yet (e.g. AMS slot state) - add them here as you build /// out real report parsing. pub struct BambuV2Printer { generic: BambuGenericPrinter, diff --git a/src/printer/bambu_commands/get_version.rs b/src/printer/bambu_commands/get_version.rs index 239555d..1a73a24 100644 --- a/src/printer/bambu_commands/get_version.rs +++ b/src/printer/bambu_commands/get_version.rs @@ -10,7 +10,7 @@ pub struct GetVersionRequest { pub struct GetVersionResponse { #[serde(flatten)] pub envelope: CommandEnvelope, - /// Not always present — `Option` handles that natively, no + /// Not always present - `Option` handles that natively, no /// `#[serde(default)]` needed (unlike the proto-generated version this /// replaced, which needed one explicit `field_attribute` call per /// field like this). diff --git a/src/printer/bambu_commands/mod.rs b/src/printer/bambu_commands/mod.rs index b81e0ee..86d5f53 100644 --- a/src/printer/bambu_commands/mod.rs +++ b/src/printer/bambu_commands/mod.rs @@ -1,21 +1,21 @@ -//! Bambu's raw MQTT command/report structs — plain hand-written Rust + +//! Bambu's raw MQTT command/report structs - plain hand-written Rust + //! serde, no protobuf. This data only ever needs to be understood by this //! crate (raw vendor wire format, never crosses into TypeScript), and the -//! actual wire format is JSON over MQTT, not binary protobuf — protobuf's +//! actual wire format is JSON over MQTT, not binary protobuf - protobuf's //! codegen tooling kept fighting us for a payoff (cross-language codegen, //! binary wire efficiency) that doesn't apply here at all. Proto stays in //! continuum-schemas for continuum.v1, which is a schema we designed //! ourselves and that both TS and Rust genuinely need. //! //! One file per Bambu *category* (`get_version.rs` for "info", `pushall.rs` -//! + `print.rs` for "pushing"/"print" — see below for why those are two +//! + `print.rs` for "pushing"/"print" - see below for why those are two //! files, not one). Copy `get_version.rs`'s shape for your next command. //! -//! Mirrors continuum-schemas' `schemas/bambulab/` JSON Schemas — same +//! Mirrors continuum-schemas' `schemas/bambulab/` JSON Schemas - same //! category split, same "print vs pushing" correction (see that //! directory's README for the full explanation): a `pushall` *request* //! goes to the "pushing" key, but the ongoing state-push stream it -//! triggers arrives under a different key, "print" — so `PushallRequest` +//! triggers arrives under a different key, "print" - so `PushallRequest` //! lives in `pushall.rs` and the thing it triggers, `PrintReport`, gets //! its own file, `print.rs`, rather than living together as if one were //! simply "the response to" the other. @@ -29,10 +29,10 @@ pub use print::{AmsState, AmsTray, AmsUnit, EmptyTray, LoadedTray, PrintReport, pub use pushall::PushallRequest; /// Fields every Bambu command/response shares. This is the "inheritance" -/// replacement for these structs — composition, not extension: every +/// replacement for these structs - composition, not extension: every /// command below *has* one of these, embedded via `#[serde(flatten)]` /// (which unwraps it onto the parent object at serialize/deserialize -/// time, so the JSON stays flat — no extra nesting), rather than +/// time, so the JSON stays flat - no extra nesting), rather than /// *extending* a base class. Exactly the same idea as `BambuGenericPrinter` /// in `../bambu.rs`, just expressed with a struct field instead of a /// struct-holding-a-struct in `impl` blocks. @@ -44,9 +44,9 @@ pub struct CommandEnvelope { /// One request you can send to a Bambu printer, keyed by Bambu's top-level /// JSON category ("info", "pushing", ...). NOTE: that top-level key is a -/// *category*, not the specific command — `CommandEnvelope::command` is +/// *category*, not the specific command - `CommandEnvelope::command` is /// what actually says which one ("get_version", "pushall", ...). A -/// category can have more than one possible request shape — if that ever +/// category can have more than one possible request shape - if that ever /// happens here, that variant's payload becomes its own small /// `#[serde(tag = "command")]` enum instead of a single struct. #[derive(serde::Serialize, Debug)] @@ -56,7 +56,7 @@ pub enum BambuRequest { Pushing(PushallRequest), } -/// Same idea, the response direction — note "print", not "pushing" (see +/// Same idea, the response direction - note "print", not "pushing" (see /// this module's doc comment). #[derive(serde::Deserialize, Debug)] #[serde(rename_all = "snake_case")] diff --git a/src/printer/bambu_commands/print.rs b/src/printer/bambu_commands/print.rs index f12d483..1b17017 100644 --- a/src/printer/bambu_commands/print.rs +++ b/src/printer/bambu_commands/print.rs @@ -1,7 +1,7 @@ //! The ongoing print-state push (root key "print"). Doesn't carry a -//! `CommandEnvelope` — no `sequence_id`/`command` seen on this one in the +//! `CommandEnvelope` - no `sequence_id`/`command` seen on this one in the //! real capture, and it may genuinely not have them (an async push, not a -//! reply to a specific request) — revisit if a real capture proves +//! reply to a specific request) - revisit if a real capture proves //! otherwise. #[derive(serde::Deserialize, Debug)] @@ -15,12 +15,12 @@ pub struct PrintReport { pub xcam: Option, pub xcam_status: Option, // The real payload has a lot more fields than shown here (this - // example was truncated) — add them the same way, one field per line, + // example was truncated) - add them the same way, one field per line, // `Option` for anything that isn't always present. } /// Bambu's own naming has the outer container and the inner array both -/// called `ams` — `print.ams.ams`, not a typo here, just what the printer +/// called `ams` - `print.ams.ams`, not a typo here, just what the printer /// actually sends. #[derive(serde::Deserialize, Debug)] pub struct AmsState { @@ -37,13 +37,13 @@ pub struct AmsUnit { } /// A tray slot is either empty (just `id`) or loaded (the full field set) -/// — two genuinely different shapes, not one shape with optional fields, +/// - two genuinely different shapes, not one shape with optional fields, /// so this is an untagged enum rather than a struct full of `Option`. /// `#[serde(deny_unknown_fields)]` on `Empty` is load-bearing: untagged /// enums try variants in order, and without it a *loaded* tray (which has /// every field `Empty` allows, plus more) would incorrectly match `Empty` /// first, since a plain struct doesn't reject extra fields by default. -/// Verified this the hard way — see git history. +/// Verified this the hard way - see git history. #[derive(serde::Deserialize, Debug)] #[serde(untagged)] pub enum AmsTray { diff --git a/src/printer/bambu_commands/pushall.rs b/src/printer/bambu_commands/pushall.rs index 24ec505..672babe 100644 --- a/src/printer/bambu_commands/pushall.rs +++ b/src/printer/bambu_commands/pushall.rs @@ -8,7 +8,7 @@ pub struct PushallRequest { pub push_target: i32, } -// No PushallResponse here — sending this request doesn't get a direct +// No PushallResponse here - sending this request doesn't get a direct // reply. It makes the printer start/refresh the ongoing print-state push, -// which arrives under a different root key ("print", not "pushing") — see +// which arrives under a different root key ("print", not "pushing") - see // print.rs for that. diff --git a/src/printer/klipper.rs b/src/printer/klipper.rs index c7b89c2..23f38e2 100644 --- a/src/printer/klipper.rs +++ b/src/printer/klipper.rs @@ -17,7 +17,7 @@ impl GenericPrinter for KlipperPrinter { &self.base } - /// OVERRIDE: a third, again-different body — Moonraker's real feed is a + /// OVERRIDE: a third, again-different body - Moonraker's real feed is a /// WebSocket (see `adapters::moonraker`). This also shows the `Err` /// path: an empty `host` fails immediately, same as any other vendor /// would if it couldn't reach its printer. @@ -29,7 +29,7 @@ impl GenericPrinter for KlipperPrinter { Ok(()) } - /// OVERRIDE: Moonraker's own **hypothetical** native API, not gcode injection — no + /// OVERRIDE: Moonraker's own **hypothetical** native API, not gcode injection - no /// percent-to-PWM conversion needed since it already takes a /// percentage directly. Real version: /// `POST /printer/gcode/script?script=SET_FAN_SPEED SPEED=0.NN`. diff --git a/src/printer/mod.rs b/src/printer/mod.rs index 6a70ee1..a925439 100644 --- a/src/printer/mod.rs +++ b/src/printer/mod.rs @@ -1,24 +1,24 @@ -//! Printer hierarchy — the Rust answer to +//! Printer hierarchy - the Rust answer to //! `GenericPrinter -> BambuV1/BambuV2/PrusaLink/PrusaSerial/Klipper`-style //! inheritance, since Rust structs can't `extend` each other. //! //! Bambu goes one level deeper than the others: `bambu.rs` has -//! `BambuGenericPrinter` (fields + behavior every Bambu printer shares — +//! `BambuGenericPrinter` (fields + behavior every Bambu printer shares - //! access code, CA trust, the TLS handshake test/fetch) which //! `BambuV1Printer`/`BambuV2Printer` each *have* one of, mirroring the //! `GenericPrinter -> BambuGenericPrinter -> BambuV1/V2` chain this project -//! actually wanted from the start — see that file's doc comment. +//! actually wanted from the start - see that file's doc comment. //! //! Three pieces, each doing one job an OOP base class would normally do: //! -//! 1. `PrinterBase` — a plain struct holding the shared *fields*. Every +//! 1. `PrinterBase` - a plain struct holding the shared *fields*. Every //! printer struct below *has* one of these (composition) instead of //! *extending* one. -//! 2. `GenericPrinter` — a trait holding the shared *behavior*. `connect()` +//! 2. `GenericPrinter` - a trait holding the shared *behavior*. `connect()` //! has no default body, so every printer type is forced to write its -//! own — that's what "overriding" looks like when there's no inherited +//! own - that's what "overriding" looks like when there's no inherited //! body to override in the first place. -//! 3. `PrinterHandle` — an enum listing the closed set of printer kinds. +//! 3. `PrinterHandle` - an enum listing the closed set of printer kinds. //! Stands in for "any subclass of GenericPrinter". //! //! Run `cargo run --example printer_polymorphism` to see it in action. @@ -41,7 +41,7 @@ pub use bambu_commands::{ pub use klipper::KlipperPrinter; pub use prusa::{PrusaLinkPrinter, PrusaSerialPrinter}; -/// A tiny error type: just a message. `String` would work too — this exists +/// A tiny error type: just a message. `String` would work too - this exists /// mainly so `?` has something concrete to convert into everywhere. #[derive(Debug, thiserror::Error)] #[error("{0}")] @@ -55,7 +55,7 @@ pub struct PrinterBase { pub host: String, } -/// The shared interface — think `interface GenericPrinter` (Java/TS) or an +/// The shared interface - think `interface GenericPrinter` (Java/TS) or an /// ABC (Python). Every vendor's struct implements this. pub trait GenericPrinter { /// Read-only access to the shared fields. @@ -64,23 +64,23 @@ pub trait GenericPrinter { /// REQUIRED, no default body: every vendor speaks a different protocol, /// so there's nothing sensible to share here. Each `impl` in /// `bambu.rs`/`prusa.rs`/`klipper.rs` provides a totally different - /// body — that's the override. + /// body - that's the override. /// /// `cargo check` will warn about `async fn` in a public trait here. /// That's about a more advanced concern (whether the resulting work can /// safely move across OS threads) that only matters once this is wired - /// into the multi-threaded daemon — safe to ignore for now. + /// into the multi-threaded daemon - safe to ignore for now. async fn connect(&mut self) -> Result<(), PrinterError>; /// REQUIRED, no default body: `percent` is 0-100, but how it actually - /// gets sent — an MQTT gcode-injection command, a REST call, raw bytes - /// on a serial port — is different for every vendor, same as + /// gets sent - an MQTT gcode-injection command, a REST call, raw bytes + /// on a serial port - is different for every vendor, same as /// `connect()`. async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError>; /// OPTIONAL: a default body every printer gets for free unless it /// writes its own. None of ours do below, so all three vendors share - /// this exact implementation — the trait-method equivalent of + /// this exact implementation - the trait-method equivalent of /// inheriting a base-class method unchanged. fn display_name(&self) -> String { format!("{} ({})", self.base().name, self.base().id) @@ -89,7 +89,7 @@ pub trait GenericPrinter { /// M106's `S` parameter is a raw 0-255 PWM value, not a percentage. Shared /// by anything that ultimately sends gcode (Bambu, PrusaLink, PrusaSerial) -/// — *not* a trait default, since Klipper's hypothetical native fan API +/// - *not* a trait default, since Klipper's hypothetical native fan API /// below takes a percent directly and has no use for this at all. Private, /// but still visible to bambu.rs/prusa.rs: Rust's module privacy reaches /// into child modules, not just the exact file it's defined in. @@ -109,7 +109,7 @@ pub enum PrinterHandle { // Hand-written delegation: each trait method just matches on the variant // and forwards to that variant's own implementation. This one block is the -// only "boilerplate tax" for not having inheritance — everything else reads +// only "boilerplate tax" for not having inheritance - everything else reads // like normal code. impl GenericPrinter for PrinterHandle { fn base(&self) -> &PrinterBase { diff --git a/src/printer/prusa.rs b/src/printer/prusa.rs index 49f1c6d..5168ef3 100644 --- a/src/printer/prusa.rs +++ b/src/printer/prusa.rs @@ -21,7 +21,7 @@ impl GenericPrinter for PrusaLinkPrinter { &self.base } - /// OVERRIDE: a completely different body from Bambu's — PrusaLink is a + /// OVERRIDE: a completely different body from Bambu's - PrusaLink is a /// plain REST API (real version: `GET /api/v1/status` with the API /// key, see `adapters::prusalink`), no persistent session to hold. async fn connect(&mut self) -> Result<(), PrinterError> { @@ -29,7 +29,7 @@ impl GenericPrinter for PrusaLinkPrinter { Ok(()) } - /// OVERRIDE: gcode via PrusaLink's command-injection endpoint — same + /// OVERRIDE: gcode via PrusaLink's command-injection endpoint - same /// underlying M106 as Bambu, reusing the shared percent->PWM /// conversion, but sent over HTTP instead of MQTT. async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError> { @@ -61,7 +61,7 @@ impl GenericPrinter for PrusaSerialPrinter { &self.base } - /// OVERRIDE: a fourth, again-different body — no network host at all, + /// OVERRIDE: a fourth, again-different body - no network host at all, /// just a local serial port (real version: open the port, e.g. via the /// `serialport`/`tokio-serial` crate, and speak Prusa's G-code-over- /// serial protocol). @@ -72,7 +72,7 @@ impl GenericPrinter for PrusaSerialPrinter { /// OVERRIDE: same gcode as PrusaLink (still M106, still the shared /// conversion), but written straight to the serial port instead of - /// sent over HTTP — a third transport for the same underlying command. + /// sent over HTTP - a third transport for the same underlying command. async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError> { let pwm = super::percent_to_pwm(percent); println!("[{}] writing to COM{}: M106 S{pwm}", self.base.name, self.com_port); diff --git a/src/uplink/mod.rs b/src/uplink/mod.rs index 03f3421..efc6f33 100644 --- a/src/uplink/mod.rs +++ b/src/uplink/mod.rs @@ -2,7 +2,7 @@ //! //! This is intentionally the simplest version of "a client that reconnects //! when it drops": no backoff curve, no channels routing messages in from -//! other parts of the program yet — just connect, say hello, send a +//! other parts of the program yet - just connect, say hello, send a //! heartbeat on a timer, and print whatever comes back. Once you're //! comfortable with this, growing it (real backoff, forwarding printer //! telemetry, dispatching cloud-issued commands) is additive, not a rewrite.