gcode_serial/
gcode_serial.rs

1use crate::models::action::{Action, Command, PrinterAction, PrinterStatus, TelemetryData};
2use crate::models::file::{FinishedPrint, GcodeFile};
3use crate::models::serial_connector::SerialConnector;
4use crate::serial::serial::Serial;
5use event_listener::Event;
6use lazy_static::lazy_static;
7use log::debug;
8use regex::Regex;
9use std::collections::VecDeque;
10use std::fs::File;
11use std::io::{BufRead, BufReader};
12use std::sync::{Arc, Mutex};
13use std::time::{SystemTime, UNIX_EPOCH};
14use tokio::sync::broadcast::Sender;
15
16lazy_static! {
17    static ref RE_MAX_Z_POS: Regex = Regex::new(r";\s*max_layer_z\s*=\s*([\d.]+)").unwrap();
18}
19
20pub struct GcodeSerial {
21    tx: Sender<Action>,
22    que: Arc<Mutex<VecDeque<String>>>,
23    event: Arc<Mutex<Event>>,
24    active_file: Option<GcodeFile>,
25}
26
27impl GcodeSerial {
28    pub fn new(tx: Sender<Action>) -> Self {
29        let q = Arc::new(Mutex::new(VecDeque::new()));
30        let event = Arc::new(Mutex::new(Event::new()));
31        GcodeSerial {
32            tx: tx.clone(),
33            que: q,
34            event,
35            active_file: None,
36        }
37    }
38
39    /// connect to printer and initialize lib
40    pub async fn start(&mut self, serial_connector: SerialConnector) {
41        let mut rx = self.tx.subscribe();
42
43        let que = self.que.clone();
44        let event = self.event.clone();
45        let tx = self.tx.clone();
46        tokio::spawn(async move {
47            let mut serial = Serial::new(tx, serial_connector, que, event).await;
48            serial.start_temp_interval();
49            serial.start_event_loop().await;
50        });
51
52        while let Ok(v) = rx.recv().await {
53            match v {
54                Action::Telemetry(_) => {}
55                Action::StateChange(s) => {
56                    match s {
57                        PrinterStatus::Disconnected => {}
58                        PrinterStatus::Active => {}
59                        PrinterStatus::Idle => {
60                            if self.active_file.is_some() {
61                                let f = self.active_file.clone().unwrap();
62
63                                let _ = self.tx.send(Action::Telemetry(
64                                    TelemetryData::PrintFinished(FinishedPrint {
65                                        name: f.name,
66                                        size: f.size,
67                                        last_modified: f.last_modified,
68                                        start_time: f.start_time,
69                                        finish_time: SystemTime::now()
70                                            .duration_since(UNIX_EPOCH)
71                                            .unwrap()
72                                            .as_millis(),
73                                    }),
74                                ));
75                                self.active_file = None;
76                            }
77                        }
78                        PrinterStatus::Errored => {}
79                    }
80                    debug!("Printer State change: {}", s);
81                }
82                Action::PrinterAction(a) => match a {
83                    PrinterAction::Cancel => {
84                        self.stop_print();
85                    }
86                    PrinterAction::Pause => {
87                        todo!()
88                    }
89                    PrinterAction::Resume => {
90                        todo!()
91                    }
92                },
93                Action::Command(c) => match c {
94                    Command::SetTemps(b, c) => {
95                        self.set_temps(b, c);
96                    }
97                    Command::StartPrint(n) => {
98                        self.start_print(n);
99                    }
100                    Command::StopPrint => {
101                        self.stop_print();
102                    }
103                },
104            }
105        }
106    }
107
108    /// set target temperatures of bed and extruder
109    pub fn set_temps(&mut self, bed_temp: u16, extruder_temp: u16) {
110        self.que
111            .lock()
112            .unwrap()
113            .push_back(format!("M140 S{}", bed_temp));
114        self.que
115            .lock()
116            .unwrap()
117            .push_back(format!("M104 S{}", extruder_temp));
118    }
119
120    /// start a new print of given gcode file path
121    /// won't start file if event que size > 10
122    pub fn start_print(&mut self, file_path: String) {
123        // if we have a large que we don't do anything
124        if self.que.lock().unwrap().len() > 10 {
125            return;
126        }
127
128        let file = File::open(file_path.to_string()).unwrap();
129
130        let unix_timestamp = file
131            .metadata()
132            .unwrap()
133            .modified()
134            .unwrap()
135            .duration_since(UNIX_EPOCH)
136            .unwrap()
137            .as_millis();
138        let size = file.metadata().unwrap().len();
139
140        let reader = BufReader::new(file);
141
142        let active_file = GcodeFile {
143            name: file_path,
144            size,
145            last_modified: unix_timestamp,
146            start_time: SystemTime::now()
147                .duration_since(UNIX_EPOCH)
148                .unwrap()
149                .as_millis(),
150        };
151        self.active_file = Some(active_file.clone());
152
153        let _ = self
154            .tx
155            .send(Action::Telemetry(TelemetryData::ActiveFileChange(Some(
156                active_file,
157            ))));
158
159        for line in reader.lines() {
160            let mut command = line.unwrap();
161
162            // match for max_layer_z commet to get max layyer height - might be prusaslicer only!
163            match RE_MAX_Z_POS.captures(command.as_str()) {
164                None => {}
165                Some(c) => {
166                    let h1: f32 = c
167                        .get(1)
168                        .map_or("0.0", |m| m.as_str())
169                        .parse()
170                        .unwrap_or(0.0);
171                    let _ = self
172                        .tx
173                        .send(Action::Telemetry(TelemetryData::MaxZHeight(h1)));
174                }
175            }
176
177            // if line starts with ; or is empty we skip it
178            if command.trim().starts_with(";") || command.trim().is_empty() {
179                continue;
180            }
181
182            // we remove comments and take the gcode cmd only
183            if command.trim().contains(";") {
184                command = command.trim().split(";").collect::<Vec<&str>>()[0].to_string();
185            }
186
187            self.que.lock().unwrap().push_back(command);
188        }
189
190        let _ = self
191            .tx
192            .send(Action::Telemetry(TelemetryData::TotalCommandCount(
193                self.que.lock().unwrap().len() as u32,
194            )));
195        let _ = self.tx.send(Action::StateChange(PrinterStatus::Active));
196        self.event.lock().unwrap().notify(42);
197    }
198
199    /// stop the active print and add the stop gcode to que
200    pub fn stop_print(&self) {
201        let mut que = self.que.lock().unwrap();
202        que.clear();
203        // run end procedure gcodes
204        que.push_back("G1 X0 Y200 F3600".to_string()); // park
205        que.push_back("G4".to_string()); // wait
206        que.push_back("M221 S100".to_string()); // reset flow
207        que.push_back("M900 K0".to_string()); // reset LA
208        que.push_back("M104 S0".to_string()); // turn off temperature
209        que.push_back("M140 S0".to_string()); // turn off heatbed
210        que.push_back("M107".to_string()); // turn off fan
211        que.push_back("M84".to_string()); // disable motors
212                                          // que.push_back("M603".to_string()); // prusa specific gcode-endprint
213        let _ = self
214            .tx
215            .send(Action::Telemetry(TelemetryData::TotalCommandCount(0)));
216        let _ = self.tx.send(Action::StateChange(PrinterStatus::Idle));
217        self.event.lock().unwrap().notify(42);
218    }
219}