Add GenericPrinter::set_fan_speed() across all five printer kinds
Same override pattern as connect(): required, no default body, five completely different bodies. - BambuGenericPrinter::set_fan_speed_impl() is shared by BambuV1Printer and BambuV2Printer (composition reuse, same as connect_impl) - both speak the same MQTT gcode-injection mechanism. - PrusaLinkPrinter and PrusaSerialPrinter both send the same M106 gcode but over different transports (HTTP command injection vs. raw serial bytes) - they share the percent->PWM conversion despite having unrelated connect() implementations. - KlipperPrinter uses a hypothetical Moonraker-native endpoint that takes a percentage directly - no PWM conversion at all, since that math only applies to the gcode-speaking vendors. percent_to_pwm() lives in mod.rs as a private free function rather than a trait default: it's genuine shared logic, but only for the subset of vendors that need it, which is exactly the case a trait default can't express cleanly. Visible to bambu.rs/prusa.rs via super:: because Rust's module privacy reaches into child modules. Verified with cargo check --all-targets and a full run of printer_polymorphism: 50% converges on the same PWM value (127) across all three gcode-based printers, Klipper's native path takes 0.50 directly.
This commit is contained in:
@@ -62,4 +62,13 @@ async fn main() {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A second trait method, same uniform call site as connect(): every
|
||||||
|
// 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.
|
||||||
|
println!();
|
||||||
|
for printer in &mut fleet {
|
||||||
|
let _ = printer.set_fan_speed(50).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,6 +252,17 @@ impl BambuGenericPrinter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<pwm>"}}` to
|
||||||
|
/// `device/{sn}/request` over the MQTTS session.
|
||||||
|
async fn set_fan_speed_impl(&mut self, percent: u8) -> Result<(), PrinterError> {
|
||||||
|
let pwm = super::percent_to_pwm(percent);
|
||||||
|
println!("[{}] sending gcode: M106 P1 S{pwm}", self.base.name);
|
||||||
|
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
|
/// version would open the MQTTS session here. Still a stub, but no
|
||||||
/// longer duplicated across two structs.
|
/// longer duplicated across two structs.
|
||||||
@@ -302,6 +313,10 @@ impl GenericPrinter for BambuV1Printer {
|
|||||||
async fn connect(&mut self) -> Result<(), PrinterError> {
|
async fn connect(&mut self) -> Result<(), PrinterError> {
|
||||||
self.generic.connect_impl().await
|
self.generic.connect_impl().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError> {
|
||||||
|
self.generic.set_fan_speed_impl(percent).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current-generation Bambu MQTT report schema (X1 Carbon/X1E, H2 series).
|
/// Current-generation Bambu MQTT report schema (X1 Carbon/X1E, H2 series).
|
||||||
@@ -341,4 +356,8 @@ impl GenericPrinter for BambuV2Printer {
|
|||||||
async fn connect(&mut self) -> Result<(), PrinterError> {
|
async fn connect(&mut self) -> Result<(), PrinterError> {
|
||||||
self.generic.connect_impl().await
|
self.generic.connect_impl().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError> {
|
||||||
|
self.generic.set_fan_speed_impl(percent).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,4 +28,13 @@ impl GenericPrinter for KlipperPrinter {
|
|||||||
println!("[{}] connecting over WebSocket (Moonraker)", self.base.name);
|
println!("[{}] connecting over WebSocket (Moonraker)", self.base.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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`.
|
||||||
|
async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError> {
|
||||||
|
println!("[{}] Moonraker: SET_FAN_SPEED SPEED={:.2}", self.base.name, percent as f32 / 100.0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-6
@@ -67,6 +67,12 @@ pub trait GenericPrinter {
|
|||||||
/// 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>;
|
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
|
||||||
|
/// `connect()`.
|
||||||
|
async fn set_fan_speed(&mut self, percent: u8) -> Result<(), PrinterError>;
|
||||||
|
|
||||||
/// OPTIONAL: a default body every printer gets for free unless it
|
/// OPTIONAL: a default body every printer gets for free unless it
|
||||||
/// writes its own. None of ours do below, so all three vendors share
|
/// 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
|
||||||
@@ -76,14 +82,17 @@ pub trait GenericPrinter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum PrinterFlavour {
|
/// M106's `S` parameter is a raw 0-255 PWM value, not a percentage. Shared
|
||||||
BambuV1Printer,
|
/// by anything that ultimately sends gcode (Bambu, PrusaLink, PrusaSerial)
|
||||||
BambuV2Printer,
|
/// — *not* a trait default, since Klipper's hypothetical native fan API
|
||||||
PrusaLinkPrinter,
|
/// below takes a percent directly and has no use for this at all. Private,
|
||||||
PrusaSerialPrinter,
|
/// but still visible to bambu.rs/prusa.rs: Rust's module privacy reaches
|
||||||
KlipperPrinter,
|
/// into child modules, not just the exact file it's defined in.
|
||||||
|
fn percent_to_pwm(percent: u8) -> u8 {
|
||||||
|
((percent.min(100) as u16 * 255) / 100) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The closed set of printer kinds this fleet can talk to.
|
/// The closed set of printer kinds this fleet can talk to.
|
||||||
pub enum PrinterHandle {
|
pub enum PrinterHandle {
|
||||||
BambuV1(BambuV1Printer),
|
BambuV1(BambuV1Printer),
|
||||||
@@ -117,4 +126,14 @@ impl GenericPrinter for PrinterHandle {
|
|||||||
PrinterHandle::Klipper(p) => p.connect().await,
|
PrinterHandle::Klipper(p) => p.connect().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::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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ impl GenericPrinter for PrusaLinkPrinter {
|
|||||||
println!("[{}] connecting over HTTP (PrusaLink)", self.base.name);
|
println!("[{}] connecting over HTTP (PrusaLink)", self.base.name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
let pwm = super::percent_to_pwm(percent);
|
||||||
|
println!("[{}] POST /api/printer/command {{\"commands\":[\"M106 S{pwm}\"]}}", self.base.name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -60,4 +69,13 @@ impl GenericPrinter for PrusaSerialPrinter {
|
|||||||
println!("[{}] connecting over serial (COM{})", self.base.name, self.com_port);
|
println!("[{}] connecting over serial (COM{})", self.base.name, self.com_port);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user