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