Reading from the serial port


#1

Hello,
I am trying to read the input from the serial port /dev/ttyACM0 or similar to which an arduino is attached which is printing text to the console.

I am using the serial crate, here is my code so far:

extern crate serial;

use std::env;
use std::io;
use std::time::Duration;

use serial::prelude::*;
use std::io::prelude::*;

fn main() {
    for arg in env::args_os().skip(1) {
        let mut port = serial::open(&arg).unwrap();
        interact(&mut port).unwrap();
    }
}

fn interact<T: SerialPort>(port: &mut T) -> io::Result<()> {
    port.reconfigure(&|settings| {
        settings.set_baud_rate(serial::Baud9600)?;
        settings.set_char_size(serial::Bits8);
        settings.set_parity(serial::ParityNone);
        settings.set_stop_bits(serial::Stop1);
        settings.set_flow_control(serial::FlowNone);
        Ok(())
    })?;

    //try!(port.set_timeout(Duration::from_millis(1000)));

    let mut string: Vec<u8> = Vec::with_capacity(30);
    loop {
        let mut buf = [0 as u8];
        port.read(&mut buf)?;

        if buf[0] >= 128 {
            continue;
        }

        match buf[0] {
            10 => {
                let s = String::from_utf8(string.clone()).expect("Found invalid UTF-8");
                println!("{}", s);
            }
            c => {
                string.push(c);
            }
        }
    }

    Ok(())
}

I do not like calling read() on a buffer with only one element, but I need to parse the newlines somehow. Also, this is very unreliable and crashes after a very short time due to a timeout Custom { kind: TimedOut, error: StringError(“Operation timed out”) }

Since I do not know if this is the right approach at all I wanted to ask here for some help.


#2

You can use std::io::BufReader to automatically parse out lines. Not sure what’s going on with your timed out error though.


#3

Your tip simplified my code a lot. Thanks!
I still get that timeout though! It works for a couple seconds, then panics.
I reduced the rate at which text is coming in over the serial port, same result.
Here is the shorter code and the backtrace:

extern crate serial;

use serial::prelude::*;
use std::io::prelude::*;

use std::env;
use std::io;
use std::time::Duration;

use std::io::BufReader;

fn main() {
for arg in env::args_os().skip(1) {
    let mut port = serial::open(&arg).unwrap();
    interact(&mut port).unwrap();
}
}

fn interact<T: SerialPort>(port: &mut T) -> io::Result<()> {
port.reconfigure(&|settings| {
    settings.set_baud_rate(serial::Baud9600)?;
    settings.set_char_size(serial::Bits8);
    settings.set_parity(serial::ParityNone);
    settings.set_stop_bits(serial::Stop1);
    settings.set_flow_control(serial::FlowNone);
    Ok(())
})?;

try!(port.set_timeout(Duration::from_millis(1000)));

let mut reader = BufReader::new(port);
let mut line = String::new();
loop {
    let len = reader.read_line(&mut line)?;
    println!("len was: {}. Line is: {}", len, line);
}
Ok(())
}

And the backtrace:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: TimedOut, error: StringError("Operation timed out") }', libcore/result.rs:945:5
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
	 at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
	 at libstd/sys_common/backtrace.rs:71
	 at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
	 at libstd/panicking.rs:211
   3: std::panicking::default_hook
	 at libstd/panicking.rs:227
   4: std::panicking::rust_panic_with_hook
	 at libstd/panicking.rs:463
   5: std::panicking::begin_panic_fmt
	 at libstd/panicking.rs:350
   6: rust_begin_unwind
	 at libstd/panicking.rs:328
   7: core::panicking::panic_fmt
	 at libcore/panicking.rs:71
   8: core::result::unwrap_failed
	 at /checkout/src/libcore/macros.rs:26
   9: <core::result::Result<T, E>>::unwrap
	 at /checkout/src/libcore/result.rs:782
  10: sermon::main
	 at src/main.rs:15
  11: std::rt::lang_start::{{closure}}
	 at /checkout/src/libstd/rt.rs:74
  12: std::panicking::try::do_call
	 at libstd/rt.rs:59
	 at libstd/panicking.rs:310
  13: __rust_maybe_catch_panic
	 at libpanic_unwind/lib.rs:105
  14: std::rt::lang_start_internal
	 at libstd/panicking.rs:289
	 at libstd/panic.rs:374
	 at libstd/rt.rs:58
  15: std::rt::lang_start
	 at /checkout/src/libstd/rt.rs:74
  16: main
  17: __libc_start_main
  18: _start

#4

You should not expect read (or read_line now) to return successfully each time. The timeout error is very likely to happen and is not necessarily an error that needs to be treated as an error.

It can be necessary to treat it when for example you just sent a message to the device and expect an ACK from it in a timely manner, you would then repeat the message or report an error.

I do not know your final use case but if you just want to print received lines, then just discard the timeout errors by continueing the loop in that case.


#5

My intention is to read, not write, and it can be lossy. So I can just ignore the error! I need to know if there was something I could do right or wrong.

Thanks for taking the time and clarifying!


#6

Updated code doesn not seem to even time out…
I suspect it has to do with using reader.lines instead of read_line.

extern crate serial;

use serial::prelude::*;
use std::io::prelude::*;

use std::env;
use std::io;
use std::time::Duration;

use std::io::BufReader;

fn main() {
for arg in env::args_os().skip(1) {
    let mut port = serial::open(&arg).unwrap();
    interact(&mut port).unwrap();
}
}

fn interact<T: SerialPort>(port: &mut T) -> io::Result<()> {
port.reconfigure(&|settings| {
    settings.set_baud_rate(serial::Baud9600)?;
    settings.set_char_size(serial::Bits8);
    settings.set_parity(serial::ParityNone);
    settings.set_stop_bits(serial::Stop1);
    settings.set_flow_control(serial::FlowNone);
    Ok(())
})?;

try!(port.set_timeout(Duration::from_millis(1000)));

let reader = BufReader::new(port);
for line in reader.lines() {
    if line.is_ok() {
	println!("{:?}",  line.unwrap_or("Reading failed".into()));
    }
}
Ok(())
}

#7

It probably still have timeouts. It does not panic because you do not forward the error anymore (like you did with the ? operator on read_line) so the loop is never broken and you don’t end up in the unwrap call in your main function.

Add an else after your if line.is_ok() and you’ll probably see the errors again.


#8

That was a bit stupid on my part! Of course I don’t see the errors that way.

However, I added an else and there are only ever one or two timeouts at the very beginning, never after. It is way worse if I omit the timeout. This made me realize I don’t understand how any of this works and why there would be problems only at the beginning. I also did not find sites where this is documented. Any tips on where I can read up on this?


#9

I think the Arduino resets itself when you open the serial port (likely so that we do not have to push the reset button to flash the program) and it probably needs some time to boot and set up the serial bus before being able to send data.

However, it’s a good practice to expect timeout anytime. If the Arduino is writing data about events it reads on some of its pins for example, you are dependant on external events that could not make the device push data for some time, hence provoking timeouts during that time.

You’re asking documentation about what topic ? (Serial timeout ? Arduino serial? Other?)


#10

Good point! That is it. I always assumed the Arduino serial console was doing that but it is the Arduino itself.

I guess I was asking about serial console in general. It’s hard to find bugs when I have no clue /overview whatsoever :slight_smile: though everything is working great now.


#11

I’m writing something that interacts with serial devices at the moment too. You might try the serialport crate. It looks like it’s inspired by the serial crate, and appears to be more actively maintained. I’m not positive, but I think with serialport the timeout is set when you configure the port, rather than having to do it later like it appears that you’re doing.


#12

Thanks for that tip, serialport is indeed way more active. Although serial works, I switched to serialport nevertheless.