Skip to content

Commit 939abc3

Browse files
addrian-77eva-cosma
authored andcommitted
feat: implement listen protocols and legacy console support
Signed-off-by: addrian-77 <lunguadrian30@gmail.com> refactor(code): restructure for legacy support Signed-off-by: addrian-77 <lunguadrian30@gmail.com> chore: remove redundant lib files Signed-off-by: addrian-77 <lunguadrian30@gmail.com> feat(serial): add terminal handling (WIP) Signed-off-by: addrian-77 <lunguadrian30@gmail.com> feat(console): implement legacy console Signed-off-by: addrian-77 <lunguadrian30@gmail.com> fix(cli): code cleanup Signed-off-by: addrian-77 <lunguadrian30@gmail.com> fix(cli): resolve clippy errors Signed-off-by: addrian-77 <lunguadrian30@gmail.com> fix: made requested changes Signed-off-by: addrian-77 <lunguadrian30@gmail.com> fix: clippy error Signed-off-by: addrian-77 <lunguadrian30@gmail.com> fix: made requested changes Signed-off-by: addrian-77 <lunguadrian30@gmail.com> fix: added error message Signed-off-by: addrian-77 <lunguadrian30@gmail.com> refactor: update using_serial function Signed-off-by: addrian-77 <lunguadrian30@gmail.com> refactor: made requested changes Signed-off-by: addrian-77 <lunguadrian30@gmail.com>
1 parent b591332 commit 939abc3

33 files changed

Lines changed: 269 additions & 71 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
[workspace]
66

7-
resolver= "3"
7+
resolver = "3"
88

99
members = [
1010
"tockloader-cli",
11-
"tock-process-console",
11+
"tock-process-console",
1212
"tbf-parser",
1313
"tockloader-lib",
1414
"tock-ui/src-tauri",

tock-process-console/Cargo.toml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ crossterm = { version = "0.27.0", features = ["event-stream"] }
1515
circular-queue = "0.2.6"
1616
tokio = { version = "1.32.0", features = ["full"] }
1717
anyhow = "1.0.75"
18-
tokio-serial = {version = "5.4.4", features = ["libudev"]}
18+
tokio-serial = { version = "5.4.4", features = ["libudev"] }
1919
tokio-stream = { version = "0.1.14" }
2020
tui-term = "0.1.6"
2121
bytes = "1.5.0"
22-
vt100 = "0.15.2"
23-
queues = "1.0.2"
24-
itertools = "0.12.1"
2522
ratatui = "0.28.0"
26-
probe-rs = "0.24.0"
27-
23+
futures = "0.3.31"
24+
tokio-util = { version = "0.7.8", features = ["full"] }
25+
console = "0.16.0"
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
use anyhow::Error;
2+
use bytes::{Buf, BufMut, BytesMut};
3+
use console::Term;
4+
use futures::stream::{SplitSink, SplitStream};
5+
use futures::{SinkExt, StreamExt};
6+
use std::io::{self, Write};
7+
use tokio::signal;
8+
use tokio_serial::SerialStream;
9+
use tokio_util::codec::{Decoder, Encoder, Framed};
10+
11+
#[derive(Debug)]
12+
struct TerminalCodec;
13+
14+
pub async fn run(stream: SerialStream) {
15+
println!("Connecting to board... (press Ctrl+C to stop)");
16+
17+
let (writer, reader) = Framed::new(stream, TerminalCodec).split();
18+
let reader_handle = tokio::spawn(listen_serial(reader));
19+
let writer_handle = tokio::spawn(write_serial(writer));
20+
tokio::select! {
21+
_ = reader_handle => {}
22+
_ = writer_handle => {}
23+
}
24+
}
25+
26+
async fn listen_serial(
27+
mut reader: SplitStream<Framed<SerialStream, TerminalCodec>>,
28+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
29+
let ctrl_c = async {
30+
signal::ctrl_c()
31+
.await
32+
.expect("Failed to install Ctrl+C handler");
33+
};
34+
let read_task = async {
35+
while let Some(line) = reader.next().await {
36+
print!("{}", line.unwrap());
37+
io::stdout().flush().unwrap();
38+
}
39+
40+
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
41+
};
42+
43+
tokio::select! {
44+
_ = ctrl_c => {
45+
}
46+
res = read_task => {
47+
res?;
48+
}
49+
}
50+
51+
Ok(())
52+
}
53+
54+
async fn write_serial(
55+
mut writer: SplitSink<Framed<SerialStream, TerminalCodec>, String>,
56+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
57+
let ctrl_c = async {
58+
signal::ctrl_c()
59+
.await
60+
.expect("Failed to install Ctrl+C handler");
61+
};
62+
63+
let write_task = async {
64+
loop {
65+
if let Some(buffer) = get_key().await? {
66+
writer.send(buffer).await?;
67+
} else {
68+
break Ok::<(), Box<dyn std::error::Error + Send + Sync>>(());
69+
}
70+
}
71+
};
72+
73+
tokio::select! {
74+
_ = ctrl_c => {}
75+
res = write_task => {
76+
res?;
77+
}
78+
}
79+
80+
Ok(())
81+
}
82+
83+
async fn get_key() -> Result<Option<String>, Error> {
84+
let console_result = tokio::task::spawn_blocking(move || Term::stdout().read_key()).await?;
85+
86+
let key = console_result?;
87+
88+
Ok(match key {
89+
console::Key::Unknown => None,
90+
console::Key::UnknownEscSeq(_) => None,
91+
console::Key::ArrowLeft => Some("\u{1B}[D".into()),
92+
console::Key::ArrowRight => Some("\u{1B}[C".into()),
93+
console::Key::ArrowUp => Some("\u{1B}[A".into()),
94+
console::Key::ArrowDown => Some("\u{1B}[B".into()),
95+
console::Key::Enter => Some("\n".into()),
96+
console::Key::Escape => None,
97+
console::Key::Backspace => Some("\x7f".into()),
98+
console::Key::Home => Some("\u{1B}[H".into()),
99+
console::Key::End => Some("\u{1B}[F".into()),
100+
console::Key::Tab => Some("\t".into()),
101+
console::Key::BackTab => Some("\t".into()),
102+
console::Key::Alt => None,
103+
console::Key::Del => Some("\u{1B}[3~".into()),
104+
console::Key::Shift => None,
105+
console::Key::Insert => None,
106+
console::Key::PageUp => None,
107+
console::Key::PageDown => None,
108+
console::Key::Char(c) => Some(c.into()),
109+
_ => todo!(),
110+
})
111+
}
112+
113+
impl Decoder for TerminalCodec {
114+
type Item = String;
115+
type Error = Error;
116+
117+
fn decode(&mut self, source: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
118+
if source.is_empty() {
119+
return Ok(None);
120+
}
121+
122+
// There may be incomplete utf-8 sequences, so interpret as much as we can.
123+
// We aren't expecting to get non-utf8 bytes. Otherwise, the decoder would get stuck!
124+
match str::from_utf8(source) {
125+
Ok(result_str) => {
126+
// Release immutable reference to source
127+
let result = result_str.to_string();
128+
129+
source.clear();
130+
Ok(Some(result))
131+
}
132+
Err(error) => {
133+
let index = error.valid_up_to();
134+
135+
if index == 0 {
136+
// Q: Returning Some("") makes it so no other bytes are read in. I have no idea why.
137+
// If you find a reason why, please edit this comment.
138+
// A: By looking at the documentaion of the 'decode' method, Ok(None) signals
139+
// that we need to read more bytes. Otherwise, returning Some("") would call
140+
// 'decode' again until Ok(None) is returned.
141+
return Ok(None);
142+
}
143+
144+
let result = str::from_utf8(&source[..index])
145+
.expect("UTF-8 string failed after verifying with 'valid_up_to()'")
146+
.to_string();
147+
source.advance(index);
148+
149+
Ok(Some(result))
150+
}
151+
}
152+
}
153+
}
154+
155+
impl Encoder<String> for TerminalCodec {
156+
type Error = Error;
157+
158+
fn encode(&mut self, item: String, dst: &mut BytesMut) -> Result<(), Self::Error> {
159+
dst.put(item.as_bytes());
160+
Ok(())
161+
}
162+
}

tock-process-console/src/lib.rs

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,2 @@
1-
// Licensed under the Apache License, Version 2.0 or the MIT License.
2-
// SPDX-License-Identifier: Apache-2.0 OR MIT
3-
// Copyright OXIDOS AUTOMOTIVE 2024.
4-
5-
mod board;
6-
mod state_store;
7-
mod termination;
8-
mod ui_management;
9-
10-
use state_store::StateStore;
11-
use termination::{create_terminator, Interrupted};
12-
use ui_management::UiManager;
13-
14-
pub async fn run() -> anyhow::Result<()> {
15-
let (terminator, mut interrupt_reader) = create_terminator();
16-
let (state_store, state_reader) = StateStore::new();
17-
let (ui_manager, action_reader) = UiManager::new();
18-
19-
tokio::try_join!(
20-
state_store.main_loop(terminator, action_reader, interrupt_reader.resubscribe()),
21-
ui_manager.main_loop(state_reader, interrupt_reader.resubscribe(),)
22-
)?;
23-
24-
if let Ok(reason) = interrupt_reader.recv().await {
25-
match reason {
26-
Interrupted::UserRequest => println!("Exited per user request"),
27-
Interrupted::OsSignal => println!("Exited because of os signal"),
28-
}
29-
} else {
30-
println!("Exited due to an unexpected error");
31-
}
32-
33-
Ok(())
34-
}
1+
pub mod legacy;
2+
pub mod pconsole;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed under the Apache License, Version 2.0 or the MIT License.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// Copyright OXIDOS AUTOMOTIVE 2024.
4+
5+
mod board;
6+
mod state_store;
7+
mod termination;
8+
mod ui_management;
9+
10+
use state_store::StateStore;
11+
use termination::{create_terminator, Interrupted};
12+
use ui_management::UiManager;
13+
14+
pub async fn run() -> anyhow::Result<()> {
15+
let (terminator, mut interrupt_reader) = create_terminator();
16+
let (state_store, state_reader) = StateStore::new();
17+
let (ui_manager, action_reader) = UiManager::new();
18+
19+
tokio::try_join!(
20+
state_store.main_loop(terminator, action_reader, interrupt_reader.resubscribe()),
21+
ui_manager.main_loop(state_reader, interrupt_reader.resubscribe(),)
22+
)?;
23+
24+
if let Ok(reason) = interrupt_reader.recv().await {
25+
match reason {
26+
Interrupted::UserRequest => println!("Exited per user request"),
27+
Interrupted::OsSignal => println!("Exited because of os signal"),
28+
}
29+
} else {
30+
println!("Exited due to an unexpected error");
31+
}
32+
33+
Ok(())
34+
}
File renamed without changes.

0 commit comments

Comments
 (0)