Add upload_project_file/send_gcode_line, real Marlin serial, Bambu protocol auto-detect
- GenericPrinter grows upload_project_file, send_gcode_line, and a generic upload_gcode<P> driver that streams a whole gcode file line by line through any vendor's send_gcode_line. - PrusaSerialPrinter's connect/send_gcode_line are real (not stubs): a held-open MarlinSerialConnection (src/printer/marlin_serial.rs) writes each line and waits for Marlin's "ok" before sending the next. Config changed from com_port: i16 to port: String + baud_rate: u32 to hold a real device path. - Bambu: BambuGenericPrinter + BambuV1Printer/BambuV2Printer collapsed into one BambuPrinter, since which MQTT/BambuMQTT version (v3.1.1/v1 vs v5/v2) a printer speaks is discovered at connect() time (or forced), not something config should pick a type for - the same physical printer can change after a firmware update. printers.toml gets a new "bambu" vendor (auto-detect, the default) alongside bambu_v1/bambu_v2 (force a specific one). - Bambu's gcode_line request is now built for real via continuum-schema-instance against continuum-schemas' JSON Schema, picking the v1 or v2 schema directory based on the detected protocol version, instead of a hand-written serde struct. - Added rumqttc as a dependency (not wired up yet - the real MQTT v5-then-v3.1.1 fallback connect and the publish/receive plumbing are still stubs).
This commit is contained in:
+95
-15
@@ -2,12 +2,10 @@
|
||||
//! `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 -
|
||||
//! 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.
|
||||
//! Bambu is a single `BambuPrinter` type, not a per-version split: which
|
||||
//! MQTT/BambuMQTT protocol generation (v1 vs v2) a given printer uses is
|
||||
//! something `connect()` discovers (or is told to force), not something
|
||||
//! config picks a *type* for - see `bambu.rs`'s doc comment.
|
||||
//!
|
||||
//! Three pieces, each doing one job an OOP base class would normally do:
|
||||
//!
|
||||
@@ -31,9 +29,10 @@
|
||||
mod bambu;
|
||||
mod bambu_commands;
|
||||
mod klipper;
|
||||
mod marlin_serial;
|
||||
mod prusa;
|
||||
|
||||
pub use bambu::{BambuGenericPrinter, BambuTls, BambuV1Printer, BambuV2Printer};
|
||||
pub use bambu::{BambuPrinter, BambuProtocolPreference, BambuProtocolVersion, BambuTls};
|
||||
pub use bambu_commands::{
|
||||
AmsState, AmsTray, AmsUnit, BambuReport, BambuRequest, CommandEnvelope, EmptyTray, GetVersionRequest,
|
||||
GetVersionResponse, LoadedTray, PrintReport, PushallRequest, XcamSettings,
|
||||
@@ -72,12 +71,34 @@ pub trait GenericPrinter {
|
||||
/// into the multi-threaded daemon - safe to ignore for now.
|
||||
async fn connect(&mut self) -> Result<(), PrinterError>;
|
||||
|
||||
|
||||
async fn publish(&mut self, payload: String) -> Result<(), PrinterError>;
|
||||
async fn receive(&mut self, payload: String) -> 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
|
||||
/// `connect()`.
|
||||
async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError>;
|
||||
|
||||
/// REQUIRED, no default body: takes the *whole file* already in memory
|
||||
/// (`data`), not a path - whatever called this already had to download
|
||||
/// it from the S3 URL the cloud handed over, so there'd be nothing on
|
||||
/// disk to point a path at anyway. Each vendor does something quite
|
||||
/// different with `data`: Bambu/PrusaLink/Klipper each do one bulk
|
||||
/// transfer to storage the printer's own firmware owns; PrusaSerial
|
||||
/// has no such storage at all - see that impl's doc comment.
|
||||
async fn upload_project_file(&mut self, file_name: String, data: Vec<u8>) -> Result<(), PrinterError>;
|
||||
|
||||
/// REQUIRED, no default body: sends exactly *one* gcode line, using
|
||||
/// whatever single-command mechanism this vendor has (an MQTT
|
||||
/// `gcode_line` publish for Bambu, an HTTP command-injection endpoint
|
||||
/// for PrusaLink/Klipper, a raw write over the port for PrusaSerial).
|
||||
/// `upload_gcode` below is the generic driver that calls this once per
|
||||
/// line for a whole file - this method is where each vendor's actual
|
||||
/// difference lives, same division of labor as every other method here.
|
||||
async fn send_gcode_line(&mut self, gcode_line: String) -> 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
|
||||
@@ -100,8 +121,7 @@ fn percent_to_pwm(percent: u8) -> u8 {
|
||||
|
||||
/// The closed set of printer kinds this fleet can talk to.
|
||||
pub enum PrinterHandle {
|
||||
BambuV1(BambuV1Printer),
|
||||
BambuV2(BambuV2Printer),
|
||||
Bambu(BambuPrinter),
|
||||
PrusaLink(PrusaLinkPrinter),
|
||||
PrusaSerial(PrusaSerialPrinter),
|
||||
Klipper(KlipperPrinter),
|
||||
@@ -114,8 +134,7 @@ pub enum PrinterHandle {
|
||||
impl GenericPrinter for PrinterHandle {
|
||||
fn base(&self) -> &PrinterBase {
|
||||
match self {
|
||||
PrinterHandle::BambuV1(p) => p.base(),
|
||||
PrinterHandle::BambuV2(p) => p.base(),
|
||||
PrinterHandle::Bambu(p) => p.base(),
|
||||
PrinterHandle::PrusaLink(p) => p.base(),
|
||||
PrinterHandle::PrusaSerial(p) => p.base(),
|
||||
PrinterHandle::Klipper(p) => p.base(),
|
||||
@@ -124,21 +143,82 @@ impl GenericPrinter for PrinterHandle {
|
||||
|
||||
async fn connect(&mut self) -> Result<(), PrinterError> {
|
||||
match self {
|
||||
PrinterHandle::BambuV1(p) => p.connect().await,
|
||||
PrinterHandle::BambuV2(p) => p.connect().await,
|
||||
PrinterHandle::Bambu(p) => p.connect().await,
|
||||
PrinterHandle::PrusaLink(p) => p.connect().await,
|
||||
PrinterHandle::PrusaSerial(p) => p.connect().await,
|
||||
PrinterHandle::Klipper(p) => p.connect().await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn publish(&mut self, payload: String) -> Result<(), PrinterError> {
|
||||
match self {
|
||||
PrinterHandle::Bambu(p) => p.publish(payload).await,
|
||||
PrinterHandle::PrusaLink(p) => p.publish(payload).await,
|
||||
PrinterHandle::PrusaSerial(p) => p.publish(payload).await,
|
||||
PrinterHandle::Klipper(p) => p.publish(payload).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn receive(&mut self, payload: String) -> Result<(), PrinterError> {
|
||||
match self {
|
||||
PrinterHandle::Bambu(p) => p.receive(payload).await,
|
||||
PrinterHandle::PrusaLink(p) => p.receive(payload).await,
|
||||
PrinterHandle::PrusaSerial(p) => p.receive(payload).await,
|
||||
PrinterHandle::Klipper(p) => p.receive(payload).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError> {
|
||||
match self {
|
||||
PrinterHandle::BambuV1(p) => p.set_fan_speed(percent).await,
|
||||
PrinterHandle::BambuV2(p) => p.set_fan_speed(percent).await,
|
||||
PrinterHandle::Bambu(p) => p.set_fan_speed(percent).await,
|
||||
PrinterHandle::PrusaLink(p) => p.set_fan_speed(percent).await,
|
||||
PrinterHandle::PrusaSerial(p) => p.set_fan_speed(percent).await,
|
||||
PrinterHandle::Klipper(p) => p.set_fan_speed(percent).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn upload_project_file(&mut self, file_name: String, data: Vec<u8>) -> Result<(), PrinterError> {
|
||||
match self {
|
||||
PrinterHandle::Bambu(p) => p.upload_project_file(file_name, data).await,
|
||||
PrinterHandle::PrusaLink(p) => p.upload_project_file(file_name, data).await,
|
||||
PrinterHandle::PrusaSerial(p) => p.upload_project_file(file_name, data).await,
|
||||
PrinterHandle::Klipper(p) => p.upload_project_file(file_name, data).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_gcode_line(&mut self, gcode_line: String) -> Result<(), PrinterError> {
|
||||
match self {
|
||||
PrinterHandle::Bambu(p) => p.send_gcode_line(gcode_line).await,
|
||||
PrinterHandle::PrusaLink(p) => p.send_gcode_line(gcode_line).await,
|
||||
PrinterHandle::PrusaSerial(p) => p.send_gcode_line(gcode_line).await,
|
||||
PrinterHandle::Klipper(p) => p.send_gcode_line(gcode_line).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits `gcode` into lines and sends each one through `printer`'s own
|
||||
/// `send_gcode_line` - works for *any* vendor, not just serial ones, since
|
||||
/// Bambu/PrusaLink/Klipper each have their own single-line injection
|
||||
/// mechanism too (see each `send_gcode_line` impl). They just don't use
|
||||
/// this for `upload_project_file` - they have real file storage to bulk-
|
||||
/// upload to instead, which is a different, faster path than sending a
|
||||
/// whole file one line at a time.
|
||||
///
|
||||
/// `P: GenericPrinter` makes this a genuine Rust generic: one function
|
||||
/// body, compiled separately for whichever concrete printer type you call
|
||||
/// it with - like a TS function typed `<P extends GenericPrinter>`, except
|
||||
/// Rust generates real, separate machine code per type instead of erasing
|
||||
/// the type at compile time. Call it with any of the five printer structs,
|
||||
/// or with `&mut PrinterHandle` itself (it implements `GenericPrinter` too,
|
||||
/// via the delegation above).
|
||||
pub async fn upload_gcode<P: GenericPrinter>(printer: &mut P, gcode: &str) -> Result<(), PrinterError> {
|
||||
for line in gcode.lines() {
|
||||
let line = line.trim();
|
||||
// Blank lines and full-line comments have nothing to send.
|
||||
if line.is_empty() || line.starts_with(';') {
|
||||
continue;
|
||||
}
|
||||
printer.send_gcode_line(line.to_string()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user