de-vri-es / serial2-rs

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

Works on Windows? #7

Closed kinire98 closed 7 months ago

kinire98 commented 1 year ago

I need to make sure this library works on Windows, because I tried using serialport-rs and I always get a "Operation timed out" error when I try to read fro mthe buffer. They have a working PR in wait to be merged but I don't know how much it will take and I need to finish the project in less than two weeks, so I need to make sure this works before considering to use it. Have you tested it in Windows? Thanks in advance :)

de-vri-es commented 1 year ago

I did test it, it should work. Although someone did report an isue with a particular serial device on windows recently.

kinire98 commented 1 year ago

Yeah, I read it. It was about a Windows serial port to USB adapter, in theory that should be handled by the drivers, ¿right? About the issue, thx for answering. I'll take a look on it tomorrow, and I will tell you. Thx again

kinire98 commented 1 year ago

Hey, I already tested it. I run into some problems. This is the code:

fn check_if_active(&mut self) -> bool {
        let write_buffer: &[u8] = &[255, 255, 255, 255];
        let mut read_buffer = vec![0; 16];
        println!("Generic write in {} port", self.name());
        self.port
            .write_all(write_buffer)
            .expect(stringify!("Write in port {} failed: ", self.name())); 
        println!("After generic write in {} port", self.name());

        thread::sleep(Duration::from_millis(75));
        println!("Generic read in {} port", self.name());
        let read = self.port
                    .read(&mut read_buffer); 
        println!("After generic read in {} port", self.name());
        match read {
            Ok(n) => {
                green!("There wasn't an error");
                if is_zero(&read_buffer) {
                    red_ln!("Didn't read anything");
                    prnt!();
                    return false;
                } else {
                    green_ln!("Read data. Something connected");
                    prnt!();
                    return true;
                }
            },
            Err(e) => {
                red_ln!("ERROR in check if active");
                prnt!();
                println!("Something went wrong while reading in the port {}: {}", self.name(), e);
            },
        }
        println!("Buffer: {:?}", read_buffer);
        false
    }

(Sorry if there is to much boilerplate in the code, I used that for debugging)
It halts in the read from the port buffer because there is no timeout. That would be a nice thing to add. I don't know if there is something wrong with the version of Windows I'm using, the device I'm testing, my code, the computer or the winapi. Because I also tried lots of other crates, the most popular serialport-rs, gave me the same error with the platform specific implementation or the platform independent, and gave me a timeout (no matter what I tried, always a timeout when reading). I don't know if it's a me problem, because I saw people in the serialport-rs repository sumbit two separate PRs and an issue in order to solve this problem. Issue:

PRs (both waiting for approval):

I don't know if you can solve this, because they mantainers said they will look it on june, and I need this to be finished by June 7, and I don't know if it will be ready by that time

de-vri-es commented 1 year ago

Have you tried SerialPort::set_read_timeout()?

That should give a timeout error when the read fails due to the timeout. It will not report a zero-sized read, because that indicates that the stream closed on Unix platforms.

kinire98 commented 1 year ago

Ok, I will try it and I tell you.

kinire98 commented 1 year ago

I tested it with the timeouts of 100milis and it gave me a timeout error, in every single read for every single device. I know it's not about the timeout, because if it were for that without timeout it should have worked. I know (I remembered this now, sorry) that it is not the device connected, the cable, Windows, the PC or the port, because I tried with a piece of software which came with one of the devices and was able to connect to it without much trouble

de-vri-es commented 1 year ago

Interesting, thanks for testing. Can you tell me what serial device you are using? Maybe I can debug things myself too.

kinire98 commented 1 year ago

Ok, the Port driver is the Windows one. And the device is an industrial IoT one, it's called an ICPCon 7065D, but it uses an RS485 interface so I also have to use a converter to convert an RS232 interface to RS485. The converter is called ICPCon I7520. The device that responds is the 7065D and it uses the DCON protocol. This devices are expensive and I'm using them provided by the company I'm working in.
I know that the problem is not with it cause the company that developes them made a program called DCON Utility Pro that allows you to test them and it worked without any issue. So I don't think that is the problem.
The code to get the stream of bytes to get the address of the device (if you read the documentation of the DCON protocol it tells you that you can make a network with the RS485 interface) is:

let write_buffer: &[u8] = "$FFID0000".as_bytes();
kinire98 commented 1 year ago

I tested this PR #94 of the serialport-rs library and it fixed my problem, it no longer timesout. Here's the link to the fork with the code if it helps you solve this problem LukaOber/serialport-rs

de-vri-es commented 1 year ago

Thanks for the extra information. It appears that the problem you are experiencing is due to hardware flow control.

By default, no hardware flow control options are modified by this library. You can enable or disable hardware flow control with Settings::set_flow_control().

I'm not sure if this will cover your needs, because hardware flow control is hard to find proper documentation for.

Do you know if your converter and/or device use a type of hardware flow control?

de-vri-es commented 1 year ago

Note that you could also bypass normal hardware flow control (just keep it disabled or explicitly disable it) and then manually set the Data Terminal Ready and/or Request To Send signals:

https://docs.rs/serial2/latest/serial2/struct.SerialPort.html#method.set_dtr https://docs.rs/serial2/latest/serial2/struct.SerialPort.html#method.set_rts

kinire98 commented 1 year ago

The device I use only need TX, RX and GND, even the cable doesn't use it. So, is Hardware control flow enabled by default? I think I disabled it. I can't look into it right now, because I'm not at work until Monday but I will keep you updated. Note: The devices I have to identify with the program use no flow control and are disabled in Windows. In that case, which is a list of things I could try in order to see if it works?

de-vri-es commented 1 year ago

Hmm, strange. Then I really don't see why the PR you linked fixes your use case. Unless for some weird reason hardware flow control is enabled by default. That would be weird though.

But you should be able to replicate what that PR did by disabling flow control and setting the RTS and DTR lines manually.

So even though it seems odd, what I would test is:

let mut serial = SerialPort::open(device_path, baud_rate)?;
serial.set_dtr(true)?;
serial.set_rts(true)?;

If that works, I would also try with only one of DTR and RTS enabled, just to figure out which one(s) are really needed.

If that doesn't work, the problem seems to be elsewhere. But this behaviour may be a weird quirk of the windows driver. Who knows..

de-vri-es commented 1 year ago

It may also have be caused by the fDsrSensitivity field. I just released 0.1.9 and 0.2.0 where this setting is disabled by default (in 0.2.0 you have to call settings.make_raw() if you pass a closure to SerialPort::open() to configure it).

kinire98 commented 1 year ago

I mean it is a really weird thing, because I use the code for that PR and the read no longer timesout, but it doesn't read from the buffer. Port settings:

Baud rate: 9600
Data bits: 8
Parity: None
Stop bits: 1
Flow control: None

With these it should work, but it doesn't fill the buffer. I don't know (with the PR) if now it's my fault, because the command I send through the line doesn't work.
Sorry if I'm being repetitive or something, but I don't really understand what you are saying, this really goes out of my scope of knowledge. I studied sysadmin (but like programming on my own) and they made me develop this program without any help, and really have no idea of what I'm doing ¯_(ツ)_/¯

de-vri-es commented 1 year ago

I'll try my best to help :) I also want to fix any potential issues in the library.

let mut serial = SerialPort::open(device_path, baud_rate)?;
serial.set_dtr(true)?;
serial.set_rts(true)?;

Did you try this? Also, please make sure to update to 0.1.10 or 0.2.1. I did add some fixes.

kinire98 commented 1 year ago

Ok, so I did that. The code:

use serial2::*;
use std::thread;
use std::time::Duration;
fn main() -> Result<(), std::io::Error> {
        println!("Open port");
        let mut serial = SerialPort::open("COM1", 9600)?;
        println!("Extract Settings");
        let mut settings = serial.get_configuration()?;
        settings.set_raw();
        println!("Set settings");
        serial.set_configuration(&settings)?;
        println!("Setting timeouts");
        serial.set_read_timeout(Duration::from_millis(100))?;
        serial.set_write_timeout(Duration::from_millis(100))?;
        println!("SET DTR and RTS");
        serial.set_dtr(true)?;
        serial.set_rts(true)?;
        println!("Writing");
        serial.write("$FFID0000".as_bytes())?;
        println!("Flushing the buffer");
        serial.flush()?;
        thread::sleep(Duration::from_millis(100));
        let mut vector = Vec::new();
        println!("Reading from the buffer");
        serial.read(&mut vector)?;
        println!("{:?}", vector);
        Ok(())
}

and I get this output:

Open port
Extract Settings
Set settings
Setting timeouts
SET DTR and RTS
Writing
Flushing the buffer
Reading from the buffer
Error: Kind(TimedOut)

It gives me a Timeout, again. I don't know what could be the problem. And a little question: what does the flush method exactly do? I've seen it in various libraries and I can't understand it very well

de-vri-es commented 1 year ago

It gives me a Timeout, again. I don't know what could be the problem.

Hmm, for now you have me stumped too. I will need to find a way to reproduce this for myself if I want to say anything sensible.

And a little question: what does the flush method exactly do? I've seen it in various libraries and I can't understand it very well

The flush function waits for the data written with write() to be sent to the UART. Without it, data could still be sitting in a kernel buffer, waiting to be transmitted.

kinire98 commented 1 year ago

Ok, if it serves you I'm using a custom made PC (provided by the company) with Windows 10 IoT 2021 (A trimmed version of Windows that cuts bloatware only sold to companies for the PCs they use in their products). If you find a solution or a test you want me to do, I'll only be here until June 7th, so up until that point I can test things, but after that I won't be able to test anything

kinire98 commented 1 year ago

I remebered something now. Every library I tried, had this issue (read timeout or if not, not reading anything), don't know why. So maybe it's not your fault. Maybe it's because the winapi is broken. List of libraries I tried:

Worth mentioning (I guess), that none of them are async, because as async in Rust isn't very well polished yet, I thought it wasn't a good idea (don't know if I was being a fool here)

DanielJoyce commented 1 year ago

Windows has really weird IO Port behavior that have unexpected behavior in the common case.

You can make some settings cheanges to the config so serial ports on windows work more like Posix

One thing would be look at how PySerial ( which is pretty robust ) does it.

Here is previous discussion on a windows fix

https://gitlab.com/susurrus/serialport-rs/-/merge_requests/78

Info on commtimeouts

https://learn.microsoft.com/en-us/windows-hardware/drivers/serports/setting-read-and-write-timeouts-for-a-serial-device

https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts

It seems page has settings that will give you posix like settings:

https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts#remarks

If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and less than MAXDWORD, one of the following occurs when the ReadFile function is called:

RossSmyth commented 9 months ago

It would be very cool if serial on Windows could work since currently none of the serial crates work on Windows (I did not try the async ones since I don't need it). I posted in https://github.com/serialport/serialport-rs/pull/94 and how that PR does not fix my issue.

de-vri-es commented 9 months ago

Yeah, agreed. What would really help me is something simple to reproduce though. A not-too-expensive serial converter I can buy that exhibits the problem so I can test it.

With the hardware I have at home, this crate works on Windows.

de-vri-es commented 9 months ago

I did just release 0.2.5 (and 0.2.6) with changes to the timeout parameters. Maybe this will solve the problem for you.

You may also need to set the DTR and RTS signals after opening the port, depending on the device you are talking too, and maybe even depending on the Windows driver for your serial port.

RossSmyth commented 9 months ago

Ok. I posted the code and hardware in the PR but I will test it again to make sure I'm not crazy.

I tested it with RealTerm with these settings: image and it works as expected.

The Rust code does the same thing. Send a packet of data and then check the response. This is with serial2 "0.2.6".

use std::io::Read;
use std::time::Duration;
use serial2::SerialPort;

const GET_STATUS: &[u8] = &[0x02];
const STATUS_NOMINAL: &[u8] = &[0x32];

fn main() {
    let mut port = SerialPort::open("COM6", 9600).expect("Failed to open COM port");
    port.set_read_timeout(Duration::from_millis(100)).unwrap();

    let mut read_buf = Vec::new();

    port.write_all(GET_STATUS).expect("Write failed!");

    port.read_to_end(&mut read_buf).unwrap();

    assert!( read_buf.as_slice() == STATUS_NOMINAL);

    read_buf.clear();
}

and it fails with the panic message:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Kind(TimedOut)', src\main.rs:18:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

and just to be through here it is will RUST_BACKTRACE="full"

RUST_BACKTRACE=full ``` thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Kind(TimedOut)', src\main.rs:18:37 stack backtrace: 0: 0x7ff648f003ac - std::sys_common::backtrace::_print::impl$0::fmt at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\sys_common\backtrace.rs:44 1: 0x7ff648f106bb - core::fmt::rt::Argument::fmt at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\core\src\fmt\rt.rs:138 2: 0x7ff648f106bb - core::fmt::write at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\core\src\fmt\mod.rs:1094 3: 0x7ff648efe53f - std::io::Write::write_fmt at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\io\mod.rs:1714 4: 0x7ff648f0015b - std::sys_common::backtrace::_print at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\sys_common\backtrace.rs:47 5: 0x7ff648f0015b - std::sys_common::backtrace::print at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\sys_common\backtrace.rs:34 6: 0x7ff648f021aa - std::panicking::default_hook::closure$1 at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panicking.rs:269 7: 0x7ff648f01dff - std::panicking::default_hook at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panicking.rs:288 8: 0x7ff648f0285e - std::panicking::rust_panic_with_hook at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panicking.rs:705 9: 0x7ff648f0274d - std::panicking::begin_panic_handler::closure$0 at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panicking.rs:597 10: 0x7ff648f00d29 - std::sys_common::backtrace::__rust_end_short_backtrace at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\sys_common\backtrace.rs:151 11: 0x7ff648f02450 - std::panicking::begin_panic_handler at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panicking.rs:593 12: 0x7ff648f15915 - core::panicking::panic_fmt at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\core\src\panicking.rs:67 13: 0x7ff648f15d43 - core::result::unwrap_failed at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\core\src\result.rs:1651 14: 0x7ff648ef3795 - enum2$ >::unwrap at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be\library\core\src\result.rs:1076 15: 0x7ff648ef1eab - SerialTester::main at C:\Users\rsmyth\Documents\SerialTester\src\main.rs:18 16: 0x7ff648ef10ab - core::ops::function::FnOnce::call_once > at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be\library\core\src\ops\function.rs:250 17: 0x7ff648ef3b9e - std::sys_common::backtrace::__rust_begin_short_backtrace > at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be\library\std\src\sys_common\backtrace.rs:135 18: 0x7ff648ef3b9e - std::sys_common::backtrace::__rust_begin_short_backtrace > at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be\library\std\src\sys_common\backtrace.rs:135 19: 0x7ff648ef21e1 - std::rt::lang_start::closure$0 > at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be\library\std\src\rt.rs:166 20: 0x7ff648efbed8 - std::rt::lang_start_internal::closure$2 at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\rt.rs:148 21: 0x7ff648efbed8 - std::panicking::try::do_call at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panicking.rs:500 22: 0x7ff648efbed8 - std::panicking::try at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panicking.rs:464 23: 0x7ff648efbed8 - std::panic::catch_unwind at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\panic.rs:142 24: 0x7ff648efbed8 - std::rt::lang_start_internal at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be/library\std\src\rt.rs:148 25: 0x7ff648ef21ba - std::rt::lang_start > at /rustc/5680fa18feaa87f3ff04063800aec256c3d4b4be\library\std\src\rt.rs:165 26: 0x7ff648ef1fb9 - main 27: 0x7ff648f142a8 - invoke_main at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 28: 0x7ff648f142a8 - __scrt_common_main_seh at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 29: 0x7ffaa9a47344 - BaseThreadInitThunk 30: 0x7ffaa9c826b1 - RtlUserThreadStart error: process didn't exit successfully: `target\debug\SerialTester.exe` (exit code: 101) ```

I am using an FTDI USB-RS485-WE.

RossSmyth commented 9 months ago

The board I am talking to does not require hardware flow control, but I can try playing with those settings next week as I am leaving work right now.

de-vri-es commented 9 months ago

Ah, I see one problem with your code: serial_port.read_to_end() will always terminate with a timeout. The serial port never reaches EOF, so read_to_end() will keep trying to read more data until finally a timeout occurs.

If you want to read one line of input, you can wrap the SerialPort in a BufReader and use BufRead::read_until().

Or it looks like maybe you just want to read exactly one byte? Then you can just call:

let mut response = [0u8; 1];
port.read(&mut response)?;
RossSmyth commented 9 months ago

I modified the constants because it is a proprietary serial protocol, so it is reading more than one byte. But regardless you are correct. I changed it to

port.write_all(GET_STATUS).expect("Write failed!");

let mut response = [0; STATUS_NOMINAL.len()];

port.read_exact(&mut response).unwrap();

assert_eq!(response, STATUS_NOMINAL);

I am used to Python serial where you just called "port.readline()" and it will read until a timeout is raised. Which may be a useful API for testing, as it is sort of dual to just the normal read(). Read is good for more put together programs, but a readline() equivalent would be good for quicker put together test programs. Where read() reads a single byte or times out, this theoretical API would read as many bytes as possible until a timeout (or some size is reached). This is relevant to me because my serial streams do not have a consistent delimiter and length, which is pretty common in serial streams as many end in CRC byte(s). I can PR an API like that if desired.

Below is the Python I have used in the past.

port.write(to_send)

out_str = port.readline()

assert out_str == compare
de-vri-es commented 7 months ago

I believe Windows compatibility is properly fixed now, so I'm closing this issue.

This one is a bit long, so if there are still problems on Windows, let's open a new issue.