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

Fix: Unable to read data on windows. Fixes #29 #94

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

LukaOber
Copy link

This PR is changing 4 things.

  1. Set fDtrControl to true
  2. Set fRtsControl to true
  3. Change the ReadEventCache
  4. Clears the Input and Output Buffer after initializing the port

Changes 1, 2 and 3 should fix the issue of not being able to read data on Windows system. I tested this code with three different types of microcontrollers (Teensy, Arduino, ESP-32) and they all worked. The 4th change is not needed for reading, but I think clearing the buffers after initialization is the correct behavior.

All the changes I made are based on the default values for the port initialization in pyserial.

As far as I can tell, these changes bring no downsides, but I am not too familiar with Windows systems or this crate.

This PR is based on zstewar1 5.0 proposal as it is the branch I am personally working with.

zstewar1 and others added 19 commits February 23, 2022 17:16
Change SerialPort to a struct, and move the implementation to a "sys"
module which selects between different possible "real" implementations.
Add a public posix module containing a posix-specific SerialPortExt
trait and the BreakDuration type.
Get the windows build working. Remove the windows-specific extensions
module because windows doesn't have any methods not available on posix.
One of the CI toolchains uses an older version of rustc, which doesn't
include as_deref, so we just do the same thing that as_deref does
internally.
Implementing the extension in the posix extension mod ensures it will
appear in documentation even if documentation is built on windows, since
that mod is cfg(doc).
Current timeouts are based on
[this comment from !78](https://gitlab.com/susurrus/serialport-rs/-/merge_requests/78#note_343695538),
copying the windows timeout settings needed to get posix-like timeout
behavior. That is, reads should return available data ASAP regardless of
timeouts, but stop early when there is a timeout.
EventCache will hold the HANDLE for a read/write when not in use or will
produce a new handle for each thread when multiple threads try to
read/write at the same time. The cache holds a single value and will
deallocate extra handles when there is already a handle stored. We have
one cache for a read_handle and one cache for a write_handle.

The expectation is that in the normal case, at most one thread will be
reading/writing at a time, and so at most one handle will be created for
each of read and write. But in abnormal cases, when multiple threads try
to read/write at the same time, we auto-create and auto-close extra
handles as needed.

Fix the into_raw_handle/into_raw_fd to drop other fields that were
previously leaked.
@Wouter-Badenhorst
Copy link

Fixed communication with Raspberry Pi Pico when using this branch.

@eldruin
Copy link
Contributor

eldruin commented Apr 27, 2023

Could you rebase this to the current state of the main branch?

@LukaOber
Copy link
Author

LukaOber commented May 4, 2023

Sure. But I will probably not get to it until June, as I am currently travelling for work and don't have access to a Windows Computer.

@kinire98
Copy link

kinire98 commented May 26, 2023

I tested with my program with this PR rather than with the crates.io version and it solved my problem of timeouts and now it works perfectly. Only thing it seems to read nothing from the buffer, don't know if it's my fault.

@sirhcel
Copy link
Contributor

sirhcel commented May 26, 2023

The 4th change is not needed for reading, but I think clearing the buffers after initialization is the correct behavior.

This sounds reasonable to me. I have little experience on Windows but that what I would expect on a POSIX system.

@Krahos
Copy link

Krahos commented May 30, 2023

It doesn't solve the timeouts problems described on #29 for me. Weirdly enough, the 4.2 version was working fine up until yesterday. The microcontroller I have is STM32L476QEI6.

@sirhcel
Copy link
Contributor

sirhcel commented May 30, 2023

It doesn't solve the timeouts problems described on #29 for me.

We seem to be on the short-handed side of Windows developers here. Could you help us to look into this by explaining your communication scenario and sharing the code you are experiencing this issue with with us?

Weirdly enough, the 4.2 version was working fine up until yesterday. The microcontroller I have is STM32L476QEI6.

This sounds strange indeed. What does your actual dependencies in Cargo.lock with respect to serialport-rs look like? We released 4.2.1 last week and maybe this causes an issue. Could you give us an example of the communication taking place in this case?

@mlsvrts
Copy link
Contributor

mlsvrts commented May 30, 2023

So, I'm not sure I agree with the changes proposed by this MR:

1/2) I think it's correct to leave DTR/RTS disabled by default -- not all hardware expects and uses these signals. I haven't looked too closely, but if we don't have support for setting these flow control signals, I would be in favor of an MR to add a common API to allow it. I do think that could be part of a 5.0 milestone. However, simply changing how they are initialized for windows does not seem like the correct solution.

  1. I haven't looked into this at all :)

  2. I'm not sure that it's always correct to clear the input/output buffers on init. Consider a scenario where I'm polling for a USB-Serial bootloader to enumerate, then attaching and reading the log -- clearing input buffers could cause me to miss the start of the log. I think users calling an API to clear buffers/providing a port builder option for it is a more robust solution.

@mlsvrts
Copy link
Contributor

mlsvrts commented May 30, 2023

It doesn't solve the timeouts problems described on #29 for me. Weirdly enough, the 4.2 version was working fine up until yesterday. The microcontroller I have is STM32L476QEI6.

I think the changes in this MR will only help for hardware that is blocking data transmission on RTS/DTR. If you are able to read some data, but your reads always timeout, it's more likely that you're fighting with the windows COMTIMEOUTS configuration.

@Krahos
Copy link

Krahos commented May 30, 2023

We seem to be on the short-handed side of Windows developers here. Could you help us to look into this by explaining your communication scenario and sharing the code you are experiencing this issue with with us?

Happy to help if I can.
I have a USB device developed in house, which exposes 2 serial ports. One is used to send commands to the device, the other one is used to read data. I'm posting here the whole interface with the device, the rest of the software is just an abstraction over this, so I don't think it's relevant:

//! This module provide raw access to the mela sensor through the Read and Write traits.

use std::{
    io::{Read, Write},
    time::Duration,
};

use anyhow::Context;
use serialport::{SerialPort, SerialPortType};

/// This trait is used as an abstraction over the `RawMela` type.
pub trait MelaSensor: Read + Write + Sized {
    /// Used as initialization for the handler.
    fn new() -> Result<Self, SensorError>;
}

/// This object represents the mela network. All interactions with the network use this.
pub struct RawMela {
    command_port: CommandPort,
    data_port: DataPort,
}

impl MelaSensor for RawMela {
    /// Looks for and opens a mela sensor connected to the machine.
    fn new() -> Result<Self, SensorError> {
        let port_names = Self::enumerate().context("Serial port enumeration failed")?;
        if port_names.len() < 2 {
            return Err(SensorError::Ports);
        }
        let (command_port, data_port) =
            Self::open(&port_names[0], &port_names[1]).context("Opening serial ports failed")?;

        Ok(RawMela {
            command_port,
            data_port,
        })
    }
}

impl Read for RawMela {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.data_port.as_mut().read_exact(buf)?;
        Ok(buf.len())
    }
}

impl Write for RawMela {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.command_port.as_mut().write_all(buf)?;
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

impl RawMela {
    /// Scans the USB ports of the system and returns their name, if at least one mela sensor is connected to them.
    fn enumerate() -> anyhow::Result<Vec<String>> {
        let ports = serialport::available_ports()
            .context("Serial port crate failed to return available ports")?;

        let mut mela_ports = Vec::new();

        for port in ports {
            if let SerialPortType::UsbPort(info) = port.port_type {
                if info.vid == 291 && info.pid == 1 {
                    mela_ports.push(port.port_name)
                }
            }
        }

        Ok(mela_ports)
    }

    /// Opens the USB ports of a mela sensor.
    fn open(command_port: &str, data_port: &str) -> anyhow::Result<(CommandPort, DataPort)> {
        let command = serialport::new(command_port, 115200)
            .timeout(Duration::from_secs(15))
            .open()
            .context("Failed to open command port")?;

        let data = serialport::new(data_port, 115200)
            .timeout(Duration::from_secs(15))
            .open()
            .context("Failed to open data port")?;

        Ok((CommandPort(command), DataPort(data)))
    }
}

/// The command port used to send commands to the sensor.
pub struct CommandPort(Box<dyn SerialPort>);

impl AsMut<Box<dyn SerialPort>> for CommandPort {
    fn as_mut(&mut self) -> &mut Box<dyn SerialPort> {
        &mut self.0
    }
}

/// The data port used to read data from the sensor.
pub struct DataPort(Box<dyn SerialPort>);

impl AsMut<Box<dyn SerialPort>> for DataPort {
    fn as_mut(&mut self) -> &mut Box<dyn SerialPort> {
        &mut self.0
    }
}

/// The possible errors that can occur when using the raw mela handler.
#[derive(Debug, thiserror::Error)]
pub enum SensorError {
    /// This variant represents the case where the data port or the command port or both weren't found.
    #[error("not enough ports found, mela sensor might not be connected")]
    Ports,
    /// Error opening or using the serial port.
    #[error("there was an error opening the serial ports")]
    SerialPort(#[from] anyhow::Error),
}

This is the code using the 4.2 version, the timeout occurs the first time I attempt to call the read. I also tried various combinations of write data terminal ready, write request to send and flow control, but same result.

This sounds strange indeed. What does your actual dependencies in Cargo.lock with respect to serialport-rs look like? We released 4.2.1 last week and maybe this causes an issue. Could you give us an example of the communication taking place in this case?

In my cargo.lock I have this:

[[package]]
name = "serialport"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12"
dependencies = [
 "CoreFoundation-sys",
 "IOKit-sys",
 "bitflags",
 "cfg-if",
 "libudev",
 "mach 0.3.2",
 "nix",
 "regex",
 "winapi",
]

It's the same for the branch using this pull request, with the only difference that the version and source are different of course.

To add more context, The timeout issue used to happen only on a few of these USB devices, so I couldn't figure out the steps to reproduce, now it seems to happen to all the 4 devices I have here.
Usually I specify the dependency as serialport=4.2, but just tried with version 4.0, 4.1 and to pin point to 4.2.1 and the issue still persists, so I don't think it's a regression of the last patch.

The device works properly if operated through PuTTY on Windows and through the software on -nix, so the issue has to be somewhere in Windows implementation, unless I'm missing something.

Let me know what else I can do to help.

edit: @mlsvrts maybe I should move this comment to the issue discussion instead? Since this pull request might not be relevant.

@jerrywrice
Copy link

jerrywrice commented Jun 8, 2023

I have the same concern as mlsvrts. When used the DTR/RTS control lines are often specific to a given (hardware) device. Typically computer to computer serial communications, especially with USB-serial cables, do not expect these lines to be active.

I'm new to this forum, but I assume there are discussions concerning (potentially) exposing more of these types of configuration settings in a future version. It's a problem when one must rely upon default settings which are not exposed from the public module interface.

@thewh1teagle
Copy link

I can confirm that it works for me and fixed the problem and now I can read.
does it gonna merged?

@Pythomancer
Copy link

Hello!
I had the same issue. It appears that the read fails with the Timed Out message when the the buffer (Vec::new()) is empty.
I used the following:
let mut data = vec![0; 200];
That resolved the issue for me, ie. non-empty intake buffer, 200-long u8 buffer in my case. It seems to me that the Timed Out message is not very helpful for this particular failure case.
Best of luck.

@akauppi
Copy link

akauppi commented Feb 18, 2024

@Pythomancer wrote:

read fails with the Timed Out message when the the buffer (Vec::new()) is empty

I, too, was bit by this. Had code like:

let mut buf: Vec<u8> = Vec::with_capacity(15);   // does not work!
let n = port.read(&mut buf).unwrap_or_else( ...

It would be nice to get at least the buffer fix (from this PR?) merged. As @Pythomancer wrote "the Timed Out message is not very helpful for this particular failure case." I agree.

@eleroy
Copy link

eleroy commented Mar 12, 2024

Hello everyone,

I've been using this branch for a while now, is there any reason why it is not merged yet ?

Thank you

@eleroy
Copy link

eleroy commented May 15, 2024

Hello everyone,

I've been using this branch for a while now, is there any reason why it is not merged yet ?

Thank you

My bad, I just made some changes in my code:

serial.write_data_terminal_ready(true)
serial.write_request_to_send(true)

It made everything worked on my usb cdc link with the current version.

@RossSmyth
Copy link
Contributor

This PR is very large and should be split into many different ones. It is also unsound wrt to provenence as currently implemented.

This PR is doing too many things at once and make it hard to review. I would split this in to

  1. Making fDtrControl & fRtsControl true
  2. Adding the new EventCache (which is currently unsound atm)
  3. Clearing the buffer
  4. Making the objects thread safe
  5. Moving the files around to a sys directory
  6. Changing the API and v5 bump

Or something like that. Many of the changes are not mentioned in the PR description. I also do not agree with some of the change as mlsvrts & jerrywrice have expressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.