serialport / serialport-rs Goto Github PK
View Code? Open in Web Editor NEWA cross-platform serial port library in Rust. Provides a blocking I/O interface and port enumeration including USB device information.
License: Other
A cross-platform serial port library in Rust. Provides a blocking I/O interface and port enumeration including USB device information.
License: Other
Hi @Susurrus !
I know you tried some time ago to use cross
in order to run tests for other arch than Linux on Intel CPU using Gitlab CI.
I think it is now possible to do it since a recent fix has been done for that :)
I gave some instructions here on how I managed to use cross
in one of my project at work using Gitlab CI and I think it could help you doing it here.
The additional part still to do in addition to what is describe in these instructions is to provide a shared library of libudev for the correct architecture.
This could be taken from the debian packages for libudev1 and libudev-dev.
We could extract these into a folder inside /builds/${CI_PROJECT_PATH}
so that they will be accessible from the cross container and configure pkgconfig to search in the correct path as libudev-sys
rust wrapper requires pkgconfig.
What do you think about that ?
It can't identify the virtual com on windows. However, I didn't test on Linux.
Hello and thank you for a great library. I was using WSL to try and run your clear_input_buffer and received the error: Error: port '/dev/ttyS4' not available: Not a typewriter
. I then tried to run list_ports I received No ports found
I then ran the program on windows using the COM4 name and it ran successfully. Am i doing something wrong or is there something going on with WSL? (I am on Ubuntu 18.04 Windows 10 version 1809).
Also, I am able to cat /dev/ttyS4
and read input from WSL.
Thank you.
Note: It may be possible to return bytes instead of strings
let serial_list = serialport::available_ports();
match serial_list {
Ok(serial_list) => {
println!("{:?}", serial_list.len());
for s in serial_list {
println!(
"{} {}",
s.port_name,
match s.port_type {
// serialport::SerialPortType::UsbPort(usb_info) => usb_info.product.unwrap(),
serialport::SerialPortType::UsbPort(usb_info) => {
format!("{:?}", usb_info.product.unwrap())
}
serialport::SerialPortType::PciPort => String::from("PciPort"),
serialport::SerialPortType::BluetoothPort => String::from("BluetoothPort"),
serialport::SerialPortType::Unknown => String::from("Unknown"),
},
);
}
}
Err(e) => {
println!("{:?}", e);
return;
}
}
4
COM27 "USB �����豸 (COM27)"
COM11 "USB �����豸 (COM11)"
COM12 "JLink CDC UART Port (COM12)"
COM10 "USB-SERIAL CH340 (COM10)"
Like the title says, I was hoping for some input on the best way to make sure that a serial port connection is still valid? Thanks!
Hi there,
I just realised available_ports()
API doesn't work on my Mac, while it works fine on Ubuntu 20.04. The reason I guess is probably caused by the deprecation of IOUSBDevice, at here: https://gitlab.com/susurrus/serialport-rs/-/blob/master/src/posix/enumerate.rs#L185
According to Apple's documentation, it seems like we need to use IOUSBHostDevice instead: https://developer.apple.com/documentation/usbdriverkit/iousbhostdevice
Here's my system info:
and here is the ioctl -p USB -l
log, and as you can see, the class name is IOUSBHostDevice
:
+-o Soul Injector Programmer@01100000 <class IOUSBHostDevice, id 0x1000521d3, registered, matched, active, busy 0 (261 ms), retain 26>
{
"sessionID" = 2832745043567
"USBSpeed" = 1
"idProduct" = 32974
"iManufacturer" = 1
"bDeviceClass" = 239
"IOPowerManagement" = {"PowerOverrideOn"=Yes,"DevicePowerState"=2,"CurrentPowerState"=2,"CapabilityFlags"=32768,"MaxPowerState"=2,"DriverPower$
"bcdDevice" = 256
"bMaxPacketSize0" = 64
"iProduct" = 2
"iSerialNumber" = 3
"bNumConfigurations" = 1
"USB Product Name" = "Soul Injector Programmer"
"USB Address" = 1
"locationID" = 17825792
"bDeviceSubClass" = 2
"bcdUSB" = 512
"kUSBSerialNumberString" = "7cdfa1e03184c737520f150d0d38"
"kUSBCurrentConfiguration" = 1
"IOCFPlugInTypes" = {"9dc7b780-9ec0-11d4-a54f-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
"bDeviceProtocol" = 1
"USBPortType" = 0
"IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
"USB Vendor Name" = "Jackson Hu"
"Device Speed" = 1
"idVendor" = 12346
"kUSBProductString" = "Soul Injector Programmer"
"USB Serial Number" = "7cdfa1e03184c737520f150d0d38"
"IOGeneralInterest" = "IOCommand is not serializable"
"kUSBAddress" = 1
"kUSBVendorString" = "Jackson Hu"
}
For the target device I connected to my computers, it's an ESP32-S3 board running TinyUSB stack, acting as a CDC-ACM serial port and it shows up as /dev/ttyACMx
on Linux, or /dev/tty.usbmodem11xx
on macOS.
Regards,
Jackson
Hi,
I am developing a J2534 API Driver for Windows + Linux, using seralport-rs as a way to talk (Via serial) to a Arduino SAM like board (Macchina M2).
I am using the GIT version of serialport-rs.
I have a background thread that constantly polls the serial port for data, and stores any incoming data in a buffer of structs called COMM_MSGS
Then when a user application wants to, it can instruct the driver to write data to the device on a separate thread.
This process works fine on Linux and OSX, but experiences really odd behavior under Windows.
Issue with windows is that data appears to arrive late according to the library, causing timeouts on the driver side. Also, it appears with linux/OSX, I can set Flow Control to None, without any side effects, where as with windows, setting flow control to None results in the M2 not being able to send data back to the PC (Seems to be stuck in the M2's serial buffer). This can be somewhat fixed by setting flow control to Hardware, however then data arrives late under windows.
I have tried both Concrete types and the dynamic serial port trait, and both have the same effect, and quite honestly, don't know what to do, or what might be causing the issue.
You can view the source in question here: https://github.com/rnd-ash/MacchinaM2-J2534-Rust/blob/main/Driver/src/comm.rs
I am working on a Raspberry Pi 4 and trying to use a DMX interface with rust and serialport.
When listing available ports I noticed that the info is not collected properly.
The USB DMX Interface is by Enttec and called DMXIS. It is available through ttyUSB0 and plugged in through the onboard usb ports of the RaspberryPi.
When listing available Ports and printing the info I get the following output:
main.rs
use serialport::{available_ports, SerialPortType};
fn main() {
let ports = available_ports().expect("No ports found!");
for port in ports {
println!("- {}", port.port_name);
if let SerialPortType::UsbPort(usb_port_info) = &port.port_type {
if let Some(product) = &usb_port_info.product {
println!(" Product: {}", product);
}
if let Some(manufacturer) = &usb_port_info.manufacturer {
println!(" Manufacturer: {}", manufacturer);
}
if let Some(serial_number) = &usb_port_info.serial_number {
println!(" Serial number: {}", serial_number);
}
}
println!("");
}
}
Output:
> cargo run
Compiling rustydmx v0.1.0 (/home/pi/rustydmx)
Finished dev [unoptimized + debuginfo] target(s) in 2.04s
Running `target/debug/rustydmx`
- /dev/ttyUSB0
Product: VL805 USB 3.0 Host Controller
Manufacturer: VIA Technologies, Inc.
Serial number: ENVVVCOF
- /dev/ttyAMA0
However when I run a python script with pyserial I get the correct info:
> python3
>>> from serial.tools import list_ports
>>> port = list(list_ports.comports())
>>> for p in port:
... print("- " + p.device)
... print(" " + p.product)
... print(" " + p.manufacturer)
...
- /dev/ttyUSB0
DMXIS
ENTTEC
- /dev/ttyAMA0
My guess is that the serialport implementation confuses the actual serialport info with the data from the built in usb hub of the RaspberryPi.
One thing I noticed was that the serial_number
seemed to be correct at least it matched the number reported when executing udevadm info --query all --name /dev/ttyUSB0 --attribute-walk
Technical data:
> uname -a
Linux raspberrypi 5.15.32-v7l+ #1538 SMP Thu Mar 31 19:39:41 BST 2022 armv7l GNU/Linux
> lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
> dmesg
[13995.091232] usb 1-1.3: new full-speed USB device number 4 using xhci_hcd
[13995.235722] usb 1-1.3: New USB device found, idVendor=0403, idProduct=6001, bcdDevice= 6.00
[13995.235753] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[13995.235773] usb 1-1.3: Product: DMXIS
[13995.235792] usb 1-1.3: Manufacturer: ENTTEC
[13995.235809] usb 1-1.3: SerialNumber: ENVVVCOF
[13995.240163] ftdi_sio 1-1.3:1.0: FTDI USB Serial Device converter detected
[13995.240324] usb 1-1.3: Detected FT232RL
[13995.254635] usb 1-1.3: FTDI USB Serial Device converter now attached to ttyUSB0
> rustc -V
rustc 1.60.0 (7737e0b5c 2022-04-04)
Hi,
I just hit a strange bug. I defined a port this way
let port = serialport::new("/dev/ttyACM0", 115200);
let port = port.timeout(Duration::from_secs(5)).
data_bits(serialport::DataBits::Eight).
stop_bits(serialport::StopBits::One).
parity(serialport::Parity::None);
let mut port = port.open()?;
And had my device reading only zero and wrong number of bytes. Then I added .baud_rate(115200); after parity setting and it started working fine.
I am missing something here?
Hello. I would like use serial port library for RS485 communication.
Are you going to implement it?
Here is a library that sets the port to RS485 mode but is not maintained/developed by author:
https://github.com/mbr/rs485-rs
Hi @Susurrus ,
I am having issues trying to cross-compile the serialport crate. I've asked stackoverflow for assistance here a few days ago but have not had any suggestions or feedback. If you were able to provide any suggestions it would be greatly appreciated.
Regards,
Nocker
Opening this issue to discuss the possibility of moving towards more idiomatic Rust APIs in the future. The changes I'm suggesting would be significant API changes, so would require a major-version update to implement.
In the Rust standard library, types like File
and TcpStream
are implemented as struct
s with platform-dependent internals. In serialport
, the user has to decide whether to use platform dependent or platform agnostic types ahead of time when developing their application. This forces the developer into two possible paths which both have some noteworthy downsides.
Going the platform-agnostic route, you will be using Box<dyn SerialPort>
as your port type. This causes an unnecessary dynamic dispatch and makes it very difficult if you later discover you do need some platform specific functionality. Since the correct implementation of SerialPort on a given platform is always known at compile time, there will never be a case where client code actually needs dynamic dispatch to work with both TTYPort
and COMPort
polymorphically. If you start out using this platform-agnostic setup and later find out you need to set some platform-specific setting, you have to change all code leading to the spot where the platform-specific setting is used into platform-specific code. That is, Box<TTYPort>
is convertible to Box<dyn SerialPort>
, but because SerialPort
doesn't inherit from Any
or provide similar downcast methods, there's no way to get back a TTYPort
from dyn SerialPort
if you only need a platform-specific method in one place.
Going the platform-specific route, you will be using either TTYPort
or COMPort
, depending on platform. Because these two types have different names, this makes it fairly inconvenient to write platform-agnostic code for parts of your application that don't depend on a particular platform. You either have to use a bunch of generics or write your own platform-specific aliases of #[cfg(unix)] type Serial = TTYPort;
, etc.
This proposal is to follow the pattern of the standard library for the SerialPort
type: make the main type provided by the crate a concrete struct with opaque internals that vary by platform. Platform specific methods can either be provided directly on that type conditional on the platform, or can be provided by a platform-specific extension trait, similar to MetadataExt
.
One concrete type used for both platform-specific and platform-agnostic operations.
fn somewhere_down_in_my_app(port: &mut SerialPort) -> Result<()> {
// do some stuff...
// I was being platform agnostic, but now I need to do one platform-specific thing temporarily. NBD!
#[cfg(unix)] {
use serialport::unix::SerialPortExt;
port.set_exclusive(false)?;
}
// ...
Ok(())
}
Clients cannot provide their own implementations of SerialPort
.
As I see it, the core value of this crate is in providing an implementation of serial ports for Rust across several major platforms, not in providing a generic interface to be used across many external implementations. If there are other useful implementations of the SerialPort
trait, they could instead be added to this as additional platform-conditional implementations through pull-requests.
If there are clients that need to generically handle both the SerialPort
type and their own custom types, but where they don't have a fully-new implementation of SerialPort worth adding to this crate, then I would suspect that they could likely cover much of their functionality through other traits, either existing ones like Read
and Write
or their own custom traits implemented just for the shared functionality that they need, just as one could implement custom traits for File
.
Concrete types are harder to mock in tests.
I would suspect that a lot of client code that interacts with SerialPort
but wants to test against a "mock" would be reasonably served by being generic over either Read
, Write
, or Read + Write
, and then just using standard library types that implement those, such as Vec
or Cursor
.
That said, there is the possibility of needing to test interactions with actual serial-port features, such as setting baud rate, in an environment where you don't want to or can't open an actual port. So there is an actual case for having a trait that covers those methods. If that's a significant concernt, I would propose having a trait like SerialPortSettings
which provides all the methods for changing baud rate, etc. For convenience in the common use case I might still have the same methods available directly on the concrete SerialPort
struct (so they work without importing the trait), but having a settings trait available would allow for those kinds of tests, if you think that's a significant thing clients want to be able to do. I will note for example that TcpStream
provides the methods for setting timeouts directly on the struct, and if users want to use that generically to check it in tests, they have to create their own trait for it.
While the Read
and Write
traits both take &mut self
in arguments, many standard library types for working with files/sockets and similar objects implement Read
and Write
for both T
and &T
, thereby allowing both reads and writes even if one only has an immutable reference to the type.
I don't know if this would work safely for serial ports, since I don't know what the concurrency-safety of serial ports is across different operating systems. However, if it is possible to safely share the RawFd/RawHandle of a serial port across multiple threads, then I think it would be good to impl Read for &TTYPort
etc., as a way to allow easier concurrent use. It's certainly possible to use try_clone
/try_clone_native
to get multiple handles to the same serial port to share one port in multiple threads, but if a single handle could be shared between threads, that might be more convenient in some cases. For example, you could then share a serial port in an Arc
without having to make an OS-level copy of the handle every time you share it, or could share by simple reference-borrow in a case like crossbeam::scope
.
The proposed change to concrete types in particular would be a very significant change in API, even compared to the kinds of changes you've made in previous major-version bumps, so such a change is probably not to be made lightly on a crate with a couple hundred downloads per day.
The suggestion to use a concrete type is made from the perspective of coming here from the standard library, but I don't really have great context for why the library was designed this way in the first place, so let me know if there's strong reasons for setting up the library this way.
If any of this does seem like a good idea, I would be able to to put together pull requests for it.
Crate version is latest published (4.2.0)
I have a USB connected cell modem that enumerates in windows as 5 COM ports. On some computers, all of these are listed in device manager under "Ports (COM & LPT)" while on others, 2 of the ports only show up under "Modems".
In ^^, COM5 and COM7 are the "modem" ports
serialport::available_ports()
It would greatly simplify the hack I currently have in place to find the modem ports if those "modem" ports could also be listed
It would be awesome if serialport
would add opt-in support for the embedded-hal traits at https://docs.rs/embedded-hal/*/embedded_hal/serial/. It would allow drivers to be easily tested on a non-embedded platform like Linux.
Would you accept a MR that adds these impls behind a feature flag?
In particular, to be able to set flag PARMRK
in c_iflag
so that a BREAK is expressed as a sequence of control characters:
When neither
IGNBRK
norBRKINT
are set, a BREAK
reads as a null byte ('\0'
), except whenPARMRK
is set, in
which case it reads as the sequence\377 \0 \0
.
Hello
I have a code like this:
let port = serialport::new(port_name, baud_rate)
.timeout(Duration::from_millis(0)) // ----- NO TIMEOUT HERE
.open()
.expect("Error: ");
let mut port = BufReader::new(port);
let mut line_buffer = String::new();
match port.read_line(&mut line_buffer) {
Ok(size) => println!("{} bytes read -> {:?}", size, line_buffer),
Err(e) => eprintln!("Error while reading: {:?}", e),
}
It will never receive a line. Change the timeout to any value and the line will be received (apparently, when the timeout expires).
This behavior doesn't seem intuitive to me.
Hi, thanks for excellent library. I was just wondering if there is any reason you all couldn't cut a new release of the library? It looks like the last release was back in June, and it would be nice to have some of the fixes that have been merged in since then in a release version. (I am specifically interested in #58 😄 )
The current behavior of read
on an open serial port is that it always returns immediately, even when there is no data available in the input buffer. This may be in contradiction with the understanding here (from tty.rs).
It's also not consistent and not obvious how to make it consistent with the description from https://linux.die.net/man/3/cfmakeraw related to canonical/non-canonical modes. Please note that according to that explanation, raw mode does not guarantee that reads blocks if no data is available. Non-canonical mode with MIN > 0; TIME == 0 or MIN == 0; TIME > 0 or MIN > 0; TIME > 0.
Please clarify what is the intention of serialport-rs implementation, and how to make read block when no data is available for a configurable maximum timeout.
https://gitlab.com/susurrus/serialport-rs/-/blob/master/src/lib.rs#L11 references the prelude which no longer exists.
The winapi crate is no longer maintained, and Microsoft are now maintaining the windows-sys crate (among others), so we should migrate to this.
I'm working with a Raspberry Pi based PLC that has a RS485 port on a hat connected through I2C and some proprietary driver.
Long story short, it does not support termios2 / BOTHER (arbitrary baud rates) for some ports.
i.e., this version of set_baud_rate
is used and does not work, and the baud rate is always set to 0 baud (and falls back to 19200):
#[cfg(any(
target_os = "android",
all(
target_os = "linux",
not(any(
target_env = "musl",
target_arch = "powerpc",
target_arch = "powerpc64"
))
)
))]
fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
let mut termios2 = ioctl::tcgets2(self.fd)?;
termios2.c_cflag &= !nix::libc::CBAUD;
termios2.c_cflag |= nix::libc::BOTHER;
termios2.c_ispeed = baud_rate;
termios2.c_ospeed = baud_rate;
println!("[1] Set baud to {}, termios = {:#?}", baud_rate, termios2);
ioctl::tcsets2(self.fd, &termios2)
}
For now I forked the library and made it use the "musl" termios code, that works perfectly (with things like B9600).
I can try to send a merge request but I'm not really sure how to deal with this properly. A new feature flag?
NOTE: This is a dependency of mio-serial < tokio-serial < tokio-modbus, so I'm not sure a feature flag can even be used in my setup....
I originally started this library because I needed serial functionality for my robotics work and wanted both a solid underlying library to use and to build a better serial terminal app. I've made some progress on both with this library and gattii. But at this point I don't have any passion for this as I have no direct use case anymore. There is clearly interest in this library from others, so rather than let it continue to languish due to my lack of willpower, I wanted to see if anyone is interested in maintaining it.
@daniel.a.joyce @carstenandrich @rnd-ash @jangernert
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.
Funny, I was just about to do a PR to add serde support, and saw that it was already added just a few days ago. Awesome!
I was wondering if you folks could possibly cherry-pick that commit (8f657fb) into the 4.0 line and release it (4.0.2)?
The problem is that we're using Yocto for embedded Linux builds and are stuck with a 2018 Edition of the compiler for now. So we can't move to 4.1 yet. And we'd love serde support.
Thanks
This is a follow-up to #97, which turned out to involve an operating system -level regression.
When enumerating serial ports on Linux and libudev, serialport 3.0 used to return manufacturer/product strings from the device, but serialport 4.0 returns strings originating from a local operating system hardware database (udev/systemd hwdb). This is usually fine, but can be problematic when you absolutely need the information from the device to distinguish/filter devices. One example is if the device in question uses Objective Development free USB IDs, where different devices can share VID/PID and must be distinguished based on the string descriptors on the device.
I propose that either a) the UsbPortInfo
struct is extended to include strings from both the device and the operating system, or b) the enumeration function could be configured whether it should fetch the device strings or OS strings. Or maybe there's more options to solve this...?
Hi, thanks for all the work on the library.
This error can be replicated on macOS Big Sur 11.1 using version 4.0.0 of the serialport crate as follows:
Create a pair of virtual serial ports with socat
socat -d -d -d pty,raw pty,raw
Call open on one of the ports (e.g. /dev/ttys002 in this example):
use std::time::Duration;
fn main() {
let builder = serialport::new("/dev/ttys002", 9600)
.timeout(Duration::from_millis(2000));
let mut port = builder.open().unwrap_or_else(|e| { // This open call fails
eprintln!("Failed to open port. Error: {}", e);
std::process::exit(1);
});
port.write(&"Hello, from Rust".as_bytes()).unwrap();
}
The call fails when trying to set the baud rate in iossiospeed with ENOTTY. Modifying the iossiospeed call to ignore the ENOTTY error results in a usable port that otherwise seems to work as expected. I've tried this with various baud rates and different socat settings (including setting the ispeed and ospeed settings) with the same results. I'll submit a PR with working code for comparison. I'm not sure if simply ignoring the ENOTTY error on macOS is an appropriate fix or if it would require something more sophisticated (is there a way to tell we are running against a virtual serial port?).
I may well be missing something obvious here so any advice or thoughts are appreciated.
Thanks!
The list ports example doesn't work on windows, as its missing the 'interface' field from the struct:
error[E0609]: no field `interface` on type `UsbPortInfo`
--> src\main.rs:33:34
|
33 | ... info.interface
| ^^^^^^^^^ unknown field
|
= note: available fields are: `vid`, `pid`, `serial_number`, `manufacturer`, `product`
Also with that section commented out, no useful information is printed out. I have 4 bluetooth ports (actually just 2 bluetooth devices but windows makes 2 ports for each) and 2 virtual com ports, but the 'type' for all of them is just 'Unknown':
Found 6 ports:
COM7
Type: Unknown
COM6
Type: Unknown
COM8
COM9
Type: Unknown
COM10
Type: Unknown
COM11
Type: Unknown
Using serialport-rs v4.2.0 with rust 1.61.0 (stable) on Windows 10
The read functionality does not work properly with CP210x. hardware_test
fails. In a custom test project read()
does not read from the input buffer and read_to_string()
always returns a timeout error, but does read from the input buffer. rx-data are checked with a scope. All test were made with the current master ( 821cdf1 ).
Environment:
$lsusb
Bus 001 Device 012: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
# ...
# lsmod during running custom test program
$ lsmod
Module Size Used by
# ...
cp210x 36864 1
# ...
# ttyUSB0 verified as CP210x
$ ls /dev | grep USB
ttyUSB0
library hardware test run:
$ cargo run --example hardware_check /dev/ttyUSB0 --loopback
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/examples/hardware_check /dev/ttyUSB0 --loopback`
Testing '/dev/ttyUSB0':
Testing baud rates...
9600: success
38400: success
115200: FAILED (baud rate 115384 does not match set baud rate 115200)
Testing non-standard baud rates...
10000: success
600000: success
1800000: FAILED (baud rate 1846153 does not match set baud rate 1800000)
Testing data bits...
Five: success
Six: success
Seven: success
Eight: success
Testing flow control...
Software: success
Hardware: success
None: success
Testing parity...
Odd: success
Even: success
None: success
Testing stop bits...
Two: success
One: success
Testing bytes to read and write...
SerialPort::bytes_to_write: success
SerialPort::bytes_to_read: success
Test clearing software buffers...
Input: success
Output: success
All: success
Testing data transmission...success
Testing data reception...FAILED
The output of the test run was verified with an scope. See the image below:
Scope decoding configuration:
Hardware (with loopback) was tested with another program (CuteCom). In that case, tx and rx were working properly, so I assume, the bug will probably be within this library or within a dependency.
https://medium.com/@manav503/using-strace-to-perform-fault-injection-in-system-calls-fcb859940895
Could be useful to ensure that expected system errors result in ergonomic and expected API errors.
let ports = serialport::available_ports().expect("No ports found!");
for p in ports {
println!("{}", p.port_name);
}
After run the code ,I got the serial port name. The port name is not correct. "/sys/class" is redundant. There are other serial port such as /dev/ttyAMA1 /dev/AMA2,but it does't enumerate all serial port actually.
pi@raspberrypi:~ $ sudo ./rpidemo
Blinking an LED on a Raspberry Pi Compute Module 4.
/sys/class/tty/ttyUSB3
/sys/class/tty/ttyUSB1
/sys/class/tty/ttyUSB2
/sys/class/tty/ttyUSB0
Currently, serialport-rs issues a unsafe { tcflush(fd, libc::TCIOFLUSH) };
shortly after opening the serial device.
https://gitlab.com/susurrus/serialport-rs/-/blob/master/src/posix/tty.rs#L123
Opening the device already asserts DTR, so there is a small window where DTR is high but data received in that window will get lost.
This was noticed with an embedded device which starts sending serial data immediately after detecting DTR. It was reproduced on both Linux and MacOS. It probably affects all systems using posix/tty.rs
.
In this case, the firmware of the embedded device is open source, so inserting a small delay between detecting DTR and sending data worked around the issue. But in general, I think if a host asserts DTR, it should really be ready to receive data.
Unfortunately, it seems to be impossible to flush the buffers before opening the device, and it's also impossible to open the device without asserting DTR. (See for example https://unix.stackexchange.com/questions/446088/how-to-prevent-dtr-on-open-for-cdc-acm)
Therefore, the only solution would be to remove the call to tcflush
(or make it optional, somehow).
What's the reason for flushing the buffers, is it really necessary?
The early return in
serialport-rs/src/posix/tty.rs
Line 117 in ea7b563
serialport-rs/src/posix/tty.rs
Lines 95 to 129 in ea7b563
close
in serialport-rs/src/posix/tty.rs
Line 131 in ea7b563
The number of jobs exceeds the concurrency limit, so some will likely need to be dropped.
I'm using this lib on Android, when when I pass a port name from Java to this lib, I receive "Error opening port Permission denied". Just wondering to what degree Android is supported (I know device enumeration isn't, that's why I'm passing available devices from the Java layer). Some specifics:
defaultConfig {
applicationId "..."
minSdk 27
targetSdk 30
versionCode 1
versionName "1.0"
ndkVersion = "23.1.7779620"
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xxx.xxx.xxx">
<uses-feature android:name =" android.hardware.usb.host"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/xxx">
<activity
android:name=".MainActivity"
android:exported="true"
android:directBootAware="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 0x0403 / 0x60??: FTDI -->
<usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
<usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
<usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
<usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
<usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->
<!-- 0x10C4 / 0xEA??: Silabs CP210x -->
<usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
<usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
<usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->
<!-- 0x067B / 0x23?3: Prolific PL2303x -->
<usb-device vendor-id="1659" product-id="8963" /> <!-- 0x2303: PL2303HX, HXD, TA, ... -->
<usb-device vendor-id="1659" product-id="9123" /> <!-- 0x23a3: PL2303GC -->
<usb-device vendor-id="1659" product-id="9139" /> <!-- 0x23b3: PL2303GB -->
<usb-device vendor-id="1659" product-id="9155" /> <!-- 0x23c3: PL2303GT -->
<usb-device vendor-id="1659" product-id="9171" /> <!-- 0x23d3: PL2303GL -->
<usb-device vendor-id="1659" product-id="9187" /> <!-- 0x23e3: PL2303GE -->
<usb-device vendor-id="1659" product-id="9203" /> <!-- 0x23f3: PL2303GS -->
<!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
<usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->
<!-- CDC driver -->
<usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
<usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino -->
<usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
<usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
<usb-device vendor-id="11914" product-id="5" /> <!-- 0x2E8A: Raspberry Pi Pico Micropython -->
</resources>
When the device is connected, Android OS prompts me to open the device in my app. When it's first connected, it asks me for permission to access it. That permission is granted, saved, and seems to persist (see below):
or when a device is connected, the user is prompted for permission to access it. The user gives permission. If I enumerate the devices in Java, with something like this, and in this enumeration, I check if the USB service / manager has permission to open the device (it does), so I proceed to open it via java, then rust lib. I've thought to myself, well, what if I don't open the device in Java, but only in rust, and perhaps that's why I don't have permission? Nope. Whether I open the device in java, or not, I'm still not permitted to access the device from rust.
val usbManager: UsbManager = getSystemService(Context.USB_SERVICE) as UsbManager
for ((path, device) in usbManager.deviceList) {
Log.w("SERIALPORT::", "Found device at: $path")
if (usbManager.hasPermission(device)) { // <-- app has permission to access device, here
Log.w("SERIALPORT::", "USB Manager has permission to open device")
val connection = usbManager.openDevice(device);
val endpt = device.getInterface(0).getEndpoint(0);
connection.claimInterface(device.getInterface(0), true);
StringFromJNI(path)
} else {
Log.w("SERIALPORT::", "USB Manager does not have permission to open device $device")
}
}
Finally, the rust function that receives the string from java, prints some helpful logs, to prove the communication between layers is functional (because debugging is a bit of a pain), and attempts to open the port_name provided. When this code is run from a Linux machine, the device is opened just fine. When opened from an Android app, I don't have permissions to open the port. In all cases, with just 1 usb devices connected to the Android phone, the path is "/dev/bus/usb/002/002", FWIW
#[no_mangle]
pub extern "system" fn Java_com_sevenTowers_sensor_1android_1native_MainActivity_00024Companion_StringFromJNI(env: JNIEnv, class: JClass, input: JString) -> jstring {
main();
debug!("Rust heard a message");
let input: String = env.get_string(input).expect("Couldn't get java string").into();
let output = env.new_string(format!("Rust received: {}", input)).expect("Couldn't get Java string");
let port = sensor_lib::sensors::open_port_by_name(input.as_str());
match port {
Some(ttyp_port) => debug!("TTY Port opened {:?}", ttyp_port),
None => error!("TTY Port not opened")
}
output.into_inner()
}
// in mod sensor_lib::sensors
pub fn open_port_by_name(port_name: &str) -> Option<TTYPort> {
debug!("Opening port: {}", port_name);
let port = serialport::new(port_name, 9600);
let result = TTYPort::open(&port);
match result {
Ok(tty_port) => {
debug!("Opened COM port {}", port_name);
Some(tty_port)
}
Err(why) => {
error!("Error opening port {}", why);
None
}
}
}
Sometimes (but not every time) I'll see LogCat print a AVC permissions issue, like this:
type=1400 audit(0.0:2315): avc: denied { search } for name="usb" dev="tmpfs" ino=608 scontext=u:r:untrusted_app:s0:c236,c256,c512,c768 tcontext=u:object_r:usb_device:s0 tclass=dir permissive=0 app=com.sevenTowers.sensor_android_native
I understand, from Google knowledge, that the above comes from the SELinux layer, basically saying I don't have permission to access this device. My USB Service says I have permission to access to the USB, but perhaps the path for the usb is permitted, and the serial protocol requires something different. This is where I'm unsure.
Does my android device have to be rooted for this library to work? It's not, currently. I'd prefer it not have to be. I know there are java libraries for accessing serial devices on Android specifically (like here https://github.com/mik3y/usb-serial-for-android), because device drivers on stock (unrooted) Android need to be provided on the app, because they're not provided by the OS. I can integrate an entirely different serial lib for Android, and abstract the parsing of each sensor in rust by passing buffers from the above lib into rust, but I'd prefer to use one lib across devices, if possible.
I've seen the clever folks over at libusb resolve this issue for unrooted Android devices with instructions like this: https://github.com/libusb/libusb/blob/master/android/README (if you're still reading, it begins at line 35)
where they request permission at the Java layer, and pass the fileDescriptor from java to the libusb native code. I'm not that familiar with this level of detail yet, but perhaps there's something to it. Or, perhaps that's just a USB interop thing, not related to serial devices. This is where I can use some help.
My goal: open a serial port from Android using this lib, parse and handle all the data in rust, as I'm doing with my desktop apps, without having to write too much more interop in Java, so this works on android.
Thanks!
In the documentation, android is described as being supported by this library. However, When I try to run my project that imports this code, I get the error:
serialport:available_ports() Error { kind: Unknown, description: "Not implemented for this OS"}
I have tested this on multiple android devices with multiple versions of Android (10, 7.1.2, 8) with the same issue.
I have poked around in the source code a bit, and I cannot find the target_os tag for android for the available_ports() function. Is there something I am missing?
In Linux, use flock(fd, LOCK_EX | LOCK_NB)
to lock the serial port.
See
This really is not an issue with serialport-rs but a question about using it to write a cross platform serial library.
We have a custom USB device that we need to access from Linux, Windows and macOS using various languages like C# and Java. The problem is on the Windows side where we a looking at developing a Windows UWP app with the Rust serial port library. In a Windows UWP application, if you want to access the serial port you need to define a capability like this:
<DeviceCapabilityName="serialcommunication">
<DeviceId="any">
<FunctionType="name:serialPort"/>
</Device>
</DeviceCapability>
When I develop a Rust only application, everything works fine because we do not need a capability like this. When I convert that application into a library, compiling to a DLL, and then try to call that code from a Windows UWP app I get Access Denied when attempting to open the serial port.
This is the code I am using:
pub fn get_serialport() -> Option<Box<SerialPort>> {
if let Ok(port) = get_port_info() {
let port_name = port.port_name;
let baud_rate = 19200;
let mut settings: SerialPortSettings = Default::default();
settings.timeout = Duration::from_millis(10);
settings.baud_rate = baud_rate.into();
if let Ok(mut port) = serialport::open_with_settings(&port_name, &settings) {
let new_port = port.try_clone();
return Some(port)
}
}
println!("returning none");
None
}
It works perfectly when I call it within a Rust application however when I call it, from the library interface, in a Windows UWP app the open_with_settings() causes the Access Denied error.
I did put this question to the Microsoft forums (https://social.msdn.microsoft.com/Forums/en-US/5800ee6e-1b97-4370-ad56-4d5634d7a249/access-usb-device-in-a-windows-uwp-application-using-a-native-library-dll?forum=windowsgeneraldevelopmentissues ) but as you can see they are not much help.
Does anyone know how to use this library to access a serial port from within a Windows UWP app?
Under UNIX OS (Mac / Linux), I can read data from a COM port however I want. calling bytes_to_read will work perfectly. If my Arduino sends 20 bytes to the serial port, bytes_to_read returns 20 bytes
With Windows, something doesn't work. calling bytes_to_read returns 0, until 4096 bytes have been queued in the serial port at which point the function returns 4096. But by that stage, the serial buffer is already full and data begins to get lost.
Calling read on windows also reads nothing until the serial device has sent 4096 bytes.
I don't understand this behavior at all. I've checked serialport-rs library and it on paper it should work perfectly. I can even say that this C++ code works perfectly with the same serial device hardware.
It would be handy if it were possible to differentiate the different ports in a composite USB Serial adapter in some cross platform way, presumably by adding a field to the UsbPortInfo struct. I've attached an lsusb -v
output for such a device. There seem to be two possible fields that would make this possible. Either the iInterface field (on line 45 and 115) which I believe is generally a human readable string or the bMasterInterface attribute in the CDCACM union (on line 55 and 125) which is a number.
The second method seems to be used to differentiate them by linux as seen below, but iInterface is available through udev.
aled@dufous-/dev: ls -l /dev/serial/by-id/
total 0
lrwxrwxrwx 1 root root 13 Jul 14 19:26 usb-Black_Sphere_Technologies_Black_Magic_Probe_DDD5ACC2-if00 -> ../../ttyACM0
lrwxrwxrwx 1 root root 13 Jul 14 19:26 usb-Black_Sphere_Technologies_Black_Magic_Probe_DDD5ACC2-if02 -> ../../ttyACM1
It looks like the library actually already queries the if number on Windows, although it is not exposed to the user. Funnily enough the example in the comment is exactly my use case, funny how life does that.
If anything I said in regards to anything USB doesn't make any sense, I apologize in advance, USB is still kind of a blackbox to me. I can probably work on a pull request on the posix side of things, but I don't own a Windows box so I have no real way of testing windows stuff
Hello.
I wrote a simple application that sent a request on the bus and then reads the response.
I would like my application to run in real-time mode.
When the process is a normal process - everything works fine when the process is a normal process.
The problem arises when I change it in process RT (sudo chrt -p 50 my_process_pid
).
Then I can't read anything from the bus - timeout.
What is wrong?
platform: BeagleBone Black
os/distro: Debian Linux 10.3 - Buster
kernel: 4.19.94-ti-rt-r59
On macOS Big Sur running on an Apple M1 device, available_ports
returns SerialPortType::PciPort
for a USB serial adapter.
SerialPortInfo { port_name: "/dev/tty.wlan-debug", port_type: PciPort }
SerialPortInfo { port_name: "/dev/tty.debug-console", port_type: PciPort }
SerialPortInfo { port_name: "/dev/tty.Bluetooth-Incoming-Port", port_type: BluetoothPort }
SerialPortInfo { port_name: "/dev/tty.-WirelessiAP", port_type: BluetoothPort }
SerialPortInfo { port_name: "/dev/tty.usbmodem1101", port_type: PciPort }
IORegistryExplorer shows that the USB device is of IOUSBHostDevice
, a subclass of IOUSBDevice
. It appears that this is specific to Apple M1 and not handled by this code at the moment.
Thank you for your work on this library. But I ran into a small showstopper :(
I've only tested this on windows so far, so maybe the situation is different on Linux.
I have set up a virtual port pair COM10
<-> COM20
via com0com
. I'm trying to write some bytes to COM10
. When nothing is connected to COM20
write()
just blocks forever. As soon as I connect to COM20
with HTerm I receive the bytes and my rust program continues running.
I would expect write()
to either fail with an error or return with Ok(0)
if nothing is connected to COM20
. Otherwise there is no way for me to handle the case that the device isn't plugged in.
edit: Serial port settings:
let buffer_size = buffer.len();
let mut total_bytes_written = 0;
loop {
let bytes_written = serial_port_guard.write(&buffer[total_bytes_written..buffer_size]).map_err(|e| {
log::error!("Failed to write bytes to serial port: {}", e);
TokioError::new(TokioErrorKind::NotFound, e.to_string())
})?;
if bytes_written == 0 {
let msg = "Can't write to serial port";
log::error!("{}", msg);
return Err(TokioError::new(TokioErrorKind::NotConnected, msg));
}
total_bytes_written += bytes_written;
if total_bytes_written >= buffer_size {
break;
}
}
Using the following code:
let mut reader = BufReader::new(connection);
let mut my_str = vec![];
if let Err(error) = reader.read_until(0xA, &mut my_str) {....}
Where I am reading some data from a serial connection. On Linux & Mac I see the read_until function executing quite fast, but on Windows (11) I see the serial connection's timeout (500ms) is what is ending the read_until command -- Which does then proceed to do the correct thing after with the correct value in the buffer. It is just comparatively slow.
An example of the output I am seeing:
Successfully sent write to serial: CONFIG
Successfully read from serial "0\n"
Successfully sent write to serial: ILLUMDAY
Successfully read from serial "10\n"
Successfully sent write to serial: ILLUMNIGHT
Successfully read from serial "25\n"
Successfully sent write to serial: ILLUMMODE
Successfully read from serial "0\n"
Successfully sent write to serial: ECHO
Successfully read from serial "0\n"
Successfully sent write to serial: ANIM
Successfully read from serial "0\n"
Is this some kind of bug with Windows or am I missing something obvious? Thanks for any input!
On linux, read will wait for serial port data until data is read in or timeout.
But in windows, read will block until it times out. If there is data in this process, it will return after timeout.
Is this a mistake in my usage, or a bug?
Today is my first time using the serialport crate. I was surprised to see that once I've located a port via available_ports(), there is no function to open it. It would be nice if SerialPortInfo has a to_builder()
function or similar.
It's also slightly confusing that SerialPortInfo has a port_name
, but serialport::new() calls it a path
.
USB serial ports can have a latency problem when you're trying to receive small packets of data, because they try to buffer data to put into one USB packet for efficiency. For example FTDI serial ports have a timeout of 16 milliseconds by default, and if you're only receiving a few bytes at a time, you will only receive data every 16ms.
Linux supports an ASYNC_LOW_LATENCY
which asks drivers to have less latency (perhaps at the efficiency of transferring large amounts of data) which alleviates this problem. Windows also seems to have a similar thing using the COMMTIMEOUTS
struct (not tested). I couldn't immediately find a similar setting for macOS.
Having a set_low_latency
method on platforms that support it would be useful.
I have an application that sometimes just hangs, seemingly forever. I've tracked the problem down to TTYPort::read
.
I'm setting a timeout on the port and I know this works, as the application experiences timeouts quite regularly. Despite this, the call to read
doesn't return. I've added some debug output, and tracked the problem to the second call in read
. super::poll::wait_read_fd
returns, nix::unistd::read
doesn't.
The serial port I'm using is connected through USB to a microcontroller, and something really weird is going on over there (longstanding USB-related bug that I wasn't able to track down so far). I'm not surprised that this is causing weird behavior on the host side. However, it would be great, if serialport-rs could respect the timeout setting under such circumstances, if at all possible.
Sometimes you want to, say, allow a long time for a buffer to be written but you want a quick read attempt (e.g., in a polling loop).
How hard would it be to implement separate read and write timeouts (a la https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.set_read_timeout and https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.set_write_timeout)?
It looks like it would be easy to split it for the posix side (since there are separate poll calls for read/write). The windows side looks like it might be similarly easy, but I have no experience with windows.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.