fleascope_rs/
serial_terminal.rs

1use serialport::SerialPort;
2use std::collections::VecDeque;
3use std::io::{ErrorKind, Read, Write};
4use std::time::{Duration, Instant};
5
6const PROMPT: &[u8] = b"> ";
7
8#[derive(Debug)]
9pub struct StatelessFleaTerminal {
10    serial: Box<dyn SerialPort>,
11}
12
13pub struct IdleFleaTerminal {
14    inner: StatelessFleaTerminal,
15}
16
17pub struct ConnectionLostError;
18
19#[derive(Debug, thiserror::Error)]
20pub enum FleaTerminalError {
21    #[error("Serial port error: {0}")]
22    SerialPort(#[from] serialport::Error),
23
24    #[error("IO error: {0}")]
25    Io(#[from] std::io::Error),
26
27    #[error("Timeout error: Expected prompt within {timeout:?}.")]
28    Timeout { timeout: Duration },
29
30    #[error("Connection lost while waiting for response")]
31    ConnectionLost,
32}
33
34impl StatelessFleaTerminal {
35    /// Create a new FleaTerminal instance
36    pub fn new(port: &str) -> Result<Self, FleaTerminalError> {
37        #[cfg(feature = "puffin")]
38        puffin::profile_function!();
39
40        let serial = serialport::new(port, 9600)
41            .timeout(Duration::from_millis(70))
42            .open()?;
43
44        let mut terminal = Self { serial };
45
46        terminal.flush()?;
47        Ok(terminal)
48    }
49
50    /// Flush the serial buffer
51    fn flush(&mut self) -> Result<(), FleaTerminalError> {
52        log::debug!("Flushing serial port buffers once");
53        self.serial.clear(serialport::ClearBuffer::All)?;
54        while self.serial.bytes_to_read().unwrap() > 0 {
55            log::debug!("Flushing serial port buffers twice");
56            self.serial.clear(serialport::ClearBuffer::Input)?;
57        }
58        loop {
59            let mut buf = [0u8; 1024];
60            match self.serial.read(&mut buf) {
61                Ok(n) => {
62                    if n == 0 {
63                        break;
64                    } else {
65                        log::debug!("Flushing serial port buffers thrice");
66                    }
67                }
68                Err(e) if e.kind() == ErrorKind::TimedOut => break,
69                Err(e) => return Err(FleaTerminalError::Io(e)),
70            }
71        }
72        Ok(())
73    }
74
75    fn read_chunk(&mut self, response: &mut Vec<u8>) -> Result<bool, ConnectionLostError> {
76        let mut read_buffer = [0u8; 1024]; // Read in chunks
77        #[cfg(feature = "puffin")]
78        puffin::profile_function!();
79        match self.serial.read(&mut read_buffer) {
80            Ok(bytes_read) if bytes_read > 0 => {
81                #[cfg(feature = "puffin")]
82                puffin::profile_scope!("process_chunk_data", format!("{}", bytes_read));
83
84                response.extend_from_slice(&read_buffer[..bytes_read]);
85
86                // Check if we have the prompt at the end
87                if response.len() >= PROMPT.len() {
88                    let potential_prompt = &response[response.len() - PROMPT.len()..];
89                    if potential_prompt == PROMPT {
90                        Ok(true)
91                    } else {
92                        Ok(false)
93                    }
94                } else {
95                    Ok(false)
96                }
97            }
98            Ok(_) => {
99                // No data available right now, but no error
100                Ok(false)
101            }
102            Err(e) if e.kind() == ErrorKind::TimedOut => {
103                // Timeout is expected in non-blocking reads
104                Ok(false)
105            }
106            Err(e) if e.kind() == ErrorKind::BrokenPipe => Err(ConnectionLostError),
107            Err(e) if e.kind() == ErrorKind::UnexpectedEof => Err(ConnectionLostError),
108            Err(e) => {
109                tracing::info!("Serial read error (kind: {:?})...{e}", e.kind());
110                panic!("Serial read error: {}", e);
111            }
112        }
113    }
114
115    fn exec_sync(
116        &mut self,
117        command: &str,
118        timeout: Option<Duration>,
119    ) -> Result<Vec<u8>, FleaTerminalError> {
120        #[cfg(feature = "puffin")]
121        puffin::profile_function!();
122
123        {
124            #[cfg(feature = "puffin")]
125            puffin::profile_scope!("serial_write_command");
126            // Send command
127            let command_with_newline = format!("{}\n", command);
128            self.serial.write_all(command_with_newline.as_bytes())?;
129        }
130
131        // Read response until prompt
132        #[cfg(feature = "puffin")]
133        puffin::profile_scope!("serial_read_response");
134
135        let mut response = Vec::new();
136        let now = Instant::now();
137
138        loop {
139            #[cfg(feature = "puffin")]
140            puffin::profile_scope!("serial_read_chunk sync");
141            match self.read_chunk(&mut response) {
142                Ok(true) => break,
143                Ok(false) => {}
144                Err(ConnectionLostError) => return Err(FleaTerminalError::ConnectionLost),
145            };
146            if let Some(t) = timeout {
147                if now.elapsed() >= t {
148                    return Err(FleaTerminalError::Timeout { timeout: t });
149                }
150            }
151        }
152
153        // Remove the prompt from the end and convert to string
154        let response_without_prompt = &response[..response.len() - PROMPT.len()];
155
156        Ok(response_without_prompt.to_vec())
157    }
158
159    /// Send CTRL-C character
160    pub fn send_ctrl_c(&mut self) -> Result<(), FleaTerminalError> {
161        self.serial.write_all(&[0x03])?;
162        Ok(())
163    }
164
165    /// Send reset command
166    pub fn send_reset(&mut self) -> Result<(), FleaTerminalError> {
167        self.serial.write_all(b"reset\n")?;
168        Ok(())
169    }
170}
171
172impl IdleFleaTerminal {
173    pub fn exec_async(mut self, command: &str) -> BusyFleaTerminal {
174        #[cfg(feature = "puffin")]
175        puffin::profile_function!();
176
177        let command_with_newline = format!("{}\n", command);
178        self.inner
179            .serial
180            .write_all(command_with_newline.as_bytes())
181            .expect("Failed to write command to serial port");
182
183        BusyFleaTerminal {
184            inner: self.inner,
185            response: Vec::new(),
186        }
187    }
188    pub fn exec_sync(&mut self, command: &str, timeout: Option<Duration>) -> Vec<u8> {
189        #[cfg(feature = "puffin")]
190        puffin::profile_function!();
191
192        self.inner
193            .exec_sync(command, timeout)
194            .expect("Failed to execute command")
195    }
196}
197impl TryFrom<StatelessFleaTerminal> for IdleFleaTerminal {
198    type Error = (StatelessFleaTerminal, FleaTerminalError);
199
200    fn try_from(mut value: StatelessFleaTerminal) -> Result<Self, Self::Error> {
201        #[cfg(feature = "puffin")]
202        puffin::profile_function!();
203
204        log::debug!("Connected to FleaScope. Sending CTRL-C to reset.");
205        match value.send_ctrl_c() {
206            Ok(_) => {}
207            Err(e) => return Err((value, e)),
208        };
209        if let Err(e) = value.flush() {
210            return Err((value, e));
211        };
212
213        log::debug!("Turning on prompt");
214        if let Err(e) = value.exec_sync("prompt on", Some(Duration::from_secs(1))) {
215            return Err((value, e));
216        };
217
218        if let Err(e) = value.flush() {
219            return Err((value, e));
220        };
221        Ok(IdleFleaTerminal { inner: value })
222    }
223}
224
225pub struct BusyFleaTerminal {
226    inner: StatelessFleaTerminal,
227    response: Vec<u8>,
228}
229
230impl BusyFleaTerminal {
231    pub fn cancel(mut self) -> IdleFleaTerminal {
232        self.inner.send_ctrl_c().expect("Failed to send CTRL-C");
233        const PROMPT_LEN: usize = PROMPT.len();
234        const BUFFER_LEN: usize = 1024;
235        let mut prompt_buffer = VecDeque::with_capacity(PROMPT_LEN);
236        let mut read_buffer = [0u8; BUFFER_LEN];
237        loop {
238            match self.inner.serial.read(&mut read_buffer) {
239                Ok(bytes_read) if bytes_read >= PROMPT_LEN => {
240                    prompt_buffer =
241                        VecDeque::from(read_buffer[bytes_read - PROMPT_LEN..bytes_read].to_vec());
242                }
243                Ok(bytes_read) if bytes_read > 0 => {
244                    for _i in 0..bytes_read {
245                        prompt_buffer.pop_front();
246                    }
247                    prompt_buffer.extend(&read_buffer[..bytes_read]);
248                }
249                Ok(_) => continue, // No data available right now, but no error
250                Err(e) if e.kind() == ErrorKind::TimedOut => continue, // Timeout is expected in non-blocking reads
251                Err(e) => panic!("Serial read error: {}", e),
252            }
253            // Check if we have the prompt at the end
254            if prompt_buffer.len() == PROMPT.len()
255                && prompt_buffer.iter().copied().eq(PROMPT.iter().copied())
256            {
257                break;
258            }
259        }
260        self.inner.flush().expect("Failed to flush serial port");
261        IdleFleaTerminal { inner: self.inner }
262    }
263
264    fn into_result(self) -> (Vec<u8>, IdleFleaTerminal) {
265        #[cfg(feature = "puffin")]
266        puffin::profile_function!();
267
268        // Remove the prompt from the end and convert to string
269        let response_without_prompt = &self.response[..self.response.len() - PROMPT.len()];
270        let response_str = response_without_prompt.to_vec();
271
272        (response_str, IdleFleaTerminal { inner: self.inner })
273    }
274
275    pub fn try_get_result(
276        mut self,
277    ) -> Result<Result<(Vec<u8>, IdleFleaTerminal), BusyFleaTerminal>, ConnectionLostError> {
278        #[cfg(feature = "puffin")]
279        puffin::profile_function!();
280
281        // There are 24000 bytes tranferred right now which takes 24ms at 1 MB/s
282        // Capturing takes about 7ms, transfer around 30ms
283        // Sleeping here leads to larger chunks, but there isn't really a benefit
284        // Timing: capture time,
285        //     7ms whatever on device (increasing with capture time),
286        //     30ms transfer
287        // Possible improvements:
288        // - Fix whatever takes so long on the device. Should be faster than 7ms
289        // - Fix whatever takes increasing amount of time on the device
290        // - Improve transfer speed by • encoding as bytes, • drop digital channels?
291        // - Live sending of data. Seems like data is way faster than data transfer
292
293        match self.inner.read_chunk(&mut self.response) {
294            Ok(true) => Ok(Ok(self.into_result())),
295            Ok(false) => Ok(Err(self)),
296            Err(ConnectionLostError) => Err(ConnectionLostError),
297        }
298    }
299}
300
301impl Read for BusyFleaTerminal {
302    fn read(&mut self, buffer: &mut [u8]) -> Result<usize, std::io::Error> {
303        #[cfg(feature = "puffin")]
304        puffin::profile_function!();
305
306        self.inner.serial.read(buffer)
307    }
308}