de-vri-es / serial2-rs

Cross platform serial ports for Rust
Other
41 stars 10 forks source link

New feature: read_until #3

Closed josejachuf closed 2 years ago

josejachuf commented 2 years ago

I will try to describe in the best way what happens

I have an app that reads the data sent via wireless to a USB device connected to the computer

To read the data entry into the device continuously I have a loop similar to the example

loop {    
    match port.read(&mut buffer) {
        Ok(0) => return Ok(()),
        Ok(n) => {
            let data = std::str::from_utf8(&buffer[..n])?;

It is stipulated that "data" have a certain structure (field separator). Once obtained, the fields were separated and actions are made. For example:

user:4:1

My customer had given me a USB device and everything worked very well.

A few days ago my customer changed the USB device hardware and the readings began to have faults The previous example now looked like this

u ser:4 :1

In this example there were 3 different readings and obviously do not respect the protocol of how the data should come

I made a change in the code by adding a sleep of 1000 ms

loop {
    sleep(Duration::from_millis(800));
    match port.read(&mut buffer) {
        Ok(0) => return Ok(()),
        Ok(n) => {
            let data = std::str::from_utf8(&buffer[..n])?;

Now the readings behaved as I expected, but I started noting that some data sent to the device were not read, they were lost, I understand why at that time the sleep was happening

I changed the sleep to 300, 500 and now 800, it seems that with 800 I read the data and I do not lose readings, but I do not have any certainty.

There is some way that if you are entering the data wait for you to finish arriving. For example, in PySerial there is read_until [1]

[1] https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.read_until

de-vri-es commented 2 years ago

Hey! Thanks for the suggestion.

You can already do this today using the standard library. To be specific, you can wrap the serial port in a std::io::BufReader. Then you can use std::io::BufRead::read_until() to read into a vector until a certain byte is found.

Would this fit your use case?

josejachuf commented 2 years ago

Hello @de-vri-es , thanks for the answer. Sorry I am very new in Rust, I do not realize how to use what you suggest.

Jose

de-vri-es commented 2 years ago

No problem! You can use it something like this:

use std::io::BufRead;

let port = serial2::SerialPort::open("/dev/ttyUSB0", 57600)?;
let mut port = std::io::BufReader::new(port);

let mut buffer = Vec::with_capacity(128);

// Keep reading until the read returns Ok(0), which indicates the stream is closed.
while port.read_until(b'\n', &mut buffer)? != 0 {
    process_message(&buffer);
    buffer.clear();
}

In that example, the question mark is used to return errors to the calling function. You can check out the Rust book if you're not familiar with it yet.

josejachuf commented 2 years ago

Thanks, I'm going to check it tonight. A more query, to port I use it to read as to write, I understand that in this way I can not write in the port

josejachuf commented 2 years ago

Something like that would work? Creating 2 instances of the Port one to read and another to write. I do not know if it is blocked when it opens. At night I try


use std::io::BufRead;

let port = serial2::SerialPort::open("/dev/ttyUSB0", 57600)?;
let mut port_r = std::io::BufReader::new(port);

let mut buffer = Vec::with_capacity(128);

// Keep reading until the read returns Ok(0), which indicates the stream is closed.
while port_r.read_until(b'\n', &mut buffer)? != 0 {
    process_message(&buffer);
    buffer.clear();
}

Jose

de-vri-es commented 2 years ago

You can still access the underlying serial port through the BufReader object with the get_mut() method:

use std::io::{BufRead, Write};

let port = serial2::SerialPort::open("/dev/ttyUSB0", 57600)?;
let mut port = std::io::BufReader::new(port);

let mut buffer = Vec::with_capacity(128);

// Keep reading until the read returns Ok(0), which indicates the stream is closed.
while port.read_until(b'\n', &mut buffer)? != 0 {
    process_message(&buffer);
    port.get_mut().write_all(b"OK\n")?; // Use `get_mut()` to write to the port.
    buffer.clear();
}

However, you should do all reads through the BufReader. Otherwise you may miss bytes that have already been read into the buffer of the BufReader.

josejachuf commented 2 years ago

Thanks. I will see in these days.

josejachuf commented 2 years ago

Hi @de-vri-es , I get an error when it starts, before sending any data to the port

'main' panicked at 'called Result::unwrap() on an Err value: Kind(TimedOut)',

[For now I'm not controlling the errors ]

use std::io::{BufRead, BufReader, Write};
use serial2::SerialPort;

fn process_message(buffer_str: &str) {
    print!("{:?}", buffer_str );
}
fn main() {

    let port = SerialPort::open("/dev/cuaU0", 9600).unwrap();
    let mut port = BufReader::new(port);

    let mut buffer = Vec::with_capacity(128);

    loop {
        while port.read_until(b'\n', &mut buffer).unwrap() != 0 {
            let buffer_str = std::str::from_utf8(&buffer[..10]).unwrap();
            process_message(&buffer_str);
            port.get_mut().write_all(b"v\n").unwrap(); // Use `get_mut()` to write to the port.
            buffer.clear();
        }
    }
}
josejachuf commented 2 years ago

@de-vri-es For now I resolved, keeping the readings until the message is completed. But I want to do later to what you suggest

de-vri-es commented 2 years ago

You could also increase the timeout of the serial port with the set_read_timeout() function.

josejachuf commented 2 years ago

Ok. I will try this. Thanks very much