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.
You can use std::io::BufReader
to automatically parse out lines. Not sure what's going on with your timed out error though.
2 Likes
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
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 continue
ing the loop in that case.
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!
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(())
}
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.
1 Like
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?
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?)
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
though everything is working great now.
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.
2 Likes
Thanks for that tip, serialport is indeed way more active. Although serial works, I switched to serialport nevertheless.