How to use serial port in Dioxus desktop app?

Hey,

I'm new to Rust/Dioxus and stucked somehow. I would like to write a desktop application to communicate with an external microcontroller via serial port. I can create a serial port and communicate with the microcontroller but don't know how to use it in Dioxus.

The serial port is an object stored on the heap and I'm trying to use its pointer in the prop struct to access it when an event happens. The pointer must be a mutable borrow reference.
I get this error warning

error[E0596]: cannot borrow cx.props.port as mutable, as it is behind a & reference
send_command( &output, &mut cx.props.port);
^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

Is this the correct way to use objects that need to be created only once for the app lifetime?

I'm grateful for any support.

use serialport::SerialPort;
use std::time::Duration;
use std::str;
use std::collections::HashMap;
use dioxus_desktop::Config;

#[allow(non_snake_case)]
use dioxus::prelude::*;

const COM_PORT: &str = "COM3";
const BAUD_RATE: u32 = 115200;

fn main() {
    let app_props = AppProps {
        port: open_serial_port(COM_PORT, BAUD_RATE)
    };

    dioxus_desktop::launch_with_props(
        app,
        app_props,
        Config::default(),
    )
}

struct AppProps {
    port: Box<dyn SerialPort>,
}

fn open_serial_port(com_port : &str, baud_rate: u32) -> Box<dyn SerialPort> {
    let sport: serialport::SerialPortBuilder = serialport::new(com_port, baud_rate);
    let sport: serialport::SerialPortBuilder = sport.timeout(Duration::new(2, 0));
    match sport.open() {
         Ok(port) => 
        {
            print!("serial port open {:?}", port);
            port
        },
        Err(e) =>
        {
            eprintln!("{:?}", e);
            std::process::exit(1);
        } 
    }
}

fn app(cx: Scope<AppProps>) -> Element {
    let led0_state: &UseState<bool> = use_state(cx, || false);
    let led_state: HashMap<bool, &str> = HashMap::from([
        (false, "off"),
        (true, "on"),
    ]);
    
    cx.render(rsx! {
            h1 { "LED 0 state: {led_state[led0_state]}"}
            button { onclick: move |_| 
                {
                    led0_state.set(!led0_state);
                    let led = "led 1 ".to_owned();
                    let output = led + led_state[led0_state];
                    send_command( &output, &mut cx.props.port);
                }, "LED 0" }
    })
}

fn send_command(cmd: &str, port: &mut Box<dyn SerialPort>)
{
    let _no_of_send_bytes: usize = match port.write(cmd.as_bytes())
    {
        Ok(s) => s,
        Err(e) => {
            println!("error writing to port: {:?}", e);
            let o: usize = 0;
            o
        }
    };
}

I don’t know anything about the serialport crate or much about dioxus but the problem is that the access you’re given to the port value is read only.

If the serial port is basically a sync that you’re sending data into you should consider wrapping it in something like a RefCell for interior mutability.

If the serial port will be updated based on what you send to it you should look into adding it to some state for dioxus to manage. Realistically I think you’ll need some state value to manage responses from the serial port rather than having the port in state itself. I don’t know how you’d do this in dioxus but I’d do it in useEffect in react.

Edit: checkout docs for this in dioxus here Dioxus | An elegant GUI library for Rust

Hey thanks for your support. Using RefCell actually is working.
Not sure though how to create a serial port depending on user input (baudrate and com port).
I think I need to distroy and create a new serial port each time the user changes the com port or baudrate.

let app_props = AppProps {
        port: RefCell::new(open_serial_port(COM_PORT, BAUD_RATE))
    };

and


cx.render(rsx! {
...
            button { onclick: move |_| 
                {
                    led0_state.set(!led0_state);
                    let led = "led 1 ".to_owned();
                    let output = led + led_state[led0_state] + "\n";
                    serial_port::send_command( &output, &mut *cx.props.port.borrow_mut() );
                }, "LED 0" }
...
}

If a serial port is something that you want use to interact with another program, I think you'd want to use dioxus's use_context hook and not hold a refcell to a serial port in your props.

I would make these changes:

  • Pass in the port as props to your app/component.
  • Use the use_context hook to spawn your port inside
  • Create an enum type that can define messages to your coroutine
    • If your port and baud rate can change, there should be an enum variant for this
    • There should be an enum variant for the messages you want to send into the port
  • Create some state for messages received from the port

The docs look pretty good and seem to have all the parts you need to piece together what it is I think you want!