mod gpio; mod serial; use thiserror::Error; use tracing::{info, warn}; #[derive(Debug, Error)] pub enum PlateChangerError { #[error("plate changer hardware not responding")] NotResponding, #[error("sensor validation failed: {0}")] SensorMismatch(String), #[error("serial transport error: {0}")] Serial(#[from] serial::SerialError), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CycleOutcome { Success, Retried, } /// Drives a mechanical plate-swap cycle: signal the changer over serial, /// wait for it to report completion, then cross-check the physical sensors /// (plate-present, bed-clear) before releasing the print queue to continue. pub struct PlateChanger { port: serial::SerialPort, } impl PlateChanger { pub fn open(path: &str, baud: u32) -> Result { Ok(Self { port: serial::SerialPort::open(path, baud)?, }) } pub async fn run_cycle(&mut self, slot: u8) -> Result { info!(slot, "starting plate change cycle"); self.port.send_command(&serial::Command::Eject).await?; self.port.await_ack(std::time::Duration::from_secs(30)).await?; self.port.send_command(&serial::Command::LoadSlot(slot)).await?; self.port.await_ack(std::time::Duration::from_secs(30)).await?; match self.validate_sensors().await { Ok(()) => Ok(CycleOutcome::Success), Err(err) => { warn!(?err, slot, "sensor validation failed, retrying cycle once"); self.port.send_command(&serial::Command::LoadSlot(slot)).await?; self.port.await_ack(std::time::Duration::from_secs(30)).await?; self.validate_sensors().await?; Ok(CycleOutcome::Retried) } } } async fn validate_sensors(&mut self) -> Result<(), PlateChangerError> { let state = gpio::read_sensor_state().await; if !state.plate_present { return Err(PlateChangerError::SensorMismatch("plate not detected on bed".into())); } if !state.bed_clear { return Err(PlateChangerError::SensorMismatch("bed obstruction detected".into())); } Ok(()) } }