1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use std::io::{self, Write};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use crossterm::{
event::{self, Event, KeyCode, KeyModifiers},
terminal::{disable_raw_mode, enable_raw_mode},
};
pub fn run(port_name: &str, baud_rate: u32) -> anyhow::Result<()> {
println!(
"Connected to {} at {} baud. Press 'Ctrl + ]' to exit.",
port_name, baud_rate
);
println!("---------------------------------------------------------------");
// 1. Open Serial Port
let mut serial_tx = serialport::new(port_name, baud_rate)
.timeout(Duration::from_millis(10))
.open()?;
// Clone the port for the reading thread (serialport supports cloning)
let mut serial_rx = serial_tx.try_clone()?;
// 2. Enable Raw Mode
enable_raw_mode()?;
// Flag to coordinate shutdown
let running = Arc::new(AtomicBool::new(true));
let running_rx = running.clone();
// 3. Spawn Thread: Serial -> Stdout
// This thread reads bytes from the device and prints them to the terminal
let rx_thread = thread::spawn(move || {
let mut buffer = [0; 1024];
let mut stdout = io::stdout();
while running_rx.load(Ordering::Relaxed) {
match serial_rx.read(&mut buffer) {
Ok(n) if n > 0 => {
// Handle line endings for display:
// Raw mode requires \r\n to move down and left.
// If the device sends just \n, we might need to fix it,
// but usually, we just pass through what we get.
// For a robust monitor, we often just write raw bytes.
let _ = stdout.write_all(&buffer[..n]);
let _ = stdout.flush();
}
Ok(_) => {} // Zero bytes read
Err(ref e) if e.kind() == io::ErrorKind::TimedOut => {
// Timeout is normal, just loop again
continue;
}
Err(e) => {
// In raw mode, eprintln might not look right, but we try our best
// We can't easily print to stderr without messing up the terminal state
// so we just break.
let _ = write!(stdout, "\r\nError reading from serial: {}\r\n", e);
break;
}
}
}
});
// 4. Main Loop: Stdin (Keyboard) -> Serial
while running.load(Ordering::Relaxed) {
// Poll for events to avoid blocking forever so we can check 'running'
if event::poll(Duration::from_millis(100))?
&& let Event::Key(key) = event::read()?
{
match key.code {
// Exit condition: Ctrl + ]
// Note: On some terminals/OSs (like macOS), Ctrl+] generates 0x1D (GS),
// which crossterm might report as Ctrl+5 because Ctrl+5 also maps to 0x1D.
KeyCode::Char(']') | KeyCode::Char('5')
if key.modifiers.contains(KeyModifiers::CONTROL) =>
{
running.store(false, Ordering::Relaxed);
break;
}
// Handle Enter key
// Handle Enter key
KeyCode::Enter => {
// Most serial shells expect \r (Carriage Return)
serial_tx.write_all(b"\r")?;
}
// Handle other Control characters
KeyCode::Char(c) if key.modifiers.contains(KeyModifiers::CONTROL) => {
// Convert user's Ctrl+<char> to actual control byte
// 'a' = 1, 'b' = 2, ... 'z' = 26
// This handles Ctrl+C (0x03) correctly sending it to device
// instead of sending literal "c"
let byte = c as u8;
if (b'a'..=b'z').contains(&byte) {
serial_tx.write_all(&[byte - b'a' + 1])?;
} else if (b'A'..=b'Z').contains(&byte) {
serial_tx.write_all(&[byte - b'A' + 1])?;
} else {
// Verify specific cases like Ctrl+\, etc if needed.
// For now, fallback to raw char if we can't map simply,
// or just ignore. Ideally we map standard ASCII control ranges.
// But usually just a-z is enough for basic usage.
// Let's at least try to send what they typed if it's not simple alpha
let mut buf = [0; 4];
let s = c.encode_utf8(&mut buf);
serial_tx.write_all(s.as_bytes())?;
}
}
// Pass through other characters
KeyCode::Char(c) => {
let mut buf = [0; 4];
let s = c.encode_utf8(&mut buf);
serial_tx.write_all(s.as_bytes())?;
}
// Handle Backspace (often tricky)
KeyCode::Backspace => {
// Send ASCII DEL (0x7F) or BS (0x08) depending on device
// Usually 0x08 (BS) or 0x7F (DEL). Let's try 0x08 first or 0x7F.
// Many terminals send 0x7F for backspace.
serial_tx.write_all(b"\x7F")?;
}
// You might need to handle arrows/special keys here if needed
_ => {}
}
}
}
// 5. Cleanup
disable_raw_mode()?;
println!("\nDisconnected.");
// Wait for RX thread to finish (optional, or just let it die with the process)
// We set running to false, so it should exit on next timeout or read.
let _ = rx_thread.join();
Ok(())
}