tokio-serial icon indicating copy to clipboard operation
tokio-serial copied to clipboard

Issues using SerialPort via Async(Read/Write)Ext

Open jangernert opened this issue 3 years ago • 2 comments

I'm trying to port an existing C# application that talks to scales to rust. I went all in on async and tokio so I thought this crate would be a good fit. Currently only the windows build has been tested with a scale connected to a serial port.

It seems to me like the serial port is not really waiting for read/write/flush to finish. Some example code still with debugging modifications:

async fn read_bytes(&self, number_of_bytes: usize) -> Result<Vec<u8>, Error> {
    let serial_port_mutex = self.port.as_ref().ok_or_else(|| {
        let msg = "Failed to read bytes: Serial Port not initialized";
        log::error!("{}", msg);
        Error::new(ErrorKind::NotConnected, msg)
    })?;
    log::info!("bytes to write: {:?}", serial_port_mutex.lock().await.bytes_to_write().unwrap());
    log::info!("bytes to read: {:?}", serial_port_mutex.lock().await.bytes_to_read().unwrap());
    let mut buffer = vec![0; number_of_bytes];
    serial_port_mutex.lock().await.read_exact(&mut buffer).await.map_err(|e| {
        log::warn!("Failed to read {} bytes: {}", number_of_bytes, e);
        e
    })?;
    log::info!("buffer: {:?}", buffer);
    Ok(buffer)
}

async fn write_bytes(&self, buffer: &[u8]) -> Result<(), Error> {
    let serial_port_mutex = self.port.as_ref().ok_or_else(|| {
        let msg = "Failed to write bytes: Serial Port not initialized";
        log::error!("{}", msg);
        Error::new(ErrorKind::NotConnected, msg)
    })?;
    serial_port_mutex.lock().await.write_all(buffer).await.map_err(|e| {
        log::error!("Failed to write bytes to serial port");
        e
    })?;
    serial_port_mutex.lock().await.flush().await.map_err(|e| {
        let msg = format!("Failed to flush serial port: {}", e);
        log::error!("{}", msg);
        Error::new(ErrorKind::BrokenPipe, msg)
    })?;
    // while serial_port_mutex.lock().await.bytes_to_write().unwrap() > 0 {
    //      tokio::time::delay_for(tokio::time::Duration::new(0, 20 * 1000000)).await;
    // }
    Ok(())
}

After writing a message to the scale, then flushing and trying to read the response of the scale the serial port indicates to still not have written the bytes from the out buffer to the port: bytes to write: 15. The read then fails with: Failed to read 1 bytes: The I/O operation has been aborted because of either a thread exit or an application request. (os error 995) Trying to force the program to wait until all bytes have been written with delay_for and then forcing it to wait again until there are bytes to read doesn't fix the problem either. Then the serial port indicates that there are no bytes to write and some bytes to read. But the read operation fails with the same error as before.

I know the only sample is using this framed de/encoder thing. But I'd rather directly write and read some bytes to the port.

jangernert avatar Jul 14 '20 07:07 jangernert

First off the serial port is a lot slower than the code writing to it. It's possible the flush has written the bytes and you're trying to read before the scale has even responded yet. There is a lot less overhead in the rust side compared to C#.

The whole write / flush / read cycle is likely happening before the scale has even responded.

Also the behavior of serial ports is different on windows vs Linux.

In Linux a timeout on a serial port is how long to wait to try to read even a single byte.

On windows a serial timeout is how long to wait to before returning with data. So even if data is available windows will wait the full timeout to try and fill buffers then return. Of course this can be changed with settings.

So the question is what OS are you on and what are the other settings.

DanielJoyce avatar Oct 30 '21 22:10 DanielJoyce