Skip to content

Commit a8e4398

Browse files
Merge branch 'serialport:main' into impl-sync
2 parents 953107a + ec256f0 commit a8e4398

File tree

8 files changed

+167
-42
lines changed

8 files changed

+167
-42
lines changed

.github/workflows/ci.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches:
66
- main
77
push:
8+
# Check for new issues from updated dependencies once a week (Friday noon).
9+
schedule:
10+
- cron: "0 12 * * 5"
811
workflow_dispatch:
912

1013
jobs:
@@ -102,6 +105,43 @@ jobs:
102105
target: x86_64-unknown-netbsd
103106
toolchain: "1.59.0"
104107

108+
# --------------------------------------------------------------------------
109+
# Semantic Versioning
110+
#
111+
# Check at least once per platform as we heavily depend on platform-specific
112+
# code. The feature groups are used for attempting to cover different
113+
# backends for a platform (like Linux with and without libudev).
114+
115+
semver-aarch64-apple-darwin:
116+
runs-on: ubuntu-latest
117+
strategy:
118+
matrix:
119+
target:
120+
- aarch64-apple-darwin
121+
- arm-linux-androideabi
122+
- x86_64-pc-windows-msvc
123+
- x86_64-unknown-freebsd
124+
- x86_64-unknown-linux-gnu
125+
- x86_64-unknown-netbsd
126+
feature-group:
127+
- "only-explicit-features"
128+
- "all-features"
129+
steps:
130+
- run: |
131+
# TODO: Harmonize with build.yaml
132+
sudo sed -i 's/azure.archive.ubuntu.com/archive.ubuntu.com/' /etc/apt/sources.list
133+
sudo apt-get -qq update
134+
sudo apt-get -qq -y install build-essential curl git pkg-config libudev-dev
135+
- uses: actions/checkout@v2
136+
- uses: dtolnay/rust-toolchain@stable
137+
with:
138+
target: ${{ matrix.target }}
139+
- uses: Swatinem/rust-cache@v2
140+
- uses: obi1kenobi/cargo-semver-checks-action@v2
141+
with:
142+
rust-target: ${{ matrix.target }}
143+
feature-group: ${{ matrix.feature-group }}
144+
105145
# --------------------------------------------------------------------------
106146
# cargo-deny
107147

CHANGELOG.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,27 @@ project adheres to [Semantic Versioning](https://semver.org/).
1313
### Removed
1414

1515

16+
## [4.7.0] - 2025-01-13
17+
18+
### Changed
19+
20+
* Enumerate ports from more subsystems on Linux without libudev.
21+
[#238](https://github.com/serialport/serialport-rs/pull/238)
22+
* Set data terminal ready (DTR) signal when opening a port by default and allow
23+
to customize this behavior through the builder.
24+
[#239](https://github.com/serialport/serialport-rs/pull/239)
25+
26+
### Fixed
27+
28+
* Retry flushing data on `EINTR` up to the ports read/write timeout.
29+
[#225](https://github.com/serialport/serialport-rs/pull/225)
30+
31+
1632
## [4.6.1] - 2024-12-01
1733

1834
### Fixed
1935

20-
* Pin subdependeny `libc` to maintain compatibility with MSRV 1.59.0.
36+
* Pin subdependency `libc` to maintain compatibility with MSRV 1.59.0.
2137
[#229](https://github.com/serialport/serialport-rs/pull/229)
2238

2339

@@ -477,7 +493,8 @@ Unreleased, happened due to a user error using `cargo-release`.
477493
* Initial release.
478494

479495

480-
[Unreleased]: https://github.com/serialport/serialport-rs/compare/v4.6.1...HEAD
496+
[Unreleased]: https://github.com/serialport/serialport-rs/compare/v4.7.0...HEAD
497+
[4.7.0]: https://github.com/serialport/serialport-rs/compare/v4.6.1...v4.7.0
481498
[4.6.1]: https://github.com/serialport/serialport-rs/compare/v4.6.0...v4.6.1
482499
[4.6.0]: https://github.com/serialport/serialport-rs/compare/v4.5.1...v4.6.0
483500
[4.5.1]: https://github.com/serialport/serialport-rs/compare/v4.5.0...v4.5.1

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "serialport"
3-
version = "4.6.2-alpha.0"
3+
version = "4.7.1-alpha.0"
44
authors = [
55
"Bryant Mairs <[email protected]>",
66
"Jesse Braham <[email protected]>",

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ expose additional platform-specific functionality use the platform-specific stru
2727
`TTYPort` for POSIX systems and `COMPort` for Windows.
2828

2929
Serial enumeration is provided on most platforms. The implementation on Linux using `glibc` relies
30-
on `libudev`, an external dynamic library that will need to be available on the system the final
31-
binary is running on. Enumeration will still be available if this feature is disabled, but won't
32-
expose as much information and may return ports that don't exist physically. However this dependency
33-
can be removed by disabling the default `libudev` feature:
30+
on `libudev` (unless you disable the default `libudev` feature), an external dynamic library that
31+
will need to be available on the system the final binary is running on. Enumeration will still be
32+
available if this feature is disabled, but won't expose as much information and may return ports
33+
that don't exist physically. However this dependency can be removed by disabling the default
34+
`libudev` feature:
3435

3536
```shell
3637
$ cargo build --no-default-features

src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ pub struct SerialPortBuilder {
331331
stop_bits: StopBits,
332332
/// Amount of time to wait to receive data before timing out
333333
timeout: Duration,
334+
/// The state to set DTR to when opening the device
335+
dtr_on_open: Option<bool>,
334336
}
335337

336338
impl SerialPortBuilder {
@@ -393,6 +395,26 @@ impl SerialPortBuilder {
393395
self
394396
}
395397

398+
/// Set data terminal ready (DTR) to the given state when opening the device
399+
///
400+
/// Note: On Linux, DTR is automatically set on open. Even if you set `dtr_on_open` to false,
401+
/// DTR will be asserted for a short moment when opening the port. This can't be prevented
402+
/// without kernel modifications.
403+
#[must_use]
404+
pub fn dtr_on_open(mut self, state: bool) -> Self {
405+
self.dtr_on_open = Some(state);
406+
self
407+
}
408+
409+
/// Preserve the state of data terminal ready (DTR) when opening the device. Your outcome may
410+
/// vary depending on the operation system. For example, Linux sets DTR by default and Windows
411+
/// doesn't.
412+
#[must_use]
413+
pub fn preserve_dtr_on_open(mut self) -> Self {
414+
self.dtr_on_open = None;
415+
self
416+
}
417+
396418
/// Open a cross-platform interface to the port with the specified settings
397419
pub fn open(self) -> Result<Box<dyn SerialPort>> {
398420
#[cfg(unix)]
@@ -837,6 +859,11 @@ pub fn new<'a>(path: impl Into<std::borrow::Cow<'a, str>>, baud_rate: u32) -> Se
837859
parity: Parity::None,
838860
stop_bits: StopBits::One,
839861
timeout: Duration::from_millis(0),
862+
// By default, set DTR when opening the device. There are USB devices performing "wait for
863+
// DTR" before sending any data and users stumbled over this multiple times (see issues #29
864+
// and #204). We are expecting little to no negative consequences from setting DTR by
865+
// default but less hassle for users.
866+
dtr_on_open: Some(true),
840867
}
841868
}
842869

src/posix/enumerate.rs

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -550,46 +550,56 @@ cfg_if! {
550550
u8::from_str_radix(&read_file_to_trimmed_string(dir, file)?, 16).ok()
551551
}
552552

553-
fn read_usb_port_info(device_path: &Path) -> Option<SerialPortType> {
554-
let device_path = device_path
553+
fn read_port_type(path: &Path) -> Option<SerialPortType> {
554+
let path = path
555555
.canonicalize()
556556
.ok()?;
557-
let subsystem = device_path.join("subsystem").canonicalize().ok()?;
557+
let subsystem = path.join("subsystem").canonicalize().ok()?;
558558
let subsystem = subsystem.file_name()?.to_string_lossy();
559559

560-
let usb_interface_path = if subsystem == "usb-serial" {
561-
device_path.parent()?
562-
} else if subsystem == "usb" {
563-
&device_path
564-
} else {
565-
return None;
566-
};
567-
let usb_device_path = usb_interface_path.parent()?;
560+
match subsystem.as_ref() {
561+
// Broadcom SoC UARTs (of Raspberry Pi devices).
562+
"amba" => Some(SerialPortType::Unknown),
563+
"pci" => Some(SerialPortType::PciPort),
564+
"pnp" => Some(SerialPortType::Unknown),
565+
"usb" => usb_port_type(&path),
566+
"usb-serial" => usb_port_type(path.parent()?),
567+
_ => None,
568+
}
569+
}
570+
571+
fn usb_port_type(interface_path: &Path) -> Option<SerialPortType> {
572+
let info = read_usb_port_info(interface_path)?;
573+
Some(SerialPortType::UsbPort(info))
574+
}
575+
576+
fn read_usb_port_info(interface_path: &Path) -> Option<UsbPortInfo> {
577+
let device_path = interface_path.parent()?;
568578

569-
let vid = read_file_to_u16(&usb_device_path, &"idVendor")?;
570-
let pid = read_file_to_u16(&usb_device_path, &"idProduct")?;
579+
let vid = read_file_to_u16(&device_path, "idVendor")?;
580+
let pid = read_file_to_u16(&device_path, "idProduct")?;
571581
#[cfg(feature = "usbportinfo-interface")]
572-
let interface = read_file_to_u8(&usb_interface_path, &"bInterfaceNumber");
573-
let serial_number = read_file_to_trimmed_string(&usb_device_path, &"serial");
574-
let product = read_file_to_trimmed_string(&usb_device_path, &"product");
575-
let manufacturer = read_file_to_trimmed_string(&usb_device_path, &"manufacturer");
576-
Some(SerialPortType::UsbPort(UsbPortInfo {
582+
let interface = read_file_to_u8(&interface_path, &"bInterfaceNumber");
583+
let serial_number = read_file_to_trimmed_string(&device_path, &"serial");
584+
let product = read_file_to_trimmed_string(&device_path, &"product");
585+
let manufacturer = read_file_to_trimmed_string(&device_path, &"manufacturer");
586+
587+
Some(UsbPortInfo {
577588
vid,
578589
pid,
579590
serial_number,
580591
manufacturer,
581592
product,
582593
#[cfg(feature = "usbportinfo-interface")]
583594
interface,
584-
}))
595+
})
585596
}
586597

587598
/// Scans `/sys/class/tty` for serial devices (on Linux systems without libudev).
588599
pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
589600
let mut vec = Vec::new();
590601
let sys_path = Path::new("/sys/class/tty/");
591602
let dev_path = Path::new("/dev");
592-
let mut s;
593603
for path in sys_path.read_dir().expect("/sys/class/tty/ doesn't exist on this system") {
594604
let raw_path = path?.path().clone();
595605
let mut path = raw_path.clone();
@@ -599,16 +609,16 @@ cfg_if! {
599609
continue;
600610
}
601611

602-
let port_type = read_usb_port_info(&path).unwrap_or(SerialPortType::Unknown);
603-
604-
path.push("driver_override");
605-
if path.is_file() {
606-
s = String::new();
607-
File::open(path)?.read_to_string(&mut s)?;
608-
if &s == "(null)\n" {
609-
continue;
610-
}
611-
}
612+
// Determine port type and proceed, if it's a known.
613+
//
614+
// TODO: Switch to a likely more readable let-else statement when our MSRV supports
615+
// it.
616+
let port_type = read_port_type(&path);
617+
let port_type = if let Some(port_type) = port_type {
618+
port_type
619+
} else {
620+
continue;
621+
};
612622

613623
// Generate the device file path `/dev/DEVICE` from the TTY class path
614624
// `/sys/class/tty/DEVICE` and emit a serial device if this path exists. There are

src/posix/tty.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::mem::MaybeUninit;
22
use std::os::unix::prelude::*;
33
use std::path::Path;
4-
use std::time::Duration;
4+
use std::time::{Duration, Instant};
55
use std::{io, mem};
66

77
use nix::fcntl::{fcntl, OFlag};
@@ -183,14 +183,23 @@ impl TTYPort {
183183
termios::set_termios(fd.0, &termios)?;
184184

185185
// Return the final port object
186-
Ok(TTYPort {
186+
let mut port = TTYPort {
187187
fd: fd.into_raw(),
188188
timeout: builder.timeout,
189189
exclusive: true,
190190
port_name: Some(builder.path.clone()),
191191
#[cfg(any(target_os = "ios", target_os = "macos"))]
192192
baud_rate: builder.baud_rate,
193-
})
193+
};
194+
195+
// Ignore setting DTR for pseudo terminals (indicated by baud_rate == 0).
196+
if builder.baud_rate > 0 {
197+
if let Some(dtr) = builder.dtr_on_open {
198+
port.write_data_terminal_ready(dtr)?;
199+
}
200+
}
201+
202+
Ok(port)
194203
}
195204

196205
/// Returns the exclusivity of the port
@@ -441,8 +450,25 @@ impl io::Write for TTYPort {
441450
}
442451

443452
fn flush(&mut self) -> io::Result<()> {
444-
nix::sys::termios::tcdrain(self.fd)
445-
.map_err(|_| io::Error::new(io::ErrorKind::Other, "flush failed"))
453+
let timeout = Instant::now() + self.timeout;
454+
loop {
455+
return match nix::sys::termios::tcdrain(self.fd) {
456+
Ok(_) => Ok(()),
457+
Err(nix::errno::Errno::EINTR) => {
458+
// Retry flushing. But only up to the ports timeout for not retrying
459+
// indefinitely in case that it gets interrupted again.
460+
if Instant::now() < timeout {
461+
continue;
462+
} else {
463+
Err(io::Error::new(
464+
io::ErrorKind::TimedOut,
465+
"timeout for retrying flush reached",
466+
))
467+
}
468+
}
469+
Err(_) => Err(io::Error::new(io::ErrorKind::Other, "flush failed")),
470+
};
471+
}
446472
}
447473
}
448474

src/windows/com.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ impl COMPort {
9393
dcb::set_flow_control(&mut dcb, builder.flow_control);
9494
dcb::set_dcb(handle, dcb)?;
9595

96+
if let Some(dtr) = builder.dtr_on_open {
97+
com.write_data_terminal_ready(dtr)?;
98+
}
99+
96100
com.set_timeout(builder.timeout)?;
97101
com.port_name = Some(builder.path.clone());
98102
Ok(com)

0 commit comments

Comments
 (0)