Skip to content

Commit 91ce6fd

Browse files
authored
Merge pull request #45 from firefly-zero/cheat-device
Device support for `cheat`
2 parents 636b638 + 90c8318 commit 91ce6fd

File tree

6 files changed

+184
-109
lines changed

6 files changed

+184
-109
lines changed

src/args.rs

+10
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ pub struct EmulatorArgs {
248248

249249
#[derive(Debug, Parser)]
250250
pub struct MonitorArgs {
251+
/// Path to serial port to connect to a running device.
251252
#[arg(long, default_value = None)]
252253
pub port: Option<String>,
253254

@@ -257,9 +258,11 @@ pub struct MonitorArgs {
257258

258259
#[derive(Debug, Parser)]
259260
pub struct LogsArgs {
261+
/// Path to serial port to connect to a running device.
260262
#[arg(long)]
261263
pub port: String,
262264

265+
/// The serial port Baud rate.
263266
#[arg(long, default_value_t = 115_200)]
264267
pub baud_rate: u32,
265268
}
@@ -291,6 +294,13 @@ pub struct CheatArgs {
291294
#[arg()]
292295
pub value: String,
293296

297+
/// Path to serial port to connect to a running device.
298+
#[arg(long, default_value = None)]
299+
pub port: Option<String>,
300+
301+
#[arg(long, default_value_t = 115_200)]
302+
pub baud_rate: u32,
303+
294304
/// Path to the project root.
295305
#[arg(default_value = ".")]
296306
pub root: PathBuf,

src/commands/cheat.rs

+55-8
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,86 @@
11
use crate::args::CheatArgs;
22
use crate::config::Config;
33
use crate::net::connect;
4+
use crate::serial::SerialStream;
45
use anyhow::{bail, Context, Result};
56
use firefly_types::{serial, Encode};
67
use std::io::{Read, Write};
78
use std::path::Path;
89
use std::time::Duration;
910

1011
pub fn cmd_cheat(args: &CheatArgs) -> Result<()> {
12+
if let Some(port) = &args.port {
13+
cheat_device(args, port)
14+
} else {
15+
cheat_emulator(args)
16+
}
17+
}
18+
19+
/// Run cheat on desktop emulator.
20+
pub fn cheat_emulator(args: &CheatArgs) -> Result<()> {
1121
println!("⏳️ connecting...");
1222
let mut stream = connect()?;
23+
stream.set_read_timeout(Some(Duration::from_secs(1)))?;
1324

1425
{
15-
let cmd = parse_command(&args.command, &args.root)?;
16-
let val = parse_value(&args.value)?;
17-
let req = serial::Request::Cheat(cmd, val);
18-
let buf = req.encode_vec().context("encode request")?;
19-
println!("⌛ sending request...");
20-
stream.write_all(&buf[..]).context("send request")?;
26+
let buf = serialize_request(args)?;
27+
println!("⌛ sending request...");
28+
stream.write_all(&buf).context("send request")?;
2129
stream.flush().context("flush request")?;
2230
}
2331

24-
stream.set_read_timeout(Some(Duration::from_secs(1)))?;
2532
for _ in 0..5 {
2633
let mut buf = vec![0; 64];
2734
stream.read(&mut buf).context("read response")?;
2835
let resp = serial::Response::decode(&buf).context("decode response")?;
2936
if let serial::Response::Cheat(result) = resp {
30-
println!("✅ response: {result}");
37+
println!("✅ response: {result}");
3138
return Ok(());
3239
}
3340
}
3441
bail!("timed out waiting for response")
3542
}
3643

44+
/// Run cheat on the connected device.
45+
pub fn cheat_device(args: &CheatArgs, port: &str) -> Result<()> {
46+
println!("⏳️ connecting...");
47+
let port = serialport::new(port, args.baud_rate)
48+
.timeout(Duration::from_secs(5))
49+
.open()
50+
.context("open the serial port")?;
51+
let mut stream = SerialStream::new(port);
52+
53+
{
54+
let cmd = parse_command(&args.command, &args.root)?;
55+
let val = parse_value(&args.value)?;
56+
let req = serial::Request::Cheat(cmd, val);
57+
println!("⌛ sending request...");
58+
stream.send(&req)?;
59+
}
60+
61+
println!("⌛ waiting for response...");
62+
for _ in 0..5 {
63+
match stream.next() {
64+
Ok(serial::Response::Cheat(result)) => {
65+
println!("✅ response: {result}");
66+
return Ok(());
67+
}
68+
Ok(serial::Response::Log(log)) => println!("🪵 {log}"),
69+
Ok(_) => (),
70+
Err(err) => println!("❌ ERROR(cli): {err}"),
71+
}
72+
}
73+
bail!("timed out waiting for response")
74+
}
75+
76+
fn serialize_request(args: &CheatArgs) -> Result<Vec<u8>> {
77+
let cmd = parse_command(&args.command, &args.root)?;
78+
let val = parse_value(&args.value)?;
79+
let req = serial::Request::Cheat(cmd, val);
80+
let buf = req.encode_vec().context("encode request")?;
81+
Ok(buf)
82+
}
83+
3784
/// Parse a cheat command as either an integer or a cheat from firefly.toml.
3885
fn parse_command(raw: &str, root: &Path) -> Result<i32> {
3986
if let Ok(n) = raw.parse::<i32>() {

src/commands/logs.rs

+9-49
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,20 @@
1-
use crate::args::LogsArgs;
1+
use crate::{args::LogsArgs, serial::SerialStream};
22
use anyhow::{Context, Result};
3-
use firefly_types::{serial::Response, Encode};
3+
use firefly_types::serial::Response;
44
use std::time::Duration;
55

66
pub fn cmd_logs(args: &LogsArgs) -> Result<()> {
7-
let mut port = serialport::new(&args.port, args.baud_rate)
8-
.timeout(Duration::from_millis(10))
7+
let port = serialport::new(&args.port, args.baud_rate)
8+
.timeout(Duration::from_secs(3600))
99
.open()
1010
.context("open the serial port")?;
11-
let mut buf = Vec::new();
11+
let mut stream = SerialStream::new(port);
1212
println!("listening...");
1313
loop {
14-
let mut chunk = vec![0; 64];
15-
let n = match port.read(chunk.as_mut_slice()) {
16-
Ok(n) => n,
17-
Err(err) => {
18-
if err.kind() == std::io::ErrorKind::TimedOut {
19-
continue;
20-
}
21-
return Err(err).context("read from serial port");
22-
}
23-
};
24-
25-
buf.extend_from_slice(&chunk[..n]);
26-
loop {
27-
let (frame, rest) = advance(&buf);
28-
buf = Vec::from(rest);
29-
if frame.is_empty() {
30-
break;
31-
}
32-
match Response::decode(&frame) {
33-
Ok(Response::Log(log)) => println!("{log}"),
34-
Ok(_) => (),
35-
Err(err) => println!("invalid message: {err}"),
36-
}
37-
}
38-
}
39-
}
40-
41-
// Given the binary stream so far, read the first COBS frame and return the rest of bytes.
42-
pub(super) fn advance(chunk: &[u8]) -> (Vec<u8>, &[u8]) {
43-
let max_len = chunk.len();
44-
let mut out_buf = vec![0; max_len];
45-
let mut dec = cobs::CobsDecoder::new(&mut out_buf);
46-
match dec.push(chunk) {
47-
Ok(Some((n_out, n_in))) => {
48-
let msg = Vec::from(&out_buf[..n_out]);
49-
(msg, &chunk[n_in..])
14+
match stream.next()? {
15+
Response::Log(log) => println!("{log}"),
16+
Response::Cheat(val) => println!("cheat response: {val}"),
17+
_ => (),
5018
}
51-
Ok(None) => (Vec::new(), chunk),
52-
Err(err) => match err {
53-
cobs::DecodeError::EmptyFrame => (Vec::new(), &[]),
54-
cobs::DecodeError::InvalidFrame { decoded_bytes } => {
55-
(Vec::new(), &chunk[decoded_bytes..])
56-
}
57-
cobs::DecodeError::TargetBufTooSmall => unreachable!(),
58-
},
5919
}
6020
}

src/commands/monitor.rs

+34-52
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use super::logs::advance;
21
use crate::args::MonitorArgs;
32
use crate::net::connect;
3+
use crate::serial::{is_timeout, SerialStream};
44
use anyhow::{Context, Result};
55
use crossterm::{cursor, event, execute, style, terminal};
66
use firefly_types::{serial, Encode};
@@ -15,8 +15,6 @@ const RBORD: u16 = 21;
1515
const KB: u32 = 1024;
1616
const MB: u32 = 1024 * KB;
1717

18-
type Port = Box<dyn serialport::SerialPort>;
19-
2018
#[derive(Default)]
2119
struct Stats {
2220
update: Option<serial::Fuel>,
@@ -54,35 +52,18 @@ pub fn cmd_monitor(_vfs: &Path, args: &MonitorArgs) -> Result<()> {
5452
}
5553

5654
fn monitor_device(port: &str, args: &MonitorArgs) -> Result<()> {
57-
let mut port = connect_device(port, args)?;
55+
let mut stream = connect_device(port, args)?;
5856
let mut stats = Stats::default();
59-
request_device_stats(&mut port, &mut stats)?;
60-
let mut buf = Vec::new();
57+
request_device_stats(&mut stream, &mut stats)?;
6158
loop {
6259
if should_exit() {
6360
return Ok(());
6461
}
65-
buf = read_device(&mut port, buf, &mut stats)?;
62+
read_device(&mut stream, &mut stats)?;
6663
render_stats(&stats).context("render stats")?;
6764
}
6865
}
6966

70-
/// Connect to running emulator using serial USB port (JTag-over-USB).
71-
fn connect_device(port: &str, args: &MonitorArgs) -> Result<Port> {
72-
let port = serialport::new(port, args.baud_rate)
73-
.timeout(Duration::from_millis(10))
74-
.open()
75-
.context("open the serial port")?;
76-
77-
execute!(
78-
io::stdout(),
79-
terminal::Clear(terminal::ClearType::All),
80-
cursor::MoveTo(0, 0),
81-
style::Print("waiting for stats..."),
82-
)?;
83-
Ok(port)
84-
}
85-
8667
fn monitor_emulator() -> Result<()> {
8768
let mut stream = connect_emulator()?;
8869
let mut stats = Stats::default();
@@ -103,39 +84,29 @@ fn read_emulator(mut stream: TcpStream, stats: &mut Stats) -> Result<TcpStream>
10384
let stream = connect().context("reconnecting")?;
10485
return Ok(stream);
10586
}
106-
parse_stats(stats, &buf[..size])?;
87+
let resp = serial::Response::decode(&buf[..size]).context("decode response")?;
88+
parse_stats(stats, resp);
10789
Ok(stream)
10890
}
10991

11092
/// Receive and parse one stats message from device.
111-
fn read_device(port: &mut Port, mut buf: Vec<u8>, stats: &mut Stats) -> Result<Vec<u8>> {
112-
let mut chunk = vec![0; 64];
113-
let n = match port.read(chunk.as_mut_slice()) {
114-
Ok(n) => n,
93+
fn read_device(stream: &mut SerialStream, stats: &mut Stats) -> Result<()> {
94+
match stream.next() {
95+
Ok(resp) => {
96+
parse_stats(stats, resp);
97+
stats.last_msg = Some(Instant::now());
98+
}
11599
Err(err) => {
116-
if err.kind() == std::io::ErrorKind::TimedOut {
117-
request_device_stats(port, stats)?;
118-
return Ok(buf);
100+
if !is_timeout(&err) {
101+
return Err(err);
119102
}
120-
return Err(err).context("read from serial port");
121103
}
122-
};
123-
124-
stats.last_msg = Some(Instant::now());
125-
buf.extend_from_slice(&chunk[..n]);
126-
loop {
127-
let (frame, rest) = advance(&buf);
128-
buf = Vec::from(rest);
129-
if frame.is_empty() {
130-
break;
131-
}
132-
parse_stats(stats, &frame)?;
133104
}
134-
Ok(buf)
105+
Ok(())
135106
}
136107

137108
/// Send a message into the running device requesting to enable stats collection.
138-
fn request_device_stats(port: &mut Port, stats: &mut Stats) -> Result<()> {
109+
fn request_device_stats(stream: &mut SerialStream, stats: &mut Stats) -> Result<()> {
139110
let now = Instant::now();
140111
let should_update = match stats.last_msg {
141112
Some(last_msg) => {
@@ -148,16 +119,12 @@ fn request_device_stats(port: &mut Port, stats: &mut Stats) -> Result<()> {
148119
if should_update {
149120
stats.last_msg = Some(now);
150121
let req = serial::Request::Stats(true);
151-
let buf = req.encode_vec().context("encode request")?;
152-
port.write_all(&buf[..]).context("send request")?;
153-
port.flush().context("flush request")?;
122+
stream.send(&req)?;
154123
}
155124
Ok(())
156125
}
157126

158-
/// Parse raw stats message using postcard. Does NOT handle COBS frames.
159-
fn parse_stats(stats: &mut Stats, buf: &[u8]) -> Result<()> {
160-
let resp = serial::Response::decode(buf).context("decode response")?;
127+
fn parse_stats(stats: &mut Stats, resp: serial::Response) {
161128
match resp {
162129
serial::Response::Cheat(_) => {}
163130
serial::Response::Log(log) => {
@@ -180,7 +147,22 @@ fn parse_stats(stats: &mut Stats, buf: &[u8]) -> Result<()> {
180147
}
181148
serial::Response::Memory(mem) => stats.mem = Some(mem),
182149
}
183-
Ok(())
150+
}
151+
152+
/// Connect to running emulator using serial USB port (JTag-over-USB).
153+
fn connect_device(port: &str, args: &MonitorArgs) -> Result<SerialStream> {
154+
let port = serialport::new(port, args.baud_rate)
155+
.timeout(Duration::from_millis(10))
156+
.open()
157+
.context("open the serial port")?;
158+
159+
execute!(
160+
io::stdout(),
161+
terminal::Clear(terminal::ClearType::All),
162+
cursor::MoveTo(0, 0),
163+
style::Print("waiting for stats..."),
164+
)?;
165+
Ok(SerialStream::new(port))
184166
}
185167

186168
/// Connect to a running emulator and enable stats.

src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod images;
2323
mod langs;
2424
mod net;
2525
mod repl_helper;
26+
mod serial;
2627
mod vfs;
2728
mod wasm;
2829

0 commit comments

Comments
 (0)