Coder Social home page Coder Social logo

Comments (6)

mlsvrts avatar mlsvrts commented on May 26, 2024 2

@craigkai Windows COMMTIMEOUTS are extremely cursed, and I'm nearly certain they're at fault here.

On Windows, if you try to read 'n' bytes from a serial port no data will be returned until**:

  1. A timeout has occurred
  2. Your buffer has been filled

In theory, you could set your read timeout to be 1ms to get only kind of bad latency, but in practice this just doesn't work and you're looking at a minimum timeout of 10+ milliseconds. So. What can you do?

  1. Read one byte at time, and just loop until you read your terminator. You can pretty much do this by using a BufReader with capacity of 1.

  2. Know how much data your responses will contain, and don't try to read_until until at least 'n' bytes are available. This results in a lot of syscalls while you busy-poll the available bytes, but lower latency. You could achieve this a few ways:
    a. Looking up the response length base on passed command from a table
    b. Adding some fixed-length header to your response that contains the message length
    c. Probably some other ways...

  3. Number 1, but use async so that you don't need to busy wait for each byte. (This is actually how PuTTy waits for data). You can make this work with mio-serial and tokio-serial, but not without hacking NamedPipe to have a 1-byte buffer

**Actually, when I told you this I was lying; Windows only behaves like this for certain configurations of COMMTIMEOUTS. Feel free to refer to this table (credit: Carsten Andrich)

Denomination termios COMMTIMEOUTS POSIX behavior Windows behavior
Polling read MIN = 0 and TIME = 0 ReadIntervalTimeout = MAXDWORD, ReadTotalTimeoutMultiplier = 0, and ReadTotalTimeoutConstant = 0 Always returns immediately Like POSIX
Blocking read MIN = 1 and TIME = 0 ReadIntervalTimeout = 0, ReadTotalTimeoutMultiplier = 0, and ReadTotalTimeoutConstant = 0 Returns when at least 1 byte is available Returns only when buffer is full
Blocking read MIN = 1 and TIME = 0 ReadIntervalTimeout = 1, ReadTotalTimeoutMultiplier = 0, and ReadTotalTimeoutConstant = 0 Returns when at least 1 byte is available Like POSIX, but presumably extra delay
Read with timeout MIN = 0 and TIME > 0 ReadIntervalTimeout = MAXDWORD, ReadTotalTimeoutMultiplier = MAXDWORD, and ReadTotalTimeoutConstant > 0 Returns when at least 1 byte is available or timeout occurs Similar to POSIX, but first ReadFile() returns only 1 byte and second ReadFile() returns the remainder
Read with timeout MIN = 0 and TIME > 0 ReadIntervalTimeout = 1, ReadTotalTimeoutMultiplier = 0, and ReadTotalTimeoutConstant > 0 Returns when at least 1 byte is available or timeout occurs Like POSIX, but presumably extra delay

from serialport-rs.

craigkai avatar craigkai commented on May 26, 2024 1

This has been so helpful, thank you so much for all the assistance!!

from serialport-rs.

mlsvrts avatar mlsvrts commented on May 26, 2024

Fast BufReader example (loopback port):

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

fn main() {
    let mut port = serialport::new("COM3", 9600)
        .timeout(std::time::Duration::from_millis(500))
        .open()
        .expect("failed to open port");

    let reader = port.try_clone()
        .expect("failed to clone port");

    port.write_all(&[0xDE, 0xAD, 0xBE, 0xEF, 0x00])
        .expect("failed to write to port");

    // Create the slow bufreader
    let mut buf = vec![];
    let mut b_reader = BufReader::with_capacity(100, reader);

    let now = std::time::Instant::now();
    if let Err(e) = b_reader.read_until(0x00, &mut buf) {
        println!("Slow reader error: {:?}", e);
    }

    println!("slow read time: {}ms", now.elapsed().as_millis());

    // Fast bufreader
    port.write_all(&[0xDE, 0xAD, 0xBE, 0xEF, 0x00])
        .expect("failed to write to port");
    
    let mut b_reader = BufReader::with_capacity(1, b_reader.into_inner());

    let now = std::time::Instant::now();
    if let Err(e) = b_reader.read_until(0x00, &mut buf) {
        println!("Fast reader error: {:?}", e);
    }

    println!("fast read time: {}ms", now.elapsed().as_millis());
}

Which gives me:

slow read time: 515ms
fast read time: 19ms

from serialport-rs.

DanielJoyce avatar DanielJoyce commented on May 26, 2024

To me this perhaps implies we need to refactor things so this is more apparent to the user. Windows behavior is apparently commonly counterintuitive.

That or the default windows serial port config needs to be changed to better mirror POSIX behavior and this information added to the docs.

from serialport-rs.

larsch avatar larsch commented on May 26, 2024

I have suggested a change to use the POSIX-like blocking read (with early return on ANY data) and timeout in #79. This fixes the general case of of read blocking until the buffer is full. It only makes it slightly more CPU intensive as it will return as much as in the FIFO any time the read call is invoked, but for serial port speeds, this is irrelevant.

This change is necessary for any serial port protocol that uses request/response scheme where the size of packets is unknown in advance, or when using mio-seriel/tokio-serial where you can't tell the underlying library how much data you want to read.

from serialport-rs.

jerrywrice avatar jerrywrice commented on May 26, 2024

I've just started using serialport-rs crate, but I also require the POSIX-like (new) blocking read() behavior that larsch mentions in his post. I would hope this could be incorporated as a new Windows specific feature that a developer can explicitly enable (opt into). Ideally this could be added as a non-breaking change, by defining additional functions or methods to activate it. Thus it would only be 'new' user code that activates the feature.

from serialport-rs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.