Coder Social home page Coder Social logo

serialport / serialport-rs Goto Github PK

View Code? Open in Web Editor NEW
399.0 7.0 97.0 633 KB

A cross-platform serial port library in Rust. Provides a blocking I/O interface and port enumeration including USB device information.

License: Other

Rust 100.00%
rust serial serialport

serialport-rs's Introduction

crates.io version badge Documentation GitHub workflow status Minimum Stable Rust Version

Introduction

serialport-rs is a general-purpose cross-platform serial port library for Rust. It provides a blocking I/O interface and port enumeration on POSIX and Windows systems.

For async I/O functionality, see the mio-serial and tokio-serial crates.

Join the discussion on Matrix! #serialport-rs:matrix.org

This project is looking for maintainers! Especially for Windows. If you are interested please let us know on Matrix, or by creating a discussion.

Overview

The library exposes cross-platform serial port functionality through the SerialPort trait. This library is structured to make this the simplest API to use to encourage cross-platform development by default. Working with the resultant Box<dyn SerialPort> type is therefore recommended. To expose additional platform-specific functionality use the platform-specific structs directly: TTYPort for POSIX systems and COMPort for Windows.

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

$ cargo build --no-default-features

It should also be noted that on macOS, both the Callout (/dev/cu.*) and Dial-in ports (/dev/tty.*) ports are enumerated, resulting in two available ports per connected serial device.

Usage

Listing available ports:

let ports = serialport::available_ports().expect("No ports found!");
for p in ports {
    println!("{}", p.port_name);
}

Opening and configuring a port:

let port = serialport::new("/dev/ttyUSB0", 115_200)
    .timeout(Duration::from_millis(10))
    .open().expect("Failed to open port");

Writing to a port:

let output = "This is a test. This is only a test.".as_bytes();
port.write(output).expect("Write failed!");

Reading from a port (default is blocking with a 0ms timeout):

let mut serial_buf: Vec<u8> = vec![0; 32];
port.read(serial_buf.as_mut_slice()).expect("Found no data!");

Some platforms expose additional functionality, which is opened using the open_native() method:

let port = serialport::new("/dev/ttyUSB0", 115_200)
    .open_native().expect("Failed to open port");

Closing a port:

serialport-rs uses the Resource Acquisition Is Initialization (RAII) paradigm and so closing a port is done when the SerialPort object is Droped either implicitly or explicitly using std::mem::drop (std::mem::drop(port)).

Examples

There are several included examples, which help demonstrate the functionality of this library and can help debug software or hardware errors.

  • clear_input_buffer - Demonstrates querying and clearing the driver input buffer.
  • clear_output_buffer - Demonstrates querying and clearing the driver output buffer.
  • duplex - Tests that a port can be successfully cloned.
  • hardware_check - Checks port/driver functionality for a single port or a pair of ports connected to each other.
  • list_ports - Lists available serial ports.
  • pseudo_terminal - Unix only. Tests that a pseudo-terminal pair can be created.
  • receive_data - Output data received on a port.
  • transmit - Transmits data regularly on a port with various port configurations. Useful for debugging.

Dependencies

Rust versions 1.59.0 and higher are supported by the library itself. There are examples requiring newer versions of Rust.

For GNU/Linux pkg-config headers are required:

  • Ubuntu: sudo apt install pkg-config
  • Fedora: sudo dnf install pkgconf-pkg-config

For other distros they may provide pkg-config through the pkgconf package instead.

For GNU/Linux libudev headers are required as well (unless you disable the default libudev feature):

  • Ubuntu: sudo apt install libudev-dev
  • Fedora: sudo dnf install systemd-devel

Platform Support

Builds and tests for all supported targets are run in CI. Failures of either block the inclusion of new code. This library should be compatible with additional targets not listed below, but no guarantees are made. Additional platforms may be added in the future if there is a need and/or demand.

  • Android
    • arm-linux-androideabi (no serial enumeration)
    • armv7-linux-androideabi (no serial enumeration)
  • FreeBSD
    • x86_64-unknown-freebsd
  • Linux
    • aarch64-unknown-linux-gnu
    • aarch64-unknown-linux-musl
    • i686-unknown-linux-gnu
    • i686-unknown-linux-musl
    • x86_64-unknown-linux-gnu
    • x86_64-unknown-linux-musl
  • macOS/iOS
    • aarch64-apple-darwin
    • aarch64-apple-ios
    • x86_64-apple-darwin
  • NetBSD
    • x86_64-unknown-netbsd (no serial enumeration)
  • Windows
    • i686-pc-windows-gnu
    • i686-pc-windows-msvc
    • x86_64-pc-windows-gnu
    • x86_64-pc-windows-msvc

Hardware Support

This library has been developed to support all serial port devices across all supported platforms. To determine how well your platform is supported, please run the hardware_check example provided with this library. It will test the driver to confirm that all possible settings are supported for a port. Additionally, it will test that data transmission is correct for those settings if you have two ports physically configured to communicate. If you experience problems with your devices, please file a bug and identify the hardware, OS, and driver in use.

Known issues:

Hardware OS Driver Issues
FTDI TTL-232R Linux ftdi_sio, Linux 4.14.11 Hardware doesn't support 5 or 6 data bits, but the driver lies about supporting 5.

Licensing

Licensed under the Mozilla Public License, version 2.0.

Contributing

Please open an issue or pull request on GitHub to contribute. Code contributions submitted for inclusion in the work by you, as defined in the MPL2.0 license, shall be licensed as the above without any additional terms or conditions.

Acknowledgments

This is the continuation of the development at https://gitlab.com/susurrus/serialport-rs. Thanks to susurrus and all other contributors to the original project on GitLab.

Special thanks to dcuddeback, willem66745, and apoloval who wrote the original serial-rs library which this library heavily borrows from.

serialport-rs's People

Contributors

astraw avatar atouchet avatar berkowski avatar caesius-tim avatar cbiffle avatar danielstuart14 avatar dhylands avatar dnedic avatar eldruin avatar g0hl1n avatar huntc avatar jannic avatar jessebraham avatar kawadakk avatar knorrfg avatar mf-dhadley avatar mlsvrts avatar munckymagik avatar nathansizemore avatar ndusart avatar problame avatar reconbot avatar rosssmyth avatar sirhcel avatar susurrus avatar thelostlambda avatar wcampbell0x2a avatar xobs avatar y0ba avatar yashi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

serialport-rs's Issues

On the USB device running Windows, the product name of the non-UTF-8 device is garbled

  • OS: win10(Code page 936)

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)"

image

BufRead::read_line() doesn't work when timeout is set to 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.

Enumate the serialport not correct in raspberry CM4

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

Issues with multiple threads under windows

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/91

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

Possible file descriptor leak in open

The early return in

return Err(Error::new(
will exit the whole function, not just the block
{
// setup TTY for binary serial port access
// Enable reading from the port and ignore all modem control lines
termios.c_cflag |= libc::CREAD | libc::CLOCAL;
// Enable raw mode which disables any implicit processing of the input or output data streams
// This also sets no timeout period and a read will block until at least one character is
// available.
unsafe { cfmakeraw(&mut termios) };
// write settings to TTY
unsafe { tcsetattr(fd, libc::TCSANOW, &termios) };
// Read back settings from port and confirm they were applied correctly
let mut actual_termios = MaybeUninit::uninit();
unsafe { tcgetattr(fd, actual_termios.as_mut_ptr()) };
let actual_termios = unsafe { actual_termios.assume_init() };
if actual_termios.c_iflag != termios.c_iflag
|| actual_termios.c_oflag != termios.c_oflag
|| actual_termios.c_lflag != termios.c_lflag
|| actual_termios.c_cflag != termios.c_cflag
{
return Err(Error::new(
ErrorKind::Unknown,
"Settings did not apply correctly",
));
};
unsafe { tcflush(fd, libc::TCIOFLUSH) };
// clear O_NONBLOCK flag
fcntl(fd, F_SETFL(nix::fcntl::OFlag::empty()))?;
Ok(())
}
.
Therefore, the call to close in
close(fd);
won't be executed.

Feature: Function create builder from SerialPortInfo struct

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/101

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.

Can't seem to be able to read data on Windows

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/113

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.

available_ports() enumeration API doesn't work on macOS 12.0

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/115

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:

image

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

`TTYPort::read` doesn't respect timeout

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/74

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.

Use UWP APIs for Windows where appropriate

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/48

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?

Re-activate more build targets for CI jobs

  • When migrating from GitLab to GitHub we've reduced the amount of build targets in error because we assumed that we could have only 20 of them
  • We recently learned that this is a limit for parallel builds
  • Let's reactivate at least popular ones (for example an OpenBSD build could have prevented #68)

Opening Port on Android "Error opening port Permission denied"

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:

Android SDK / NDK targets:

    defaultConfig {
        applicationId "..."
        minSdk 27
        targetSdk 30
        versionCode 1
        versionName "1.0"
        ndkVersion = "23.1.7779620"
    }

Android Manifest, giving access to certain devices

<?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>

Device filters, which when connected, prompt the user for permission to access the particular USB device (which, in my case, is a 232 to USB FTDI chip (vendorId: 0403, Product Id: 6001):

<?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):

When app starts MainActivity.onCreate()

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")
            }
        }

simple rust lib code

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
            }
        }
    }

AVC prompts

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

SELinux permissions

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.

My question:

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!

available_ports returns incorrect SerialPortType

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/106

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.

separate read and write timeout settings

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.

Missing COM ports in Windows

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".
image
In ^^, COM5 and COM7 are the "modem" 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

Support low latency mode

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/55

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.

'list_ports.rs' example doesn't work

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

`read` is always non-blocking even when no data is available to read

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/89

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.

Manufacturer/product strings from device VS from operating system during enumeration

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/98

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...?

Baud rate in new doesn't work?

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/102

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?

open on MacOS virtual serial port opened with socat fails with "not a typewriter" (ENOTTY)

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/105

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!

bufReader read_until not handled correctly on Windows

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!

serde support in the 4.0.x line

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

Ubuntu 20.10: hardware_test receive fails

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/108

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:

  • Ubuntu 20.10 (kernel 5.8.0-48-generic)
  • Rust: rustc 1.51.0 (2fd73fabe 2021-03-23)
$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:

SCR04

Scope decoding configuration:

SCR05

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.

RaspberryPi: Available ports are not containing the correct info

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)

Serial Port enumeration not supported for android

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/72

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?

tcflush on open may cause data loss

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/117

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?

Seeking new maintainer!

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/111

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

Need to disable termios2 / BOTHER (use termios and Bxxx for baud rate)

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/78

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....

Windows - bytes_to_read returns 0 until 4096 bytes have been written to the port?

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/95

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.

Linux PREEMPT RT - weird behavior

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/104

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

Windows Subsystem for Linux (WSL) Port '/dev/ttySX' not available: Not a typewriter

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/52

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.

Enable differentiation of different devices on a single USB serial port adapter

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/81

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.

example.txt

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

New release?

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 ๐Ÿ˜„ )

Idomatic APIs

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/112

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.

Concrete Types

In the Rust standard library, types like File and TcpStream are implemented as structs 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.

Advantages

  • One concrete type used for both platform-specific and platform-agnostic operations.

    • Avoids unnecessary dynamic dispatch.
    • Clients can easily use platform-specific features on an as-needed basis without having to change types.
    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(())
    }
    • Simpler type-name.

Disadvantages

  • 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.

Implemeting for Immutable References

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.

Discussion/Implementation

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.

Tests on cross-compiled architectures using cross

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/58

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 ?

Windows: write() blocks if nothing is connected to the port

This issue was migrated from GitLab. The original issue can be found here:
https://gitlab.com/susurrus/serialport-rs/-/issues/84

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:

  • timout: 500 ms
  • baud rate: 9600
  • parity: None
  • data bits: Eight
  • stop bits: One
  • flow control: Software
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;
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.