Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't seem to be able to read data on Windows #29

Open
jessebraham opened this issue Feb 7, 2022 · 15 comments
Open

Can't seem to be able to read data on Windows #29

jessebraham opened this issue Feb 7, 2022 · 15 comments
Labels
migrated This issue was migrated from GitLab windows This issue is specific to Windows systems

Comments

@jessebraham
Copy link
Member

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/113

This code works on Linux, and I think it used to work on Windows as well, if I remember correctly, I might have used a different version of the crate. But it does not work with 4.0.1.

The code:

use std::thread;

use serialport::{available_ports, SerialPortType};
use std::time::Duration;

fn main() {
    let mut buf = [0u8; 256];

    'retry: loop {
        let ports = available_ports().unwrap_or_default();
        let ports: Vec<_> = ports
            .iter()
            .filter(|p| match &p.port_type {
                SerialPortType::UsbPort(info) => info.vid == 0x1209 || info.pid == 0x6969,
                _ => false,
            })
            .collect();

        // we haven't found any ports, wait a bit, and try again
        if ports.len() == 0 {
            println!("no ports found, retrying");
            thread::sleep(Duration::from_millis(1000));
            continue;
        }

        let port_info = ports[0];
        let found_port = serialport::new(&port_info.port_name.to_owned(), 115200)
            .timeout(Duration::from_millis(5000))
            .open();

        let mut found_port = match found_port {
            Ok(port) => port,
            Err(err) => {
                println!("error opening port: {}, retrying", err);
                thread::sleep(Duration::from_millis(1000));
                continue;
            }
        };

        println!("opened {}", port_info.port_name);

        loop {
            // we should always have a port at this time
            let n = match found_port.read(&mut buf[..]) {
                Ok(n) => n,
                Err(err) => {
                    println!("error while reading: {}, reconnecting", err);

                    //if err.kind() == io::ErrorKind::TimedOut {
                    //    continue;
                    //}

                    thread::sleep(Duration::from_millis(1000));
                    continue 'retry;
                }
            };

            println!("got {} bytes of data", n);
        }
    }
}

In case the timeout is enabled, the read always times out, even though data is sent several times a second. If timeout is disabled, read hangs indefinitely.

Using a buffer of 1byte didn't seem to solve the issue either.

Using other applications for reading the serial port works just fine.

Edit in case it might matter:

This code is cross-compiled from linux. It's receiving binary data from an usb dongle with firmware also written in rust, using https://github.com/mvirkkunen/usbd-serial which emulates a CDC-ACM device. The serial settings are correct, as previously mentioned, reading works on both linux with this exact same code, and in windows using other programs.

I've also tested both https://github.com/jacobsa/go-serial and https://github.com/tarm/serial, both work just fine in Windows when cross-compiled. Here's the code I've used to test:

package main

import (
	"io"
	"log"

	"github.com/jacobsa/go-serial/serial"
)

func main() {
	s, err := serial.Open(serial.OpenOptions{
		PortName:              "COM3",
		BaudRate:              115200,
		DataBits:              8,
		StopBits:              1,
		ParityMode:            serial.PARITY_NONE,
		InterCharacterTimeout: 500,
		MinimumReadSize:       5000, // timeout?
	})
	if err != nil {
		panic(err)
	}

	log.Println("opened port")

	for {
		n, err := io.CopyN(io.Discard, s, 256)
		log.Println(n, err)
	}
}

I get a continuous stream of 256bytes logs, no timeouts, because the device keeps sending data.

@jessebraham jessebraham added the migrated This issue was migrated from GitLab label Feb 7, 2022
@jessebraham jessebraham added the windows This issue is specific to Windows systems label Mar 2, 2022
@vladimire-chen
Copy link

Is there a solution to this problem now?

@mlsvrts
Copy link
Contributor

mlsvrts commented Nov 15, 2022

@vladimire-chen Do you see the same behavior if you use PR #55?

@vladimire-chen
Copy link

let mut data_buf = vec![0;1024]; 
while port.bytes_to_read().unwrap() < 1024{
    thread::sleep(Duration::from_millis(1));
}
println!("{}", port.bytes_to_read().unwrap());

port.read(&mut data_buf[..]).unwrap();

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: TimedOut, error: "Operation timed out" }', src\lib\serial.rs:64:42
stack backtrace:

@mlsvrts I explicitly waited until the number of bytes reached my requirement before performing the read operation, but it still times out, which makes me wonder

@DamenH
Copy link

DamenH commented Feb 24, 2023

@mlsvrts I am also seeing this issue with the above code, including with #55, in addition to some code I tried with a minimum test of windows-sys (as opposed to winapi). I can open the COM port, and I see that the program is actually holding the port (other programs can't access the port while the program is running), but I only receive timeouts when doing read operations.

Programs such as putty and Tera Term work as I'd expect. Also using packages like serialport for NodeJS work too.

@kiranshila
Copy link

I'm also experiencing this issue, also with a USB CDC-ACM device

@Wouter-Badenhorst
Copy link

Wouter-Badenhorst commented Apr 20, 2023

Currently experiencing this issue when trying to read data from RPI Pico through serial. Always reports 0 bytes to read, but serial output can be read from other serial monitoring tools.

fn main() {
    let mut port = serialport::new("COM7", 115200)
        .timeout(std::time::Duration::from_millis(1000))
        .parity(serialport::Parity::None)
        .stop_bits(serialport::StopBits::One)
        .flow_control(serialport::FlowControl::None)
        .data_bits(serialport::DataBits::Eight)
        .open()
        .unwrap();

    loop {


        let buffer_bytes = port.bytes_to_read().unwrap();

        println!("{:?}", buffer_bytes);

        // Read data from the serial port
        let mut buffer: Vec<u8> = vec![0; 100];
        let bytes_read = port.read(buffer.as_mut_slice()).unwrap();
        let message = String::from_utf8_lossy(&buffer[..bytes_read]);
        println!("Received: {}", message);
    }
}

@LukaOber
Copy link

I just opened a PR with a possible fix to the problem. If you could check it out and test if this fixes your problems, it would be much appreciated.

@Wouter-Badenhorst
Copy link

I just opened a PR with a possible fix to the problem. If you could check it out and test if this fixes your problems, it would be much appreciated.

Tested and worked for use with RPI Pico

@Krahos
Copy link

Krahos commented May 31, 2023

Continuing here the discussion opened on PR #94 (see my comment here), since it seems a more appropriate place. A colleague used the same USB device on the same port, with the .net SerialPort API and with the code I wrote using this crate. The settings are exactly the same, but the behaviour is different. I'm looking at the source code of the .NET framework here to see if there is any difference in the implementation, but I'm not sure exactly where to look at. In the mean time, if anyone has any suggestions I'd be happy to test the fixes and open a pull request.

@mlsvrts
Copy link
Contributor

mlsvrts commented May 31, 2023

Continuing here the discussion opened on PR #94 (see my comment here), since it seems a more appropriate place. A colleague used the same USB device on the same port, with the .net SerialPort API and with the code I wrote using this crate. The settings are exactly the same, but the behaviour is different. I'm looking at the source code of the .NET framework here to see if there is any difference in the implementation, but I'm not sure exactly where to look at. In the mean time, if anyone has any suggestions I'd be happy to test the fixes and open a pull request.

You should check if/how .NET is setting windows COMTIMEOUTS; my guess this is the root of your issue.

It's also my understanding that .NET can/will set DTR differently from this library. The simplest thing to do is measure and compare the state of the various flow-control serial pins to determine if this is your issue -- I have seen some USB CDC serial implementations that have 'wait for DTR' or other such flow control.

USB CDC host-side can signal both DTR and RTS to the device driver.

Edit:

You can also try mode COMX /STATUS for checking the sideband signal states from a command prompt.

@Krahos
Copy link

Krahos commented Jun 6, 2023

A little update:
I couldn't find any fundamental difference between the .NET library and this crate. However, on another Windows machine, the software works with most devices. Just a couple of them don't work.
The firmware guy also specified that our serial ports are virtual, so the flow control should be managed by the USB protocol, however he's not sure.

The simplest thing to do is measure and compare the state of the various flow-control serial pins to determine if this is your issue

About this, I have no idea how to do it.

Also, if I don't set a timeout, the call hangs indefinitely.

What's weird about all this is that it works for most devices on most machines, but I can't figure out what's the root cause of the issue for those combinations of USB devices and Windows machines where it doesn't.

@bastian2001
Copy link

I checked on two Windows machines (Windows 10 and 11), using three different devices each: an Arduino Leonardo (USB controller integrated into ATmega32u4), an Arduino Nano (legit FTDI chip), and an RP2040 (Raspberry Pi Pico, also with CDC). All six configurations fail. Both with my code that runs this crate, as well as another person's code (Youtube Tutorial, cloned their GH repo and ran it), who also uses this crate. Is there anything I can do, like tell cargo to use the PR #94 rather than the latest?

@tbeckley
Copy link

tbeckley commented Nov 30, 2023

Another voice here. Interfacing with a commercial instrument using I think a FTDI chipset. I can read the first time I run the program after plugging in the device, but not subsequent times (I get the timeout). I tried PR#94 build (for anyone else looking you can use serialport = { git = "https://github.com/serialport/serialport-rs", rev = "310ab9c64b7bbeaea52255a7a4059f2d798c8214" } in your cargo.toml) and it didn't fix the issue for me.

Turns out you can restore your serial port access for one call per program with a workaround. I've worked around it using the 4.2.2 release with a python script that opens and releases the serial port before the rust program runs.

import serial

with serial.Serial("COM4", 1200, timeout=2) as ser:
    _val = ser.readline()

Then I just run python scripts\reset_serial.py && cargo run to do my debugging. Not great by a long shot but it works.

@tbeckley
Copy link

Further investigation reveals this workaround doesn't always work. Seems to work if your rust program runs quick enough (before there's any more data in the serial buffer). Yes, seriously. If your program takes longer to build then it just hangs. I've moved to calling the python script in a process window inside rust just before I make my read calls and that seems to work more reliably.

@mbwilding
Copy link

mbwilding commented Dec 22, 2023

We need an option to toggle on DTR (Data Terminal Ready). I tested in C# and that was the only way I could get responses from the device I was trying talking to. By default it is off in the standard dotnet serial library.

EDIT: Going to try this:

    pub fn open_via_port(&mut self, port: &str) -> Result<()> {
        let port_settings = serialport::new(port, 115_200)
            .data_bits(serialport::DataBits::Eight)
            .flow_control(serialport::FlowControl::None)
            .parity(serialport::Parity::None)
            .stop_bits(serialport::StopBits::One)
            .timeout(Duration::from_secs(5));

        let mut port = port_settings.open().map_err(|e| {
            let err_msg = format!("Failed to open serial port: {} ({:?})", &port, e);
            error!("{}", err_msg);
            anyhow!(err_msg)
        })?;

        port.write_data_terminal_ready(true)?;

        self.port = Some(port);

        Ok(())
    }

EDIT: This worked for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
migrated This issue was migrated from GitLab windows This issue is specific to Windows systems
Projects
None yet
Development

No branches or pull requests