1use std::time::Duration;
10
11use super::control::ControlError;
12use crate::client::LanMqttClient;
13use crate::config::ResolvedTarget;
14use crate::core::command::Command as ProtoCommand;
15use crate::core::project::PlateInspection;
16use crate::core::session::CommandOutcome;
17use crate::core::start::{self, PrintStartParams};
18
19pub struct StartRequest {
21 pub file: String,
22 pub plate: u32,
23 pub use_ams: bool,
24 pub ams_map: Vec<i32>,
25 pub bed_type: String,
26 pub timelapse: bool,
31 pub inspection: Option<PlateInspection>,
35}
36
37impl StartRequest {
38 pub fn to_command(&self) -> ProtoCommand {
42 let params = PrintStartParams {
43 file: self.file.clone(),
44 plate: self.plate,
45 use_ams: self.use_ams,
46 ams_map: self.ams_map.clone(),
47 bed_type: self.bed_type.clone(),
48 timelapse: self.timelapse,
49 };
50 start::build_command(¶ms, self.inspection.as_ref())
51 }
52}
53
54pub trait Starter: Send + Sync {
56 fn start(&self, req: &StartRequest) -> Result<CommandOutcome, ControlError>;
57}
58
59pub struct LiveStarter {
61 target: ResolvedTarget,
62}
63
64impl LiveStarter {
65 pub fn new(target: ResolvedTarget) -> Self {
66 Self { target }
67 }
68}
69
70impl Starter for LiveStarter {
71 fn start(&self, req: &StartRequest) -> Result<CommandOutcome, ControlError> {
72 LanMqttClient::new(self.target.clone())
73 .with_timeout(Duration::from_secs(30))
74 .send_and_verify(&req.to_command())
75 .map_err(|e| ControlError::Transport(e.to_string()))
76 }
77}
78
79pub struct FakeStarter;
81
82impl Starter for FakeStarter {
83 fn start(&self, _req: &StartRequest) -> Result<CommandOutcome, ControlError> {
84 Ok(CommandOutcome::Verified)
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn three_mf_builds_a_project_file_with_ftp_url_and_ams() {
94 let req = StartRequest {
95 file: "/cache/coin.gcode.3mf".to_string(),
96 plate: 2,
97 use_ams: true,
98 ams_map: vec![0, 3],
99 bed_type: "auto".to_string(),
100 timelapse: false,
101 inspection: None,
102 };
103 match req.to_command() {
104 ProtoCommand::ProjectFile(pf) => {
105 assert_eq!(pf.url, "ftp:///cache/coin.gcode.3mf");
106 assert_eq!(pf.plate, 2);
107 assert_eq!(pf.subtask_name, "coin.gcode.3mf");
108 assert!(pf.use_ams);
109 assert_eq!(pf.ams_mapping, vec![0, 3]);
110 assert!(!pf.timelapse, "timelapse off unless requested");
111 }
112 other => panic!("expected ProjectFile, got {other:?}"),
113 }
114 }
115
116 #[test]
117 fn timelapse_flag_flows_into_the_project_file() {
118 let req = StartRequest {
119 file: "/cube.gcode.3mf".to_string(),
120 plate: 1,
121 use_ams: false,
122 ams_map: vec![],
123 bed_type: "auto".to_string(),
124 timelapse: true,
125 inspection: None,
126 };
127 match req.to_command() {
128 ProtoCommand::ProjectFile(pf) => {
129 assert!(pf.timelapse, "arms the sliced timelapse gcode")
130 }
131 other => panic!("expected ProjectFile, got {other:?}"),
132 }
133 }
134
135 #[test]
136 fn raw_gcode_builds_a_gcode_file() {
137 let req = StartRequest {
138 file: "/test.gcode".to_string(),
139 plate: 1,
140 use_ams: false,
141 ams_map: vec![],
142 bed_type: "auto".to_string(),
143 timelapse: false,
144 inspection: None,
145 };
146 assert!(matches!(req.to_command(), ProtoCommand::GcodeFile(f) if f == "/test.gcode"));
147 }
148}