de-vri-es / serial2-rs

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

Having to communicate with a pretty old serial LON bus, requiring DTR/RTS Control #37

Open ronnybremer opened 2 weeks ago

ronnybremer commented 2 weeks ago

First of all, thank you for this crate and all the hard work going into it. Serial communication seems to pop up every now and then so being able to do it natively in Rust and across platforms is a major win.

In a community project I need to communicate with a fairly old LON bus, attached to a serial port under Windows. I was able to capture the initial port setup done by an outdated program and it seems to be pretty straight forward: 19200 baud, 8/n/1. However, I noticed two odd things when comparing it with the init sequences I have seen in the past:

  1. it requires DTR/RTS Control, which seems to have been used for asynchronous modems before RTS/CTS became standard for flow control
  2. it specifies a different Eof character: 0x00

Basically, the IOCTL_SERIAL_SET_HANDFLOW structure should look like this:

 ControlHandShake=1
  FlowReplace=64
  XonLimit=0
  XoffLimit=4096

According to the Windows SDK this resembles a DTR Control handshake and a RTS Control flow control.

I am not sure which role the Eof character would play, as the default is 0x1A and each character sequence read from the serial LON bus ends in a 0x0D.

Would it be possible to achieve these settings with this crate?

de-vri-es commented 2 weeks ago

Hey!

In principle you should be able to talk to the device. People did report some issues on Windows though, which tend to be difficult to debug since you need the right combination of hardware to test it.

That said,

  1. it requires DTR/RTS Control, which seems to have been used for asynchronous modems before RTS/CTS became standard for flow control

Do you mean DTR/DSR flow control? Or do you mean DTR/DSR and RTS/CTS flow control at the same time? To my knowledge there is no "normal" flow control mechanism using DTR/RTS.

Currently, serial2 only supports RTS/CTS flow control and XON/XOFF flow control. In most scenarios, you don't actually need DTR/DSR flow control because a modern Linux device can practically always keep up with the other side of the line. So you could simply set the DTR line high with set_dtr() directly after opening the port. That may be enough already.

However, I do think it would be nice to implement full support for DTR/DSR flow control. Sadly, when I started on serial2 I didn't read up on flow control enough. Instead, I just adapted the API from existing crates. The end result is that right now, you can only enable one type of flow control using set_flow_control(). In reality, it should be possible to enable RTS/CTS, DTR/DSR and even XON/XOFF flow control independently from eachother.

There is a pretty straight-forward fix though: we can deprecate get/set_flow_control() and add individual functions for get/set_rts_cts_flow_control(), get/set_dtr_dsr_flow_control() and get/set_xon_xoff_flow_control() (maybe the setter for XON/XOFF should include configuration for the XON and XOFF characters too).

In the mean time, if setting DTR high is not enough, you can also manually implement hardware flow control using set_dtr() and read_drs(). If you have a strange non-standard flow control mechanism that needs other signals, you can also use set_rts() and read_cts().

Another option is to use the windows and/or linux feature to gain raw access as_raw_dcb_mut() and as_raw_termios().

But I would like to implement first class support for your use case. So please let me know what you think about all this rambling and what seems to make sense for you :)

  1. it specifies a different Eof character: 0x00

serial2 doesn't do anything special with the data stream itself. So the choice of a character signaling the end of a message/record/transmission is totally up to you.

Basically, the IOCTL_SERIAL_SET_HANDFLOW structure should look like this:

Note that we use SetCommState with a DCB struct instead of doing manual IOCTLs. I assume behind the scenes SetCommState translates to a bunch of IOCTL syscalls, but I'm not 100% sure.

Either way, I would prefer to stick with SetCommState if possible. That seems to be intended as user facing documentation, while the IOCTL documentation seems to be meant for driver implementations.

de-vri-es commented 2 weeks ago

Ah.. Unix doesn't support hardware flow control for DTR/DSR... Only allows you to do it manually..

So I guess that's why other libraries don't expose an option to enable it ^_^

Do you think that simply setting DTR high after opening will be sufficient for you?

ronnybremer commented 2 weeks ago

Thank you @de-vri-es for your response.

This really isn't a topic I need to work with very often, so I am just scratching the surface here.

Do you mean DTR/DSR flow control? Or do you mean DTR/DSR and RTS/CTS flow control at the same time? To my knowledge there is no "normal" flow control mechanism using DTR/RTS.

Do be honest, I did some read-up on flow control and handshake protocols on serial devices yesterday and I honestly cannot answer that question. For me it also looks suspicious as it's neither Software nor Hardware. Basically I reverse engineered it from the IOCTL calls I could observe.

So your assumption might be absolutely correct, it's probably neither and just used in this way. Data is flowing in and out pretty slowly (8 to 30 characters in size per message, one every 100 ms or so), so I will see if I can get away without any flow control.

However, I do think it would be nice to implement full support for DTR/DSR flow control. Sadly, when I started on serial2 I didn't read up on flow control enough. Instead, I just adapted the API from existing crates. The end result is that right now, you can only enable one type of flow control using set_flow_control(). In reality, it should be possible to enable RTS/CTS, DTR/DSR and even XON/XOFF flow control independently from eachother.

Thats what I read too, all three types of flow control can be used together, individually or not at all. All have their specific purpose so it seems.

But I would like to implement first class support for your use case. So please let me know what you think about all this rambling and what seems to make sense for you :)

I really appreciate your assistance :) From what I can see right now I need to test with the physical box attached and work from there. Since my goal is to migrate from Windows to Raspberry PI I can rule out all Windows specifics soon. But before I do that I wanted to make sure the communication works with the same hardware we use right now before changing to many moving parts.

I cannot even say if this is really a use case or if it is just a specific way to initialize the serial port, resulting in the hand flow structure posted above. Since I can observe setting DTR and RTS to on before sending the first chuck of data and no more changes to the signaling lines after that, I will try to use the same approach. No flow control and manually raising those signals before sending the first data.

  1. it specifies a different Eof character: 0x00

serial2 doesn't do anything special with the data stream itself. So the choice of a character signaling the end of a message/record/transmission is totally up to you.

I see. Though I am not sure if the Windows or Linux drivers are interpreting this character for some reason. Thats why I wanted to set it to 0x00. During my first test I will leave them untouched.

Either way, I would prefer to stick with SetCommState if possible. That seems to be intended as user facing documentation, while the IOCTL documentation seems to be meant for driver implementations.

Fully agree, we are at user level and should stay there. The serial port monitor I am using is showing the actual driver interaction with the serial port, that's why I don't see what's called within the Windows API.

I will do some testing this week and keep you posted.

ronnybremer commented 2 weeks ago

Ah.. Unix doesn't support hardware flow control for DTR/DSR... Only allows you to do it manually..

So I guess that's why other libraries don't expose an option to enable it ^_^

Makes a lot of sense :)

de-vri-es commented 2 weeks ago

Alright, let me know if you get stuck somewhere.

It may also be easier to get communication to work on a RPi than on Windows. There were some reports from people that seem to suggest not all Windows drivers can mimic Unix behavior on read (return any available data instead of blocking until the entire buffer is full).

But I can't really confirm these reports either, since it could have been flow control settings or wiring issues instead of driver bugs.

If you really do need hardware flow control with the DTR/DSR lines, you could always make a cable where RTS/CTS on one side are wired to DTR/DSR on the other side :upside_down_face: This seems to be what most people suggest with Linux.