use std::time::Duration;
use crate::client::LanMqttClient;
use crate::config::ResolvedTarget;
use crate::core::command::{AmsControl, Command as ProtoCommand, LedNode, SpeedLevel};
use crate::core::session::{CommandOutcome, VerifyStage};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HomeAxes {
All,
X,
Y,
Z,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Axis {
X,
Y,
Z,
}
impl Axis {
pub fn as_str(self) -> &'static str {
match self {
Axis::X => "X",
Axis::Y => "Y",
Axis::Z => "Z",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TempPart {
Nozzle,
Bed,
}
pub fn temp_line(part: TempPart, celsius: u32) -> String {
match part {
TempPart::Nozzle => format!("M104 S{celsius}"),
TempPart::Bed => format!("M140 S{celsius}"),
}
}
#[derive(Debug, Clone)]
pub enum ControlAction {
Pause,
Resume,
Stop,
ClearError,
Light {
node: LedNode,
on: bool,
},
Speed(SpeedLevel),
Gcode(String),
Home(HomeAxes),
Move {
axis: Axis,
delta: f64,
feedrate: u32,
},
Extrude {
delta: f64,
feedrate: u32,
},
SetTemp {
part: TempPart,
celsius: u32,
},
Calibrate {
bed_level: bool,
vibration: bool,
motor_noise: bool,
},
Ams(AmsControl),
AmsChange {
target: u32,
curr_temp: i64,
tar_temp: i64,
},
Reboot,
DisableSteppers,
}
impl ControlAction {
fn into_command(self) -> ProtoCommand {
match self {
ControlAction::Pause => ProtoCommand::Pause,
ControlAction::Resume => ProtoCommand::Resume,
ControlAction::Stop => ProtoCommand::Stop,
ControlAction::ClearError => ProtoCommand::CleanPrintError,
ControlAction::Light { node, on } => ProtoCommand::Led { node, on },
ControlAction::Speed(level) => ProtoCommand::PrintSpeed(level),
ControlAction::Gcode(line) => ProtoCommand::GcodeLine(line),
ControlAction::Home(axes) => ProtoCommand::GcodeLine(
match axes {
HomeAxes::All => "G28",
HomeAxes::X => "G28 X",
HomeAxes::Y => "G28 Y",
HomeAxes::Z => "G28 Z",
}
.to_string(),
),
ControlAction::Move {
axis,
delta,
feedrate,
} => ProtoCommand::GcodeLine(format!(
"G91\nG1 {}{delta} F{feedrate}\nG90",
axis.as_str()
)),
ControlAction::Extrude { delta, feedrate } => {
ProtoCommand::GcodeLine(format!("M83\nG1 E{delta} F{feedrate}\nM82"))
}
ControlAction::SetTemp { part, celsius } => {
ProtoCommand::GcodeLine(temp_line(part, celsius))
}
ControlAction::Calibrate {
bed_level,
vibration,
motor_noise,
} => ProtoCommand::Calibration {
bed_level,
vibration,
motor_noise,
},
ControlAction::Ams(action) => ProtoCommand::AmsControl(action),
ControlAction::AmsChange {
target,
curr_temp,
tar_temp,
} => ProtoCommand::AmsChangeFilament {
target,
curr_temp,
tar_temp,
},
ControlAction::Reboot => ProtoCommand::Reboot,
ControlAction::DisableSteppers => ProtoCommand::GcodeLine("M84".to_string()),
}
}
fn needs_verify(&self) -> bool {
!matches!(self, ControlAction::Reboot)
}
}
#[derive(Debug)]
pub enum ControlError {
Transport(String),
}
pub type ControlResult = Result<CommandOutcome, ControlError>;
pub trait Controller: Send + Sync {
fn execute(&self, action: ControlAction) -> ControlResult;
}
pub struct LiveController {
target: ResolvedTarget,
timeout: Duration,
}
impl LiveController {
pub fn new(target: ResolvedTarget) -> Self {
Self {
target,
timeout: Duration::from_secs(15),
}
}
}
impl Controller for LiveController {
fn execute(&self, action: ControlAction) -> ControlResult {
let client = LanMqttClient::new(self.target.clone()).with_timeout(self.timeout);
let needs_verify = action.needs_verify();
let cmd = action.into_command();
if needs_verify {
client
.send_and_verify(&cmd)
.map_err(|e| ControlError::Transport(e.to_string()))
} else {
client
.send_fire(&cmd)
.map(|()| CommandOutcome::Unverified {
stage: VerifyStage::Ack,
})
.map_err(|e| ControlError::Transport(e.to_string()))
}
}
}
pub struct FakeController {
outcome: Result<CommandOutcome, String>,
}
impl FakeController {
pub fn verified() -> Self {
Self {
outcome: Ok(CommandOutcome::Verified),
}
}
#[cfg(test)]
pub fn returning(outcome: CommandOutcome) -> Self {
Self {
outcome: Ok(outcome),
}
}
#[cfg(test)]
pub fn failing() -> Self {
Self {
outcome: Err("fake transport failure".to_string()),
}
}
}
impl Controller for FakeController {
fn execute(&self, _action: ControlAction) -> ControlResult {
match &self.outcome {
Ok(o) => Ok(o.clone()),
Err(e) => Err(ControlError::Transport(e.clone())),
}
}
}