Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using stdio as a 'Device' #86

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::io;

pub enum Interface {
SerialPort(Box<dyn serialport::SerialPort>),
Stdio,
}

impl io::Write for Interface {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Interface::SerialPort(s) => s.write(buf),
Interface::Stdio => io::stdout().write(buf),
}
}

fn flush(&mut self) -> io::Result<()> {
match self {
Interface::SerialPort(s) => s.flush(),
Interface::Stdio => io::stdout().flush(),
}
}
}

impl io::Read for Interface {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Interface::SerialPort(s) => s.read(buf),
Interface::Stdio => io::stdin().read(buf),
}
}
}
Comment on lines +1 to +31
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementing SerialPort for Stdio seems to be a bit hacky indeed. Could we create a new struct called Interface that implements io::Read and io::Write and then two builder functions: from_serial() and from_read() ?
Not sure how to handle the configuration of the serial port then (baud, parity etc)..

Something like this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serial settings are ignored. From ui point of view perhaps it would make most sense if the serial related things were greyed out once "stdio" is selected? However I am not sure how I would accomplish something like that..

Copy link
Owner

@hacknus hacknus Oct 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more like this:

/// STDIO interface
#[derive(Debug)]
pub struct StdioInterface<stdio> {
    pub(crate) stdio: stdio,
}

/// Serial interface
#[derive(Debug)]
pub struct SerialInterface<SerialPort> {
    pub(crate) serial: SerialPort,
}


/// Write data
pub trait WriteData: private::Sealed {
    /// Error type
    type Error;
    /// Write data.
    fn write(&mut self, payload: &str) -> Result<(), Self::Error>;
   /// Flush data.
    fn flush(&mut self, payload: &str) -> Result<(), Self::Error>;
}

/// Read data
pub trait ReadData: private::Sealed {
    /// Error type
    type Error;
    /// Read some data.
    fn read(&mut self) -> Result<String, Self::Error>;
}

impl<SerialPort, E> ReadData for SerialInterface<SerialPort >
    where
        SerialPort: // traits
{
    type Error = Error<E>;
    // ... 
}

impl<stdio, E> ReadData for StdioInterface<stdio>
    where
        stdio: // traits
{
    type Error = Error<E>;
    // ... 
}

impl<SerialPort, E> WriteData for SerialInterface<SerialPort >
    where
        SerialPort: // traits
{
    type Error = Error<E>;
    // ... 
}

impl<stdio, E> WriteData for StdioInterface<stdio>
    where
        stdio: // traits
{
    type Error = Error<E>;
    // ... 
}

But not sure, if this works...
greying out the serial settings should be possible in the GUI.

Copy link
Author

@usbalbin usbalbin Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I misunderstand, that would require casting Box<dyn SerialPort> into Box<dyn WriteData> which as far as I know is not possible. oh...

There is however also serialport::new_native which returns different things depending on OS. At that point we would no longer need the Box<dyn ...> and just #[cfg] into the right enum variant.

Copy link
Author

@usbalbin usbalbin Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I misunderstand, that would require casting Box into Box which as far as I know is not possible. oh...

Oh I think maybe I understand now(?). Since the type is wrapped in a newtype it would be the newtype that could then be turned into a Box which would be fine. However that would be double boxing...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, this is not as straight forward as I expected...

9 changes: 3 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::serial::{load_serial_settings, serial_thread, Device};

mod data;
mod gui;
mod interface;
mod io;
mod serial;
mod toggle;
Expand All @@ -34,12 +35,8 @@ const PREFS_KEY: &str = "config/gui";
const PREFS_KEY_SERIAL: &str = "config/serial_devices";

fn split(payload: &str) -> Vec<f32> {
let mut split_data: Vec<&str> = vec![];
for s in payload.split(':') {
split_data.extend(s.split(','));
}
split_data
.iter()
payload
.split(&[':', ',', '=', ' ', '\t'])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just to get the output of ping to look nice in the screenshot. I am happy to revert this if you want

.map(|x| x.trim())
.flat_map(|x| x.parse::<f32>())
.collect()
Expand Down
78 changes: 41 additions & 37 deletions src/serial.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::io::{BufRead, BufReader};
use std::io::{BufRead, BufReader, Write};
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};

use preferences::Preferences;
use serde::{Deserialize, Serialize};
use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};
use serialport::{DataBits, FlowControl, Parity, StopBits};

use crate::data::{get_epoch_ms, SerialDirection};
use crate::interface::Interface;
use crate::{print_to_console, Packet, Print, APP_INFO, PREFS_KEY_SERIAL};

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -74,16 +75,13 @@ impl Default for Device {
}
}

fn serial_write(
port: &mut BufReader<Box<dyn SerialPort>>,
cmd: &[u8],
) -> Result<usize, std::io::Error> {
fn serial_write(port: &mut BufReader<Interface>, cmd: &[u8]) -> Result<usize, std::io::Error> {
let write_port = port.get_mut();
write_port.write(cmd)
}

fn serial_read(
port: &mut BufReader<Box<dyn SerialPort>>,
port: &mut BufReader<Interface>,
serial_buf: &mut String,
) -> Result<usize, std::io::Error> {
port.read_line(serial_buf)
Expand Down Expand Up @@ -112,32 +110,41 @@ pub fn serial_thread(

let device = get_device(&devices_lock, &device_lock);

let mut port = match serialport::new(&device.name, device.baud_rate)
.timeout(Duration::from_millis(100))
.open()
{
Ok(p) => {
if let Ok(mut connected) = connected_lock.write() {
*connected = true;
}
print_to_console(
&print_lock,
Print::Ok(format!(
"Connected to serial port: {} @ baud = {}",
device.name, device.baud_rate
)),
);
BufReader::new(p)
let mut port = if device.name == "stdio" {
if let Ok(mut connected) = connected_lock.write() {
*connected = true;
}
Err(err) => {
if let Ok(mut write_guard) = device_lock.write() {
write_guard.name.clear();
print_to_console(&print_lock, Print::Ok(format!("Connected to stdio")));

BufReader::new(Interface::Stdio)
} else {
match serialport::new(&device.name, device.baud_rate)
.timeout(Duration::from_millis(100))
.open()
{
Ok(p) => {
if let Ok(mut connected) = connected_lock.write() {
*connected = true;
}
print_to_console(
&print_lock,
Print::Ok(format!(
"Connected to serial port: {} @ baud = {}",
device.name, device.baud_rate
)),
);
BufReader::new(Interface::SerialPort(p))
}
Err(err) => {
if let Ok(mut write_guard) = device_lock.write() {
write_guard.name.clear();
}
print_to_console(
&print_lock,
Print::Error(format!("Error connecting: {}", err)),
);
continue;
}
print_to_console(
&print_lock,
Print::Error(format!("Error connecting: {}", err)),
);
continue;
}
};

Expand Down Expand Up @@ -176,6 +183,7 @@ fn available_devices() -> Vec<String> {
.unwrap()
.iter()
.map(|p| p.port_name.clone())
.chain(std::iter::once("stdio".into()))
.collect()
}

Expand Down Expand Up @@ -228,7 +236,7 @@ fn disconnected(
}

fn perform_writes(
port: &mut BufReader<Box<dyn SerialPort>>,
port: &mut BufReader<Interface>,
send_rx: &Receiver<String>,
raw_data_tx: &Sender<Packet>,
t_zero: Instant,
Expand All @@ -251,11 +259,7 @@ fn perform_writes(
}
}

fn perform_reads(
port: &mut BufReader<Box<dyn SerialPort>>,
raw_data_tx: &Sender<Packet>,
t_zero: Instant,
) {
fn perform_reads(port: &mut BufReader<Interface>, raw_data_tx: &Sender<Packet>, t_zero: Instant) {
let mut buf = "".to_string();
match serial_read(port, &mut buf) {
Ok(_) => {
Expand Down