1use crate::{
5 configs::get_config,
6 create_recursive,
7 debug::run_debug_output,
8 map_miette,
9 screen_buffer::UICommand,
10 serial_actor::{
11 SerialActor, SerialEvent, SerialMessage,
12 tasks::{run_file_output, run_stdin_input, run_stdout_output},
13 },
14};
15use crossterm::{
16 cursor, event, execute,
17 style::Stylize,
18 terminal::{self, ClearType},
19};
20use miette::{Context, IntoDiagnostic};
21use serial2_tokio::SerialPort;
22use std::{
23 io::{self, Write},
24 path::PathBuf,
25};
26
27pub async fn interactive_session(
29 connection: SerialPort,
30 file: Option<String>,
31 debug: bool,
32 port_name: &str,
33) -> miette::Result<()> {
34 let mut stdout = io::stdout();
36 terminal::enable_raw_mode()
37 .into_diagnostic()
38 .wrap_err("Failed to enable raw mode.".red())?;
39 execute!(
40 stdout,
41 terminal::EnterAlternateScreen,
42 terminal::SetTitle(port_name),
43 terminal::Clear(ClearType::All),
44 event::EnableBracketedPaste,
45 event::EnableMouseCapture,
46 cursor::MoveTo(0, 0)
47 )
48 .into_diagnostic()
49 .wrap_err("Failed to setup the terminal.".red())?;
50
51 let (command_tx, command_rx) = tokio::sync::mpsc::channel::<SerialMessage>(100);
53 let (ui_tx, ui_rx) = tokio::sync::mpsc::channel::<UICommand>(100);
54 let (broadcast_event_tx, _) = tokio::sync::broadcast::channel::<SerialEvent>(128);
55 let stdout_rx = broadcast_event_tx.subscribe();
56
57 let mut tasks = tokio::task::JoinSet::new();
59
60 if let Some(path) = file {
61 let config = get_config();
62 let default_out_dir = PathBuf::from(&config.defaults.out_dir);
63 let input_path = PathBuf::from(path);
64
65 let file_path = if input_path.is_absolute() {
66 let parent = input_path.parent().unwrap_or(&default_out_dir);
67 create_recursive!(parent);
68 input_path
69 } else {
70 let joined_path = default_out_dir.join(input_path);
71 let parent_path = joined_path.parent().expect("Does not have root");
72 create_recursive!(parent_path);
73 joined_path
74 };
75
76 let file_rx = broadcast_event_tx.subscribe();
77 tasks.spawn(run_file_output(file_rx, file_path));
78 }
79
80 if debug {
81 let debug_rx = broadcast_event_tx.subscribe();
82 tasks.spawn(run_debug_output(debug_rx));
83 }
84
85 let actor = SerialActor::new(connection, command_rx, broadcast_event_tx);
86 tasks.spawn(actor.run());
87
88 tasks.spawn(run_stdout_output(stdout_rx, ui_rx));
89 tasks.spawn(run_stdin_input(command_tx, ui_tx));
90
91 tasks.join_all().await;
92 ensure_terminal_cleanup(stdout);
93 Ok(())
94}
95
96pub fn open_connection(baud: u32, port: &str) -> miette::Result<SerialPort> {
100 let settings = |mut s: serial2_tokio::Settings| -> std::io::Result<serial2_tokio::Settings> {
101 s.set_raw();
102 s.set_baud_rate(baud)?;
103 s.set_char_size(serial2_tokio::CharSize::Bits8);
104 s.set_stop_bits(serial2_tokio::StopBits::One);
105 s.set_parity(serial2_tokio::Parity::None);
106 s.set_flow_control(serial2_tokio::FlowControl::None);
107 Ok(s)
108 };
109 let con = map_miette!(
110 SerialPort::open(port, settings),
111 format!("Failed to open port '{}'", port),
112 format!(
113 "{} {} [OPTIONS] [PORT] [COMMAND]",
114 "USAGE:".bold().underlined(),
115 "sericom".bold()
116 ),
117 help = format!(
118 "To see available ports, try `{}`.",
119 "sericom list-ports".bold().cyan()
120 )
121 )?;
122 Ok(con)
123}
124
125pub fn get_settings(baud: u32, port: &str) -> miette::Result<()> {
127 let mut stdout = io::stdout();
129 let con = open_connection(baud, port)?;
130 let settings = map_miette!(
131 con.get_configuration(),
132 format!("Failed to get settings for port '{}'", port),
133 format!(
134 "{} {} [OPTIONS] {} <PORT>",
135 "USAGE:".bold().underlined(),
136 "sericom list-settings".bold(),
137 "--port".bold()
138 )
139 )?;
140 let b = map_miette!(
141 settings.get_baud_rate(),
142 format!("Failed to get the baud rate for port '{}'", port),
143 format!(
144 "{} {} [OPTIONS] {} <PORT>",
145 "USAGE:".bold().underlined(),
146 "sericom list-settings".bold(),
147 "--port".bold()
148 )
149 )?;
150 let c = map_miette!(
151 settings.get_char_size(),
152 format!("Failed to get the char size for port '{}'", port),
153 format!(
154 "{} {} [OPTIONS] {} <PORT>",
155 "USAGE:".bold().underlined(),
156 "sericom list-settings".bold(),
157 "--port".bold()
158 )
159 )?;
160 let s = map_miette!(
161 settings.get_stop_bits(),
162 format!("Failed to get stop bits for port '{}'", port),
163 format!(
164 "{} {} [OPTIONS] {} <PORT>",
165 "USAGE:".bold().underlined(),
166 "sericom list-settings".bold(),
167 "--port".bold()
168 )
169 )?;
170 let p = map_miette!(
171 settings.get_parity(),
172 format!("Failed to get parity for port '{}'", port),
173 format!(
174 "{} {} [OPTIONS] {} <PORT>",
175 "USAGE:".bold().underlined(),
176 "sericom list-settings".bold(),
177 "--port".bold()
178 )
179 )?;
180 let f = map_miette!(
181 settings.get_flow_control(),
182 format!("Failed to get flow control for port '{}'", port),
183 format!(
184 "{} {} [OPTIONS] {} <PORT>",
185 "USAGE:".bold().underlined(),
186 "sericom list-settings".bold(),
187 "--port".bold()
188 )
189 )?;
190
191 let cts = map_miette!(
192 con.read_cts(),
193 format!("Failed to read CTS for port '{}'", port),
194 format!(
195 "{} {} [OPTIONS] {} <PORT>",
196 "USAGE:".bold().underlined(),
197 "sericom list-settings".bold(),
198 "--port".bold()
199 )
200 )?;
201 let dsr = map_miette!(
202 con.read_dsr(),
203 format!("Failed to read DSR for port '{}'", port),
204 format!(
205 "{} {} [OPTIONS] {} <PORT>",
206 "USAGE:".bold().underlined(),
207 "sericom list-settings".bold(),
208 "--port".bold()
209 )
210 )?;
211 let ri = map_miette!(
212 con.read_ri(),
213 format!("Failed to read RI for port '{}'", port),
214 format!(
215 "{} {} [OPTIONS] {} <PORT>",
216 "USAGE:".bold().underlined(),
217 "sericom list-settings".bold(),
218 "--port".bold()
219 )
220 )?;
221 let cd = map_miette!(
222 con.read_cd(),
223 format!("Failed to read CD for port '{}'", port),
224 format!(
225 "{} {} [OPTIONS] {} <PORT>",
226 "USAGE:".bold().underlined(),
227 "sericom list-settings".bold(),
228 "--port".bold()
229 )
230 )?;
231
232 write!(stdout, "Baud rate: {b}\r\n")
233 .into_diagnostic()
234 .wrap_err("Failed to write to stdout.".red())?;
235 write!(stdout, "Char size: {c}\r\n")
236 .into_diagnostic()
237 .wrap_err("Failed to write to stdout.".red())?;
238 write!(stdout, "Stop bits: {s}\r\n")
239 .into_diagnostic()
240 .wrap_err("Failed to write to stdout.".red())?;
241 write!(stdout, "Parity mechanism: {p}\r\n")
242 .into_diagnostic()
243 .wrap_err("Failed to write to stdout.".red())?;
244 write!(stdout, "Flow control: {f}\r\n")
245 .into_diagnostic()
246 .wrap_err("Failed to write to stdout.".red())?;
247 write!(stdout, "Clear To Send line: {cts}\r\n")
248 .into_diagnostic()
249 .wrap_err("Failed to write to stdout.".red())?;
250 write!(stdout, "Data Set Ready line: {dsr}\r\n")
251 .into_diagnostic()
252 .wrap_err("Failed to write to stdout.".red())?;
253 write!(stdout, "Ring Indicator line: {ri}\r\n")
254 .into_diagnostic()
255 .wrap_err("Failed to write to stdout.".red())?;
256 write!(stdout, "Carrier Detect line: {cd}\r\n")
257 .into_diagnostic()
258 .wrap_err("Failed to write to stdout.".red())?;
259
260 Ok(())
261}
262
263pub fn list_serial_ports() -> miette::Result<()> {
268 let mut stdout = io::stdout();
269 let ports = map_miette!(
270 SerialPort::available_ports(),
271 "Could not list available ports."
272 )?;
273 for path in ports {
274 if let Some(path) = path.to_str() {
275 let line = [path, "\r\n"].concat();
276 stdout
277 .write(line.as_bytes())
278 .into_diagnostic()
279 .wrap_err("Failed to write to stdout.".red())?
280 } else {
281 continue;
282 };
283 }
284 Ok(())
285}
286
287pub fn valid_baud_rate(s: &str) -> Result<u32, String> {
289 let baud: u32 = s
290 .parse()
291 .map_err(|_| format!("`{s}` isn't a valid baud rate"))?;
292 if serial2_tokio::COMMON_BAUD_RATES.contains(&baud) {
293 Ok(baud)
294 } else {
295 Err(format!(
296 "'{}' is not a valid baud rate; valid baud rates include {:?}",
297 baud,
298 serial2_tokio::COMMON_BAUD_RATES
299 ))
300 }
301}
302
303fn ensure_terminal_cleanup(mut stdout: io::Stdout) {
304 use crossterm::{
305 cursor::Show,
306 execute,
307 terminal::{LeaveAlternateScreen, disable_raw_mode},
308 };
309 let _ = execute!(
310 stdout,
311 event::DisableMouseCapture,
312 event::DisableBracketedPaste,
313 LeaveAlternateScreen,
314 Show
315 );
316 let _ = disable_raw_mode();
317 let _ = stdout.flush();
318}