serialport-4.7.0/.cargo/config.toml000064400000000000000000000003141046102023000153310ustar 00000000000000[env] # Some of our tests make use of actual serial ports. Run all tests sequentially # by default to avoid race conditions (see # https://github.com/rust-lang/cargo/issues/8430). RUST_TEST_THREADS = "1" serialport-4.7.0/.cargo_vcs_info.json0000644000000001360000000000100132300ustar { "git": { "sha1": "4f8e46b3b4c956a0945f76def83d84cc28ac5360" }, "path_in_vcs": "" }serialport-4.7.0/.github/workflows/build.yaml000064400000000000000000000107321046102023000174030ustar 00000000000000name: Build on: workflow_call: inputs: disable_extra_builds: type: boolean disable_tests: type: boolean extra_packages: type: string runs_on: default: ubuntu-latest type: string target: required: true type: string toolchain: default: stable type: string continue-on-error: default: false type: boolean env: # While we could define these on a per-job basis, there's no harm in simply # defining all environment variables for each job. This has the added benefit # of keeping them all together in one place. CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc CARGO_TARGET_ARMV5TE_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-gcc CARGO_TARGET_ARMV5TE_UNKNOWN_LINUX_MUSLEABI_LINKER: arm-linux-gnueabi-gcc CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER: arm-linux-gnueabihf-gcc CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-gcc CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABI_LINKER: arm-linux-gnueabi-gcc CARGO_TARGET_MIPS64EL_UNKNOWN_LINUX_GNUABI64_LINKER: mips64el-linux-gnuabi64-gcc CARGO_TARGET_MIPS64_UNKNOWN_LINUX_GNUABI64_LINKER: mips64-linux-gnuabi64-gcc CARGO_TARGET_MIPSEL_UNKNOWN_LINUX_GNU_LINKER: mipsel-linux-gnu-gcc CARGO_TARGET_MIPSEL_UNKNOWN_LINUX_MUSL_LINKER: mipsel-linux-gnu-gcc CARGO_TARGET_MIPS_UNKNOWN_LINUX_GNU_LINKER: mips-linux-gnu-gcc CARGO_TARGET_MIPS_UNKNOWN_LINUX_MUSL_LINKER: mips-linux-gnu-gcc CARGO_TARGET_POWERPC64LE_UNKNOWN_LINUX_GNU_LINKER: powerpc64le-linux-gnu-gcc CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_LINKER: powerpc64-linux-gnu-gcc CARGO_TARGET_POWERPC_UNKNOWN_LINUX_GNU_LINKER: powerpc-linux-gnu-gcc CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER: s390x-linux-gnu-gcc # Pretty cargo output! CARGO_TERM_COLOR: always # Enable cross compilation for `pkg_config`. PKG_CONFIG_ALLOW_CROSS: 1 # Deny warnings. RUSTFLAGS: -D warnings jobs: build: runs-on: ${{ inputs.runs_on }} continue-on-error: ${{ inputs.continue-on-error }} steps: - name: Build | install dependencies if: inputs.runs_on == 'ubuntu-latest' run: | sudo sed -i 's/azure.archive.ubuntu.com/archive.ubuntu.com/' /etc/apt/sources.list sudo apt-get -qq update sudo apt-get -qq -y install build-essential curl git pkg-config ${{ inputs.extra_packages }} - name: Build | add mingw32 to path if: inputs.runs_on == 'windows-2019' shell: bash run: | echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH - name: Build | checkout uses: actions/checkout@v2 - name: Build | install toolchain uses: dtolnay/rust-toolchain@stable with: target: ${{ inputs.target }} toolchain: ${{ inputs.toolchain }} - name: Build | rust-cache uses: Swatinem/rust-cache@v2 - name: Build | build library (default features) run: cargo build --target=${{ inputs.target }} - name: Build | build library (all features) run: cargo build --all-features --target=${{ inputs.target }} - name: Build | build examples (default features) if: ${{ inputs.disable_extra_builds == false }} run: cargo build --examples --target=${{ inputs.target }} - name: Build | build examples (all features) if: ${{ inputs.disable_extra_builds == false }} run: cargo build --examples --all-features --target=${{ inputs.target }} - name: Build | build tests (default features) if: ${{ inputs.disable_extra_builds == false }} run: cargo build --tests --target=${{ inputs.target }} - name: Build | run tests (default features) if: ${{ inputs.disable_tests == false }} run: cargo test --no-fail-fast --features ignore-hardware-tests --target=${{ inputs.target }} - name: Build | build tests (all features) if: ${{ inputs.disable_extra_builds == false }} run: cargo build --tests --all-features --target=${{ inputs.target }} - name: Build | run tests (all features) if: ${{ inputs.disable_tests == false }} # Enabling all features includes ignoring hardware tests. run: cargo test --no-fail-fast --all-features --target=${{ inputs.target }} serialport-4.7.0/.github/workflows/ci.yaml000064400000000000000000000200031046102023000166670ustar 00000000000000name: CI on: pull_request: branches: - main push: # Check for new issues from updated dependencies once a week (Friday noon). schedule: - cron: "0 12 * * 5" workflow_dispatch: jobs: # -------------------------------------------------------------------------- # LINT lint: runs-on: ubuntu-latest steps: - name: Lint | install dependencies run: | sudo sed -i 's/azure.archive.ubuntu.com/archive.ubuntu.com/' /etc/apt/sources.list sudo apt-get -qq update sudo apt install -qq -y libudev-dev - name: Lint | checkout uses: actions/checkout@v2 - name: Lint | install toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: clippy, rustfmt - name: Lint | rust-cache uses: Swatinem/rust-cache@v2 - name: Lint | check formatting run: cargo fmt -- --check - name: Lint | clippy run: cargo clippy --all-targets --all-features # -------------------------------------------------------------------------- # MSRV # # Check at least once per platform. msrv-aarch64-apple-darwin: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: aarch64-apple-darwin toolchain: "1.59.0" msrv-arm-linux-androideabi: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: arm-linux-androideabi toolchain: "1.59.0" msrv-x86_64-unknown-freebsd: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: x86_64-unknown-freebsd toolchain: "1.59.0" msrv-x86_64-unknown-linux-gnu: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true extra_packages: libudev-dev target: x86_64-unknown-linux-gnu toolchain: "1.59.0" msrv-x86_64-unknown-linux-musl: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true extra_packages: gcc-aarch64-linux-gnu target: aarch64-unknown-linux-musl toolchain: "1.59.0" msrv-x86_64-pc-windows-msvc: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true runs_on: windows-2019 target: x86_64-pc-windows-msvc toolchain: "1.59.0" msrv-x86_64-unknown-netbsd: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: x86_64-unknown-netbsd toolchain: "1.59.0" # -------------------------------------------------------------------------- # Semantic Versioning # # Check at least once per platform as we heavily depend on platform-specific # code. The feature groups are used for attempting to cover different # backends for a platform (like Linux with and without libudev). semver-aarch64-apple-darwin: runs-on: ubuntu-latest strategy: matrix: target: - aarch64-apple-darwin - arm-linux-androideabi - x86_64-pc-windows-msvc - x86_64-unknown-freebsd - x86_64-unknown-linux-gnu - x86_64-unknown-netbsd feature-group: - "only-explicit-features" - "all-features" steps: - run: | # TODO: Harmonize with build.yaml sudo sed -i 's/azure.archive.ubuntu.com/archive.ubuntu.com/' /etc/apt/sources.list sudo apt-get -qq update sudo apt-get -qq -y install build-essential curl git pkg-config libudev-dev - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@stable with: target: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - uses: obi1kenobi/cargo-semver-checks-action@v2 with: rust-target: ${{ matrix.target }} feature-group: ${{ matrix.feature-group }} # -------------------------------------------------------------------------- # cargo-deny cargo-deny: runs-on: ubuntu-latest strategy: matrix: checks: - advisories - bans licenses sources # Prevent sudden announcement of a new advisory from failing ci: continue-on-error: ${{ matrix.checks == 'advisories' }} steps: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 with: command: check ${{ matrix.checks }} # -------------------------------------------------------------------------- # BUILD aarch64-apple-darwin: uses: ./.github/workflows/build.yaml with: disable_tests: true runs_on: macos-latest target: aarch64-apple-darwin aarch64-apple-ios: uses: ./.github/workflows/build.yaml with: disable_tests: true runs_on: macos-latest target: aarch64-apple-ios aarch64-unknown-linux-gnu: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true extra_packages: libudev-dev gcc-aarch64-linux-gnu libc6-dev-arm64-cross target: aarch64-unknown-linux-gnu aarch64-unknown-linux-musl: uses: ./.github/workflows/build.yaml with: disable_tests: true extra_packages: gcc-aarch64-linux-gnu target: aarch64-unknown-linux-musl arm-linux-androideabi: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: arm-linux-androideabi armv7-linux-androideabi: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: armv7-linux-androideabi i686-pc-windows-gnu: uses: ./.github/workflows/build.yaml with: runs_on: windows-2019 target: i686-pc-windows-gnu i686-pc-windows-msvc: uses: ./.github/workflows/build.yaml with: runs_on: windows-2019 target: i686-pc-windows-msvc i686-unknown-linux-gnu: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true extra_packages: libudev-dev gcc-multilib target: i686-unknown-linux-gnu i686-unknown-linux-musl: uses: ./.github/workflows/build.yaml with: extra_packages: libudev-dev gcc-multilib target: i686-unknown-linux-musl x86_64-apple-darwin: uses: ./.github/workflows/build.yaml with: runs_on: macos-latest target: x86_64-apple-darwin x86_64-pc-windows-gnu: uses: ./.github/workflows/build.yaml with: runs_on: windows-2019 target: x86_64-pc-windows-gnu x86_64-pc-windows-msvc: uses: ./.github/workflows/build.yaml with: runs_on: windows-2019 target: x86_64-pc-windows-msvc x86_64-unknown-freebsd: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: x86_64-unknown-freebsd x86_64-unknown-linux-gnu: uses: ./.github/workflows/build.yaml with: extra_packages: libudev-dev target: x86_64-unknown-linux-gnu x86_64-unknown-linux-musl: uses: ./.github/workflows/build.yaml with: target: x86_64-unknown-linux-musl x86_64-unknown-netbsd: uses: ./.github/workflows/build.yaml with: disable_extra_builds: true disable_tests: true target: x86_64-unknown-netbsd # -------------------------------------------------------------------------- # NIGHTLY BUILD aarch64-apple-darwin-nightly: uses: ./.github/workflows/build.yaml with: continue-on-error: true disable_tests: true runs_on: macos-latest target: aarch64-apple-darwin toolchain: nightly x86_64-pc-windows-msvc-nightly: uses: ./.github/workflows/build.yaml with: continue-on-error: true runs_on: windows-2019 target: x86_64-pc-windows-msvc toolchain: nightly x86_64-unknown-linux-gnu-nightly: uses: ./.github/workflows/build.yaml with: continue-on-error: true extra_packages: libudev-dev target: x86_64-unknown-linux-gnu toolchain: nightly serialport-4.7.0/.gitignore000064400000000000000000000000371046102023000140100ustar 00000000000000target Cargo.lock *.bk *~ .env serialport-4.7.0/CHANGELOG.md000064400000000000000000000522311046102023000136340ustar 00000000000000# Change log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] ### Added ### Changed ### Fixed ### Removed ## [4.7.0] - 2025-01-13 ### Changed * Enumerate ports from more subsystems on Linux without libudev. [#238](https://github.com/serialport/serialport-rs/pull/238) * Set data terminal ready (DTR) signal when opening a port by default and allow to customize this behavior through the builder. [#239](https://github.com/serialport/serialport-rs/pull/239) ### Fixed * Retry flushing data on `EINTR` up to the ports read/write timeout. [#225](https://github.com/serialport/serialport-rs/pull/225) ## [4.6.1] - 2024-12-01 ### Fixed * Pin subdependency `libc` to maintain compatibility with MSRV 1.59.0. [#229](https://github.com/serialport/serialport-rs/pull/229) ## [4.6.0] - 2024-10-21 ### Added * Add recommendation on how to interpret `UsbPortInfo::interface_number`. [#219](https://github.com/serialport/serialport-rs/pull/219) * Add support for retrieving USB port info on Linux without libudev. [#220](https://github.com/serialport/serialport-rs/pull/220) ### Changed * Switched from core-foundation-sys to core-foundation for more conveniently working with Core Foundation types for enumeration on macOS. [#218](https://github.com/serialport/serialport-rs/pull/218) * Refactored output from example `list_ports` (alignment and order) for easily comparing different runs. [#220](https://github.com/serialport/serialport-rs/pull/220) ### Fixed * Fix enumeration USB reported as PCI devices which do not have a (short) serial number. [#160](https://github.com/serialport/serialport-rs/pull/160) * Fix ignoring the status of several function calls into Core Foundation on mac OS. [#218](https://github.com/serialport/serialport-rs/pull/218) ## [4.5.1] - 2024-09-20 ### Fixed * Fix ignoring errors from setting baud rate and ignoring unsupported baud rates. [#213](https://github.com/serialport/serialport-rs/pull/213) * Remove leftover debug output from `posix::poll::wait_fd`. [#216](https://github.com/serialport/serialport-rs/pull/216) ## [4.5.0] - 2024-08-05 ### Added * Add `IntoRawHandle` implementation for `COMPort` [#199](https://github.com/serialport/serialport-rs/pull/199) * Add MODALIAS as additional source of information for USB devices on Linux [#170](https://github.com/serialport/serialport-rs/pull/170) ### Changed * Replace using regex crate for parsing device identification strings for `available_ports` on Windows. This is now done by some bespoke code to significantly reduce build times. [#201](https://github.com/serialport/serialport-rs/pull/201) * Switch from ANSI to Unicode/UTF-16 string API on Windows. [#89](https://github.com/serialport/serialport-rs/pull/89) ### Fixed * Fix looking up `UsbPortInfo::interface` on macOS. [#193](https://github.com/serialport/serialport-rs/pull/193) * Fix issues with very long timeout values like `Duration::MAX` by clamping to maximum supported value for underlying platform. [#207](https://github.com/serialport/serialport-rs/issues/207), [#208](https://github.com/serialport/serialport-rs/pull/208) ## [4.4.0] - 2024-06-26 ### Added * Add conversions between `DataBits`, `StopBits` types and their numeric representations. * Add `FromStr` implementation for `FlowControl`. [#163](https://github.com/serialport/serialport-rs/pull/163) ### Changed * Several changes for CI hygiene. ### Fixed * Fix a bug where `available_ports()` returned disabled devices on Windows. [#144](https://github.com/serialport/serialport-rs/pull/144) * Fix a bug on Windows where the `WriteTotalTimeoutConstant` field hasn't been configured properly when the `set_timeout` method is called. [#124](https://github.com/serialport/serialport-rs/issues/124) * Fix a longstanding bug on Windows where timeouts of length zero (`Duration::ZERO`) actually resulted in waiting indefinitely. [#79](https://github.com/serialport/serialport-rs/pull/79) * Fix missing modem ports from `available_ports()` on Windows. [#81](https://github.com/serialport/serialport-rs/issues/81) [#84](https://github.com/serialport/serialport-rs/pull/84) * Fix MSRV incompatibility with sub-dependency of clap. [#186](https://github.com/serialport/serialport-rs/pull/186) ## [4.3.0] - 2023-12-11 ### Changed * Raise MSRV from 1.56.1 to 1.59.0 and Rust edition from 2018 to 2021. [#137](https://github.com/serialport/serialport-rs/pull/137) * Update `bitflags` dependency to 2.4.0. [#127](https://github.com/serialport/serialport-rs/pull/127) * Open serial devices with `O_CLOEXEC` (Posix). This will close the device handles when starting a child process. In particular this means that a serial device can be reopened after making SW update of a Tauri application. [#130](https://github.com/serialport/serialport-rs/pull/130) * Prefer USB device manufacturer and model information from the actual USB device over the information from udev's database. [#137](https://github.com/serialport/serialport-rs/pull/137) ### Fixed * Fixes a bug on Windows where composite devices would show a incorrect serial number. [#141](https://github.com/serialport/serialport-rs/pull/141) * Fixes a bug on Linux without udev where `available_ports()` returned wrong device file paths. [#122](https://github.com/serialport/serialport-rs/pull/122) * Fixes a bug on Windows where some USB device serial numbers were truncated. [#131](https://github.com/serialport/serialport-rs/pull/131) * Switches to maintained sys crates for CoreFoundation and IOKit on macOS. [#112](https://github.com/serialport/serialport-rs/issues/112), [#136](https://github.com/serialport/serialport-rs/pull/136) ## [4.2.2] - 2023-08-03 ### Fixed * Fixes a bug on the Raspberry Pi 4, which results in USB-devices being detected as PCI-devices. [#113](https://github.com/serialport/serialport-rs/pull/113) ## [4.2.1] - 2023-05-21 ### Added * Add support for reporting the USB device interface (feature-gated by _usbserialinfo-interface_). [#47](https://github.com/serialport/serialport-rs/pull/47), [#101](https://github.com/serialport/serialport-rs/pull/101) * Add example for loopback testing with real hardware. [#69](https://github.com/serialport/serialport-rs/pull/69) * Implement `fmt::Debug` and `fmt::Display` for `SerialPort` and related enums. [#91](https://github.com/serialport/serialport-rs/pull/91) ### Changed * Migrated from unmaintainted dependency `mach` to `mach2`. * Update dependency `nix` from 0.24.1 to 0.26.0 and raise MSRV to 1.56.1. [#67](https://github.com/serialport/serialport-rs/pull/67), [#75](https://github.com/serialport/serialport-rs/pull/75), [#78](https://github.com/serialport/serialport-rs/pull/78) ### Fixed * Skip attempts to set baud rate 0 on macOS. [#58](https://github.com/serialport/serialport-rs/pull/58) * Fix getting actual result value from `tiocmget`. [#61](https://github.com/serialport/serialport-rs/pull/61/files) * Fix serial number retrieval procedure on macOS. [#65](https://github.com/serialport/serialport-rs/pull/65) * Fix port name retrieval procedure for Unicode names on Windows. [#63](https://github.com/serialport/serialport-rs/pull/63) * Fix compilation for OpenBSD due to missing use declaration. [#68](https://github.com/serialport/serialport-rs/pull/68) * A number of memory leaks have been addressed when using serialport-rs. [#98](https://github.com/serialport/serialport-rs/pull/98) ## [4.2.0] - 2022-06-02 ### Added * Add `serde` support behind a feature flag. [#51](https://github.com/serialport/serialport-rs/pull/51) ### Changed * Request exclusive access when opening a POSIX serial port by default. [#44](https://github.com/serialport/serialport-rs/pull/44) * Updated `nix` dependency to 0.24.1 and limited features. [#46](https://github.com/serialport/serialport-rs/pull/46) * Derive the `Clone` trait for `Error`. [#53](https://github.com/serialport/serialport-rs/pull/53) * Enumerate callout devices in addition to dial-in devices on macOS. [#54](https://github.com/serialport/serialport-rs/pull/54) * Revert to edition 2018 to allow for use with older compiler versions. ### Fixed * Set port timeout to a non-zero value before performing loopback test. [#45](https://github.com/serialport/serialport-rs/pull/45) ## [4.1.0] - 2022-04-04 ### Added * impl `SerialPort` for `&mut T`. This allows a `&mut T (where T: SerialPort)` to be used in a context where `impl SerialPort` is expected. [!114](https://gitlab.com/susurrus/serialport-rs/-/merge_requests/114) ### Changed * Updated `nix` dependency to 0.23.1. * Remove useless call to tcflush on open. [#40](https://github.com/serialport/serialport-rs/pull/40) ### Fixed * Improved support for recent versions of macOS. [!104](https://gitlab.com/susurrus/serialport-rs/-/merge_requests/104) * Fix filehandle leak in open() on Windows. [#36](https://github.com/serialport/serialport-rs/pull/36) * Make sure fd is properly closed if initialization fails. [#39](https://github.com/serialport/serialport-rs/pull/39) [#41](https://github.com/serialport/serialport-rs/pull/41) ## [4.0.1] - 2021-04-17 ### Changed * Update maintenance status to looking for a new maintainer. ### Fixed * Properly initialize DCB structure on Windows. This fixes some non-functional devices. [!97](https://gitlab.com/susurrus/serialport-rs/-/merge_requests/97) ## [4.0.0] - 2020-12-17 ### Added * Added `send_break()` to `TTYPort`. [!69](https://gitlab.com/susurrus/serialport-rs/merge_requests/69) * Enable `available_ports()` for Linux musl targets and those without the `libudev` feature enabled by scanning `/sys/` for ports. [!72](https://gitlab.com/susurrus/serialport-rs/merge_requests/72) * `ENOENT` and `EACCES` errors are now exposed as `NotFound` and `PermissionDenied` errors on Linux. [!80](https://gitlab.com/susurrus/serialport-rs/merge_requests/80) * `try_clone_native()` was added to `COMPort` and `TTYPort` to complement `SerialPort::try_clone()` but returning the concrete type instead. [!85](https://gitlab.com/susurrus/serialport-rs/merge_requests/85) * Added `set_break()` and `clear_break()` to `SerialPort`. [!70](https://gitlab.com/susurrus/serialport-rs/merge_requests/70) ### Changed * Minimum supported Rust version is now 1.36.0 to support the `mem::MaybeUninit` feature. * The platform-specific `TTYPort`/`BreakDuration` and `COMPort` are now at the root level rather than under the `posix` and `windows` submodules respectively. * Opening `SerialPort` s now uses the builder pattern through `serialport::new()`. See the README for concrete examples. [!73](https://gitlab.com/susurrus/serialport-rs/merge_requests/73) * `SerialPorts`s are no longer opened with a default timeout of 1ms. * Under linux, the `manufacturer` and `product` fields of `UsbPortInfo` now take their values from the `ID_VENDOR_FROM_DATABASE` and `ID_MODEL_FROM_DATABASE` udev properties respectively, instead of the `ID_VENDOR` and `ID_MODEL` properties that were used before. When the `_FROM_DATABASE` values are not available, it falls back to the old behavior. [!86](https://gitlab.com/susurrus/serialport-rs/merge_requests/86) * POSIX ports are no longer opened in exclusive mode. After opening they can be made exclusive via `TTYPort::set_exclusive()`. [!98](https://gitlab.com/susurrus/serialport-rs/merge_requests/98) ### Fixed * Raised the version specification for `bitflags` to 1.0.4. Previously it was set to 1.0.0, but this version of `bitflags` is actually incompatible with Rust 2018 style macro imports that `serialport-rs` requires. [!83](https://gitlab.com/susurrus/serialport-rs/merge_requests/83) ### Removed * Removed the `serialport::prelude` module. Types should be explicitly imported or can be glob-imported from the root like `use serialport::*`. [!82](https://gitlab.com/susurrus/serialport-rs/merge_requests/82) ## [3.3.0] - 2019-06-12 ### Added * Added support for arbitrary baud rates on macOS and iOS. ### Changed * Minimum supported Rust version is now 1.31 to support using the 2018 edition of Rust. ### Fixed * Upgraded `sparc64-unknown-linux-gnu` to Tier 2 support. ## [3.2.0] - 2019-01-01 ### Added * Port enumeration is now supported on FreeBSD. ### Changed * Minimum supported Rust version changed to 1.24.1. * Made `aarch64-unknown-linux-musl` a Tier-2 supported target. ### Fixed * Fixed software flow control for POSIX systems. [!54](https://gitlab.com/susurrus/serialport-rs/merge_requests/54) ### Removed * Removed support for `x86_64-unknown-linux-gnux32`. ## [3.1.0] - 2018-11-02 ### Added * Added `bytes_to_read()`, `bytes_to_write()`, and `clear()` to `SerialPort`. Also added example scripts for using them. * Added Tier 2 support for: * `armv5te-unknown-linux-musleabi` * Added "libudev" feature to allow for disabling linking to `libudev` on Linux. ## [3.0.0] - 2018-07-14 ### Added * Arbitrary baud rates are now supported on BSDs, Linux, and Windows. * Added Tier 1 support for `{i586|i686|x86_64}-unknown-linux-musl`. * Added Tier 2 support for: * `{arm|armv7}-linux-androideabi` * `i686-linux-android` * `{i686|x86_64}-unknown-freebsd` * `arm-unknown-linux-musleabi` * `armv7-unknown-linux-musleabihf` * `{mips64|mips64el}-unknown-linux-gnuabi64` * `armv5te-unknown-linux-gnueabi` * `{aarch64|mips|mipsel|powerpc64|powerpc64le|powerpc|s390x}-unknown-linux-gnu` * `{mips|mipsel}-unknown-linux-musl` * `x86_64-unknown-netbsd` * Added Tier 3 support for: * `{aarch64|x86_64}-linux-android` * `aarch64-unknown-linux-musl` * `sparc64-unknown-linux-gnu`, * `x86_64-unknown-linux-gnux32` ### Changed * Most port configuration methods now return a `Result<()>`. * Renamed `SerialPort::port_name()` to `name()`. ### Fixed * On Windows, the `port_name` field on `SerialPortInfo` included an extraneous trailing nul byte character. ### Removed * The `BaudRate` enum was removed in favor of a `u32`. ## [2.3.0] - 2018-03-13 ### Added * Added `examples/hardware_check.rs` for use in debugging library or driver issues when using physical serial ports. * Added `SerialPort::try_clone` which allows for cloning a port for full-duplex reading and writing. ### Changed * Removed configuration caching for serial ports. The underlying implementations for all platforms cached a configuration struct so that modifying the port settings involved a single switch into kernel space. This has been removed so now two system calls are needed for every configuration change. This is probably a slight performance regression, but should allow the new `SerialPort::try_clone` interface to work as people expect. ### Fixed * `TTYPort::into_raw_fd` will now work as expected. It previously closed the port so the returned file descriptor would be invalid. * 921600 baud is now supported on NetBSD and FreeBSD. ## 2.2.0 - 2018-03-13 Unreleased, happened due to a user error using `cargo-release`. ## [2.1.0] - 2018-02-14 ### Added * `impl FromRawHandle` for `COMPort`. ### Changed * Specific IO-related errors are now returned instead of mapping every IO error to Unknown. This makes it possible to catch things like time-out errors. * Changed all baud rates to be reported as the discrete `BaudRate::Baud*` types rather than as the `BaudRate::BaudOther(*)` type. ### Fixed * Modem-type USB serial devices are now enumerated on macOS. This now allows connected Arduinos to be detected. * Compilation on FreeBSD and NetBSD was fixed by removing the 921600 baud rates. These will be re-added in a future release. ## [2.0.0] - 2017-12-18 ### Added * USB device information is now returned in calls to `available_ports()`. * Serial port enumeration is now supported on Mac. * Serial port enumeration now attempts to return the interface used for the port (USB, PCI, Bluetooth, Unknown). * `BaudRate::standard_rates()` provides a vector of cross-platform baud rates. * `SerialPort` trait is now `Send`. ### Changed * Software license has changed from LGPLv3+ to MPL-2.0. This makes it possible to use this library in any Rust project if it's unmodified. * Mac is now a Tier 2 supported platform. * Removed `BaudRate::from_speed(usize)` and `BaudRate::speed -> usize` in favor of the `From` and `Into` traits. * Removed `available_baud_rates` in favor of `BaudRate::platform_rates()` as this has a more clear semantic meaning. The returned list of baud rates is now also correct for all supported platforms. * Removed `termios` dependency in favor of `nix`. This is a big step towards supporting additional platforms. ### Fixed * Stop bits are now specified properly (had been reversed). Thanks to @serviushack. (MR#9) * `TTYPort::pair()` is now thread-safe. * `TTYPort::open()` no longer leaks file descriptors if it errors. Thanks to @daniel. (MR#12) * Fixed compilation when targeting Android. ## [1.0.1] - 2017-02-20 ### Fixed * `read()` now properly blocks for at least one character. * Compilation now works on Mac. ## [1.0.0] - 2017-02-13 ### Changed * Various documentation/README updates. * Minor formatting fixes (from rustfmt). ### Fixed * Platform-specific examples are now only built on appropriate platforms. ## [0.9.0] - 2017-02-09 ### Added * `impl Debug` for `COMPort`. * `exclusive()` and `set_exclusive()` for `TTYPort`. * `port_name()` for `SerialPort`. * `impl FromRawFd` and `impl IntoRawFd` for `TTYPort`. * `pair()` for `TTYPort`. ## [0.3.0] - 2017-01-28 ### Added * `open_with_settings()` to support initializing the port with custom settings. * `SerialPortSettings` is now publically usable being exported in the prelude, having all public and commented fields, and a `Default` impl. ### Changed * `TTYPort/COMPort::open()` now take a `SerialPortSettings` argument and return concrete types. * `serialport::open()` now initializes the port to reasonable defaults. * Removed all instances of `try!()` for `?`. * `SerialPort::set_all()` now borrows `SerialPortSettings`. ## [0.2.4] - 2017-01-26 ### Added * Report an Unimplemented error for unsupported unix targets. ### Changed * Minor changes suggested by Clippy. * Reworked Cargo.toml to more easily support additional targets. ### Fixed * AppVeyor badge should now be properly displayed. ## [0.2.3] - 2017-01-21 ### Added * Specify AppVeyor build status badge for crates.io. ## [0.2.2] - 2017-01-21 * No changes, purely a version increment to push new crate metadata to crates.io. ## [0.2.1] - 2017-01-21 ### Added * Specify category for crates.io. ## [0.2.0] - 2017-01-07 ### Added * Added a changelog. * Added a getter/setter pair for all settings at once. * An error is thrown if settings weren't correctly applied on POSIX. ## [0.1.1] - 2016-12-23 ### Changed * Fixed compilation on x86_64-pc-windows-gnu target. * Added contributors to README. * Clarified license terms in the README. ## [0.1.0] - 2016-12-22 ### Added * Initial release. [Unreleased]: https://github.com/serialport/serialport-rs/compare/v4.7.0...HEAD [4.7.0]: https://github.com/serialport/serialport-rs/compare/v4.6.1...v4.7.0 [4.6.1]: https://github.com/serialport/serialport-rs/compare/v4.6.0...v4.6.1 [4.6.0]: https://github.com/serialport/serialport-rs/compare/v4.5.1...v4.6.0 [4.5.1]: https://github.com/serialport/serialport-rs/compare/v4.5.0...v4.5.1 [4.5.0]: https://github.com/serialport/serialport-rs/compare/v4.4.0...v4.5.0 [4.4.0]: https://github.com/serialport/serialport-rs/compare/v4.3.0...v4.4.0 [4.3.0]: https://github.com/serialport/serialport-rs/compare/v4.2.2...v4.3.0 [4.2.2]: https://github.com/serialport/serialport-rs/compare/v4.2.1...v4.2.2 [4.2.1]: https://github.com/serialport/serialport-rs/compare/v4.2.0...v4.2.1 [4.2.0]: https://github.com/serialport/serialport-rs/compare/v4.1.0...v4.2.0 [4.1.0]: https://github.com/serialport/serialport-rs/compare/v4.0.1...v4.1.0 [4.0.1]: https://github.com/serialport/serialport-rs/compare/v4.0.0...v4.0.1 [4.0.0]: https://github.com/serialport/serialport-rs/compare/v3.3.0...v4.0.0 [3.3.0]: https://github.com/serialport/serialport-rs/compare/v3.2.0...v3.3.0 [3.2.0]: https://github.com/serialport/serialport-rs/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/serialport/serialport-rs/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/serialport/serialport-rs/compare/v2.3.0...v3.0.0 [2.3.0]: https://github.com/serialport/serialport-rs/compare/v2.1.0...v2.3.0 [2.1.0]: https://github.com/serialport/serialport-rs/compare/v2.0.0...v2.1.0 [2.0.0]: https://github.com/serialport/serialport-rs/compare/v1.0.1...v2.0.0 [1.0.1]: https://github.com/serialport/serialport-rs/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/serialport/serialport-rs/compare/v0.9.0...v1.0.0 [0.9.0]: https://github.com/serialport/serialport-rs/compare/v0.3.0...v0.9.0 [0.3.0]: https://github.com/serialport/serialport-rs/compare/v0.2.4...v0.3.0 [0.2.4]: https://github.com/serialport/serialport-rs/compare/v0.2.3...v0.2.4 [0.2.3]: https://github.com/serialport/serialport-rs/compare/v0.2.2...v0.2.3 [0.2.2]: https://github.com/serialport/serialport-rs/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/serialport/serialport-rs/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/serialport/serialport-rs/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/serialport/serialport-rs/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/serialport/serialport-rs/releases/tag/v0.1.0 serialport-4.7.0/Cargo.lock0000644000000441170000000000100112120ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "assert_hex" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7010f1430f0fc8ca80bdb5e5d074db68776a2e268ec6cf80b53712d3ea4bca7" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags 1.3.2", "clap_derive", "clap_lex", "indexmap", "once_cell", "strsim", "termcolor", "textwrap", ] [[package]] name = "clap_derive" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "core-foundation" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "env_logger" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", ] [[package]] name = "envconfig" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea81cc7e21f55a9d9b1efb6816904978d0bfbe31a50347cb24b2e75564bcac9b" dependencies = [ "envconfig_derive", ] [[package]] name = "envconfig_derive" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfca278e5f84b45519acaaff758ebfa01f18e96998bc24b8f1b722dd804b9bf" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "io-kit-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" dependencies = [ "core-foundation-sys", "mach2", ] [[package]] name = "libc" version = "0.2.163" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fdaeca4cf44ed4ac623e86ef41f056e848dbeab7ec043ecb7326ba300b36fd0" [[package]] name = "libudev" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" dependencies = [ "libc", "libudev-sys", ] [[package]] name = "libudev-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" dependencies = [ "libc", "pkg-config", ] [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "mach2" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "nix" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", ] [[package]] name = "once_cell" version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" [[package]] name = "os_str_bytes" version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" dependencies = [ "memchr", ] [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "syn 1.0.109", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger", "log", "rand", ] [[package]] name = "quickcheck_macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rstest" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d912f35156a3f99a66ee3e11ac2e0b3f34ac85a07e05263d05a7e2c8810d616f" dependencies = [ "cfg-if", "proc-macro2", "quote", "rustc_version", "syn 1.0.109", ] [[package]] name = "rstest_reuse" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" dependencies = [ "quote", "rand", "rustc_version", "syn 2.0.56", ] [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustversion" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn 2.0.56", ] [[package]] name = "serialport" version = "4.7.0" dependencies = [ "assert_hex", "bitflags 2.6.0", "cfg-if", "clap", "core-foundation", "core-foundation-sys", "envconfig", "io-kit-sys", "libc", "libudev", "mach2", "nix", "os_str_bytes", "quickcheck", "quickcheck_macros", "rstest", "rstest_reuse", "rustversion", "scopeguard", "serde", "unescaper", "winapi", ] [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2415488199887523e74fd9a5f7be804dfd42d868ae0eca382e3917094d210e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", "syn 2.0.56", ] [[package]] name = "unescaper" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" dependencies = [ "thiserror", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.56", ] serialport-4.7.0/Cargo.toml0000644000000077420000000000100112400ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.59.0" name = "serialport" version = "4.7.0" authors = [ "Bryant Mairs ", "Jesse Braham ", ] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A cross-platform low-level serial port library." documentation = "https://docs.rs/serialport" readme = "README.md" keywords = [ "serial", "hardware", "system", "RS232", ] categories = ["hardware-support"] license = "MPL-2.0" repository = "https://github.com/serialport/serialport-rs" [lib] name = "serialport" path = "src/lib.rs" [[example]] name = "clear_input_buffer" path = "examples/clear_input_buffer.rs" [[example]] name = "clear_output_buffer" path = "examples/clear_output_buffer.rs" [[example]] name = "duplex" path = "examples/duplex.rs" [[example]] name = "hardware_check" path = "examples/hardware_check.rs" [[example]] name = "list_ports" path = "examples/list_ports.rs" [[example]] name = "loopback" path = "examples/loopback.rs" [[example]] name = "pseudo_terminal" path = "examples/pseudo_terminal.rs" [[example]] name = "receive_data" path = "examples/receive_data.rs" [[example]] name = "transmit" path = "examples/transmit.rs" [[test]] name = "config" path = "tests/config.rs" [[test]] name = "test_baudrate" path = "tests/test_baudrate.rs" [[test]] name = "test_serialport" path = "tests/test_serialport.rs" [[test]] name = "test_timeout" path = "tests/test_timeout.rs" [[test]] name = "test_try_clone" path = "tests/test_try_clone.rs" [[test]] name = "test_tty" path = "tests/test_tty.rs" [dependencies.cfg-if] version = "1.0.0" [dependencies.scopeguard] version = "1.1" [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dev-dependencies.assert_hex] version = "0.4.1" [dev-dependencies.clap] version = "3.1.6" features = ["derive"] [dev-dependencies.envconfig] version = "0.10.0" [dev-dependencies.libc] version = ">=0.2.0, <=0.2.163" [dev-dependencies.os_str_bytes] version = ">=6.0, <6.6.0" [dev-dependencies.quickcheck] version = "1.0.3" [dev-dependencies.quickcheck_macros] version = "1.0.0" [dev-dependencies.rstest] version = "0.12.0" default-features = false [dev-dependencies.rstest_reuse] version = "0.6.0" [dev-dependencies.rustversion] version = "1.0.16" [features] default = ["libudev"] ignore-hardware-tests = [] usbportinfo-interface = [] [target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies.libudev] version = "0.3.0" optional = true [target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies.unescaper] version = "0.1.3" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.core-foundation] version = "0.10.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.core-foundation-sys] version = "0.8.4" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.io-kit-sys] version = "0.4.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.mach2] version = "0.4.1" [target."cfg(unix)".dependencies.bitflags] version = "2.4.0" [target."cfg(unix)".dependencies.nix] version = "0.26" features = [ "fs", "ioctl", "poll", "signal", "term", ] default-features = false [target."cfg(windows)".dependencies.winapi] version = "0.3.9" features = [ "cguid", "commapi", "errhandlingapi", "fileapi", "guiddef", "handleapi", "minwinbase", "minwindef", "ntdef", "setupapi", "winbase", "winerror", "winnt", ] serialport-4.7.0/Cargo.toml.orig000064400000000000000000000045161046102023000147150ustar 00000000000000[package] name = "serialport" version = "4.7.0" authors = [ "Bryant Mairs ", "Jesse Braham ", ] edition = "2021" rust-version = "1.59.0" description = "A cross-platform low-level serial port library." documentation = "https://docs.rs/serialport" repository = "https://github.com/serialport/serialport-rs" license = "MPL-2.0" keywords = ["serial", "hardware", "system", "RS232"] categories = ["hardware-support"] [target."cfg(unix)".dependencies] bitflags = "2.4.0" nix = { version = "0.26", default-features = false, features = ["fs", "ioctl", "poll", "signal", "term"] } [target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies] libudev = { version = "0.3.0", optional = true } unescaper = "0.1.3" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation = "0.10.0" core-foundation-sys = "0.8.4" io-kit-sys = "0.4.0" mach2 = "0.4.1" [target."cfg(windows)".dependencies.winapi] version = "0.3.9" features = [ "cguid", "commapi", "errhandlingapi", "fileapi", "guiddef", "handleapi", "minwinbase", "minwindef", "ntdef", "setupapi", "winbase", "winerror", "winnt", ] [dependencies] cfg-if = "1.0.0" scopeguard = "1.1" serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] assert_hex = "0.4.1" clap = { version = "3.1.6", features = ["derive"] } envconfig = "0.10.0" # TODES Remove pinning this subdependency (of clap) when we are bumping our # MSRV (libc raised its MSRV with a patch release 0.2.167 from 1.19.0 to # 1.63.0). Trick the resolver into picking a compatible release of libc by # adding it as a direct dependency meanwhile. libc = ">=0.2.0, <=0.2.163" # TODO: Remove pinning this subdependency of clap when we are bumping our MSRV. # (There has been an incompatible change with the MSRV of os_str_bytes with # 6.6.0) Until then we are tricking the dependency resolver into using a # compatible version by adding it as a direct dependency here. os_str_bytes = ">=6.0, <6.6.0" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" rstest = { version = "0.12.0", default-features = false } rstest_reuse = "0.6.0" rustversion = "1.0.16" [features] default = ["libudev"] ignore-hardware-tests = [] # TODO: Make the feature unconditionally available with the next major release # (5.0) and remove this feature gate. usbportinfo-interface = [] serialport-4.7.0/LICENSE.txt000064400000000000000000000003031046102023000136370ustar 00000000000000This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. serialport-4.7.0/NOTES.md000064400000000000000000000043601046102023000132350ustar 00000000000000# macOS / iOS Macs use the usual `termios` TTY implementation that other POSIXes use, but support non-standard baud rates through the `iossiospeed` ioctl (as of Mac OS X 10.4). To support non-standard baud rates on Mac, there are three main approaches: 1. Always use `iossiospeed` 2. Use `iossiospeed` for non-standard bauds, but `termios` with standard bauds 3. Use `iossiospeed` always by default and fail-over to the termios approach ## Implementation notes This library uses the first approach. Given that macOS as far back as 10.4 supports it (2005), there seem to be no downsides. Internally, baud rates within the `termios` struct are kept at 9600 when that struct is read & written. This means that anytime the `termios` struct is written back (using `tcsetattr` a call to `iossiospeed` follows it. Additionally, the `termios` struct is not cached and instead retrieved on every settings adjustment. While this can increase the number of system calls when changing port settings, it removes the need to keep state consistent and instead the kernel's state can always be considered the canonical source. ## Platform notes `iossiospeed` has no official documentation that can be found by searching https://developer.apple.com. However [IOSerialTestLib.c](https://opensource.apple.com/source/IOSerialFamily/IOSerialFamily-93/tests/IOSerialTestLib.c.auto.html) can be found on Apple's open source code repository and has some example code for using this API. Experimentation has shown that there are a few key features to using `iossiospeed`: * `iossiospeed` should be called after setting the `termios` struct via `tcsetattr` as that resets the baud rate and you cannot put custom baud rates in the `termios` struct. * Calling `iossiospeed` will modify the `termios` struct in the kernel such that you can no longer round-trip the `termios` struct. The following code will fail: ```C struct termios t; tcgetattr(fd, &t); tcsetattr(fd, TCSANOW, &t) ``` ## Reference implementations * [picocom](https://github.com/npat-efault/picocom) follows the second approach. However they also cache the existing `termios` struct. # Additional References * [Understanding UNIX termios VMIN and VTIME](http://unixwiz.net/techtips/termios-vmin-vtime.html) serialport-4.7.0/README.md000064400000000000000000000166131046102023000133060ustar 00000000000000[![crates.io version badge](https://img.shields.io/crates/v/serialport.svg)](https://crates.io/crates/serialport) [![Documentation](https://docs.rs/serialport/badge.svg)](https://docs.rs/serialport) [![GitHub workflow status](https://img.shields.io/github/actions/workflow/status/serialport/serialport-rs/ci.yaml?branch=main&logo=github)](https://github.com/serialport/serialport-rs/actions) [![Minimum Stable Rust Version](https://img.shields.io/badge/Rust-1.59.0-blue?logo=rust)](https://blog.rust-lang.org/2022/02/24/Rust-1.59.0.html) # 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](https://github.com/berkowski/mio-serial) and [tokio-serial](https://github.com/berkowski/tokio-serial) crates. Join the discussion on Matrix! [#serialport-rs:matrix.org](https://matrix.to/#/#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](https://github.com/serialport/serialport-rs/discussions/new).** # 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` 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` (unless you disable the default `libudev` feature), 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: ```shell $ 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: ```rust let ports = serialport::available_ports().expect("No ports found!"); for p in ports { println!("{}", p.port_name); } ``` Opening and configuring a port: ```rust let port = serialport::new("/dev/ttyUSB0", 115_200) .timeout(Duration::from_millis(10)) .open().expect("Failed to open port"); ``` Writing to a port: ```rust 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): ```rust let mut serial_buf: Vec = 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: ```rust 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 `Drop`ed 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 some tests (not requiring actual hardware) for major 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](https://www.mozilla.org/en-US/MPL/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 . 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-4.7.0/TESTING.md000064400000000000000000000014341046102023000134610ustar 00000000000000How to test `serialport-rs` for development. Without hardware: 1. Compilation 2. `cargo test` With a single unconnected device: `cargo run --example hardware_check ` And when wired in a physical loopback mode: `cargo run --example hardware_check --loopback` With two devices connected to each other: * `cargo run --example hardware_check --loopback-port ` * Also `cargo run --example heartbeat ` in one terminal and `cargo run --example receive_data ` in another Can also verify trickier settings (like non-standard baud rates) using serial terminal programs like: * `screen` (POSIX) * [CoolTerm](http://freeware.the-meiers.org/) (macOS) * [RealTerm](https://sourceforge.net/projects/realterm/) (Windows) serialport-4.7.0/deny.toml000064400000000000000000000200551046102023000136560ustar 00000000000000# Note that all fields that take a lint level have these possible values: # * deny - An error will be produced and the check will fail # * warn - A warning will be produced, but the check will not fail # * allow - No warning or error will be produced, though in some cases a note # will be [graph] # If 1 or more target triples (and optionally, target_features) are specified, # only the specified targets will be checked when running `cargo deny check`. # This means, if a particular package is only ever used as a target specific # dependency, such as, for example, the `nix` crate only being used via the # `target_family = "unix"` configuration, that only having windows targets in # this list would mean the nix crate, as well as any of its exclusive # dependencies not shared by any other crates, would be ignored, as the target # list here is effectively saying which targets you are building for. targets = [ { triple = "aarch64-apple-darwin" }, { triple = "aarch64-apple-ios" }, { triple = "aarch64-unknown-linux-gnu" }, { triple = "aarch64-unknown-linux-musl" }, { triple = "arm-linux-androideabi" }, { triple = "armv7-linux-androideabi" }, { triple = "i686-pc-windows-gnu" }, { triple = "i686-pc-windows-msvc" }, { triple = "i686-unknown-linux-gnu" }, { triple = "i686-unknown-linux-musl" }, { triple = "x86_64-apple-darwin" }, { triple = "x86_64-pc-windows-gnu" }, { triple = "x86_64-pc-windows-msvc" }, { triple = "x86_64-unknown-linux-gnu" }, { triple = "x86_64-unknown-linux-musl" }, { triple = "x86_64-unknown-netbsd" }, ] # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] # Selects the default behavior for checking advisories. version = 2 # The path where the advisory database is cloned/fetched into db-path = "~/.cargo/advisory-db" # The url(s) of the advisory databases to use db-urls = ["https://github.com/rustsec/advisory-db"] # The lint level for crates that have been yanked from their source registry yanked = "deny" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ "RUSTSEC-2021-0145", # caused by unmaintained atty "RUSTSEC-2024-0370", # caused by unmaintained proc-macro-error used by some examples "RUSTSEC-2024-0375", # caused by umnaintained atty (again, with migration hint) ] # Threshold for security vulnerabilities, any vulnerability with a CVSS score # lower than the range specified will be ignored. Note that ignored advisories # will still output a note when they are encountered. # * None - CVSS Score 0.0 # * Low - CVSS Score 0.1 - 3.9 # * Medium - CVSS Score 4.0 - 6.9 # * High - CVSS Score 7.0 - 8.9 # * Critical - CVSS Score 9.0 - 10.0 #severity-threshold = # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. # See Git Authentication for more information about setting up git authentication. #git-fetch-with-cli = true # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # Selects the default behavior for checking licenses. version = 2 # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ "Apache-2.0", "BSD-2-Clause", "MIT", "MPL-2.0", "Unicode-3.0", "Unicode-DFS-2016", ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. # [possible values: any between 0.0 and 1.0]. confidence-threshold = 0.8 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list #{ allow = ["Zlib"], name = "adler32", version = "*" }, ] # Some crates don't have (easily) machine readable licensing information, # adding a clarification entry for it allows you to manually specify the # licensing information #[[licenses.clarify]] # The name of the crate the clarification applies to #name = "ring" # The optional version constraint for the crate #version = "*" # The SPDX expression for the license requirements of the crate #expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents #{ path = "LICENSE", hash = 0xbd0eed23 } #] [licenses.private] # If true, ignores workspace crates that aren't published, or are only # published to private registries. # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked registries = [ #"https://sekretz.com/registry ] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected multiple-versions = "deny" # Lint level for when a crate version requirement is `*` wildcards = "deny" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted # * simplest-path - The path to the version with the fewest edges is highlighted # * all - Both lowest-version and simplest-path are used highlight = "all" # List of crates that are allowed. Use with care! allow = [ ] # List of crates to deny deny = [ ] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive # dependencies starting at the specified crate, up to a certain depth, which is # by default infinite skip-tree = [ { name = "clap", version = "~3.2" }, # https://github.com/serialport/serialport-rs/pull/76 ] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html [sources] # Lint level for what to happen when a crate from a crate registry that is not # in the allow list is encountered unknown-registry = "deny" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "deny" # List of URLs for allowed crate registries. Defaults to the crates.io index # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [] [sources.allow-org] # 1 or more github.com organizations to allow git sources for #github = [""] # 1 or more gitlab.com organizations to allow git sources for #gitlab = [""] # 1 or more bitbucket.org organizations to allow git sources for #bitbucket = [""] serialport-4.7.0/doc/dev_notes.md000064400000000000000000000002121046102023000150700ustar 00000000000000# Developer Notes This is a collection of additional documentation about the design decisions made in the development of `serialport-rs`.serialport-4.7.0/doc/index.md000064400000000000000000000021621046102023000142170ustar 00000000000000# serialport-rs Serial ports are some of the oldest external interfaces exposed by system kernels and their interfaces are quite clumsy. Additionally there are plenty of caveats in each platform's interface that we attempt to hide through the safe cross-platform interface. Many details of these interfaces are not well documented and so the following resources are an attempt to remedy that situation. They also serve as helpful developer documentation for `serialport-rs`. ## Resources [Platform API overview](./platforms.md) [Developer notes](./dev_notes.md) ## References * * * * * * serialport-4.7.0/doc/platforms.md000064400000000000000000000051211046102023000151150ustar 00000000000000# Platform API Overview The three primary platforms have considerable differences in even their basic blocking APIs that are worth outlining in more detail. Many aspects of the cross-platform API came about because of these differences. ## Windows Surprisingly enough, Windows has the most sane out of all of the interfaces. There is a singular `DCB` struct that contains all configuration necessary for the port. Once a `DCB` struct is created and configured, it's quite easy to configure the port and call `SetCommState()`. Note that this struct supports arbitrary baud rates by default. The `DCB` struct for a given `HANDLE` can also be retrieved from the `GetCommState()` function. ## Linux & Android Linux and Android provide both the Termios and Termios2 APIs. The Termios API allows for all configuration necessary, but does not support arbitrary baud rates. In this API the speed members of the struct are not accessible and the `cfsetXspeed()` functions must be used to configure them. And the only appropriate values are the `B*` constants. The Termios2 API, on the other hand, supports arbitrary baud rates. Instead of using `cfsetXspeed` and the `B*` constants, you can modify the `c_ispeed` and `c_ospeed` fields of the `termios2` struct directly. ## The BSDs (DragonFlyBSD, FreeBSD, NetBSD, OpenBSD) The BSDs basically **only** have the Termios2 API, but they call it Termios. It supports arbitrary baud rates out of the gate as the `termios2.c_ispeed` and `termios2.c_ospeed` fields are directly settable to the desired baud rate. ### FreeBSD * https://docs.freebsd.org/en/books/handbook/serialcomms/#serial ### NetBSD * https://man.netbsd.org/tty.4 * https://man.netbsd.org/com.4 * https://www.netbsd.org/docs/Hardware/Misc/serial.html * https://www.netbsd.org/ports/hp300/faq.html ### OpenBSD * https://man.openbsd.org/tty.4 ## macOS and iOS While macOS and iOS have the heritage of a BSD, their support is slightly different. In theory, they support arbitrary baud rates in their Termios API much like the BSDs, but in practice this doesn't work with many hardware devices, as it's dependent on driver support. Instead, Apple added the `IOSSIOSPEED` ioctl in Mac OS X 10.4, which can set the baud rate to an arbitrary value. As the oldest macOS version supported by Rust is 10.7, it's available on all Mac platforms. This API requires the port to be set into raw mode with `cfmakeraw`, and must be done after every call to `tcsetattr`, as that will reset the baud rate. Additionally, there is no way to retrieve the actual baud rate from the OS. This is therefore the clunkiest API of any platform. serialport-4.7.0/examples/clear_input_buffer.rs000064400000000000000000000075511046102023000200520ustar 00000000000000// // Provides a way to test clearing and querying the size of the serial input buffer. // // USAGE: // // 1. Connect a serial device to your host computer. E.g. an Arduino loaded with the example sketch // given below // 2. Run this example // 3. Observe the output - it reports how many bytes have been received from the device and are // waiting to be read // 4. Press the Return key to make the example clear the input buffer. You should see the number of // bytes queued to read momentarily drop to 0 // 5. Press Ctrl+D (Unix) or Ctrl+Z (Win) to quit // // The following Arduino firmware could be used to generate input: // // ``` // #include // // void setup() { // Serial.begin(9600); // while (!Serial); // wait for serial port to connect. Needed for native USB // } // // int iter = 0; // // void loop() { // Serial.print(iter); // if (++iter == 10) { // Serial.println(); // iter = 0; // } // delay(1000 / 20); // } // ``` use std::error::Error; use std::io::{self, Read}; use std::panic::panic_any; use std::sync::mpsc; use std::thread; use std::time::Duration; use clap::{Arg, Command}; use serialport::ClearBuffer; fn main() { let matches = Command::new("Serialport Example - Clear Input Buffer") .about("Reports how many bytes are waiting to be read and allows the user to clear the input buffer") .disable_version_flag(true) .arg(Arg::new("port") .help("The device path to a serial port") .use_value_delimiter(false) .required(true)) .arg(Arg::new("baud") .help("The baud rate to connect at") .use_value_delimiter(false) .required(true)) .get_matches(); let port_name = matches.value_of("port").unwrap(); let baud_rate = matches.value_of("baud").unwrap(); let exit_code = match run(port_name, baud_rate) { Ok(_) => 0, Err(e) => { println!("Error: {}", e); 1 } }; std::process::exit(exit_code); } fn run(port_name: &str, baud_rate: &str) -> Result<(), Box> { let rate = baud_rate .parse::() .map_err(|_| format!("Invalid baud rate '{}' specified", baud_rate))?; let port = serialport::new(port_name, rate) .timeout(Duration::from_millis(10)) .open() .map_err(|ref e| format!("Port '{}' not available: {}", &port_name, e))?; let chan_clear_buf = input_service(); println!("Connected to {} at {} baud", &port_name, &baud_rate); println!("Ctrl+D (Unix) or Ctrl+Z (Win) to stop. Press Return to clear the buffer."); loop { println!( "Bytes available to read: {}", port.bytes_to_read().expect("Error calling bytes_to_read") ); match chan_clear_buf.try_recv() { Ok(_) => { println!("------------------------- Discarding buffer ------------------------- "); port.clear(ClearBuffer::Input) .expect("Failed to discard input buffer") } Err(mpsc::TryRecvError::Empty) => (), Err(mpsc::TryRecvError::Disconnected) => { println!("Stopping."); break; } } thread::sleep(Duration::from_millis(100)); } Ok(()) } fn input_service() -> mpsc::Receiver<()> { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let mut buffer = [0; 32]; loop { // Block awaiting any user input match io::stdin().read(&mut buffer) { Ok(0) => { drop(tx); // EOF, drop the channel and stop the thread break; } Ok(_bytes_read) => tx.send(()).unwrap(), // Signal main to clear the buffer Err(e) => panic_any(e), } } }); rx } serialport-4.7.0/examples/clear_output_buffer.rs000064400000000000000000000107751046102023000202550ustar 00000000000000// // Provides a way to test clearing and querying the size of the serial output buffer. // // USAGE: // // 1. Connect a serial device to your host computer. E.g. an Arduino could be used. It will be able // to receive data without any specific sketch loaded. // 2. Run this example // 3. Observe the output - it reports how many bytes are waiting to be sent to the connected device // 4. Press the Return key to make the example clear the output buffer. You should see the number // of bytes queued to send momentarily drop to 0 // 5. Try passing different values for the buffer-size argument to see how that affects the speed // and saturation point of the output buffer // 6. Press Ctrl+D (Unix) or Ctrl+Z (Win) to quit // use std::error::Error; use std::io::{self, Read}; use std::panic::panic_any; use std::sync::mpsc; use std::thread; use std::time::Duration; use clap::{Arg, ArgMatches, Command}; use serialport::ClearBuffer; const DEFAULT_BLOCK_SIZE: &str = "128"; fn main() { let block_size_help = format!( "The size in bytes of the block of data to write to the port (default: {} bytes)", DEFAULT_BLOCK_SIZE ); let matches = Command::new("Serialport Example - Clear Output Buffer") .about("Reports how many bytes are waiting to be read and allows the user to clear the output buffer") .disable_version_flag(true) .arg(Arg::new("port") .help("The device path to a serial port") .use_value_delimiter(false) .required(true)) .arg(Arg::new("baud") .help("The baud rate to connect at") .use_value_delimiter(false) .required(true)) .arg(Arg::new("block-size") .help(Some(block_size_help.as_str())) .use_value_delimiter(false) .default_value(DEFAULT_BLOCK_SIZE)) .get_matches(); let port_name = matches.value_of("port").unwrap(); let baud_rate = matches.value_of("baud").unwrap(); let block_size = ArgMatches::value_of_t(&matches, "block-size").unwrap_or_else(|e| e.exit()); let exit_code = match run(port_name, baud_rate, block_size) { Ok(_) => 0, Err(e) => { println!("Error: {}", e); 1 } }; std::process::exit(exit_code); } fn run(port_name: &str, baud_rate: &str, block_size: usize) -> Result<(), Box> { let rate = baud_rate .parse::() .map_err(|_| format!("Invalid baud rate '{}' specified", baud_rate))?; let mut port = serialport::new(port_name, rate) .timeout(Duration::from_millis(10)) .open() .map_err(|ref e| format!("Port '{}' not available: {}", &port_name, e))?; let chan_clear_buf = input_service(); println!("Connected to {} at {} baud", &port_name, &baud_rate); println!("Ctrl+D (Unix) or Ctrl+Z (Win) to stop. Press Return to clear the buffer."); let block = vec![0; block_size]; // This loop writes the block repeatedly, as fast as possible, to try to saturate the // output buffer. If you don't see much data queued to send, try changing the block size. loop { match port.write_all(&block) { Ok(_) => (), Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (), Err(e) => panic!("Error while writing data to the port: {}", e), }; match chan_clear_buf.try_recv() { Ok(_) => { println!("------------------------- Discarding buffer ------------------------- "); port.clear(ClearBuffer::Output) .expect("Failed to discard output buffer") } Err(mpsc::TryRecvError::Empty) => (), Err(mpsc::TryRecvError::Disconnected) => { println!("Stopping."); break; } } println!( "Bytes queued to send: {}", port.bytes_to_write().expect("Error calling bytes_to_write") ); } Ok(()) } fn input_service() -> mpsc::Receiver<()> { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let mut buffer = [0; 32]; loop { // Block awaiting any user input match io::stdin().read(&mut buffer) { Ok(0) => { drop(tx); // EOF, drop the channel and stop the thread break; } Ok(_bytes_read) => tx.send(()).unwrap(), // Signal main to clear the buffer Err(e) => panic_any(e), } } }); rx } serialport-4.7.0/examples/duplex.rs000064400000000000000000000031211046102023000155020ustar 00000000000000//! Duplex example //! //! This example tests the ability to clone a serial port. It works by creating //! a new file descriptor, and therefore a new `SerialPort` object that's safe //! to send to a new thread. //! //! This example selects the first port on the system, clones the port into a child //! thread that writes data to the port every second. While this is running the parent //! thread continually reads from the port. //! //! To test this, have a physical or virtual loopback device connected as the //! only port in the system. use std::io::Write; use std::time::Duration; use std::{io, thread}; fn main() { // Open the first serialport available. let port_name = &serialport::available_ports().expect("No serial port")[0].port_name; let mut port = serialport::new(port_name, 9600) .open() .expect("Failed to open serial port"); // Clone the port let mut clone = port.try_clone().expect("Failed to clone"); // Send out 4 bytes every second thread::spawn(move || loop { clone .write_all(&[5, 6, 7, 8]) .expect("Failed to write to serial port"); thread::sleep(Duration::from_millis(1000)); }); // Read the four bytes back from the cloned port let mut buffer: [u8; 1] = [0; 1]; loop { match port.read(&mut buffer) { Ok(bytes) => { if bytes == 1 { println!("Received: {:?}", buffer); } } Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (), Err(e) => eprintln!("{:?}", e), } } } serialport-4.7.0/examples/hardware_check.rs000064400000000000000000000326151046102023000171450ustar 00000000000000//! This example performs a test using real hardware ports. //! //! This tool serves as a debugging aid when encountering errors using `serialport-rs`. It should //! expose any kernel or driver bugs that your system may have by running physical ports through //! many configurations. //! //! There are 3 ways to run this example: //! //! 1) With a single port not connected to an external device: //! `cargo run --example hardware_check /dev/ttyUSB0` //! //! 2) With a single port physically connected in loopback mode (RX<->TX) //! `cargo run --example hardware_check /dev/ttyUSB0 --loopback` //! //! 3) With two ports physically connected to each other //! `cargo run --example hardware_check /dev/ttyUSB0 /dev/ttyUSB1` use std::io::Write; use std::str; use std::time::Duration; use assert_hex::assert_eq_hex; use clap::{Arg, Command}; use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPort, StopBits}; const TEST_MESSAGE: &[u8] = "Test Message".as_bytes(); fn main() { let matches = Command::new("Serialport Example - Hardware Check") .about("Test hardware capabilities of serial ports") .disable_version_flag(true) .arg(Arg::new("port") .help("The device path to a serial port") .use_value_delimiter(false) .required(true)) .arg(Arg::new("loopback") .help("Run extra tests if the port is configured for hardware loopback. Mutually exclusive with the --loopback-port option") .use_value_delimiter(false) .conflicts_with("loopback-port") .long("loopback")) .arg(Arg::new("loopback-port") .help("The device path of a second serial port that is connected to the first serial port. Mutually exclusive with the --loopback option.") .use_value_delimiter(false) .takes_value(true) .long("loopback-port")) .get_matches(); let port1_name = matches.value_of("port").unwrap(); let port2_name = matches.value_of("loopback-port").unwrap_or(""); let port1_loopback = matches.is_present("loopback"); // Loopback mode is only available when a single port is specified if port1_loopback && !port2_name.is_empty() { eprintln!("ERROR: loopback mode can only be enabled when a single port is specified."); ::std::process::exit(1); } // Run single-port tests on port1 let mut port1 = match serialport::new(port1_name, 9600).open() { Err(e) => { eprintln!("Failed to open \"{}\". Error: {}", port1_name, e); ::std::process::exit(1); } Ok(p) => p, }; test_single_port(&mut *port1, port1_loopback); if !port2_name.is_empty() { // Run single-port tests on port2 let mut port2 = match serialport::new(port2_name, 9600).open() { Err(e) => { eprintln!("Failed to open \"{}\". Error: {}", port2_name, e); ::std::process::exit(1); } Ok(p) => p, }; test_single_port(&mut *port2, false); // Test loopback pair test_dual_ports(&mut *port1, &mut *port2); } } macro_rules! baud_rate_check { ($port:ident, $baud:expr) => { let baud_rate = $baud; if let Err(e) = $port.set_baud_rate(baud_rate) { println!(" {:?}: FAILED ({})", baud_rate, e); } match $port.baud_rate() { Err(_) => println!(" {:?}: FAILED (error retrieving baud rate)", baud_rate), Ok(r) if r != baud_rate => println!( " {:?}: FAILED (baud rate {:?} does not match set baud rate {:?})", baud_rate, r, baud_rate ), Ok(_) => println!(" {:?}: success", baud_rate), } }; } macro_rules! data_bits_check { ($port:ident, $data_bits:path) => { let data_bits = $data_bits; if let Err(e) = $port.set_data_bits(data_bits) { println!(" {:?}: FAILED ({})", data_bits, e); } else { match $port.data_bits() { Err(_) => println!("FAILED to retrieve data bits"), Ok(r) if r != data_bits => println!( " {:?}: FAILED (data bits {:?} does not match set data bits {:?})", data_bits, r, data_bits ), Ok(_) => println!(" {:?}: success", data_bits), } } }; } macro_rules! flow_control_check { ($port:ident, $flow_control:path) => { let flow_control = $flow_control; if let Err(e) = $port.set_flow_control(flow_control) { println!(" {:?}: FAILED ({})", flow_control, e); } else { match $port.flow_control() { Err(_) => println!("FAILED to retrieve flow control"), Ok(r) if r != flow_control => println!( " {:?}: FAILED (flow control {:?} does not match set flow control {:?})", flow_control, r, flow_control ), Ok(_) => println!(" {:?}: success", flow_control), } } }; } macro_rules! parity_check { ($port:ident, $parity:path) => { let parity = $parity; if let Err(e) = $port.set_parity(parity) { println!(" {:?}: FAILED ({})", parity, e); } else { match $port.parity() { Err(_) => println!("FAILED to retrieve parity"), Ok(r) if r != parity => println!( " {:?}: FAILED (parity {:?} does not match set parity {:?})", parity, r, parity ), Ok(_) => println!(" {:?}: success", parity), } } }; } macro_rules! stop_bits_check { ($port:ident, $stop_bits:path) => { let stop_bits = $stop_bits; if let Err(e) = $port.set_stop_bits(stop_bits) { println!(" {:?}: FAILED ({})", stop_bits, e); } else { match $port.stop_bits() { Err(_) => println!("FAILED to retrieve stop bits"), Ok(r) if r != stop_bits => println!( "FAILED, stop bits {:?} does not match set stop bits {:?}", r, stop_bits ), Ok(_) => println!(" {:?}: success", stop_bits), } } }; } macro_rules! clear_check { ($port:ident, $buffer_direction:path) => { let buffer_direction = $buffer_direction; match $port.clear(buffer_direction) { Ok(_) => println!(" {:?}: success", buffer_direction), Err(ref e) => println!(" {:?}: FAILED ({})", buffer_direction, e), } }; } macro_rules! call_query_method_check { ($port:ident, $func:path) => { match $func($port) { Ok(_) => println!(" {}: success", stringify!($func)), Err(ref e) => println!(" {}: FAILED ({})", stringify!($func), e), } }; } fn test_single_port(port: &mut dyn serialport::SerialPort, loopback: bool) { println!("Testing '{}':", port.name().unwrap()); // Test setting standard baud rates println!("Testing baud rates..."); baud_rate_check!(port, 9600); baud_rate_check!(port, 38_400); baud_rate_check!(port, 115_200); // Test setting non-standard baud rates println!("Testing non-standard baud rates..."); baud_rate_check!(port, 10_000); baud_rate_check!(port, 600_000); baud_rate_check!(port, 1_800_000); // Test setting the data bits println!("Testing data bits..."); data_bits_check!(port, DataBits::Five); data_bits_check!(port, DataBits::Six); data_bits_check!(port, DataBits::Seven); data_bits_check!(port, DataBits::Eight); // Test setting flow control println!("Testing flow control..."); flow_control_check!(port, FlowControl::Software); flow_control_check!(port, FlowControl::Hardware); flow_control_check!(port, FlowControl::None); // Test setting parity println!("Testing parity..."); parity_check!(port, Parity::Odd); parity_check!(port, Parity::Even); parity_check!(port, Parity::None); // Test setting stop bits println!("Testing stop bits..."); stop_bits_check!(port, StopBits::Two); stop_bits_check!(port, StopBits::One); // Test bytes to read and write println!("Testing bytes to read and write..."); call_query_method_check!(port, SerialPort::bytes_to_write); call_query_method_check!(port, SerialPort::bytes_to_read); // Test clearing a buffer println!("Test clearing software buffers..."); clear_check!(port, ClearBuffer::Input); clear_check!(port, ClearBuffer::Output); clear_check!(port, ClearBuffer::All); // Test transmitting data print!("Testing data transmission..."); std::io::stdout().flush().unwrap(); // Make sure the port has sane defaults set_defaults(port); let msg = "Test Message"; port.write_all(msg.as_bytes()) .expect("Unable to write bytes."); println!("success"); if loopback { print!("Testing data reception..."); port.set_timeout(Duration::from_millis(250)).ok(); let mut buf = [0u8; 12]; if let Err(e) = port.read_exact(&mut buf) { println!("FAILED ({})", e); } else { assert_eq!( str::from_utf8(&buf).unwrap(), msg, "Received message does not match sent" ); println!("success"); } } } fn check_test_message(sender: &mut dyn SerialPort, receiver: &mut dyn SerialPort) { // Ignore any "residue" from previous tests. sender.clear(ClearBuffer::All).unwrap(); receiver.clear(ClearBuffer::All).unwrap(); let send_buf = TEST_MESSAGE; let mut recv_buf = [0u8; TEST_MESSAGE.len()]; sender.write_all(send_buf).unwrap(); sender.flush().unwrap(); match receiver.read_exact(&mut recv_buf) { Ok(()) => { assert_eq_hex!(recv_buf, send_buf, "Received message does not match sent",); println!(" success"); } Err(e) => println!("FAILED: {:?}", e), } } fn test_dual_ports(port1: &mut dyn serialport::SerialPort, port2: &mut dyn serialport::SerialPort) { println!( "Testing paired ports '{}' and '{}':", port1.name().unwrap(), port2.name().unwrap() ); // Make sure both ports are set to sane defaults set_defaults(port1); set_defaults(port2); // Test sending strings from port1 to port2 println!( " Transmitting from {} to {}...", port1.name().unwrap(), port2.name().unwrap() ); let baud_rate = 2_000_000; println!(" At {},8,n,1,noflow...", baud_rate); std::io::stdout().flush().unwrap(); if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { check_test_message(port1, port2); check_test_message(port2, port1); } else { println!("FAILED (does this platform & port support arbitrary baud rates?)"); } let baud_rate = 115_200; println!(" At {},8,n,1,noflow...", baud_rate); std::io::stdout().flush().unwrap(); if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { check_test_message(port1, port2); check_test_message(port2, port1); } else { println!("FAILED"); } let baud_rate = 57_600; println!(" At {},8,n,1,noflow...", baud_rate); std::io::stdout().flush().unwrap(); if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { check_test_message(port1, port2); check_test_message(port2, port1); } else { println!("FAILED"); } let baud_rate = 10_000; println!(" At {},8,n,1,noflow...", baud_rate); std::io::stdout().flush().unwrap(); if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { check_test_message(port1, port2); check_test_message(port2, port1); } else { println!("FAILED (does this platform & port support arbitrary baud rates?)"); } let baud_rate = 9600; println!(" At {},8,n,1,noflow...", baud_rate); std::io::stdout().flush().unwrap(); if port1.set_baud_rate(baud_rate).is_ok() && port2.set_baud_rate(baud_rate).is_ok() { check_test_message(port1, port2); check_test_message(port2, port1); } else { println!("FAILED"); } // Test flow control port1.set_flow_control(FlowControl::Software).unwrap(); port2.set_flow_control(FlowControl::Software).unwrap(); println!(" At 9600,8,n,1,softflow..."); std::io::stdout().flush().unwrap(); check_test_message(port1, port2); check_test_message(port2, port1); port1.set_flow_control(FlowControl::Hardware).unwrap(); port2.set_flow_control(FlowControl::Hardware).unwrap(); println!(" At 9600,8,n,1,hardflow..."); std::io::stdout().flush().unwrap(); check_test_message(port1, port2); check_test_message(port2, port1); } fn set_defaults(port: &mut dyn serialport::SerialPort) { port.set_baud_rate(9600).unwrap(); port.set_data_bits(DataBits::Eight).unwrap(); port.set_flow_control(FlowControl::Software).unwrap(); port.set_parity(Parity::None).unwrap(); port.set_stop_bits(StopBits::One).unwrap(); // TODO: Clean up timeouts and use a less-arbitrary value here. The previous timeout of 0 made // test_dual_ports fail due to a timeout where having at least some some made them pass. port.set_timeout(Duration::from_millis(1000)).unwrap(); } serialport-4.7.0/examples/list_ports.rs000064400000000000000000000046011046102023000164070ustar 00000000000000use serialport::{available_ports, SerialPortType}; fn main() { match available_ports() { Ok(mut ports) => { // Let's output ports in a stable order to facilitate comparing the output from // different runs (on different platforms, with different features, ...). ports.sort_by_key(|i| i.port_name.clone()); match ports.len() { 0 => println!("No ports found."), 1 => println!("Found 1 port:"), n => println!("Found {} ports:", n), }; for p in ports { println!(" {}", p.port_name); match p.port_type { SerialPortType::UsbPort(info) => { println!(" Type: USB"); println!(" VID: {:04x}", info.vid); println!(" PID: {:04x}", info.pid); #[cfg(feature = "usbportinfo-interface")] println!( " Interface: {}", info.interface .as_ref() .map_or("".to_string(), |x| format!("{:02x}", *x)) ); println!( " Serial Number: {}", info.serial_number.as_ref().map_or("", String::as_str) ); println!( " Manufacturer: {}", info.manufacturer.as_ref().map_or("", String::as_str) ); println!( " Product: {}", info.product.as_ref().map_or("", String::as_str) ); } SerialPortType::BluetoothPort => { println!(" Type: Bluetooth"); } SerialPortType::PciPort => { println!(" Type: PCI"); } SerialPortType::Unknown => { println!(" Type: Unknown"); } } } } Err(e) => { eprintln!("{:?}", e); eprintln!("Error listing serial ports"); } } } serialport-4.7.0/examples/loopback.rs000064400000000000000000000201731046102023000160010ustar 00000000000000//! This example performs a loopback test using real hardware ports //! //! Additionally, some data will be collected and logged during the test to provide some //! rudimentary benchmarking information. When 'split-port' is specified, the serial port will //! be split into two channels that read/write "simultaneously" from multiple threads. //! //! You can also provide the length (in bytes) of data to test with, and the number of iterations to perform or //! a list of raw bytes to transmit. //! //! To run this example: //! //! 1) `cargo run --example loopback /dev/ttyUSB0` //! //! 2) `cargo run --example loopback /dev/ttyUSB0 --split-port` //! //! 3) `cargo run --example loopback /dev/ttyUSB0 -i 100 -l 32 -b 9600` //! //! 4) `cargo run --example loopback /dev/ttyUSB8 --bytes 222,173,190,239` use std::time::{Duration, Instant}; use clap::Parser; use serialport::SerialPort; /// Serialport Example - Loopback #[derive(Parser)] struct Args { /// The device path to a serialport port: String, /// The number of read/write iterations to perform #[clap(short, long, default_value = "100")] iterations: usize, /// The number of bytes written per transaction /// /// Ignored when bytes are passed directly from the command-line #[clap(short, long, default_value = "8")] length: usize, /// The baudrate to open the port with #[clap(short, long, default_value = "115200")] baudrate: u32, /// Bytes to write to the serial port /// /// When not specified, the bytes transmitted count up #[clap(long, use_value_delimiter = true)] bytes: Option>, /// Split the port to read/write from multiple threads #[clap(long)] split_port: bool, } fn main() { let args = Args::parse(); // Open the serial port let mut port = match serialport::new(&args.port, args.baudrate) .timeout(Duration::MAX) .open() { Err(e) => { eprintln!("Failed to open \"{}\". Error: {}", args.port, e); ::std::process::exit(1); } Ok(p) => p, }; // Setup stat-tracking let length = args.length; let data: Vec = args .bytes .unwrap_or_else(|| (0..length).map(|i| i as u8).collect()); let (mut read_stats, mut write_stats) = Stats::new(args.iterations, &data); // Run the tests if args.split_port { loopback_split(&mut port, &mut read_stats, &mut write_stats); } else { loopback_standard(&mut port, &mut read_stats, &mut write_stats); } // Print the results println!("Loopback {}:", args.port); println!(" data-length: {} bytes", read_stats.data.len()); println!(" iterations: {}", read_stats.iterations); println!(" read:"); println!(" total: {:.6}s", read_stats.total()); println!(" average: {:.6}s", read_stats.average()); println!(" max: {:.6}s", read_stats.max()); println!(" write:"); println!(" total: {:.6}s", write_stats.total()); println!(" average: {:.6}s", write_stats.average()); println!(" max: {:.6}s", write_stats.max()); println!(" total: {:.6}s", read_stats.total() + write_stats.total()); println!( " bytes/s: {:.6}", (read_stats.data.len() as f32) / (read_stats.average() + write_stats.average()) ) } /// Capture read/write times to calculate average durations #[derive(Clone)] struct Stats<'a> { pub data: &'a [u8], pub times: Vec, pub iterations: usize, now: Instant, } impl<'a> Stats<'a> { /// Create new read/write stats fn new(iterations: usize, data: &'a [u8]) -> (Self, Self) { ( Self { data, times: Vec::with_capacity(iterations), iterations, now: Instant::now(), }, Self { data, times: Vec::with_capacity(iterations), iterations, now: Instant::now(), }, ) } /// Start a duration timer fn start(&mut self) { self.now = Instant::now(); } /// Store a duration fn stop(&mut self) { self.times.push(self.now.elapsed()); } /// Provides the total time elapsed fn total(&self) -> f32 { self.times.iter().map(|d| d.as_secs_f32()).sum() } /// Provides average time per transaction fn average(&self) -> f32 { self.total() / (self.times.len() as f32) } /// Provides the maximum transaction time fn max(&self) -> f32 { self.times .iter() .max() .map(|d| d.as_secs_f32()) .unwrap_or(0.0) } } fn loopback_standard<'a>( port: &mut Box, read_stats: &mut Stats<'a>, write_stats: &mut Stats<'a>, ) { let mut buf = vec![0u8; read_stats.data.len()]; for _ in 0..read_stats.iterations { // Write data to the port write_stats.start(); port.write_all(write_stats.data) .expect("failed to write to serialport"); write_stats.stop(); // Read data back from the port read_stats.start(); port.read_exact(&mut buf) .expect("failed to read from serialport"); read_stats.stop(); // Crash on error for (i, x) in buf.iter().enumerate() { if read_stats.data[i] != *x { eprintln!( "Expected byte '{:02X}' but got '{:02X}'", read_stats.data[i], x ); ::std::process::exit(2); } } } } #[rustversion::before(1.63)] fn loopback_split<'a>( _port: &mut Box, _read_stats: &mut Stats<'a>, _write_stats: &mut Stats<'a>, ) { unimplemented!("requires Rust 1.63 or later"); } #[rustversion::since(1.63)] fn loopback_split<'a>( port: &mut Box, read_stats: &mut Stats<'a>, write_stats: &mut Stats<'a>, ) { let mut buf = vec![0u8; read_stats.data.len()]; let mut rport = match port.try_clone() { Ok(p) => p, Err(e) => { eprintln!("Failed to clone port: {}", e); ::std::process::exit(3); } }; // Manage threads for read/writing; port usage is not async, so threads can easily deadlock: // // 1. Read Thread: Park -> Read -> Unpark Write ──────┐ // └──────────────────────────────────┘ // 2. Write Thread: Write -> Unpark Read -> Park ──────┐ // └──────────────────────────────────┘ std::thread::scope(|scope| { // Get handle for writing thread let wr_thread = std::thread::current(); // Spawn a thread that reads data for n iterations let handle = scope.spawn(move || { for _ in 0..read_stats.iterations { // Wait for the write to complete std::thread::park(); read_stats.start(); rport .read_exact(&mut buf) .expect("failed to read from serialport"); read_stats.stop(); // Crash on error for (i, x) in buf.iter().enumerate() { if read_stats.data[i] != *x { eprintln!( "Expected byte '{:02X}' but got '{:02X}'", read_stats.data[i], x ); ::std::process::exit(2); } } // Allow the writing thread to start wr_thread.unpark(); } }); // Write data to the port for n iterations for _ in 0..write_stats.iterations { write_stats.start(); port.write_all(write_stats.data) .expect("failed to write to serialport"); write_stats.stop(); // Notify that the write completed handle.thread().unpark(); // Wait for read to complete std::thread::park(); } }); } serialport-4.7.0/examples/pseudo_terminal.rs000064400000000000000000000024331046102023000174000ustar 00000000000000//! Pseudo terminal example. #[cfg(unix)] fn main() { use std::io::{Read, Write}; use std::os::unix::prelude::*; use std::str; use std::thread; use std::time; use serialport::{SerialPort, TTYPort}; let (mut master, mut slave) = TTYPort::pair().expect("Unable to create pseudo-terminal pair"); // Master ptty has no associated path on the filesystem. println!( "Master ptty fd: {}, path: {:?}", master.as_raw_fd(), master.name() ); println!( "Slave ptty fd: {}, path: {:?}", slave.as_raw_fd(), slave.name() ); // Receive buffer. let mut buf = [0u8; 512]; println!("Sending 5 messages from master to slave."); // Send 5 messages. for x in 1..6 { let msg = format!("Message #{}", x); // Send the message on the master assert_eq!(master.write(msg.as_bytes()).unwrap(), msg.len()); // Receive on the slave let bytes_recvd = slave.read(&mut buf).unwrap(); assert_eq!(bytes_recvd, msg.len()); let msg_recvd = str::from_utf8(&buf[..bytes_recvd]).unwrap(); assert_eq!(msg_recvd, msg); println!("Slave Rx: {}", msg_recvd); thread::sleep(time::Duration::from_secs(1)); } } #[cfg(not(unix))] fn main() {} serialport-4.7.0/examples/receive_data.rs000064400000000000000000000035351046102023000166250ustar 00000000000000use std::io::{self, Write}; use std::time::Duration; use clap::{Arg, Command}; fn main() { let matches = Command::new("Serialport Example - Receive Data") .about("Reads data from a serial port and echoes it to stdout") .disable_version_flag(true) .arg( Arg::new("port") .help("The device path to a serial port") .use_value_delimiter(false) .required(true), ) .arg( Arg::new("baud") .help("The baud rate to connect at") .use_value_delimiter(false) .required(true) .validator(valid_baud), ) .get_matches(); let port_name = matches.value_of("port").unwrap(); let baud_rate = matches.value_of("baud").unwrap().parse::().unwrap(); let port = serialport::new(port_name, baud_rate) .timeout(Duration::from_millis(10)) .open(); match port { Ok(mut port) => { let mut serial_buf: Vec = vec![0; 1000]; println!("Receiving data on {} at {} baud:", &port_name, &baud_rate); loop { match port.read(serial_buf.as_mut_slice()) { Ok(t) => { io::stdout().write_all(&serial_buf[..t]).unwrap(); io::stdout().flush().unwrap(); } Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (), Err(e) => eprintln!("{:?}", e), } } } Err(e) => { eprintln!("Failed to open \"{}\". Error: {}", port_name, e); ::std::process::exit(1); } } } fn valid_baud(val: &str) -> Result<(), String> { val.parse::() .map(|_| ()) .map_err(|_| format!("Invalid baud rate '{}' specified", val)) } serialport-4.7.0/examples/transmit.rs000064400000000000000000000064161046102023000160540ustar 00000000000000use std::io::{self, Write}; use std::time::Duration; use clap::{Arg, Command}; use serialport::{DataBits, StopBits}; fn main() { let matches = Command::new("Serialport Example - Heartbeat") .about("Write bytes to a serial port at 1Hz") .disable_version_flag(true) .arg( Arg::new("port") .help("The device path to a serial port") .required(true), ) .arg( Arg::new("baud") .help("The baud rate to connect at") .use_value_delimiter(false) .required(true) .validator(valid_baud), ) .arg( Arg::new("stop-bits") .long("stop-bits") .help("Number of stop bits to use") .takes_value(true) .possible_values(["1", "2"]) .default_value("1"), ) .arg( Arg::new("data-bits") .long("data-bits") .help("Number of data bits to use") .takes_value(true) .possible_values(["5", "6", "7", "8"]) .default_value("8"), ) .arg( Arg::new("rate") .long("rate") .help("Frequency (Hz) to repeat transmission of the pattern (0 indicates sending only once") .takes_value(true) .default_value("1"), ) .arg( Arg::new("string") .long("string") .help("String to transmit") .takes_value(true) .default_value("."), ) .get_matches(); let port_name = matches.value_of("port").unwrap(); let baud_rate = matches.value_of("baud").unwrap().parse::().unwrap(); let stop_bits = match matches.value_of("stop-bits") { Some("2") => StopBits::Two, _ => StopBits::One, }; let data_bits = match matches.value_of("data-bits") { Some("5") => DataBits::Five, Some("6") => DataBits::Six, Some("7") => DataBits::Seven, _ => DataBits::Eight, }; let rate = matches.value_of("rate").unwrap().parse::().unwrap(); let string = matches.value_of("string").unwrap(); let builder = serialport::new(port_name, baud_rate) .stop_bits(stop_bits) .data_bits(data_bits); println!("{:?}", &builder); let mut port = builder.open().unwrap_or_else(|e| { eprintln!("Failed to open \"{}\". Error: {}", port_name, e); ::std::process::exit(1); }); println!( "Writing '{}' to {} at {} baud at {}Hz", &string, &port_name, &baud_rate, &rate ); loop { match port.write(string.as_bytes()) { Ok(_) => { print!("{}", &string); std::io::stdout().flush().unwrap(); } Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (), Err(e) => eprintln!("{:?}", e), } if rate == 0 { return; } std::thread::sleep(Duration::from_millis((1000.0 / (rate as f32)) as u64)); } } fn valid_baud(val: &str) -> std::result::Result<(), String> { val.parse::() .map(|_| ()) .map_err(|_| format!("Invalid baud rate '{}' specified", val)) } serialport-4.7.0/src/lib.rs000064400000000000000000000675741046102023000137460ustar 00000000000000//! serialport-rs is a cross-platform serial port library. //! //! The goal of this library is to expose a cross-platform and platform-specific API for enumerating //! and using blocking I/O with serial ports. This library exposes a similar API to that provided //! by [Qt's `QSerialPort` library](https://doc.qt.io/qt-5/qserialport.html). //! //! # Feature Overview //! //! The library has been organized such that there is a high-level `SerialPort` trait that provides //! a cross-platform API for accessing serial ports. This is the preferred method of interacting //! with ports. The `SerialPort::new().open*()` and `available_ports()` functions in the root //! provide cross-platform functionality. //! //! For platform-specific functionality, this crate is split into a `posix` and `windows` API with //! corresponding `TTYPort` and `COMPort` structs (that both implement the `SerialPort` trait). //! Using the platform-specific `SerialPort::new().open*()` functions will return the //! platform-specific port object which allows access to platform-specific functionality. #![deny( clippy::dbg_macro, missing_docs, missing_debug_implementations, missing_copy_implementations )] // Document feature-gated elements on docs.rs. See // https://doc.rust-lang.org/rustdoc/unstable-features.html?highlight=doc(cfg#doccfg-recording-what-platforms-or-features-are-required-for-code-to-be-present // and // https://doc.rust-lang.org/rustdoc/unstable-features.html#doc_auto_cfg-automatically-generate-doccfg // for details. #![cfg_attr(docsrs, feature(doc_auto_cfg))] // Don't worry about needing to `unwrap()` or otherwise handle some results in // doc tests. #![doc(test(attr(allow(unused_must_use))))] use std::error::Error as StdError; use std::fmt; use std::io; use std::str::FromStr; use std::time::Duration; #[cfg(unix)] mod posix; #[cfg(unix)] pub use posix::{BreakDuration, TTYPort}; #[cfg(windows)] mod windows; #[cfg(windows)] pub use windows::COMPort; #[cfg(test)] pub(crate) mod tests; /// A type for results generated by interacting with serial ports /// /// The `Err` type is hard-wired to [`serialport::Error`](struct.Error.html). pub type Result = std::result::Result; /// Categories of errors that can occur when interacting with serial ports /// /// This list is intended to grow over time and it is not recommended to /// exhaustively match against it. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ErrorKind { /// The device is not available. /// /// This could indicate that the device is in use by another process or was /// disconnected while performing I/O. NoDevice, /// A parameter was incorrect. InvalidInput, /// An unknown error occurred. Unknown, /// An I/O error occurred. /// /// The type of I/O error is determined by the inner `io::ErrorKind`. Io(io::ErrorKind), } /// An error type for serial port operations #[derive(Debug, Clone)] pub struct Error { /// The kind of error this is pub kind: ErrorKind, /// A description of the error suitable for end-users pub description: String, } impl Error { /// Instantiates a new error pub fn new>(kind: ErrorKind, description: T) -> Self { Error { kind, description: description.into(), } } /// Returns the corresponding `ErrorKind` for this error. pub fn kind(&self) -> ErrorKind { self.kind } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { fmt.write_str(&self.description) } } impl StdError for Error { fn description(&self) -> &str { &self.description } } impl From for Error { fn from(io_error: io::Error) -> Error { Error::new(ErrorKind::Io(io_error.kind()), format!("{}", io_error)) } } impl From for io::Error { fn from(error: Error) -> io::Error { let kind = match error.kind { ErrorKind::NoDevice => io::ErrorKind::NotFound, ErrorKind::InvalidInput => io::ErrorKind::InvalidInput, ErrorKind::Unknown => io::ErrorKind::Other, ErrorKind::Io(kind) => kind, }; io::Error::new(kind, error.description) } } /// Number of bits per character #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DataBits { /// 5 bits per character Five, /// 6 bits per character Six, /// 7 bits per character Seven, /// 8 bits per character Eight, } impl fmt::Display for DataBits { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { DataBits::Five => write!(f, "Five"), DataBits::Six => write!(f, "Six"), DataBits::Seven => write!(f, "Seven"), DataBits::Eight => write!(f, "Eight"), } } } impl From for u8 { fn from(value: DataBits) -> Self { match value { DataBits::Five => 5, DataBits::Six => 6, DataBits::Seven => 7, DataBits::Eight => 8, } } } impl TryFrom for DataBits { type Error = (); fn try_from(value: u8) -> core::result::Result { match value { 5 => Ok(Self::Five), 6 => Ok(Self::Six), 7 => Ok(Self::Seven), 8 => Ok(Self::Eight), _ => Err(()), } } } /// Parity checking modes /// /// When parity checking is enabled (`Odd` or `Even`) an extra bit is transmitted with /// each character. The value of the parity bit is arranged so that the number of 1 bits in the /// character (including the parity bit) is an even number (`Even`) or an odd number /// (`Odd`). /// /// Parity checking is disabled by setting `None`, in which case parity bits are not /// transmitted. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Parity { /// No parity bit. None, /// Parity bit sets odd number of 1 bits. Odd, /// Parity bit sets even number of 1 bits. Even, } impl fmt::Display for Parity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Parity::None => write!(f, "None"), Parity::Odd => write!(f, "Odd"), Parity::Even => write!(f, "Even"), } } } /// Number of stop bits /// /// Stop bits are transmitted after every character. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum StopBits { /// One stop bit. One, /// Two stop bits. Two, } impl fmt::Display for StopBits { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { StopBits::One => write!(f, "One"), StopBits::Two => write!(f, "Two"), } } } impl From for u8 { fn from(value: StopBits) -> Self { match value { StopBits::One => 1, StopBits::Two => 2, } } } impl TryFrom for StopBits { type Error = (); fn try_from(value: u8) -> core::result::Result { match value { 1 => Ok(Self::One), 2 => Ok(Self::Two), _ => Err(()), } } } /// Flow control modes #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum FlowControl { /// No flow control. None, /// Flow control using XON/XOFF bytes. Software, /// Flow control using RTS/CTS signals. Hardware, } impl fmt::Display for FlowControl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { FlowControl::None => write!(f, "None"), FlowControl::Software => write!(f, "Software"), FlowControl::Hardware => write!(f, "Hardware"), } } } impl FromStr for FlowControl { type Err = (); fn from_str(s: &str) -> core::result::Result { match s { "None" | "none" | "n" => Ok(FlowControl::None), "Software" | "software" | "SW" | "sw" | "s" => Ok(FlowControl::Software), "Hardware" | "hardware" | "HW" | "hw" | "h" => Ok(FlowControl::Hardware), _ => Err(()), } } } /// Specifies which buffer or buffers to purge when calling [`clear`] /// /// [`clear`]: trait.SerialPort.html#tymethod.clear #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ClearBuffer { /// Specify to clear data received but not read Input, /// Specify to clear data written but not yet transmitted Output, /// Specify to clear both data received and data not yet transmitted All, } /// A struct containing all serial port settings #[derive(Debug, Clone, PartialEq, Eq)] pub struct SerialPortBuilder { /// The port name, usually the device path path: String, /// The baud rate in symbols-per-second baud_rate: u32, /// Number of bits used to represent a character sent on the line data_bits: DataBits, /// The type of signalling to use for controlling data transfer flow_control: FlowControl, /// The type of parity to use for error checking parity: Parity, /// Number of bits to use to signal the end of a character stop_bits: StopBits, /// Amount of time to wait to receive data before timing out timeout: Duration, /// The state to set DTR to when opening the device dtr_on_open: Option, } impl SerialPortBuilder { /// Set the path to the serial port // TODO: Switch to `clone_into` when bumping our MSRV past 1.63 and remove this exemption. #[allow(clippy::assigning_clones)] #[must_use] pub fn path<'a>(mut self, path: impl Into>) -> Self { self.path = path.into().as_ref().to_owned(); self } /// Set the baud rate in symbols-per-second #[must_use] pub fn baud_rate(mut self, baud_rate: u32) -> Self { self.baud_rate = baud_rate; self } /// Set the number of bits used to represent a character sent on the line #[must_use] pub fn data_bits(mut self, data_bits: DataBits) -> Self { self.data_bits = data_bits; self } /// Set the type of signalling to use for controlling data transfer #[must_use] pub fn flow_control(mut self, flow_control: FlowControl) -> Self { self.flow_control = flow_control; self } /// Set the type of parity to use for error checking #[must_use] pub fn parity(mut self, parity: Parity) -> Self { self.parity = parity; self } /// Set the number of bits to use to signal the end of a character #[must_use] pub fn stop_bits(mut self, stop_bits: StopBits) -> Self { self.stop_bits = stop_bits; self } /// Set the amount of time to wait to receive data before timing out /// ///
/// /// The accuracy is limited by the underlying platform's capabilities. Longer timeouts will be /// clamped to the maximum supported value which is expected to be in the magnitude of a few /// days. /// ///
#[must_use] pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; self } /// Set data terminal ready (DTR) to the given state when opening the device #[must_use] pub fn dtr_on_open(mut self, state: bool) -> Self { self.dtr_on_open = Some(state); self } /// Preserve the state of data terminal ready (DTR) when opening the device. Your outcome may /// vary depending on the operation system. For example, Linux sets DTR by default and Windows /// doesn't. #[must_use] pub fn preserve_dtr_on_open(mut self) -> Self { self.dtr_on_open = None; self } /// Open a cross-platform interface to the port with the specified settings pub fn open(self) -> Result> { #[cfg(unix)] return posix::TTYPort::open(&self).map(|p| Box::new(p) as Box); #[cfg(windows)] return windows::COMPort::open(&self).map(|p| Box::new(p) as Box); #[cfg(not(any(unix, windows)))] Err(Error::new( ErrorKind::Unknown, "open() not implemented for platform", )) } /// Open a platform-specific interface to the port with the specified settings #[cfg(unix)] pub fn open_native(self) -> Result { posix::TTYPort::open(&self) } /// Open a platform-specific interface to the port with the specified settings #[cfg(windows)] pub fn open_native(self) -> Result { windows::COMPort::open(&self) } } /// A trait for serial port devices /// /// This trait is all that's necessary to implement a new serial port driver /// for a new platform. pub trait SerialPort: Send + io::Read + io::Write { // Port settings getters /// Returns the name of this port if it exists. /// /// This name may not be the canonical device name and instead be shorthand. /// Additionally it may not exist for virtual ports. fn name(&self) -> Option; /// Returns the current baud rate. /// /// This may return a value different from the last specified baud rate depending on the /// platform as some will return the actual device baud rate rather than the last specified /// baud rate. fn baud_rate(&self) -> Result; /// Returns the character size. /// /// This function returns `None` if the character size could not be determined. This may occur /// if the hardware is in an uninitialized state or is using a non-standard character size. /// Setting a baud rate with `set_char_size()` should initialize the character size to a /// supported value. fn data_bits(&self) -> Result; /// Returns the flow control mode. /// /// This function returns `None` if the flow control mode could not be determined. This may /// occur if the hardware is in an uninitialized state or is using an unsupported flow control /// mode. Setting a flow control mode with `set_flow_control()` should initialize the flow /// control mode to a supported value. fn flow_control(&self) -> Result; /// Returns the parity-checking mode. /// /// This function returns `None` if the parity mode could not be determined. This may occur if /// the hardware is in an uninitialized state or is using a non-standard parity mode. Setting /// a parity mode with `set_parity()` should initialize the parity mode to a supported value. fn parity(&self) -> Result; /// Returns the number of stop bits. /// /// This function returns `None` if the number of stop bits could not be determined. This may /// occur if the hardware is in an uninitialized state or is using an unsupported stop bit /// configuration. Setting the number of stop bits with `set_stop-bits()` should initialize the /// stop bits to a supported value. fn stop_bits(&self) -> Result; /// Returns the current timeout. fn timeout(&self) -> Duration; // Port settings setters /// Sets the baud rate. /// /// ## Errors /// /// If the implementation does not support the requested baud rate, this function may return an /// `InvalidInput` error. Even if the baud rate is accepted by `set_baud_rate()`, it may not be /// supported by the underlying hardware. fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()>; /// Sets the character size. fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()>; /// Sets the flow control mode. fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()>; /// Sets the parity-checking mode. fn set_parity(&mut self, parity: Parity) -> Result<()>; /// Sets the number of stop bits. fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()>; /// Sets the timeout for future I/O operations. /// ///
/// /// The accuracy is limited by the underlying platform's capabilities. Longer timeouts will be /// clamped to the maximum supported value which is expected to be in the magnitude of a few /// days. /// ///
fn set_timeout(&mut self, timeout: Duration) -> Result<()>; // Functions for setting non-data control signal pins /// Sets the state of the RTS (Request To Send) control signal. /// /// Setting a value of `true` asserts the RTS control signal. `false` clears the signal. /// /// ## Errors /// /// This function returns an error if the RTS control signal could not be set to the desired /// state on the underlying hardware: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn write_request_to_send(&mut self, level: bool) -> Result<()>; /// Writes to the Data Terminal Ready pin /// /// Setting a value of `true` asserts the DTR control signal. `false` clears the signal. /// /// ## Errors /// /// This function returns an error if the DTR control signal could not be set to the desired /// state on the underlying hardware: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn write_data_terminal_ready(&mut self, level: bool) -> Result<()>; // Functions for reading additional pins /// Reads the state of the CTS (Clear To Send) control signal. /// /// This function returns a boolean that indicates whether the CTS control signal is asserted. /// /// ## Errors /// /// This function returns an error if the state of the CTS control signal could not be read /// from the underlying hardware: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn read_clear_to_send(&mut self) -> Result; /// Reads the state of the Data Set Ready control signal. /// /// This function returns a boolean that indicates whether the DSR control signal is asserted. /// /// ## Errors /// /// This function returns an error if the state of the DSR control signal could not be read /// from the underlying hardware: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn read_data_set_ready(&mut self) -> Result; /// Reads the state of the Ring Indicator control signal. /// /// This function returns a boolean that indicates whether the RI control signal is asserted. /// /// ## Errors /// /// This function returns an error if the state of the RI control signal could not be read from /// the underlying hardware: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn read_ring_indicator(&mut self) -> Result; /// Reads the state of the Carrier Detect control signal. /// /// This function returns a boolean that indicates whether the CD control signal is asserted. /// /// ## Errors /// /// This function returns an error if the state of the CD control signal could not be read from /// the underlying hardware: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn read_carrier_detect(&mut self) -> Result; /// Gets the number of bytes available to be read from the input buffer. /// /// # Errors /// /// This function may return the following errors: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn bytes_to_read(&self) -> Result; /// Get the number of bytes written to the output buffer, awaiting transmission. /// /// # Errors /// /// This function may return the following errors: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn bytes_to_write(&self) -> Result; /// Discards all bytes from the serial driver's input buffer and/or output buffer. /// /// # Errors /// /// This function may return the following errors: /// /// * `NoDevice` if the device was disconnected. /// * `Io` for any other type of I/O error. fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()>; // Misc methods /// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the /// same serial connection. Please note that if you want a real asynchronous serial port you /// should look at [mio-serial](https://crates.io/crates/mio-serial) or /// [tokio-serial](https://crates.io/crates/tokio-serial). /// /// Also, you must be very careful when changing the settings of a cloned `SerialPort` : since /// the settings are cached on a per object basis, trying to modify them from two different /// objects can cause some nasty behavior. /// /// # Errors /// /// This function returns an error if the serial port couldn't be cloned. fn try_clone(&self) -> Result>; /// Start transmitting a break fn set_break(&self) -> Result<()>; /// Stop transmitting a break fn clear_break(&self) -> Result<()>; } impl SerialPort for &mut T { fn name(&self) -> Option { (**self).name() } fn baud_rate(&self) -> Result { (**self).baud_rate() } fn data_bits(&self) -> Result { (**self).data_bits() } fn flow_control(&self) -> Result { (**self).flow_control() } fn parity(&self) -> Result { (**self).parity() } fn stop_bits(&self) -> Result { (**self).stop_bits() } fn timeout(&self) -> Duration { (**self).timeout() } fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { (**self).set_baud_rate(baud_rate) } fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { (**self).set_data_bits(data_bits) } fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { (**self).set_flow_control(flow_control) } fn set_parity(&mut self, parity: Parity) -> Result<()> { (**self).set_parity(parity) } fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { (**self).set_stop_bits(stop_bits) } fn set_timeout(&mut self, timeout: Duration) -> Result<()> { (**self).set_timeout(timeout) } fn write_request_to_send(&mut self, level: bool) -> Result<()> { (**self).write_request_to_send(level) } fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { (**self).write_data_terminal_ready(level) } fn read_clear_to_send(&mut self) -> Result { (**self).read_clear_to_send() } fn read_data_set_ready(&mut self) -> Result { (**self).read_data_set_ready() } fn read_ring_indicator(&mut self) -> Result { (**self).read_ring_indicator() } fn read_carrier_detect(&mut self) -> Result { (**self).read_carrier_detect() } fn bytes_to_read(&self) -> Result { (**self).bytes_to_read() } fn bytes_to_write(&self) -> Result { (**self).bytes_to_write() } fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { (**self).clear(buffer_to_clear) } fn try_clone(&self) -> Result> { (**self).try_clone() } fn set_break(&self) -> Result<()> { (**self).set_break() } fn clear_break(&self) -> Result<()> { (**self).clear_break() } } impl fmt::Debug for dyn SerialPort { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "SerialPort ( ")?; if let Some(n) = self.name().as_ref() { write!(f, "name: {} ", n)?; }; if let Ok(b) = self.baud_rate().as_ref() { write!(f, "baud_rate: {} ", b)?; }; if let Ok(b) = self.data_bits().as_ref() { write!(f, "data_bits: {} ", b)?; }; if let Ok(c) = self.flow_control().as_ref() { write!(f, "flow_control: {} ", c)?; } if let Ok(p) = self.parity().as_ref() { write!(f, "parity: {} ", p)?; } if let Ok(s) = self.stop_bits().as_ref() { write!(f, "stop_bits: {} ", s)?; } write!(f, ")") } } /// Contains all possible USB information about a `SerialPort` #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UsbPortInfo { /// Vendor ID pub vid: u16, /// Product ID pub pid: u16, /// Serial number (arbitrary string) pub serial_number: Option, /// Manufacturer (arbitrary string) pub manufacturer: Option, /// Product name (arbitrary string) pub product: Option, /// The interface index of the USB serial port. This can be either the interface number of /// the communication interface (as is the case on Windows and Linux) or the data /// interface (as is the case on macOS), so you should recognize both interface numbers. #[cfg(feature = "usbportinfo-interface")] pub interface: Option, } /// The physical type of a `SerialPort` #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum SerialPortType { /// The serial port is connected via USB UsbPort(UsbPortInfo), /// The serial port is connected via PCI (permanent port) PciPort, /// The serial port is connected via Bluetooth BluetoothPort, /// It can't be determined how the serial port is connected Unknown, } /// A device-independent implementation of serial port information #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SerialPortInfo { /// The short name of the serial port pub port_name: String, /// The hardware device type that exposes this port pub port_type: SerialPortType, } /// Construct a builder of `SerialPort` objects /// /// `SerialPort` objects are built using the Builder pattern through the `new` function. The /// resultant `SerialPortBuilder` object can be copied, reconfigured, and saved making working with /// multiple serial ports a little easier. /// /// To open a new serial port: /// ```no_run /// serialport::new("/dev/ttyUSB0", 9600).open().expect("Failed to open port"); /// ``` pub fn new<'a>(path: impl Into>, baud_rate: u32) -> SerialPortBuilder { SerialPortBuilder { path: path.into().into_owned(), baud_rate, data_bits: DataBits::Eight, flow_control: FlowControl::None, parity: Parity::None, stop_bits: StopBits::One, timeout: Duration::from_millis(0), // By default, set DTR when opening the device. There are USB devices performing "wait for // DTR" before sending any data and users stumbled over this multiple times (see issues #29 // and #204). We are expecting little to no negative consequences from setting DTR by // default but less hassle for users. dtr_on_open: Some(true), } } /// Returns a list of all serial ports on system /// /// It is not guaranteed that these ports exist or are available even if they're /// returned by this function. pub fn available_ports() -> Result> { #[cfg(unix)] return crate::posix::available_ports(); #[cfg(windows)] return crate::windows::available_ports(); #[cfg(not(any(unix, windows)))] Err(Error::new( ErrorKind::Unknown, "available_ports() not implemented for platform", )) } serialport-4.7.0/src/posix/enumerate.rs000064400000000000000000000743101046102023000163110ustar 00000000000000use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]{ use std::ffi::OsStr; } } cfg_if! { if #[cfg(any(target_os = "ios", target_os = "macos"))] { use core_foundation::base::CFType; use core_foundation::base::TCFType; use core_foundation::dictionary::CFDictionary; use core_foundation::dictionary::CFMutableDictionary; use core_foundation::number::CFNumber; use core_foundation::string::CFString; use core_foundation_sys::base::{kCFAllocatorDefault, CFRetain}; use io_kit_sys::*; use io_kit_sys::keys::*; use io_kit_sys::serial::keys::*; use io_kit_sys::types::*; use io_kit_sys::usb::lib::*; use nix::libc::{c_char, c_void}; use std::ffi::CStr; use std::mem::MaybeUninit; } } #[cfg(any( target_os = "freebsd", target_os = "ios", target_os = "linux", target_os = "macos" ))] use crate::SerialPortType; #[cfg(any(target_os = "ios", target_os = "linux", target_os = "macos"))] use crate::UsbPortInfo; #[cfg(any( target_os = "android", target_os = "ios", all(target_os = "linux", not(target_env = "musl"), feature = "libudev"), target_os = "macos", target_os = "netbsd", target_os = "openbsd", ))] use crate::{Error, ErrorKind}; use crate::{Result, SerialPortInfo}; /// Retrieves the udev property value named by `key`. If the value exists, then it will be /// converted to a String, otherwise None will be returned. #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn udev_property_as_string(d: &libudev::Device, key: &str) -> Option { d.property_value(key) .and_then(OsStr::to_str) .map(|s| s.to_string()) } /// Retrieves the udev property value named by `key`. This function assumes that the retrieved /// string is comprised of hex digits and the integer value of this will be returned as a u16. /// If the property value doesn't exist or doesn't contain valid hex digits, then an error /// will be returned. /// This function uses a built-in type's `from_str_radix` to implementation to perform the /// actual conversion. #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn udev_hex_property_as_int( d: &libudev::Device, key: &str, from_str_radix: &dyn Fn(&str, u32) -> std::result::Result, ) -> Result { if let Some(hex_str) = d.property_value(key).and_then(OsStr::to_str) { if let Ok(num) = from_str_radix(hex_str, 16) { Ok(num) } else { Err(Error::new(ErrorKind::Unknown, "value not hex string")) } } else { Err(Error::new(ErrorKind::Unknown, "key not found")) } } /// Looks up a property which is provided in two "flavors": Where special charaters and whitespaces /// are encoded/escaped and where they are replaced (with underscores). This is for example done /// by udev for manufacturer and model information. /// /// See /// https://github.com/systemd/systemd/blob/38c258398427d1f497268e615906759025e51ea6/src/udev/udev-builtin-usb_id.c#L432 /// for details. #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn udev_property_encoded_or_replaced_as_string( d: &libudev::Device, encoded_key: &str, replaced_key: &str, ) -> Option { udev_property_as_string(d, encoded_key) .and_then(|s| unescaper::unescape(&s).ok()) .or_else(|| udev_property_as_string(d, replaced_key)) .map(udev_restore_spaces) } /// Converts the underscores from `udev_replace_whitespace` back to spaces quick and dirtily. We /// are ignoring the different types of whitespaces and the substitutions from `udev_replace_chars` /// deliberately for keeping a low profile. /// /// See /// https://github.com/systemd/systemd/blob/38c258398427d1f497268e615906759025e51ea6/src/shared/udev-util.c#L281 /// for more details. #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn udev_restore_spaces(source: String) -> String { source.replace('_', " ") } #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn port_type(d: &libudev::Device) -> Result { match d.property_value("ID_BUS").and_then(OsStr::to_str) { Some("usb") => { let serial_number = udev_property_as_string(d, "ID_SERIAL_SHORT"); // For devices on the USB, udev also provides manufacturer and product information from // its hardware dataase. Use this as a fallback if this information is not provided // from the device itself. let manufacturer = udev_property_encoded_or_replaced_as_string(d, "ID_VENDOR_ENC", "ID_VENDOR") .or_else(|| udev_property_as_string(d, "ID_VENDOR_FROM_DATABASE")); let product = udev_property_encoded_or_replaced_as_string(d, "ID_MODEL_ENC", "ID_MODEL") .or_else(|| udev_property_as_string(d, "ID_MODEL_FROM_DATABASE")); Ok(SerialPortType::UsbPort(UsbPortInfo { vid: udev_hex_property_as_int(d, "ID_VENDOR_ID", &u16::from_str_radix)?, pid: udev_hex_property_as_int(d, "ID_MODEL_ID", &u16::from_str_radix)?, serial_number, manufacturer, product, #[cfg(feature = "usbportinfo-interface")] interface: udev_hex_property_as_int(d, "ID_USB_INTERFACE_NUM", &u8::from_str_radix) .ok(), })) } Some("pci") => { let usb_properties = vec![ d.property_value("ID_USB_VENDOR_ID"), d.property_value("ID_USB_MODEL_ID"), ] .into_iter() .collect::>>(); if usb_properties.is_some() { // For USB devices reported at a PCI bus, there is apparently no fallback // information from udevs hardware database provided. let manufacturer = udev_property_encoded_or_replaced_as_string( d, "ID_USB_VENDOR_ENC", "ID_USB_VENDOR", ); let product = udev_property_encoded_or_replaced_as_string( d, "ID_USB_MODEL_ENC", "ID_USB_MODEL", ); Ok(SerialPortType::UsbPort(UsbPortInfo { vid: udev_hex_property_as_int(d, "ID_USB_VENDOR_ID", &u16::from_str_radix)?, pid: udev_hex_property_as_int(d, "ID_USB_MODEL_ID", &u16::from_str_radix)?, serial_number: udev_property_as_string(d, "ID_USB_SERIAL_SHORT"), manufacturer, product, #[cfg(feature = "usbportinfo-interface")] interface: udev_hex_property_as_int( d, "ID_USB_INTERFACE_NUM", &u8::from_str_radix, ) .ok(), })) } else { Ok(SerialPortType::PciPort) } } None => find_usb_interface_from_parents(d.parent()) .and_then(get_modalias_from_device) .as_deref() .and_then(parse_modalias) .map_or(Ok(SerialPortType::Unknown), |port_info| { Ok(SerialPortType::UsbPort(port_info)) }), _ => Ok(SerialPortType::Unknown), } } #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn find_usb_interface_from_parents(parent: Option) -> Option { let mut p = parent?; // limit the query depth for _ in 1..4 { match p.devtype() { None => match p.parent() { None => break, Some(x) => p = x, }, Some(s) => { if s.to_str()? == "usb_interface" { break; } else { match p.parent() { None => break, Some(x) => p = x, } } } } } Some(p) } #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn get_modalias_from_device(d: libudev::Device) -> Option { Some( d.property_value("MODALIAS") .and_then(OsStr::to_str)? .to_owned(), ) } // MODALIAS = usb:v303Ap1001d0101dcEFdsc02dp01ic02isc02ip00in00 // v 303A (device vendor) // p 1001 (device product) // d 0101 (bcddevice) // dc EF (device class) // dsc 02 (device subclass) // dp 01 (device protocol) // ic 02 (interface class) // isc 02 (interface subclass) // ip 00 (interface protocol) // in 00 (interface number) #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] fn parse_modalias(moda: &str) -> Option { // Find the start of the string, will start with "usb:" let mod_start = moda.find("usb:v")?; // Tail to update while searching. let mut mod_tail = moda.get(mod_start + 5..)?; // The next four characters should be hex values of the vendor. let vid = mod_tail.get(..4)?; mod_tail = mod_tail.get(4..)?; // The next portion we care about is the device product ID. let pid_start = mod_tail.find('p')?; let pid = mod_tail.get(pid_start + 1..pid_start + 5)?; Some(UsbPortInfo { vid: u16::from_str_radix(vid, 16).ok()?, pid: u16::from_str_radix(pid, 16).ok()?, serial_number: None, manufacturer: None, product: None, // Only attempt to find the interface if the feature is enabled. #[cfg(feature = "usbportinfo-interface")] interface: mod_tail.get(pid_start + 4..).and_then(|mod_tail| { mod_tail.find("in").and_then(|i_start| { mod_tail .get(i_start + 2..i_start + 4) .and_then(|interface| u8::from_str_radix(interface, 16).ok()) }) }), }) } #[cfg(any(target_os = "ios", target_os = "macos"))] fn get_parent_device_by_type( device: io_object_t, parent_type: *const c_char, ) -> Option { let parent_type = unsafe { CStr::from_ptr(parent_type) }; use mach2::kern_return::KERN_SUCCESS; let mut device = device; loop { let mut class_name = MaybeUninit::<[c_char; 128]>::uninit(); unsafe { IOObjectGetClass(device, class_name.as_mut_ptr() as *mut c_char) }; let class_name = unsafe { class_name.assume_init() }; let name = unsafe { CStr::from_ptr(&class_name[0]) }; if name == parent_type { return Some(device); } let mut parent = MaybeUninit::uninit(); if unsafe { IORegistryEntryGetParentEntry(device, kIOServiceClass, parent.as_mut_ptr()) != KERN_SUCCESS } { return None; } device = unsafe { parent.assume_init() }; } } #[cfg(any(target_os = "ios", target_os = "macos"))] #[allow(non_upper_case_globals)] /// Returns a specific property of the given device as an integer. fn get_int_property(device_type: io_registry_entry_t, property: &str) -> Result { let cf_property = CFString::new(property); let cf_type_ref = unsafe { IORegistryEntryCreateCFProperty( device_type, cf_property.as_concrete_TypeRef(), kCFAllocatorDefault, 0, ) }; if cf_type_ref.is_null() { return Err(Error::new(ErrorKind::Unknown, "Failed to get property")); } let cf_type = unsafe { CFType::wrap_under_create_rule(cf_type_ref) }; cf_type .downcast::() .and_then(|n| n.to_i64()) .map(|n| n as u32) .ok_or(Error::new( ErrorKind::Unknown, "Failed to get numerical value", )) } #[cfg(any(target_os = "ios", target_os = "macos"))] /// Returns a specific property of the given device as a string. fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Result { let cf_property = CFString::new(property); let cf_type_ref = unsafe { IORegistryEntryCreateCFProperty( device_type, cf_property.as_concrete_TypeRef(), kCFAllocatorDefault, 0, ) }; if cf_type_ref.is_null() { return Err(Error::new(ErrorKind::Unknown, "Failed to get property")); } let cf_type = unsafe { CFType::wrap_under_create_rule(cf_type_ref) }; cf_type .downcast::() .map(|s| s.to_string()) .ok_or(Error::new(ErrorKind::Unknown, "Failed to get string value")) } #[cfg(any(target_os = "ios", target_os = "macos"))] /// Determine the serial port type based on the service object (like that returned by /// `IOIteratorNext`). Specific properties are extracted for USB devices. fn port_type(service: io_object_t) -> SerialPortType { let bluetooth_device_class_name = b"IOBluetoothSerialClient\0".as_ptr() as *const c_char; let usb_device_class_name = b"IOUSBHostInterface\0".as_ptr() as *const c_char; let legacy_usb_device_class_name = kIOUSBDeviceClassName; let maybe_usb_device = get_parent_device_by_type(service, usb_device_class_name) .or_else(|| get_parent_device_by_type(service, legacy_usb_device_class_name)); if let Some(usb_device) = maybe_usb_device { SerialPortType::UsbPort(UsbPortInfo { vid: get_int_property(usb_device, "idVendor").unwrap_or_default() as u16, pid: get_int_property(usb_device, "idProduct").unwrap_or_default() as u16, serial_number: get_string_property(usb_device, "USB Serial Number").ok(), manufacturer: get_string_property(usb_device, "USB Vendor Name").ok(), product: get_string_property(usb_device, "USB Product Name").ok(), // Apple developer documentation indicates `bInterfaceNumber` is the supported key for // looking up the composite usb interface id. `idVendor` and `idProduct` are included in the same tables, so // we will lookup the interface number using the same method. See: // // https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_driverkit_transport_usb // https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/USBBook/USBOverview/USBOverview.html#//apple_ref/doc/uid/TP40002644-BBCEACAJ #[cfg(feature = "usbportinfo-interface")] interface: get_int_property(usb_device, "bInterfaceNumber") .map(|x| x as u8) .ok(), }) } else if get_parent_device_by_type(service, bluetooth_device_class_name).is_some() { SerialPortType::BluetoothPort } else { SerialPortType::PciPort } } cfg_if! { if #[cfg(any(target_os = "ios", target_os = "macos"))] { /// Scans the system for serial ports and returns a list of them. /// The `SerialPortInfo` struct contains the name of the port which can be used for opening it. pub fn available_ports() -> Result> { use mach2::kern_return::KERN_SUCCESS; use mach2::port::{mach_port_t, MACH_PORT_NULL}; let mut vec = Vec::new(); unsafe { // Create a dictionary for specifying the search terms against the IOService let classes_to_match = IOServiceMatching(kIOSerialBSDServiceValue); if classes_to_match.is_null() { return Err(Error::new( ErrorKind::Unknown, "IOServiceMatching returned a NULL dictionary.", )); } let mut classes_to_match = CFMutableDictionary::wrap_under_create_rule(classes_to_match); // Populate the search dictionary with a single key/value pair indicating that we're // searching for serial devices matching the RS232 device type. let search_key = CStr::from_ptr(kIOSerialBSDTypeKey); let search_key = CFString::from_static_string(search_key.to_str().map_err(|_| Error::new(ErrorKind::Unknown, "Failed to convert search key string"))?); let search_value = CStr::from_ptr(kIOSerialBSDAllTypes); let search_value = CFString::from_static_string(search_value.to_str().map_err(|_| Error::new(ErrorKind::Unknown, "Failed to convert search key string"))?); classes_to_match.set(search_key, search_value); // Get an interface to IOKit let mut master_port: mach_port_t = MACH_PORT_NULL; let mut kern_result = IOMasterPort(MACH_PORT_NULL, &mut master_port); if kern_result != KERN_SUCCESS { return Err(Error::new( ErrorKind::Unknown, format!("ERROR: {}", kern_result), )); } // Run the search. IOServiceGetMatchingServices consumes one reference count of // classes_to_match, so explicitly retain. // // TODO: We could also just mem::forget classes_to_match like in // TCFType::into_CFType. Is there a special reason that there is no // TCFType::into_concrete_TypeRef()? CFRetain(classes_to_match.as_CFTypeRef()); let mut matching_services = MaybeUninit::uninit(); kern_result = IOServiceGetMatchingServices( kIOMasterPortDefault, classes_to_match.as_concrete_TypeRef(), matching_services.as_mut_ptr(), ); if kern_result != KERN_SUCCESS { return Err(Error::new( ErrorKind::Unknown, format!("ERROR: {}", kern_result), )); } let matching_services = matching_services.assume_init(); let _matching_services_guard = scopeguard::guard((), |_| { IOObjectRelease(matching_services); }); loop { // Grab the next result. let modem_service = IOIteratorNext(matching_services); // Break out if we've reached the end of the iterator if modem_service == MACH_PORT_NULL { break; } let _modem_service_guard = scopeguard::guard((), |_| { IOObjectRelease(modem_service); }); // Fetch all properties of the current search result item. let mut props = MaybeUninit::uninit(); let result = IORegistryEntryCreateCFProperties( modem_service, props.as_mut_ptr(), kCFAllocatorDefault, 0, ); if result == KERN_SUCCESS { // A successful call to IORegistryEntryCreateCFProperties indicates that a // properties dict has been allocated and we as the caller are in charge of // releasing it. let props = props.assume_init(); let props: CFDictionary = CFDictionary::wrap_under_create_rule(props); for key in ["IOCalloutDevice", "IODialinDevice"].iter() { let cf_key = CFString::new(key); if let Some(cf_ref) = props.find(cf_key) { let cf_type = CFType::wrap_under_get_rule(*cf_ref); match cf_type .downcast::() .map(|s| s.to_string()) { Some(path) => { vec.push(SerialPortInfo { port_name: path, port_type: port_type(modem_service), }); } None => return Err(Error::new(ErrorKind::Unknown, format!("Failed to get string value for {}", key))), } } else { return Err(Error::new(ErrorKind::Unknown, format!("Key {} missing in dict", key))); } } } else { return Err(Error::new(ErrorKind::Unknown, format!("ERROR: {}", result))); } } } Ok(vec) } } else if #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] { /// Scans the system for serial ports and returns a list of them. /// The `SerialPortInfo` struct contains the name of the port /// which can be used for opening it. pub fn available_ports() -> Result> { let mut vec = Vec::new(); if let Ok(context) = libudev::Context::new() { let mut enumerator = libudev::Enumerator::new(&context)?; enumerator.match_subsystem("tty")?; let devices = enumerator.scan_devices()?; for d in devices { if let Some(p) = d.parent() { if let Some(devnode) = d.devnode() { if let Some(path) = devnode.to_str() { if let Some(driver) = p.driver() { if driver == "serial8250" && crate::new(path, 9600).open().is_err() { continue; } } // Stop bubbling up port_type errors here so problematic ports are just // skipped instead of causing no ports to be returned. if let Ok(pt) = port_type(&d) { vec.push(SerialPortInfo { port_name: String::from(path), port_type: pt, }); } } } } } } Ok(vec) } } else if #[cfg(target_os = "linux")] { use std::fs::File; use std::io::Read; use std::path::Path; fn read_file_to_trimmed_string(dir: &Path, file: &str) -> Option { let path = dir.join(file); let mut s = String::new(); File::open(path).ok()?.read_to_string(&mut s).ok()?; Some(s.trim().to_owned()) } fn read_file_to_u16(dir: &Path, file: &str) -> Option { u16::from_str_radix(&read_file_to_trimmed_string(dir, file)?, 16).ok() } #[cfg(feature = "usbportinfo-interface")] fn read_file_to_u8(dir: &Path, file: &str) -> Option { u8::from_str_radix(&read_file_to_trimmed_string(dir, file)?, 16).ok() } fn read_port_type(path: &Path) -> Option { let path = path .canonicalize() .ok()?; let subsystem = path.join("subsystem").canonicalize().ok()?; let subsystem = subsystem.file_name()?.to_string_lossy(); match subsystem.as_ref() { // Broadcom SoC UARTs (of Raspberry Pi devices). "amba" => Some(SerialPortType::Unknown), "pci" => Some(SerialPortType::PciPort), "pnp" => Some(SerialPortType::Unknown), "usb" => usb_port_type(&path), "usb-serial" => usb_port_type(path.parent()?), _ => None, } } fn usb_port_type(interface_path: &Path) -> Option { let info = read_usb_port_info(interface_path)?; Some(SerialPortType::UsbPort(info)) } fn read_usb_port_info(interface_path: &Path) -> Option { let device_path = interface_path.parent()?; let vid = read_file_to_u16(&device_path, "idVendor")?; let pid = read_file_to_u16(&device_path, "idProduct")?; #[cfg(feature = "usbportinfo-interface")] let interface = read_file_to_u8(&interface_path, &"bInterfaceNumber"); let serial_number = read_file_to_trimmed_string(&device_path, &"serial"); let product = read_file_to_trimmed_string(&device_path, &"product"); let manufacturer = read_file_to_trimmed_string(&device_path, &"manufacturer"); Some(UsbPortInfo { vid, pid, serial_number, manufacturer, product, #[cfg(feature = "usbportinfo-interface")] interface, }) } /// Scans `/sys/class/tty` for serial devices (on Linux systems without libudev). pub fn available_ports() -> Result> { let mut vec = Vec::new(); let sys_path = Path::new("/sys/class/tty/"); let dev_path = Path::new("/dev"); for path in sys_path.read_dir().expect("/sys/class/tty/ doesn't exist on this system") { let raw_path = path?.path().clone(); let mut path = raw_path.clone(); path.push("device"); if !path.is_dir() { continue; } // Determine port type and proceed, if it's a known. // // TODO: Switch to a likely more readable let-else statement when our MSRV supports // it. let port_type = read_port_type(&path); let port_type = if let Some(port_type) = port_type { port_type } else { continue; }; // Generate the device file path `/dev/DEVICE` from the TTY class path // `/sys/class/tty/DEVICE` and emit a serial device if this path exists. There are // no further checks (yet) due to `Path::is_file` reports only regular files. // // See https://github.com/serialport/serialport-rs/issues/66 for details. if let Some(file_name) = raw_path.file_name() { let device_file = dev_path.join(file_name); if !device_file.exists() { continue; } vec.push(SerialPortInfo { port_name: device_file.to_string_lossy().to_string(), port_type, }); } } Ok(vec) } } else if #[cfg(target_os = "freebsd")] { use std::path::Path; /// Scans the system for serial ports and returns a list of them. /// The `SerialPortInfo` struct contains the name of the port /// which can be used for opening it. pub fn available_ports() -> Result> { let mut vec = Vec::new(); let dev_path = Path::new("/dev/"); for path in dev_path.read_dir()? { let path = path?; let filename = path.file_name(); let filename_string = filename.to_string_lossy(); if filename_string.starts_with("cuaU") || filename_string.starts_with("cuau") || filename_string.starts_with("cuad") { if !filename_string.ends_with(".init") && !filename_string.ends_with(".lock") { vec.push(SerialPortInfo { port_name: path.path().to_string_lossy().to_string(), port_type: SerialPortType::Unknown, }); } } } Ok(vec) } } else { /// Enumerating serial ports on this platform is not supported pub fn available_ports() -> Result> { Err(Error::new( ErrorKind::Unknown, "Not implemented for this OS", )) } } } #[cfg(all( test, target_os = "linux", not(target_env = "musl"), feature = "libudev" ))] mod tests { use super::*; use quickcheck_macros::quickcheck; #[quickcheck] fn quickcheck_parse_modalias_does_not_panic_from_random_data(modalias: String) -> bool { let _ = parse_modalias(&modalias); true } #[test] fn parse_modalias_canonical() { const MODALIAS: &str = "usb:v303Ap1001d0101dcEFdsc02dp01ic02isc02ip00in0C"; let port_info = parse_modalias(MODALIAS).expect("parse failed"); assert_eq!(port_info.vid, 0x303A, "vendor parse invalid"); assert_eq!(port_info.pid, 0x1001, "product parse invalid"); #[cfg(feature = "usbportinfo-interface")] assert_eq!(port_info.interface, Some(0x0C), "interface parse invalid"); } #[test] fn parse_modalias_corner_cases() { assert!(parse_modalias("").is_none()); assert!(parse_modalias("usb").is_none()); assert!(parse_modalias("usb:").is_none()); assert!(parse_modalias("usb:vdcdc").is_none()); assert!(parse_modalias("usb:pdcdc").is_none()); // Just vendor and product IDs. let info = parse_modalias("usb:vdcdcpabcd").unwrap(); assert_eq!(info.vid, 0xdcdc); assert_eq!(info.pid, 0xabcd); #[cfg(feature = "usbportinfo-interface")] assert!(info.interface.is_none()); // Vendor and product ID plus an interface number. let info = parse_modalias("usb:v1234p5678indc").unwrap(); assert_eq!(info.vid, 0x1234); assert_eq!(info.pid, 0x5678); #[cfg(feature = "usbportinfo-interface")] assert_eq!(info.interface, Some(0xdc)); } } serialport-4.7.0/src/posix/error.rs000064400000000000000000000025101046102023000154460ustar 00000000000000use std::io; use crate::{Error, ErrorKind}; #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] impl From for Error { fn from(e: libudev::Error) -> Error { use libudev::ErrorKind as K; let kind = match e.kind() { K::NoMem => ErrorKind::Unknown, K::InvalidInput => ErrorKind::InvalidInput, K::Io(a) => ErrorKind::Io(a), }; Error::new(kind, e.description()) } } impl From for Error { fn from(e: nix::Error) -> Error { use io::ErrorKind as IO; use nix::errno::Errno as E; use ErrorKind as K; let kind = match e { E::ETIMEDOUT => K::Io(IO::TimedOut), E::ECONNABORTED => K::Io(IO::ConnectionAborted), E::ECONNRESET => K::Io(IO::ConnectionReset), E::ECONNREFUSED => K::Io(IO::ConnectionRefused), E::ENOTCONN => K::Io(IO::NotConnected), E::EADDRINUSE => K::Io(IO::AddrInUse), E::EADDRNOTAVAIL => K::Io(IO::AddrNotAvailable), E::EAGAIN => K::Io(IO::WouldBlock), E::EINTR => K::Io(IO::Interrupted), E::EACCES => K::Io(IO::PermissionDenied), E::ENOENT => K::Io(IO::NotFound), _ => K::Unknown, }; Error::new(kind, e.desc()) } } serialport-4.7.0/src/posix/ioctl.rs000064400000000000000000000131451046102023000154350ustar 00000000000000use std::os::unix::io::RawFd; use bitflags::bitflags; use nix::libc; use crate::Result; // These are wrapped in a module because they're `pub` by default mod raw { use nix::libc; use nix::{ioctl_none_bad, ioctl_read, ioctl_read_bad, ioctl_write_ptr, ioctl_write_ptr_bad}; ioctl_none_bad!(tiocexcl, libc::TIOCEXCL); ioctl_none_bad!(tiocnxcl, libc::TIOCNXCL); ioctl_read_bad!(tiocmget, libc::TIOCMGET, libc::c_int); ioctl_none_bad!(tiocsbrk, libc::TIOCSBRK); ioctl_none_bad!(tioccbrk, libc::TIOCCBRK); #[cfg(any(target_os = "android", target_os = "linux"))] ioctl_read_bad!(fionread, libc::FIONREAD, libc::c_int); // See: /usr/include/sys/filio.h #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd" ))] ioctl_read!(fionread, b'f', 127, libc::c_int); #[cfg(any(target_os = "android", target_os = "linux"))] ioctl_read_bad!(tiocoutq, libc::TIOCOUTQ, libc::c_int); // See: /usr/include/sys/ttycom.h #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd" ))] ioctl_read!(tiocoutq, b't', 115, libc::c_int); ioctl_write_ptr_bad!(tiocmbic, libc::TIOCMBIC, libc::c_int); ioctl_write_ptr_bad!(tiocmbis, libc::TIOCMBIS, libc::c_int); ioctl_read!( #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] tcgets2, b'T', 0x2A, libc::termios2 ); ioctl_write_ptr!( #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] tcsets2, b'T', 0x2B, libc::termios2 ); #[cfg(any(target_os = "ios", target_os = "macos"))] const IOSSIOSPEED: libc::c_ulong = 0x80045402; ioctl_write_ptr_bad!( #[cfg(any(target_os = "ios", target_os = "macos"))] iossiospeed, IOSSIOSPEED, libc::speed_t ); } bitflags! { /// Flags to indicate which wires in a serial connection to use pub struct SerialLines: libc::c_int { const DATA_SET_READY = libc::TIOCM_DSR; const DATA_TERMINAL_READY = libc::TIOCM_DTR; const REQUEST_TO_SEND = libc::TIOCM_RTS; const SECONDARY_TRANSMIT = libc::TIOCM_ST; const SECONDARY_RECEIVE = libc::TIOCM_SR; const CLEAR_TO_SEND = libc::TIOCM_CTS; const DATA_CARRIER_DETECT = libc::TIOCM_CAR; const RING = libc::TIOCM_RNG; } } pub fn tiocexcl(fd: RawFd) -> Result<()> { unsafe { raw::tiocexcl(fd) } .map(|_| ()) .map_err(|e| e.into()) } pub fn tiocnxcl(fd: RawFd) -> Result<()> { unsafe { raw::tiocnxcl(fd) } .map(|_| ()) .map_err(|e| e.into()) } pub fn tiocmget(fd: RawFd) -> Result { let mut status: libc::c_int = 0; unsafe { raw::tiocmget(fd, &mut status) } .map(|_| SerialLines::from_bits_truncate(status)) .map_err(|e| e.into()) } pub fn tiocsbrk(fd: RawFd) -> Result<()> { unsafe { raw::tiocsbrk(fd) } .map(|_| ()) .map_err(|e| e.into()) } pub fn tioccbrk(fd: RawFd) -> Result<()> { unsafe { raw::tioccbrk(fd) } .map(|_| ()) .map_err(|e| e.into()) } pub fn fionread(fd: RawFd) -> Result { let mut retval: libc::c_int = 0; unsafe { raw::fionread(fd, &mut retval) } .map(|_| retval as u32) .map_err(|e| e.into()) } pub fn tiocoutq(fd: RawFd) -> Result { let mut retval: libc::c_int = 0; unsafe { raw::tiocoutq(fd, &mut retval) } .map(|_| retval as u32) .map_err(|e| e.into()) } pub fn tiocmbic(fd: RawFd, status: SerialLines) -> Result<()> { let bits = status.bits() as libc::c_int; unsafe { raw::tiocmbic(fd, &bits) } .map(|_| ()) .map_err(|e| e.into()) } pub fn tiocmbis(fd: RawFd, status: SerialLines) -> Result<()> { let bits = status.bits() as libc::c_int; unsafe { raw::tiocmbis(fd, &bits) } .map(|_| ()) .map_err(|e| e.into()) } #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] pub fn tcgets2(fd: RawFd) -> Result { let mut options = std::mem::MaybeUninit::uninit(); match unsafe { raw::tcgets2(fd, options.as_mut_ptr()) } { Ok(_) => unsafe { Ok(options.assume_init()) }, Err(e) => Err(e.into()), } } #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] pub fn tcsets2(fd: RawFd, options: &libc::termios2) -> Result<()> { unsafe { raw::tcsets2(fd, options) } .map(|_| ()) .map_err(|e| e.into()) } #[cfg(any(target_os = "ios", target_os = "macos"))] pub fn iossiospeed(fd: RawFd, baud_rate: &libc::speed_t) -> Result<()> { unsafe { raw::iossiospeed(fd, baud_rate) } .map(|_| ()) .map_err(|e| e.into()) } serialport-4.7.0/src/posix/mod.rs000064400000000000000000000001701046102023000150740ustar 00000000000000pub use self::enumerate::*; pub use self::tty::*; mod enumerate; mod error; mod ioctl; mod poll; mod termios; mod tty; serialport-4.7.0/src/posix/poll.rs000064400000000000000000000112451046102023000152700ustar 00000000000000#![allow(non_camel_case_types, dead_code)] use std::io; use std::os::unix::io::RawFd; use std::slice; use std::time::Duration; use nix::libc::c_int; use nix::poll::{PollFd, PollFlags}; #[cfg(target_os = "linux")] use nix::sys::signal::SigSet; #[cfg(any(target_os = "linux", test))] use nix::sys::time::TimeSpec; pub fn wait_read_fd(fd: RawFd, timeout: Duration) -> io::Result<()> { wait_fd(fd, PollFlags::POLLIN, timeout) } pub fn wait_write_fd(fd: RawFd, timeout: Duration) -> io::Result<()> { wait_fd(fd, PollFlags::POLLOUT, timeout) } fn wait_fd(fd: RawFd, events: PollFlags, timeout: Duration) -> io::Result<()> { use nix::errno::Errno::{EIO, EPIPE}; let mut fd = PollFd::new(fd, events); let wait = match poll_clamped(&mut fd, timeout) { Ok(r) => r, Err(e) => return Err(io::Error::from(crate::Error::from(e))), }; // All errors generated by poll or ppoll are already caught by the nix wrapper around libc, so // here we only need to check if there's at least 1 event if wait != 1 { return Err(io::Error::new( io::ErrorKind::TimedOut, "Operation timed out", )); } // Check the result of ppoll() by looking at the revents field match fd.revents() { Some(e) if e == events => return Ok(()), // If there was a hangout or invalid request Some(e) if e.contains(PollFlags::POLLHUP) || e.contains(PollFlags::POLLNVAL) => { return Err(io::Error::new(io::ErrorKind::BrokenPipe, EPIPE.desc())); } Some(_) | None => (), } Err(io::Error::new(io::ErrorKind::Other, EIO.desc())) } /// Poll with a duration clamped to the maximum value representable by the `TimeSpec` used by /// `ppoll`. #[cfg(target_os = "linux")] fn poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result { let spec = clamped_time_spec(timeout); nix::poll::ppoll(slice::from_mut(fd), Some(spec), Some(SigSet::empty())) } #[cfg(any(target_os = "linux", test))] // The type time_t is deprecaten on musl. The nix crate internally uses this type and makes an // exeption for the deprecation for musl. And so do we. // // See https://github.com/rust-lang/libc/issues/1848 which is referenced from every exemption used // in nix. #[cfg_attr(target_env = "musl", allow(deprecated))] fn clamped_time_spec(duration: Duration) -> TimeSpec { use nix::libc::c_long; use nix::sys::time::time_t; // We need to clamp manually as TimeSpec::from_duration translates durations with more than // i64::MAX seconds to negative timespans. This happens due to casting to i64 and is still the // case as of nix 0.29. let secs_limit = time_t::MAX as u64; let secs = duration.as_secs(); if secs <= secs_limit { TimeSpec::new(secs as time_t, duration.subsec_nanos() as c_long) } else { TimeSpec::new(time_t::MAX, 999_999_999) } } // Poll with a duration clamped to the maximum millisecond value representable by the `c_int` used // by `poll`. #[cfg(not(target_os = "linux"))] fn poll_clamped(fd: &mut PollFd, timeout: Duration) -> nix::Result { let millis = clamped_millis_c_int(timeout); nix::poll::poll(slice::from_mut(fd), millis) } #[cfg(any(not(target_os = "linux"), test))] fn clamped_millis_c_int(duration: Duration) -> c_int { let secs_limit = (c_int::MAX as u64) / 1000; let secs = duration.as_secs(); if secs <= secs_limit { secs as c_int * 1000 + duration.subsec_millis() as c_int } else { c_int::MAX } } #[cfg(test)] mod tests { use super::*; use crate::tests::timeout::MONOTONIC_DURATIONS; #[test] fn clamped_millis_c_int_is_monotonic() { let mut last = clamped_millis_c_int(Duration::ZERO); for (i, d) in MONOTONIC_DURATIONS.iter().enumerate() { let next = clamped_millis_c_int(*d); assert!( next >= last, "{next} >= {last} failed for {d:?} at index {i}" ); last = next; } } #[test] fn clamped_millis_c_int_zero_is_zero() { assert_eq!(0, clamped_millis_c_int(Duration::ZERO)); } #[test] fn clamped_time_spec_is_monotonic() { let mut last = clamped_time_spec(Duration::ZERO); for (i, d) in MONOTONIC_DURATIONS.iter().enumerate() { let next = clamped_time_spec(*d); assert!( next >= last, "{next} >= {last} failed for {d:?} at index {i}" ); last = next; } } #[test] fn clamped_time_spec_zero_is_zero() { let spec = clamped_time_spec(Duration::ZERO); assert_eq!(0, spec.tv_sec()); assert_eq!(0, spec.tv_nsec()); } } serialport-4.7.0/src/posix/termios.rs000064400000000000000000000204601046102023000160030ustar 00000000000000// A set of helper functions for working with the `termios` and `termios2` structs use cfg_if::cfg_if; use crate::{DataBits, FlowControl, Parity, Result, StopBits}; use nix::libc; use std::os::unix::prelude::*; cfg_if! { if #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd", all( target_os = "linux", any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" ) ) ))] { pub(crate) type Termios = libc::termios; } else if #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] { pub(crate) type Termios = libc::termios2; } else { compile_error!("Unsupported platform. See crate documentation for supported platforms"); } } // The termios struct isn't used for storing the baud rate, but it can be affected by other // calls in this lib to the IOSSIOSPEED ioctl. So whenever we get this struct, make sure to // reset the input & output baud rates to a safe default. This is accounted for by the // corresponding set_termios that is mac-specific and always calls IOSSIOSPEED. #[cfg(any(target_os = "ios", target_os = "macos",))] pub(crate) fn get_termios(fd: RawFd) -> Result { use std::mem::MaybeUninit; let mut termios = MaybeUninit::uninit(); let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; nix::errno::Errno::result(res)?; let mut termios = unsafe { termios.assume_init() }; termios.c_ispeed = self::libc::B9600; termios.c_ospeed = self::libc::B9600; Ok(termios) } #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", all( target_os = "linux", any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" ) ) ))] pub(crate) fn get_termios(fd: RawFd) -> Result { use std::mem::MaybeUninit; let mut termios = MaybeUninit::uninit(); let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; nix::errno::Errno::result(res)?; unsafe { Ok(termios.assume_init()) } } #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] pub(crate) fn get_termios(fd: RawFd) -> Result { crate::posix::ioctl::tcgets2(fd) } #[cfg(any(target_os = "ios", target_os = "macos",))] pub(crate) fn set_termios(fd: RawFd, termios: &libc::termios, baud_rate: u32) -> Result<()> { let res = unsafe { libc::tcsetattr(fd, libc::TCSANOW, termios) }; nix::errno::Errno::result(res)?; // Note: attempting to set the baud rate on a pseudo terminal via this ioctl call will fail // with the `ENOTTY` error. if baud_rate > 0 { crate::posix::ioctl::iossiospeed(fd, &(baud_rate as libc::speed_t))?; } Ok(()) } #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", all( target_os = "linux", any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" ) ) ))] pub(crate) fn set_termios(fd: RawFd, termios: &libc::termios) -> Result<()> { let res = unsafe { libc::tcsetattr(fd, libc::TCSANOW, termios) }; nix::errno::Errno::result(res)?; Ok(()) } #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] pub(crate) fn set_termios(fd: RawFd, termios: &Termios) -> Result<()> { crate::posix::ioctl::tcsets2(fd, termios) } pub(crate) fn set_parity(termios: &mut Termios, parity: Parity) { match parity { Parity::None => { termios.c_cflag &= !(libc::PARENB | libc::PARODD); termios.c_iflag &= !libc::INPCK; termios.c_iflag |= libc::IGNPAR; } Parity::Odd => { termios.c_cflag |= libc::PARENB | libc::PARODD; termios.c_iflag |= libc::INPCK; termios.c_iflag &= !libc::IGNPAR; } Parity::Even => { termios.c_cflag &= !libc::PARODD; termios.c_cflag |= libc::PARENB; termios.c_iflag |= libc::INPCK; termios.c_iflag &= !libc::IGNPAR; } }; } pub(crate) fn set_flow_control(termios: &mut Termios, flow_control: FlowControl) { match flow_control { FlowControl::None => { termios.c_iflag &= !(libc::IXON | libc::IXOFF); termios.c_cflag &= !libc::CRTSCTS; } FlowControl::Software => { termios.c_iflag |= libc::IXON | libc::IXOFF; termios.c_cflag &= !libc::CRTSCTS; } FlowControl::Hardware => { termios.c_iflag &= !(libc::IXON | libc::IXOFF); termios.c_cflag |= libc::CRTSCTS; } }; } pub(crate) fn set_data_bits(termios: &mut Termios, data_bits: DataBits) { let size = match data_bits { DataBits::Five => libc::CS5, DataBits::Six => libc::CS6, DataBits::Seven => libc::CS7, DataBits::Eight => libc::CS8, }; termios.c_cflag &= !libc::CSIZE; termios.c_cflag |= size; } pub(crate) fn set_stop_bits(termios: &mut Termios, stop_bits: StopBits) { match stop_bits { StopBits::One => termios.c_cflag &= !libc::CSTOPB, StopBits::Two => termios.c_cflag |= libc::CSTOPB, }; } #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] pub(crate) fn set_baud_rate(termios: &mut Termios, baud_rate: u32) -> Result<()> { termios.c_cflag &= !nix::libc::CBAUD; termios.c_cflag |= nix::libc::BOTHER; termios.c_ispeed = baud_rate; termios.c_ospeed = baud_rate; Ok(()) } // BSDs use the baud rate as the constant value so there's no translation necessary #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] pub(crate) fn set_baud_rate(termios: &mut Termios, baud_rate: u32) -> Result<()> { let res = unsafe { libc::cfsetspeed(termios, baud_rate.into()) }; nix::errno::Errno::result(res)?; Ok(()) } #[cfg(all( target_os = "linux", any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" ) ))] pub(crate) fn set_baud_rate(termios: &mut Termios, baud_rate: u32) -> Result<()> { use crate::{Error, ErrorKind}; use self::libc::{ B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000, B460800, B500000, B576000, B921600, }; use self::libc::{ B110, B115200, B1200, B134, B150, B1800, B19200, B200, B230400, B2400, B300, B38400, B4800, B50, B57600, B600, B75, B9600, }; let baud_rate = match baud_rate { 50 => B50, 75 => B75, 110 => B110, 134 => B134, 150 => B150, 200 => B200, 300 => B300, 600 => B600, 1200 => B1200, 1800 => B1800, 2400 => B2400, 4800 => B4800, 9600 => B9600, 19_200 => B19200, 38_400 => B38400, 57_600 => B57600, 115_200 => B115200, 230_400 => B230400, 460_800 => B460800, 500_000 => B500000, 576_000 => B576000, 921_600 => B921600, 1_000_000 => B1000000, 1_152_000 => B1152000, 1_500_000 => B1500000, 2_000_000 => B2000000, 2_500_000 => B2500000, 3_000_000 => B3000000, 3_500_000 => B3500000, 4_000_000 => B4000000, _ => return Err(Error::new(ErrorKind::InvalidInput, "Unsupported baud rate")), }; let res = unsafe { libc::cfsetspeed(termios, baud_rate) }; nix::errno::Errno::result(res)?; Ok(()) } serialport-4.7.0/src/posix/tty.rs000064400000000000000000000654311046102023000151500ustar 00000000000000use std::mem::MaybeUninit; use std::os::unix::prelude::*; use std::path::Path; use std::time::{Duration, Instant}; use std::{io, mem}; use nix::fcntl::{fcntl, OFlag}; use nix::{libc, unistd}; use crate::posix::ioctl::{self, SerialLines}; use crate::posix::termios; use crate::{ ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort, SerialPortBuilder, StopBits, }; /// Convenience method for removing exclusive access from /// a fd and closing it. fn close(fd: RawFd) { // remove exclusive access let _ = ioctl::tiocnxcl(fd); // On Linux and BSD, we don't need to worry about return // type as EBADF means the fd was never open or is already closed // // Linux and BSD guarantee that for any other error code the // fd is already closed, though MacOSX does not. // // close() also should never be retried, and the error code // in most cases in purely informative let _ = unistd::close(fd); } /// A serial port implementation for POSIX TTY ports /// /// The port will be closed when the value is dropped. This struct /// should not be instantiated directly by using `TTYPort::open()`. /// Instead, use the cross-platform `serialport::new()`. Example: /// /// ```no_run /// let mut port = serialport::new("/dev/ttyS0", 115200).open().expect("Unable to open"); /// # let _ = &mut port; /// ``` /// /// Note: on macOS, when connecting to a pseudo-terminal (`pty` opened via /// `posix_openpt`), the `baud_rate` should be set to 0; this will be used to /// explicitly _skip_ an attempt to set the baud rate of the file descriptor /// that would otherwise happen via an `ioctl` command. /// /// ``` /// use serialport::{TTYPort, SerialPort}; /// /// let (mut master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); /// # let _ = &mut master; /// # let _ = &mut slave; /// // ... elsewhere /// let mut port = TTYPort::open(&serialport::new(slave.name().unwrap(), 0)).expect("Unable to open"); /// # let _ = &mut port; /// ``` #[derive(Debug)] pub struct TTYPort { fd: RawFd, timeout: Duration, exclusive: bool, port_name: Option, #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate: u32, } /// Specifies the duration of a transmission break #[derive(Clone, Copy, Debug)] pub enum BreakDuration { /// 0.25-0.5s Short, /// Specifies a break duration that is platform-dependent Arbitrary(std::num::NonZeroI32), } /// Wrapper for RawFd to assure that it's properly closed, /// even if the enclosing function exits early. /// /// This is similar to the (nightly-only) std::os::unix::io::OwnedFd. struct OwnedFd(RawFd); impl Drop for OwnedFd { fn drop(&mut self) { close(self.0); } } impl OwnedFd { fn into_raw(self) -> RawFd { let fd = self.0; mem::forget(self); fd } } impl TTYPort { /// Opens a TTY device as a serial port. /// /// `path` should be the path to a TTY device, e.g., `/dev/ttyS0`. /// /// Ports are opened in exclusive mode by default. If this is undesirable /// behavior, use `TTYPort::set_exclusive(false)`. /// /// If the port settings differ from the default settings, characters received /// before the new settings become active may be garbled. To remove those /// from the receive buffer, call `TTYPort::clear(ClearBuffer::Input)`. /// /// ## Errors /// /// * `NoDevice` if the device could not be opened. This could indicate that /// the device is already in use. /// * `InvalidInput` if `path` is not a valid device name. /// * `Io` for any other error while opening or initializing the device. pub fn open(builder: &SerialPortBuilder) -> Result { use nix::fcntl::FcntlArg::F_SETFL; use nix::libc::{cfmakeraw, tcgetattr, tcsetattr}; let path = Path::new(&builder.path); let fd = OwnedFd(nix::fcntl::open( path, OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC, nix::sys::stat::Mode::empty(), )?); // Try to claim exclusive access to the port. This is performed even // if the port will later be set as non-exclusive, in order to respect // other applications that may have an exclusive port lock. ioctl::tiocexcl(fd.0)?; let mut termios = MaybeUninit::uninit(); nix::errno::Errno::result(unsafe { tcgetattr(fd.0, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; // 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.0, libc::TCSANOW, &termios) }; // Read back settings from port and confirm they were applied correctly let mut actual_termios = MaybeUninit::uninit(); unsafe { tcgetattr(fd.0, 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", )); }; #[cfg(any(target_os = "ios", target_os = "macos"))] if builder.baud_rate > 0 { unsafe { libc::tcflush(fd.0, libc::TCIOFLUSH) }; } // clear O_NONBLOCK flag fcntl(fd.0, F_SETFL(nix::fcntl::OFlag::empty()))?; // Configure the low-level port settings let mut termios = termios::get_termios(fd.0)?; termios::set_parity(&mut termios, builder.parity); termios::set_flow_control(&mut termios, builder.flow_control); termios::set_data_bits(&mut termios, builder.data_bits); termios::set_stop_bits(&mut termios, builder.stop_bits); #[cfg(not(any(target_os = "ios", target_os = "macos")))] termios::set_baud_rate(&mut termios, builder.baud_rate)?; #[cfg(any(target_os = "ios", target_os = "macos"))] termios::set_termios(fd.0, &termios, builder.baud_rate)?; #[cfg(not(any(target_os = "ios", target_os = "macos")))] termios::set_termios(fd.0, &termios)?; // Return the final port object let mut port = TTYPort { fd: fd.into_raw(), timeout: builder.timeout, exclusive: true, port_name: Some(builder.path.clone()), #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate: builder.baud_rate, }; // Ignore setting DTR for pseudo terminals (indicated by baud_rate == 0). if builder.baud_rate > 0 { if let Some(dtr) = builder.dtr_on_open { port.write_data_terminal_ready(dtr)?; } } Ok(port) } /// Returns the exclusivity of the port /// /// If a port is exclusive, then trying to open the same device path again /// will fail. pub fn exclusive(&self) -> bool { self.exclusive } /// Sets the exclusivity of the port /// /// If a port is exclusive, then trying to open the same device path again /// will fail. /// /// See the man pages for the tiocexcl and tiocnxcl ioctl's for more details. /// /// ## Errors /// /// * `Io` for any error while setting exclusivity for the port. pub fn set_exclusive(&mut self, exclusive: bool) -> Result<()> { let setting_result = if exclusive { ioctl::tiocexcl(self.fd) } else { ioctl::tiocnxcl(self.fd) }; setting_result?; self.exclusive = exclusive; Ok(()) } fn set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> Result<()> { if level { ioctl::tiocmbis(self.fd, pin) } else { ioctl::tiocmbic(self.fd, pin) } } fn read_pin(&mut self, pin: ioctl::SerialLines) -> Result { ioctl::tiocmget(self.fd).map(|pins| pins.contains(pin)) } /// Create a pair of pseudo serial terminals /// /// ## Returns /// Two connected `TTYPort` objects: `(master, slave)` /// /// ## Errors /// Attempting any IO or parameter settings on the slave tty after the master /// tty is closed will return errors. /// /// On some platforms manipulating the master port will fail and only /// modifying the slave port is possible. /// /// ## Examples /// /// ``` /// use serialport::TTYPort; /// /// let (mut master, mut slave) = TTYPort::pair().unwrap(); /// /// # let _ = &mut master; /// # let _ = &mut slave; /// ``` pub fn pair() -> Result<(Self, Self)> { // Open the next free pty. let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?; // Grant access to the associated slave pty nix::pty::grantpt(&next_pty_fd)?; // Unlock the slave pty nix::pty::unlockpt(&next_pty_fd)?; // Get the path of the attached slave ptty #[cfg(not(any( target_os = "linux", target_os = "android", target_os = "emscripten", target_os = "fuchsia" )))] let ptty_name = unsafe { nix::pty::ptsname(&next_pty_fd)? }; #[cfg(any( target_os = "linux", target_os = "android", target_os = "emscripten", target_os = "fuchsia" ))] let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?; // Open the slave port #[cfg(any(target_os = "ios", target_os = "macos"))] let baud_rate = 9600; let fd = nix::fcntl::open( Path::new(&ptty_name), OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, nix::sys::stat::Mode::empty(), )?; // Set the port to a raw state. Using these ports will not work without this. let mut termios = MaybeUninit::uninit(); let res = unsafe { crate::posix::tty::libc::tcgetattr(fd, termios.as_mut_ptr()) }; if let Err(e) = nix::errno::Errno::result(res) { close(fd); return Err(e.into()); } let mut termios = unsafe { termios.assume_init() }; unsafe { crate::posix::tty::libc::cfmakeraw(&mut termios) }; unsafe { crate::posix::tty::libc::tcsetattr(fd, libc::TCSANOW, &termios) }; fcntl( fd, nix::fcntl::FcntlArg::F_SETFL(nix::fcntl::OFlag::empty()), )?; let slave_tty = TTYPort { fd, timeout: Duration::from_millis(100), exclusive: true, port_name: Some(ptty_name), #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate, }; // Manually construct the master port here because the // `tcgetattr()` doesn't work on Mac, Solaris, and maybe other // BSDs when used on the master port. let master_tty = TTYPort { fd: next_pty_fd.into_raw_fd(), timeout: Duration::from_millis(100), exclusive: true, port_name: None, #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate, }; Ok((master_tty, slave_tty)) } /// Sends 0-valued bits over the port for a set duration pub fn send_break(&self, duration: BreakDuration) -> Result<()> { match duration { BreakDuration::Short => nix::sys::termios::tcsendbreak(self.fd, 0), BreakDuration::Arbitrary(n) => nix::sys::termios::tcsendbreak(self.fd, n.get()), } .map_err(|e| e.into()) } /// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the /// same serial connection. Please note that if you want a real asynchronous serial port you /// should look at [mio-serial](https://crates.io/crates/mio-serial) or /// [tokio-serial](https://crates.io/crates/tokio-serial). /// /// Also, you must be very careful when changing the settings of a cloned `SerialPort` : since /// the settings are cached on a per object basis, trying to modify them from two different /// objects can cause some nasty behavior. /// /// This is the same as `SerialPort::try_clone()` but returns the concrete type instead. /// /// # Errors /// /// This function returns an error if the serial port couldn't be cloned. pub fn try_clone_native(&self) -> Result { let fd_cloned: i32 = fcntl(self.fd, nix::fcntl::F_DUPFD_CLOEXEC(self.fd))?; Ok(TTYPort { fd: fd_cloned, exclusive: self.exclusive, port_name: self.port_name.clone(), timeout: self.timeout, #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate: self.baud_rate, }) } } impl Drop for TTYPort { fn drop(&mut self) { close(self.fd); } } impl AsRawFd for TTYPort { fn as_raw_fd(&self) -> RawFd { self.fd } } impl IntoRawFd for TTYPort { fn into_raw_fd(self) -> RawFd { // Pull just the file descriptor out. We also prevent the destructor // from being run by calling `mem::forget`. If we didn't do this, the // port would be closed, which would make `into_raw_fd` unusable. let TTYPort { fd, .. } = self; mem::forget(self); fd } } /// Get the baud speed for a port from its file descriptor #[cfg(any(target_os = "ios", target_os = "macos"))] fn get_termios_speed(fd: RawFd) -> u32 { let mut termios = MaybeUninit::uninit(); let res = unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) }; nix::errno::Errno::result(res).expect("Failed to get termios data"); let termios = unsafe { termios.assume_init() }; assert_eq!(termios.c_ospeed, termios.c_ispeed); termios.c_ospeed as u32 } impl FromRawFd for TTYPort { unsafe fn from_raw_fd(fd: RawFd) -> Self { TTYPort { fd, timeout: Duration::from_millis(100), exclusive: ioctl::tiocexcl(fd).is_ok(), // It is not trivial to get the file path corresponding to a file descriptor. // We'll punt on it and set it to `None` here. port_name: None, // It's not guaranteed that the baud rate in the `termios` struct is correct, as // setting an arbitrary baud rate via the `iossiospeed` ioctl overrides that value, // but extract that value anyways as a best-guess of the actual baud rate. #[cfg(any(target_os = "ios", target_os = "macos"))] baud_rate: get_termios_speed(fd), } } } impl io::Read for TTYPort { fn read(&mut self, buf: &mut [u8]) -> io::Result { if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) { return Err(io::Error::from(Error::from(e))); } nix::unistd::read(self.fd, buf).map_err(|e| io::Error::from(Error::from(e))) } } impl io::Write for TTYPort { fn write(&mut self, buf: &[u8]) -> io::Result { if let Err(e) = super::poll::wait_write_fd(self.fd, self.timeout) { return Err(io::Error::from(Error::from(e))); } nix::unistd::write(self.fd, buf).map_err(|e| io::Error::from(Error::from(e))) } fn flush(&mut self) -> io::Result<()> { let timeout = Instant::now() + self.timeout; loop { return match nix::sys::termios::tcdrain(self.fd) { Ok(_) => Ok(()), Err(nix::errno::Errno::EINTR) => { // Retry flushing. But only up to the ports timeout for not retrying // indefinitely in case that it gets interrupted again. if Instant::now() < timeout { continue; } else { Err(io::Error::new( io::ErrorKind::TimedOut, "timeout for retrying flush reached", )) } } Err(_) => Err(io::Error::new(io::ErrorKind::Other, "flush failed")), }; } } } impl SerialPort for TTYPort { fn name(&self) -> Option { self.port_name.clone() } /// Returns the port's baud rate /// /// On some platforms this will be the actual device baud rate, which may differ from the /// desired baud rate. #[cfg(any( target_os = "android", all( target_os = "linux", not(any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" )) ) ))] fn baud_rate(&self) -> Result { let termios2 = ioctl::tcgets2(self.fd)?; assert!(termios2.c_ospeed == termios2.c_ispeed); Ok(termios2.c_ospeed) } /// Returns the port's baud rate /// /// On some platforms this will be the actual device baud rate, which may differ from the /// desired baud rate. #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] fn baud_rate(&self) -> Result { let termios = termios::get_termios(self.fd)?; let ospeed = unsafe { libc::cfgetospeed(&termios) }; let ispeed = unsafe { libc::cfgetispeed(&termios) }; assert!(ospeed == ispeed); Ok(ospeed as u32) } /// Returns the port's baud rate /// /// On some platforms this will be the actual device baud rate, which may differ from the /// desired baud rate. #[cfg(any(target_os = "ios", target_os = "macos"))] fn baud_rate(&self) -> Result { Ok(self.baud_rate) } /// Returns the port's baud rate /// /// On some platforms this will be the actual device baud rate, which may differ from the /// desired baud rate. #[cfg(all( target_os = "linux", any( target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64" ) ))] fn baud_rate(&self) -> Result { use self::libc::{ B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000, B460800, B500000, B576000, B921600, }; use self::libc::{ B110, B115200, B1200, B134, B150, B1800, B19200, B200, B230400, B2400, B300, B38400, B4800, B50, B57600, B600, B75, B9600, }; let termios = termios::get_termios(self.fd)?; let ospeed = unsafe { libc::cfgetospeed(&termios) }; let ispeed = unsafe { libc::cfgetispeed(&termios) }; assert!(ospeed == ispeed); let res: u32 = match ospeed { B50 => 50, B75 => 75, B110 => 110, B134 => 134, B150 => 150, B200 => 200, B300 => 300, B600 => 600, B1200 => 1200, B1800 => 1800, B2400 => 2400, B4800 => 4800, B9600 => 9600, B19200 => 19_200, B38400 => 38_400, B57600 => 57_600, B115200 => 115_200, B230400 => 230_400, B460800 => 460_800, B500000 => 500_000, B576000 => 576_000, B921600 => 921_600, B1000000 => 1_000_000, B1152000 => 1_152_000, B1500000 => 1_500_000, B2000000 => 2_000_000, B2500000 => 2_500_000, B3000000 => 3_000_000, B3500000 => 3_500_000, B4000000 => 4_000_000, _ => unreachable!(), }; Ok(res) } fn data_bits(&self) -> Result { let termios = termios::get_termios(self.fd)?; match termios.c_cflag & libc::CSIZE { libc::CS8 => Ok(DataBits::Eight), libc::CS7 => Ok(DataBits::Seven), libc::CS6 => Ok(DataBits::Six), libc::CS5 => Ok(DataBits::Five), _ => Err(Error::new( ErrorKind::Unknown, "Invalid data bits setting encountered", )), } } fn flow_control(&self) -> Result { let termios = termios::get_termios(self.fd)?; if termios.c_cflag & libc::CRTSCTS == libc::CRTSCTS { Ok(FlowControl::Hardware) } else if termios.c_iflag & (libc::IXON | libc::IXOFF) == (libc::IXON | libc::IXOFF) { Ok(FlowControl::Software) } else { Ok(FlowControl::None) } } fn parity(&self) -> Result { let termios = termios::get_termios(self.fd)?; if termios.c_cflag & libc::PARENB == libc::PARENB { if termios.c_cflag & libc::PARODD == libc::PARODD { Ok(Parity::Odd) } else { Ok(Parity::Even) } } else { Ok(Parity::None) } } fn stop_bits(&self) -> Result { let termios = termios::get_termios(self.fd)?; if termios.c_cflag & libc::CSTOPB == libc::CSTOPB { Ok(StopBits::Two) } else { Ok(StopBits::One) } } fn timeout(&self) -> Duration { self.timeout } #[cfg(any( target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "linux" ))] fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_baud_rate(&mut termios, baud_rate)?; termios::set_termios(self.fd, &termios) } // Mac OS needs special logic for setting arbitrary baud rates. #[cfg(any(target_os = "ios", target_os = "macos"))] fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { ioctl::iossiospeed(self.fd, &(baud_rate as libc::speed_t))?; self.baud_rate = baud_rate; Ok(()) } fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_flow_control(&mut termios, flow_control); #[cfg(any(target_os = "ios", target_os = "macos"))] return termios::set_termios(self.fd, &termios, self.baud_rate); #[cfg(not(any(target_os = "ios", target_os = "macos")))] return termios::set_termios(self.fd, &termios); } fn set_parity(&mut self, parity: Parity) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_parity(&mut termios, parity); #[cfg(any(target_os = "ios", target_os = "macos"))] return termios::set_termios(self.fd, &termios, self.baud_rate); #[cfg(not(any(target_os = "ios", target_os = "macos")))] return termios::set_termios(self.fd, &termios); } fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_data_bits(&mut termios, data_bits); #[cfg(any(target_os = "ios", target_os = "macos"))] return termios::set_termios(self.fd, &termios, self.baud_rate); #[cfg(not(any(target_os = "ios", target_os = "macos")))] return termios::set_termios(self.fd, &termios); } fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { let mut termios = termios::get_termios(self.fd)?; termios::set_stop_bits(&mut termios, stop_bits); #[cfg(any(target_os = "ios", target_os = "macos"))] return termios::set_termios(self.fd, &termios, self.baud_rate); #[cfg(not(any(target_os = "ios", target_os = "macos")))] return termios::set_termios(self.fd, &termios); } fn set_timeout(&mut self, timeout: Duration) -> Result<()> { self.timeout = timeout; Ok(()) } fn write_request_to_send(&mut self, level: bool) -> Result<()> { self.set_pin(SerialLines::REQUEST_TO_SEND, level) } fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { self.set_pin(SerialLines::DATA_TERMINAL_READY, level) } fn read_clear_to_send(&mut self) -> Result { self.read_pin(SerialLines::CLEAR_TO_SEND) } fn read_data_set_ready(&mut self) -> Result { self.read_pin(SerialLines::DATA_SET_READY) } fn read_ring_indicator(&mut self) -> Result { self.read_pin(SerialLines::RING) } fn read_carrier_detect(&mut self) -> Result { self.read_pin(SerialLines::DATA_CARRIER_DETECT) } fn bytes_to_read(&self) -> Result { ioctl::fionread(self.fd) } fn bytes_to_write(&self) -> Result { ioctl::tiocoutq(self.fd) } fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { let buffer_id = match buffer_to_clear { ClearBuffer::Input => libc::TCIFLUSH, ClearBuffer::Output => libc::TCOFLUSH, ClearBuffer::All => libc::TCIOFLUSH, }; let res = unsafe { nix::libc::tcflush(self.fd, buffer_id) }; nix::errno::Errno::result(res) .map(|_| ()) .map_err(|e| e.into()) } fn try_clone(&self) -> Result> { match self.try_clone_native() { Ok(p) => Ok(Box::new(p)), Err(e) => Err(e), } } fn set_break(&self) -> Result<()> { ioctl::tiocsbrk(self.fd) } fn clear_break(&self) -> Result<()> { ioctl::tioccbrk(self.fd) } } #[test] fn test_ttyport_into_raw_fd() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. // TODO: Convert this to a statement-level attribute once // https://github.com/rust-lang/rust/issues/15701 is on stable. // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe #![allow(unused_variables)] let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair"); // First test with the master let master_fd = master.into_raw_fd(); let mut termios = MaybeUninit::uninit(); let res = unsafe { nix::libc::tcgetattr(master_fd, termios.as_mut_ptr()) }; if res != 0 { close(master_fd); panic!("tcgetattr on the master port failed"); } // And then the slave let slave_fd = slave.into_raw_fd(); let res = unsafe { nix::libc::tcgetattr(slave_fd, termios.as_mut_ptr()) }; if res != 0 { close(slave_fd); panic!("tcgetattr on the master port failed"); } close(master_fd); close(slave_fd); } serialport-4.7.0/src/tests/mod.rs000064400000000000000000000001351046102023000150750ustar 00000000000000use cfg_if::cfg_if; cfg_if! { if #[cfg(test)] { pub(crate) mod timeout; } } serialport-4.7.0/src/tests/timeout.rs000064400000000000000000000024601046102023000160070ustar 00000000000000use std::time::Duration; /// A sequence of strongly monotonic inrceasing durations. Introduced for testing conversions from /// `Duration` to platform-specific types. pub(crate) const MONOTONIC_DURATIONS: [Duration; 17] = [ Duration::ZERO, Duration::from_nanos(1), Duration::from_millis(1), Duration::from_secs(1), Duration::from_secs(i16::MAX as u64 - 1), Duration::from_secs(i16::MAX as u64), Duration::from_secs(i16::MAX as u64 + 1), Duration::from_secs(i32::MAX as u64 - 1), Duration::from_secs(i32::MAX as u64), Duration::from_secs(i32::MAX as u64 + 1), Duration::from_secs(i64::MAX as u64 - 1), Duration::from_secs(i64::MAX as u64), Duration::from_secs(i64::MAX as u64 + 1), Duration::from_secs(u64::MAX - 1), Duration::from_secs(u64::MAX), Duration::new(u64::MAX, 1_000_000), Duration::MAX, ]; #[cfg(test)] mod tests { use super::*; #[test] fn basic_durations_properties() { assert_eq!(Duration::ZERO, *MONOTONIC_DURATIONS.first().unwrap()); assert_eq!(Duration::MAX, *MONOTONIC_DURATIONS.last().unwrap()); // Check that this array is monotonic. let mut last = MONOTONIC_DURATIONS[0]; for next in MONOTONIC_DURATIONS { assert!(last <= next); last = next; } } } serialport-4.7.0/src/windows/com.rs000064400000000000000000000355111046102023000154320ustar 00000000000000use std::mem::MaybeUninit; use std::os::windows::prelude::*; use std::time::Duration; use std::{io, ptr}; use winapi::shared::minwindef::*; use winapi::um::commapi::*; use winapi::um::fileapi::*; use winapi::um::handleapi::*; use winapi::um::processthreadsapi::GetCurrentProcess; use winapi::um::winbase::*; use winapi::um::winnt::{ DUPLICATE_SAME_ACCESS, FILE_ATTRIBUTE_NORMAL, GENERIC_READ, GENERIC_WRITE, HANDLE, MAXDWORD, }; use crate::windows::dcb; use crate::{ ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort, SerialPortBuilder, StopBits, }; /// A serial port implementation for Windows COM ports /// /// The port will be closed when the value is dropped. However, this struct /// should not be instantiated directly by using `COMPort::open()`, instead use /// the cross-platform `serialport::open()` or /// `serialport::open_with_settings()`. #[derive(Debug)] pub struct COMPort { handle: HANDLE, timeout: Duration, port_name: Option, } unsafe impl Send for COMPort {} impl COMPort { /// Opens a COM port as a serial device. /// /// `port` should be the name of a COM port, e.g., `COM1`. /// /// If the COM port handle needs to be opened with special flags, use /// `from_raw_handle` method to create the `COMPort`. Note that you should /// set the different settings before using the serial port using `set_all`. /// /// ## Errors /// /// * `NoDevice` if the device could not be opened. This could indicate that /// the device is already in use. /// * `InvalidInput` if `port` is not a valid device name. /// * `Io` for any other I/O error while opening or initializing the device. pub fn open(builder: &SerialPortBuilder) -> Result { let mut name = Vec::::with_capacity(4 + builder.path.len() + 1); name.extend(r"\\.\".encode_utf16()); name.extend(builder.path.encode_utf16()); name.push(0); let handle = unsafe { CreateFileW( name.as_ptr(), GENERIC_READ | GENERIC_WRITE, 0, ptr::null_mut(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 as HANDLE, ) }; if handle == INVALID_HANDLE_VALUE { return Err(super::error::last_os_error()); } // create the COMPort here so the handle is getting closed // if one of the calls to `get_dcb()` or `set_dcb()` fails let mut com = COMPort::open_from_raw_handle(handle as RawHandle); let mut dcb = dcb::get_dcb(handle)?; dcb::init(&mut dcb); dcb::set_baud_rate(&mut dcb, builder.baud_rate); dcb::set_data_bits(&mut dcb, builder.data_bits); dcb::set_parity(&mut dcb, builder.parity); dcb::set_stop_bits(&mut dcb, builder.stop_bits); dcb::set_flow_control(&mut dcb, builder.flow_control); dcb::set_dcb(handle, dcb)?; if let Some(dtr) = builder.dtr_on_open { com.write_data_terminal_ready(dtr)?; } com.set_timeout(builder.timeout)?; com.port_name = Some(builder.path.clone()); Ok(com) } /// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the /// same serial connection. Please note that if you want a real asynchronous serial port you /// should look at [mio-serial](https://crates.io/crates/mio-serial) or /// [tokio-serial](https://crates.io/crates/tokio-serial). /// /// Also, you must be very careful when changing the settings of a cloned `SerialPort` : since /// the settings are cached on a per object basis, trying to modify them from two different /// objects can cause some nasty behavior. /// /// This is the same as `SerialPort::try_clone()` but returns the concrete type instead. /// /// # Errors /// /// This function returns an error if the serial port couldn't be cloned. pub fn try_clone_native(&self) -> Result { let process_handle: HANDLE = unsafe { GetCurrentProcess() }; let mut cloned_handle: HANDLE = INVALID_HANDLE_VALUE; unsafe { DuplicateHandle( process_handle, self.handle, process_handle, &mut cloned_handle, 0, TRUE, DUPLICATE_SAME_ACCESS, ); if cloned_handle != INVALID_HANDLE_VALUE { Ok(COMPort { handle: cloned_handle, port_name: self.port_name.clone(), timeout: self.timeout, }) } else { Err(super::error::last_os_error()) } } } fn escape_comm_function(&mut self, function: DWORD) -> Result<()> { match unsafe { EscapeCommFunction(self.handle, function) } { 0 => Err(super::error::last_os_error()), _ => Ok(()), } } fn read_pin(&mut self, pin: DWORD) -> Result { let mut status: DWORD = 0; match unsafe { GetCommModemStatus(self.handle, &mut status) } { 0 => Err(super::error::last_os_error()), _ => Ok(status & pin != 0), } } fn open_from_raw_handle(handle: RawHandle) -> Self { // It is not trivial to get the file path corresponding to a handle. // We'll punt and set it `None` here. COMPort { handle: handle as HANDLE, timeout: Duration::from_millis(100), port_name: None, } } fn timeout_constant(duration: Duration) -> DWORD { let milliseconds = duration.as_millis(); // In the way we are setting up COMMTIMEOUTS, a timeout_constant of MAXDWORD gets rejected. // Let's clamp the timeout constant for values of MAXDWORD and above. See remarks at // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts. // // This effectively throws away accuracy for really long timeouts but at least preserves a // long-ish timeout. But just casting to DWORD would result in presumably unexpected short // and non-monotonic timeouts from cutting off the higher bits. u128::min(milliseconds, MAXDWORD as u128 - 1) as DWORD } } impl Drop for COMPort { fn drop(&mut self) { unsafe { CloseHandle(self.handle); } } } impl AsRawHandle for COMPort { fn as_raw_handle(&self) -> RawHandle { self.handle as RawHandle } } impl FromRawHandle for COMPort { unsafe fn from_raw_handle(handle: RawHandle) -> Self { COMPort::open_from_raw_handle(handle) } } impl IntoRawHandle for COMPort { fn into_raw_handle(self) -> RawHandle { let Self { handle, .. } = self; handle as RawHandle } } impl io::Read for COMPort { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut len: DWORD = 0; match unsafe { ReadFile( self.handle, buf.as_mut_ptr() as LPVOID, buf.len() as DWORD, &mut len, ptr::null_mut(), ) } { 0 => Err(io::Error::last_os_error()), _ => { if len != 0 { Ok(len as usize) } else { Err(io::Error::new( io::ErrorKind::TimedOut, "Operation timed out", )) } } } } } impl io::Write for COMPort { fn write(&mut self, buf: &[u8]) -> io::Result { let mut len: DWORD = 0; match unsafe { WriteFile( self.handle, buf.as_ptr() as LPVOID, buf.len() as DWORD, &mut len, ptr::null_mut(), ) } { 0 => Err(io::Error::last_os_error()), _ => Ok(len as usize), } } fn flush(&mut self) -> io::Result<()> { match unsafe { FlushFileBuffers(self.handle) } { 0 => Err(io::Error::last_os_error()), _ => Ok(()), } } } impl SerialPort for COMPort { fn name(&self) -> Option { self.port_name.clone() } fn timeout(&self) -> Duration { self.timeout } fn set_timeout(&mut self, timeout: Duration) -> Result<()> { let timeout_constant = Self::timeout_constant(timeout); let mut timeouts = COMMTIMEOUTS { ReadIntervalTimeout: MAXDWORD, ReadTotalTimeoutMultiplier: MAXDWORD, ReadTotalTimeoutConstant: timeout_constant, WriteTotalTimeoutMultiplier: 0, WriteTotalTimeoutConstant: timeout_constant, }; if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == 0 { return Err(super::error::last_os_error()); } self.timeout = timeout; Ok(()) } fn write_request_to_send(&mut self, level: bool) -> Result<()> { if level { self.escape_comm_function(SETRTS) } else { self.escape_comm_function(CLRRTS) } } fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> { if level { self.escape_comm_function(SETDTR) } else { self.escape_comm_function(CLRDTR) } } fn read_clear_to_send(&mut self) -> Result { self.read_pin(MS_CTS_ON) } fn read_data_set_ready(&mut self) -> Result { self.read_pin(MS_DSR_ON) } fn read_ring_indicator(&mut self) -> Result { self.read_pin(MS_RING_ON) } fn read_carrier_detect(&mut self) -> Result { self.read_pin(MS_RLSD_ON) } fn baud_rate(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; Ok(dcb.BaudRate as u32) } fn data_bits(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; match dcb.ByteSize { 5 => Ok(DataBits::Five), 6 => Ok(DataBits::Six), 7 => Ok(DataBits::Seven), 8 => Ok(DataBits::Eight), _ => Err(Error::new( ErrorKind::Unknown, "Invalid data bits setting encountered", )), } } fn parity(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; match dcb.Parity { ODDPARITY => Ok(Parity::Odd), EVENPARITY => Ok(Parity::Even), NOPARITY => Ok(Parity::None), _ => Err(Error::new( ErrorKind::Unknown, "Invalid parity bits setting encountered", )), } } fn stop_bits(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; match dcb.StopBits { TWOSTOPBITS => Ok(StopBits::Two), ONESTOPBIT => Ok(StopBits::One), _ => Err(Error::new( ErrorKind::Unknown, "Invalid stop bits setting encountered", )), } } fn flow_control(&self) -> Result { let dcb = dcb::get_dcb(self.handle)?; if dcb.fOutxCtsFlow() != 0 || dcb.fRtsControl() != 0 { Ok(FlowControl::Hardware) } else if dcb.fOutX() != 0 || dcb.fInX() != 0 { Ok(FlowControl::Software) } else { Ok(FlowControl::None) } } fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_baud_rate(&mut dcb, baud_rate); dcb::set_dcb(self.handle, dcb) } fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_data_bits(&mut dcb, data_bits); dcb::set_dcb(self.handle, dcb) } fn set_parity(&mut self, parity: Parity) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_parity(&mut dcb, parity); dcb::set_dcb(self.handle, dcb) } fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_stop_bits(&mut dcb, stop_bits); dcb::set_dcb(self.handle, dcb) } fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> { let mut dcb = dcb::get_dcb(self.handle)?; dcb::set_flow_control(&mut dcb, flow_control); dcb::set_dcb(self.handle, dcb) } fn bytes_to_read(&self) -> Result { let mut errors: DWORD = 0; let mut comstat = MaybeUninit::uninit(); if unsafe { ClearCommError(self.handle, &mut errors, comstat.as_mut_ptr()) != 0 } { unsafe { Ok(comstat.assume_init().cbInQue) } } else { Err(super::error::last_os_error()) } } fn bytes_to_write(&self) -> Result { let mut errors: DWORD = 0; let mut comstat = MaybeUninit::uninit(); if unsafe { ClearCommError(self.handle, &mut errors, comstat.as_mut_ptr()) != 0 } { unsafe { Ok(comstat.assume_init().cbOutQue) } } else { Err(super::error::last_os_error()) } } fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> { let buffer_flags = match buffer_to_clear { ClearBuffer::Input => PURGE_RXABORT | PURGE_RXCLEAR, ClearBuffer::Output => PURGE_TXABORT | PURGE_TXCLEAR, ClearBuffer::All => PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR, }; if unsafe { PurgeComm(self.handle, buffer_flags) != 0 } { Ok(()) } else { Err(super::error::last_os_error()) } } fn try_clone(&self) -> Result> { match self.try_clone_native() { Ok(p) => Ok(Box::new(p)), Err(e) => Err(e), } } fn set_break(&self) -> Result<()> { if unsafe { SetCommBreak(self.handle) != 0 } { Ok(()) } else { Err(super::error::last_os_error()) } } fn clear_break(&self) -> Result<()> { if unsafe { ClearCommBreak(self.handle) != 0 } { Ok(()) } else { Err(super::error::last_os_error()) } } } #[cfg(test)] mod tests { use super::*; use crate::tests::timeout::MONOTONIC_DURATIONS; #[test] fn timeout_constant_is_monotonic() { let mut last = COMPort::timeout_constant(Duration::ZERO); for (i, d) in MONOTONIC_DURATIONS.iter().enumerate() { let next = COMPort::timeout_constant(*d); assert!( next >= last, "{next} >= {last} failed for {d:?} at index {i}" ); last = next; } } #[test] fn timeout_constant_zero_is_zero() { assert_eq!(0, COMPort::timeout_constant(Duration::ZERO)); } } serialport-4.7.0/src/windows/dcb.rs000064400000000000000000000066611046102023000154100ustar 00000000000000use std::mem::MaybeUninit; use winapi::shared::minwindef::*; use winapi::um::commapi::*; use winapi::um::winbase::*; use winapi::um::winnt::HANDLE; use crate::{DataBits, FlowControl, Parity, Result, StopBits}; pub(crate) fn get_dcb(handle: HANDLE) -> Result { let mut dcb: DCB = unsafe { MaybeUninit::zeroed().assume_init() }; dcb.DCBlength = std::mem::size_of::() as u32; if unsafe { GetCommState(handle, &mut dcb) } != 0 { Ok(dcb) } else { Err(super::error::last_os_error()) } } /// Initialize the DCB struct /// Set all values that won't be affected by `SerialPortBuilder` options. pub(crate) fn init(dcb: &mut DCB) { // dcb.DCBlength // dcb.BaudRate // dcb.BitFields // dcb.wReserved // dcb.XonLim // dcb.XoffLim // dcb.ByteSize // dcb.Parity // dcb.StopBits dcb.XonChar = 17; dcb.XoffChar = 19; dcb.ErrorChar = '\0' as winapi::ctypes::c_char; dcb.EofChar = 26; // dcb.EvtChar // always true for communications resources dcb.set_fBinary(TRUE as DWORD); // dcb.set_fParity() // dcb.set_fOutxCtsFlow() // serialport-rs doesn't support toggling DSR: so disable fOutxDsrFlow dcb.set_fOutxDsrFlow(FALSE as DWORD); dcb.set_fDtrControl(DTR_CONTROL_DISABLE); // disable because fOutxDsrFlow is disabled as well dcb.set_fDsrSensitivity(FALSE as DWORD); // dcb.set_fTXContinueOnXoff() // dcb.set_fOutX() // dcb.set_fInX() dcb.set_fErrorChar(FALSE as DWORD); // fNull: when set to TRUE null bytes are discarded when received. // null bytes won't be discarded by serialport-rs dcb.set_fNull(FALSE as DWORD); // dcb.set_fRtsControl() // serialport-rs does not handle the fAbortOnError behaviour, so we must make sure it's not enabled dcb.set_fAbortOnError(FALSE as DWORD); } pub(crate) fn set_dcb(handle: HANDLE, mut dcb: DCB) -> Result<()> { if unsafe { SetCommState(handle, &mut dcb as *mut _) != 0 } { Ok(()) } else { Err(super::error::last_os_error()) } } pub(crate) fn set_baud_rate(dcb: &mut DCB, baud_rate: u32) { dcb.BaudRate = baud_rate as DWORD; } pub(crate) fn set_data_bits(dcb: &mut DCB, data_bits: DataBits) { dcb.ByteSize = match data_bits { DataBits::Five => 5, DataBits::Six => 6, DataBits::Seven => 7, DataBits::Eight => 8, }; } pub(crate) fn set_parity(dcb: &mut DCB, parity: Parity) { dcb.Parity = match parity { Parity::None => NOPARITY, Parity::Odd => ODDPARITY, Parity::Even => EVENPARITY, }; dcb.set_fParity(if parity == Parity::None { FALSE } else { TRUE } as DWORD); } pub(crate) fn set_stop_bits(dcb: &mut DCB, stop_bits: StopBits) { dcb.StopBits = match stop_bits { StopBits::One => ONESTOPBIT, StopBits::Two => TWOSTOPBITS, }; } pub(crate) fn set_flow_control(dcb: &mut DCB, flow_control: FlowControl) { match flow_control { FlowControl::None => { dcb.set_fOutxCtsFlow(0); dcb.set_fRtsControl(0); dcb.set_fOutX(0); dcb.set_fInX(0); } FlowControl::Software => { dcb.set_fOutxCtsFlow(0); dcb.set_fRtsControl(0); dcb.set_fOutX(1); dcb.set_fInX(1); } FlowControl::Hardware => { dcb.set_fOutxCtsFlow(1); dcb.set_fRtsControl(1); dcb.set_fOutX(0); dcb.set_fInX(0); } } } serialport-4.7.0/src/windows/enumerate.rs000064400000000000000000000657101046102023000166450ustar 00000000000000use std::collections::HashSet; use std::{mem, ptr}; use winapi::ctypes::c_void; use winapi::shared::guiddef::*; use winapi::shared::minwindef::*; use winapi::shared::winerror::*; use winapi::um::cfgmgr32::*; use winapi::um::cguid::GUID_NULL; use winapi::um::setupapi::*; use winapi::um::winnt::{KEY_READ, REG_SZ}; use winapi::um::winreg::*; use crate::{Error, ErrorKind, Result, SerialPortInfo, SerialPortType, UsbPortInfo}; /// takes normal Rust `str` and outputs a null terminated UTF-16 encoded string fn as_utf16(utf8: &str) -> Vec { utf8.encode_utf16().chain(Some(0)).collect() } /// takes a UTF-16 encoded slice (null termination not required) /// and converts to a UTF8 Rust string. Trailing null chars are removed fn from_utf16_lossy_trimmed(utf16: &[u16]) -> String { String::from_utf16_lossy(utf16) .trim_end_matches(0 as char) .to_string() } /// According to the MSDN docs, we should use SetupDiGetClassDevs, SetupDiEnumDeviceInfo /// and SetupDiGetDeviceInstanceId in order to enumerate devices. /// https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/enumerating-installed-devices fn get_ports_guids() -> Result> { // SetupDiGetClassDevs returns the devices associated with a particular class of devices. // We want the list of devices which are listed as COM ports (generally those that show up in the // Device Manager as "Ports (COM & LPT)" which is otherwise known as the "Ports" class). // // The list of system defined classes can be found here: // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-setup-classes-available-to-vendors let class_names = ["Ports", "Modem"]; let mut guids: Vec = Vec::new(); for class_name in class_names { let class_name_w = as_utf16(class_name); let mut num_guids: DWORD = 1; // Initially assume that there is only 1 guid per name. let class_start_idx = guids.len(); // start idx for this name (for potential resize with multiple guids) // first attempt with size == 1, second with the size returned from the first try for _ in 0..2 { guids.resize(class_start_idx + num_guids as usize, GUID_NULL); let guid_buffer = &mut guids[class_start_idx..]; // Find out how many GUIDs are associated with this class name. num_guids will tell us how many there actually are. let res = unsafe { SetupDiClassGuidsFromNameW( class_name_w.as_ptr(), guid_buffer.as_mut_ptr(), guid_buffer.len() as DWORD, &mut num_guids, ) }; if res == FALSE { return Err(Error::new( ErrorKind::Unknown, "Unable to determine number of Ports GUIDs", )); } let len_cmp = guid_buffer.len().cmp(&(num_guids as usize)); // under allocated if len_cmp == std::cmp::Ordering::Less { continue; // retry } // allocation > required len else if len_cmp == std::cmp::Ordering::Greater { guids.truncate(class_start_idx + num_guids as usize); } break; // next name } } Ok(guids) } #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct HwidMatches<'hwid> { vid: &'hwid str, pid: &'hwid str, serial: Option<&'hwid str>, interface: Option<&'hwid str>, } impl<'hwid> HwidMatches<'hwid> { fn new(hwid: &'hwid str) -> Option { // When we match something, update this so that we are always looking forward let mut hwid_tail = hwid; // VID_(?P[[:xdigit:]]{4}) let vid_start = hwid.find("VID_")?; // We won't match for hex characters here. That can be done when parsed. let vid = hwid_tail.get(vid_start + 4..vid_start + 8)?; hwid_tail = hwid_tail.get(vid_start + 8..)?; // [&+]PID_(?P[[:xdigit:]]{4}) let pid = if hwid_tail.starts_with("&PID_") || hwid_tail.starts_with("+PID_") { // We will let the hex parser fail if there are not hex digits. hwid_tail.get(5..9)? } else { return None; }; hwid_tail = hwid_tail.get(9..)?; // (?:[&+]MI_(?P[[:xdigit:]]{2})){0,1} let iid = if hwid_tail.starts_with("&MI_") || hwid_tail.starts_with("+MI_") { // We will let the hex parser fail if there are not hex digits. let iid = hwid_tail.get(4..6); hwid_tail = hwid_tail.get(6..).unwrap_or(hwid_tail); iid } else { None }; // ([\\+](?P\w+))? with slightly modified check for alphanumeric characters instead // of regex word character // // TODO: Fix returning no serial number at all for devices without one. The previous regex // and the code below return the first thing from the intance ID. See issue #203. let serial = if hwid_tail.starts_with('\\') || hwid_tail.starts_with('+') { hwid_tail.get(1..).and_then(|tail| { let index = tail .char_indices() .find(|&(_, char)| !char.is_alphanumeric()) .map(|(index, _)| index) .unwrap_or(tail.len()); tail.get(..index) }) } else { None }; Some(Self { vid, pid, serial, interface: iid, }) } } /// Windows usb port information can be determined by the port's HWID string. /// /// This function parses the HWID string using regex, and returns the USB port /// information if the hardware ID can be parsed correctly. The manufacturer /// and product names cannot be determined from the HWID string, so those are /// set as None. /// /// For composite USB devices, the HWID string will be for the interface. In /// this case, the parent HWID string must be provided so that the correct /// serial number can be determined. /// /// Some HWID examples are: /// - MicroPython pyboard: USB\VID_F055&PID_9802\385435603432 /// - BlackMagic GDB Server: USB\VID_1D50&PID_6018&MI_00\6&A694CA9&0&0000 /// - BlackMagic UART port: USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0002 /// - FTDI Serial Adapter: FTDIBUS\VID_0403+PID_6001+A702TB52A\0000 fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> Option { let mut caps = HwidMatches::new(hardware_id)?; let interface = caps.interface.and_then(|m| u8::from_str_radix(m, 16).ok()); if interface.is_some() { // If this is a composite device, we need to parse the parent's HWID to get the correct information. caps = HwidMatches::new(parent_hardware_id?)?; } Some(UsbPortInfo { vid: u16::from_str_radix(caps.vid, 16).ok()?, pid: u16::from_str_radix(caps.pid, 16).ok()?, serial_number: caps.serial.map(str::to_string), manufacturer: None, product: None, #[cfg(feature = "usbportinfo-interface")] interface, }) } struct PortDevices { /// Handle to a device information set. hdi: HDEVINFO, /// Index used by iterator. dev_idx: DWORD, } impl PortDevices { // Creates PortDevices object which represents the set of devices associated with a particular // Ports class (given by `guid`). pub fn new(guid: &GUID) -> Self { PortDevices { hdi: unsafe { SetupDiGetClassDevsW(guid, ptr::null(), ptr::null_mut(), DIGCF_PRESENT) }, dev_idx: 0, } } } impl Iterator for PortDevices { type Item = PortDevice; /// Iterator which returns a PortDevice from the set of PortDevices associated with a /// particular PortDevices class (guid). fn next(&mut self) -> Option { let mut port_dev = PortDevice { hdi: self.hdi, devinfo_data: SP_DEVINFO_DATA { cbSize: mem::size_of::() as DWORD, ClassGuid: GUID_NULL, DevInst: 0, Reserved: 0, }, }; let res = unsafe { SetupDiEnumDeviceInfo(self.hdi, self.dev_idx, &mut port_dev.devinfo_data) }; if res == FALSE { None } else { self.dev_idx += 1; Some(port_dev) } } } impl Drop for PortDevices { fn drop(&mut self) { // Release the PortDevices object allocated in the constructor. unsafe { SetupDiDestroyDeviceInfoList(self.hdi); } } } struct PortDevice { /// Handle to a device information set. hdi: HDEVINFO, /// Information associated with this device. pub devinfo_data: SP_DEVINFO_DATA, } impl PortDevice { /// Retrieves the device instance id string associated with this device's parent. /// This is useful for determining the serial number of a composite USB device. fn parent_instance_id(&mut self) -> Option { let mut result_buf = [0u16; MAX_PATH]; let mut parent_device_instance_id = 0; let res = unsafe { CM_Get_Parent(&mut parent_device_instance_id, self.devinfo_data.DevInst, 0) }; if res == CR_SUCCESS { let buffer_len = result_buf.len() - 1; let res = unsafe { CM_Get_Device_IDW( parent_device_instance_id, result_buf.as_mut_ptr(), buffer_len as ULONG, 0, ) }; if res == CR_SUCCESS { Some(from_utf16_lossy_trimmed(&result_buf)) } else { None } } else { None } } /// Retrieves the device instance id string associated with this device. Some examples of /// instance id strings are: /// * MicroPython Board: USB\VID_F055&PID_9802\385435603432 /// * FTDI USB Adapter: FTDIBUS\VID_0403+PID_6001+A702TB52A\0000 /// * Black Magic Probe (Composite device with 2 UARTS): /// * GDB Port: USB\VID_1D50&PID_6018&MI_00\6&A694CA9&0&0000 /// * UART Port: USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0002 /// /// Reference: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/device-instance-ids fn instance_id(&mut self) -> Option { let mut result_buf = [0u16; MAX_DEVICE_ID_LEN]; let working_buffer_len = result_buf.len() - 1; // always null terminated let mut desired_result_len = 0; // possibly larger than the buffer let res = unsafe { SetupDiGetDeviceInstanceIdW( self.hdi, &mut self.devinfo_data, result_buf.as_mut_ptr(), working_buffer_len as DWORD, &mut desired_result_len, ) }; if res == FALSE { // Try to retrieve hardware id property. self.property(SPDRP_HARDWAREID) } else { let actual_result_len = working_buffer_len.min(desired_result_len as usize); Some(from_utf16_lossy_trimmed(&result_buf[..actual_result_len])) } } /// Retrieves the problem status of this device. For example, `CM_PROB_DISABLED` indicates /// the device has been disabled in Device Manager. fn problem(&mut self) -> Option { let mut status = 0; let mut problem_number = 0; let res = unsafe { CM_Get_DevNode_Status( &mut status, &mut problem_number, self.devinfo_data.DevInst, 0, ) }; if res == CR_SUCCESS { Some(problem_number) } else { None } } // Retrieves the port name (i.e. COM6) associated with this device. pub fn name(&mut self) -> String { // https://learn.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdiopendevregkey let hkey = unsafe { SetupDiOpenDevRegKey( self.hdi, &mut self.devinfo_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ, ) }; if hkey as *mut c_void == winapi::um::handleapi::INVALID_HANDLE_VALUE { // failed to open registry key. Return empty string as the failure case return String::new(); } // https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexw let mut port_name_buffer = [0u16; MAX_PATH]; let buffer_byte_len = 2 * port_name_buffer.len() as DWORD; let mut byte_len = buffer_byte_len; let mut value_type = 0; let value_name = as_utf16("PortName"); let err = unsafe { RegQueryValueExW( hkey, value_name.as_ptr(), ptr::null_mut(), &mut value_type, port_name_buffer.as_mut_ptr() as *mut u8, &mut byte_len, ) }; unsafe { RegCloseKey(hkey) }; if FAILED(err) { // failed to query registry for some reason. Return empty string as the failure case return String::new(); } // https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types if value_type != REG_SZ || byte_len % 2 != 0 || byte_len > buffer_byte_len { // read something but it wasn't the expected registry type return String::new(); } // len of u16 chars, not bytes let len = buffer_byte_len as usize / 2; let port_name = &port_name_buffer[0..len]; from_utf16_lossy_trimmed(port_name) } // Determines the port_type for this device, and if it's a USB port populate the various fields. pub fn port_type(&mut self) -> SerialPortType { self.instance_id() .map(|s| (s, self.parent_instance_id())) // Get parent instance id if it exists. .and_then(|(d, p)| parse_usb_port_info(&d, p.as_deref())) .map(|mut info: UsbPortInfo| { info.manufacturer = self.property(SPDRP_MFG); info.product = self.property(SPDRP_FRIENDLYNAME); SerialPortType::UsbPort(info) }) .unwrap_or(SerialPortType::Unknown) } // Retrieves a device property and returns it, if it exists. Returns None if the property // doesn't exist. fn property(&mut self, property_id: DWORD) -> Option { let mut value_type = 0; let mut property_buf = [0u16; MAX_PATH]; let res = unsafe { SetupDiGetDeviceRegistryPropertyW( self.hdi, &mut self.devinfo_data, property_id, &mut value_type, property_buf.as_mut_ptr() as PBYTE, property_buf.len() as DWORD, ptr::null_mut(), ) }; if res == FALSE || value_type != REG_SZ { return None; } // Using the unicode version of 'SetupDiGetDeviceRegistryProperty' seems to report the // entire mfg registry string. This typically includes some driver information that we should discard. // Example string: 'FTDI5.inf,%ftdi%;FTDI' from_utf16_lossy_trimmed(&property_buf) .split(';') .last() .map(str::to_string) } } /// Not all COM ports are listed under the "Ports" device class /// The full list of COM ports is available from the registry at /// HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM /// /// port of https://learn.microsoft.com/en-us/windows/win32/sysinfo/enumerating-registry-subkeys fn get_registry_com_ports() -> HashSet { let mut ports_list = HashSet::new(); let reg_key = as_utf16("HARDWARE\\DEVICEMAP\\SERIALCOMM"); let key_ptr = reg_key.as_ptr(); let mut ports_key = std::ptr::null_mut(); // SAFETY: ffi, all inputs are correct let open_res = unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_ptr, 0, KEY_READ, &mut ports_key) }; if SUCCEEDED(open_res) { let mut class_name_buff = [0u16; MAX_PATH]; let mut class_name_size = MAX_PATH as u32; let mut sub_key_count = 0; let mut largest_sub_key = 0; let mut largest_class_string = 0; let mut num_key_values = 0; let mut longest_value_name = 0; let mut longest_value_data = 0; let mut size_security_desc = 0; let mut last_write_time = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0, }; // SAFETY: ffi, all inputs are correct let query_res = unsafe { RegQueryInfoKeyW( ports_key, class_name_buff.as_mut_ptr(), &mut class_name_size, std::ptr::null_mut(), &mut sub_key_count, &mut largest_sub_key, &mut largest_class_string, &mut num_key_values, &mut longest_value_name, &mut longest_value_data, &mut size_security_desc, &mut last_write_time, ) }; if SUCCEEDED(query_res) { for idx in 0..num_key_values { let mut val_name_buff = [0u16; MAX_PATH]; let mut val_name_size = MAX_PATH as u32; let mut value_type = 0; let mut val_data = [0u16; MAX_PATH]; let buffer_byte_len = 2 * val_data.len() as DWORD; // len doubled let mut byte_len = buffer_byte_len; // SAFETY: ffi, all inputs are correct let res = unsafe { RegEnumValueW( ports_key, idx, val_name_buff.as_mut_ptr(), &mut val_name_size, std::ptr::null_mut(), &mut value_type, val_data.as_mut_ptr() as *mut u8, &mut byte_len, ) }; if FAILED(res) || value_type != REG_SZ // only valid for text values || byte_len % 2 != 0 // out byte len should be a multiple of u16 size || byte_len > buffer_byte_len { break; } // key data is returned as u16 // SAFETY: data_size is checked and pointer is valid let val_data = from_utf16_lossy_trimmed(unsafe { let utf16_len = byte_len / 2; // utf16 len std::slice::from_raw_parts(val_data.as_ptr(), utf16_len as usize) }); ports_list.insert(val_data); } } // SAFETY: ffi, all inputs are correct unsafe { RegCloseKey(ports_key) }; } ports_list } /// List available serial ports on the system. pub fn available_ports() -> Result> { let mut ports = Vec::new(); for guid in get_ports_guids()? { let port_devices = PortDevices::new(&guid); for mut port_device in port_devices { // Ignore nonfunctional devices if port_device.problem() != Some(0) { continue; } let port_name = port_device.name(); debug_assert!( port_name.as_bytes().last().map_or(true, |c| *c != b'\0'), "port_name has a trailing nul: {:?}", port_name ); // This technique also returns parallel ports, so we filter these out. if port_name.starts_with("LPT") { continue; } ports.push(SerialPortInfo { port_name, port_type: port_device.port_type(), }); } } // ports identified through the registry have no additional information let mut raw_ports_set = get_registry_com_ports(); if raw_ports_set.len() > ports.len() { // remove any duplicates. HashSet makes this relatively cheap for port in ports.iter() { raw_ports_set.remove(&port.port_name); } // add remaining ports as "unknown" type for raw_port in raw_ports_set { ports.push(SerialPortInfo { port_name: raw_port, port_type: SerialPortType::Unknown, }) } } Ok(ports) } #[cfg(test)] mod tests { use super::*; use quickcheck_macros::quickcheck; #[test] fn from_utf16_lossy_trimmed_trimming_empty() { assert_eq!("", from_utf16_lossy_trimmed(&[])); assert_eq!("", from_utf16_lossy_trimmed(&[0])); } #[test] fn from_utf16_lossy_trimmed_trimming() { let test_str = "Testing"; let wtest_str: Vec = as_utf16(test_str); let wtest_str_trailing = wtest_str .iter() .copied() .chain([0, 0, 0, 0]) // add some null chars .collect::>(); let and_back = from_utf16_lossy_trimmed(&wtest_str_trailing); assert_eq!(test_str, and_back); } // Check that passing some random data to HwidMatches::new() does not cause a panic. #[quickcheck] fn quickcheck_hwidmatches_new_does_not_panic_from_random_input(hwid: String) -> bool { let _ = HwidMatches::new(&hwid); true } // Corner cases which might not always represent what we want to/should parse. But they at // least illustrate how we are parsing device identification strings today. #[test] fn test_hwidmatches_new_corner_cases() { assert!(HwidMatches::new("").is_none()); assert!(HwidMatches::new("ROOT").is_none()); assert!(HwidMatches::new("ROOT\\").is_none()); assert!(HwidMatches::new("USB\\").is_none()); assert!(HwidMatches::new("USB\\VID_1234").is_none()); assert!(HwidMatches::new("USB\\PID_1234").is_none()); assert!(HwidMatches::new("USB\\MI_12").is_none()); assert_eq!( HwidMatches::new("VID_1234&PID_5678").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: None, interface: None, } ); assert_eq!( HwidMatches::new("ABC\\VID_1234&PID_5678&MI_90").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: None, interface: Some("90"), } ); assert_eq!( HwidMatches::new("FTDIBUS\\VID_1234&PID_5678&MI_90").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: None, interface: Some("90"), } ); assert_eq!( HwidMatches::new("USB\\VID_1234+PID_5678+MI_90").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: None, interface: Some("90"), } ); assert_eq!( HwidMatches::new("FTDIBUS\\VID_1234+PID_5678\\0000").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: Some("0000"), interface: None, } ); } #[test] fn test_hwidmatches_new_standard_cases_ftdi() { assert_eq!( HwidMatches::new("FTDIBUS\\VID_1234+PID_5678+SERIAL123\\0000").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: Some("SERIAL123"), interface: None, } ); } #[test] fn test_hwidmatches_new_standard_cases_usb() { assert_eq!( HwidMatches::new("USB\\VID_1234&PID_5678").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: None, interface: None, } ); assert_eq!( HwidMatches::new("USB\\VID_1234&PID_5678&MI_90").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: None, interface: Some("90"), } ); assert_eq!( HwidMatches::new("USB\\VID_1234&PID_5678\\SERIAL123").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: Some("SERIAL123"), interface: None, } ); assert_eq!( HwidMatches::new("USB\\VID_1234&PID_5678&MI_90\\SERIAL123").unwrap(), HwidMatches { vid: "1234", pid: "5678", serial: Some("SERIAL123"), interface: Some("90"), } ); } #[test] fn test_parsing_usb_port_information() { let madeup_hwid = r"USB\VID_1D50&PID_6018+6&A694CA9&0&0000"; let info = parse_usb_port_info(madeup_hwid, None).unwrap(); // TODO: Fix returning no serial at all for devices without one. See issue #203. assert_eq!(info.serial_number, Some("6".to_string())); let bm_uart_hwid = r"USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0000"; let bm_parent_hwid = r"USB\VID_1D50&PID_6018\85A12F01"; let info = parse_usb_port_info(bm_uart_hwid, Some(bm_parent_hwid)).unwrap(); assert_eq!(info.vid, 0x1D50); assert_eq!(info.pid, 0x6018); assert_eq!(info.serial_number, Some("85A12F01".to_string())); #[cfg(feature = "usbportinfo-interface")] assert_eq!(info.interface, Some(2)); let ftdi_serial_hwid = r"FTDIBUS\VID_0403+PID_6001+A702TB52A\0000"; let info = parse_usb_port_info(ftdi_serial_hwid, None).unwrap(); assert_eq!(info.vid, 0x0403); assert_eq!(info.pid, 0x6001); assert_eq!(info.serial_number, Some("A702TB52A".to_string())); #[cfg(feature = "usbportinfo-interface")] assert_eq!(info.interface, None); let pyboard_hwid = r"USB\VID_F055&PID_9802\385435603432"; let info = parse_usb_port_info(pyboard_hwid, None).unwrap(); assert_eq!(info.vid, 0xF055); assert_eq!(info.pid, 0x9802); assert_eq!(info.serial_number, Some("385435603432".to_string())); #[cfg(feature = "usbportinfo-interface")] assert_eq!(info.interface, None); let unicode_serial = r"USB\VID_F055&PID_9802\3854356β03432&test"; let info = parse_usb_port_info(unicode_serial, None).unwrap(); assert_eq!(info.serial_number.as_deref(), Some("3854356β03432")); let unicode_serial = r"USB\VID_F055&PID_9802\3854356β03432"; let info = parse_usb_port_info(unicode_serial, None).unwrap(); assert_eq!(info.serial_number.as_deref(), Some("3854356β03432")); let unicode_serial = r"USB\VID_F055&PID_9802\3854356β"; let info = parse_usb_port_info(unicode_serial, None).unwrap(); assert_eq!(info.serial_number.as_deref(), Some("3854356β")); } } serialport-4.7.0/src/windows/error.rs000064400000000000000000000037231046102023000160050ustar 00000000000000use std::io; use std::ptr; use winapi::shared::minwindef::DWORD; use winapi::shared::winerror::*; use winapi::um::errhandlingapi::GetLastError; use winapi::um::winbase::{ FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, }; use winapi::um::winnt::{LANG_SYSTEM_DEFAULT, MAKELANGID, SUBLANG_SYS_DEFAULT, WCHAR}; use crate::{Error, ErrorKind}; pub fn last_os_error() -> Error { let errno = errno(); let kind = match errno { ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND | ERROR_ACCESS_DENIED => ErrorKind::NoDevice, _ => ErrorKind::Io(io::ErrorKind::Other), }; Error::new(kind, error_string(errno).trim()) } // the rest of this module is borrowed from libstd fn errno() -> u32 { unsafe { GetLastError() } } fn error_string(errnum: u32) -> String { #![allow(non_snake_case)] // This value is calculated from the macro // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) let langId = MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) as DWORD; let mut buf = [0 as WCHAR; 2048]; unsafe { let res = FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, ptr::null_mut(), errnum as DWORD, langId as DWORD, buf.as_mut_ptr(), buf.len() as DWORD, ptr::null_mut(), ); if res == 0 { // Sometimes FormatMessageW can fail e.g. system doesn't like langId, let fm_err = errno(); return format!( "OS Error {} (FormatMessageW() returned error {})", errnum, fm_err ); } let b = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); let msg = String::from_utf16(&buf[..b]); match msg { Ok(msg) => msg, Err(..) => format!( "OS Error {} (FormatMessageW() returned invalid UTF-16)", errnum ), } } } serialport-4.7.0/src/windows/mod.rs000064400000000000000000000001371046102023000154270ustar 00000000000000pub use self::com::*; pub use self::enumerate::*; mod com; mod dcb; mod enumerate; mod error; serialport-4.7.0/tests/config.rs000064400000000000000000000015241046102023000147770ustar 00000000000000// Configuration for integration tests. This crate is about interacting with real serial ports and // so some tests need actual hardware. use envconfig::Envconfig; use rstest::fixture; // Configuration for tests requiring acutual hardware. // // For conveniently pulling this configuration into a test case as a parameter, you might want to // use the test fixture [`hw_config`]. #[derive(Clone, Debug, Envconfig, Eq, PartialEq)] pub struct HardwareConfig { #[envconfig(from = "SERIALPORT_TEST_PORT_1")] pub port_1: String, #[envconfig(from = "SERIALPORT_TEST_PORT_2")] pub port_2: String, } // Test fixture for conveniently pulling the actual hardware configuration into test cases. // // See [`fixture`](rstest::fixture) for details. #[fixture] pub fn hw_config() -> HardwareConfig { HardwareConfig::init_from_env().unwrap() } serialport-4.7.0/tests/test_baudrate.rs000064400000000000000000000127601046102023000163640ustar 00000000000000mod config; use config::{hw_config, HardwareConfig}; use rstest::rstest; use rstest_reuse::{self, apply, template}; use serialport::SerialPort; use std::ops::Range; const RESET_BAUD_RATE: u32 = 300; /// Returs an acceptance interval for the actual baud rate returned from the device after setting /// the supplied value. For example, the CP2102 driver on Linux returns the baud rate actually /// configured a the device rather than the the value set. fn accepted_actual_baud_for(baud: u32) -> Range { let delta = baud / 200; baud.checked_sub(delta).unwrap()..baud.checked_add(delta).unwrap() } fn check_baud_rate(port: &dyn SerialPort, baud: u32) { let accepted = accepted_actual_baud_for(baud); let actual = port.baud_rate().unwrap(); assert!(accepted.contains(&actual)); } #[template] #[rstest] #[case(9600)] #[case(57600)] #[case(115200)] fn standard_baud_rates(#[case] baud: u32) {} #[template] #[rstest] #[case(1000)] #[case(42000)] #[case(100000)] fn non_standard_baud_rates(#[case] baud: u32) {} /// Test cases for setting the baud rate via [`SerialPortBuilder`]. mod builder { use super::*; #[apply(standard_baud_rates)] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_standard_baud_rate(hw_config: HardwareConfig, #[case] baud: u32) { let port = serialport::new(hw_config.port_1, RESET_BAUD_RATE) .baud_rate(baud) .open() .unwrap(); check_baud_rate(port.as_ref(), baud); } #[apply(non_standard_baud_rates)] #[cfg_attr( any( feature = "ignore-hardware-tests", not(all(target_os = "linux", target_env = "musl")), ), ignore )] fn test_non_standard_baud_rate_fails_where_not_supported( hw_config: HardwareConfig, #[case] baud: u32, ) { let res = serialport::new(hw_config.port_1, RESET_BAUD_RATE) .baud_rate(baud) .open(); assert!(res.is_err()); } #[apply(non_standard_baud_rates)] #[cfg_attr( any( feature = "ignore-hardware-tests", all(target_os = "linux", target_env = "musl"), ), ignore )] fn test_non_standard_baud_rate_succeeds_where_supported( hw_config: HardwareConfig, #[case] baud: u32, ) { let port = serialport::new(hw_config.port_1, RESET_BAUD_RATE) .baud_rate(baud) .open() .unwrap(); check_baud_rate(port.as_ref(), baud); } } /// Test cases for setting the baud rate via [`serialport::new`]. mod new { use super::*; #[apply(standard_baud_rates)] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_standard_baud_rate(hw_config: HardwareConfig, #[case] baud: u32) { let port = serialport::new(hw_config.port_1, baud).open().unwrap(); check_baud_rate(port.as_ref(), baud); } #[apply(non_standard_baud_rates)] #[cfg_attr( any( feature = "ignore-hardware-tests", not(all(target_os = "linux", target_env = "musl")), ), ignore )] fn test_non_standard_baud_rate_fails_where_not_supported( hw_config: HardwareConfig, #[case] baud: u32, ) { assert!(serialport::new(hw_config.port_1, baud).open().is_err()); } #[apply(non_standard_baud_rates)] #[cfg_attr( any( feature = "ignore-hardware-tests", all(target_os = "linux", target_env = "musl"), ), ignore )] fn test_non_standard_baud_rate_succeeds_where_supported( hw_config: HardwareConfig, #[case] baud: u32, ) { let port = serialport::new(hw_config.port_1, baud).open().unwrap(); check_baud_rate(port.as_ref(), baud); } } /// Test cases for setting the baud rate via [`SerialPort::set_baud`]. mod set_baud { use super::*; #[apply(standard_baud_rates)] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_standard_baud_rate(hw_config: HardwareConfig, #[case] baud: u32) { let mut port = serialport::new(hw_config.port_1, RESET_BAUD_RATE) .open() .unwrap(); check_baud_rate(port.as_ref(), RESET_BAUD_RATE); port.set_baud_rate(baud).unwrap(); check_baud_rate(port.as_ref(), baud); } #[apply(non_standard_baud_rates)] #[cfg_attr( any( feature = "ignore-hardware-tests", not(all(target_os = "linux", target_env = "musl")), ), ignore )] fn test_non_standard_baud_rate_fails_where_not_supported( hw_config: HardwareConfig, #[case] baud: u32, ) { let mut port = serialport::new(hw_config.port_1, RESET_BAUD_RATE) .open() .unwrap(); check_baud_rate(port.as_ref(), RESET_BAUD_RATE); assert!(port.set_baud_rate(baud).is_err()); check_baud_rate(port.as_ref(), RESET_BAUD_RATE); } #[apply(non_standard_baud_rates)] #[cfg_attr( any( feature = "ignore-hardware-tests", all(target_os = "linux", target_env = "musl"), ), ignore )] fn test_non_standard_baud_rate_succeeds_where_supported( hw_config: HardwareConfig, #[case] baud: u32, ) { let mut port = serialport::new(hw_config.port_1, RESET_BAUD_RATE) .open() .unwrap(); check_baud_rate(port.as_ref(), RESET_BAUD_RATE); port.set_baud_rate(baud).unwrap(); check_baud_rate(port.as_ref(), baud); } } serialport-4.7.0/tests/test_serialport.rs000064400000000000000000000042761046102023000167640ustar 00000000000000//! Tests for the `SerialPort` trait. mod config; use config::{hw_config, HardwareConfig}; use rstest::rstest; use serialport::*; use std::time::Duration; #[rstest] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_listing_ports() { let ports = serialport::available_ports().expect("No ports found!"); for p in ports { println!("{}", p.port_name); } } #[rstest] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_opening_found_ports(hw_config: HardwareConfig) { // There is no guarantee that we even might open the ports returned by `available_ports`. But // the ports we are using for testing shall be among them. let ports = serialport::available_ports().unwrap(); assert!(ports.iter().any(|info| info.port_name == hw_config.port_1)); assert!(ports.iter().any(|info| info.port_name == hw_config.port_2)); } #[rstest] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_opening_port(hw_config: HardwareConfig) { serialport::new(hw_config.port_1, 9600).open().unwrap(); } #[rstest] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_opening_native_port(hw_config: HardwareConfig) { serialport::new(hw_config.port_1, 9600) .open_native() .unwrap(); } #[rstest] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_configuring_ports(hw_config: HardwareConfig) { serialport::new(hw_config.port_1, 9600) .data_bits(DataBits::Five) .flow_control(FlowControl::None) .parity(Parity::None) .stop_bits(StopBits::One) .timeout(Duration::from_millis(1)) .open() .unwrap(); } #[rstest] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_duplicating_port_config(hw_config: HardwareConfig) { let port1_config = serialport::new(hw_config.port_1, 9600) .data_bits(DataBits::Five) .flow_control(FlowControl::None) .parity(Parity::None) .stop_bits(StopBits::One) .timeout(Duration::from_millis(1)); let port2_config = port1_config .clone() .path(hw_config.port_2) .baud_rate(115_200); let _port1 = port1_config.open().unwrap(); let _port1 = port2_config.open().unwrap(); } serialport-4.7.0/tests/test_timeout.rs000064400000000000000000000157451046102023000162710ustar 00000000000000mod config; use config::{hw_config, HardwareConfig}; use rstest::rstest; use serialport::*; use std::io::Read; use std::thread; use std::time::{Duration, Instant}; #[rstest] #[case(1, Vec::from(b"abcdef"))] #[case( 20, Vec::from(b"0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") )] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_read_returns_available_data_before_timeout( hw_config: HardwareConfig, #[case] chunk_size: usize, #[case] message: Vec, ) { let send_period = Duration::from_millis(500); let receive_timeout = Duration::from_millis(3000); let marign = Duration::from_millis(100); let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap(); let mut receiver = serialport::new(hw_config.port_2, 115200) .timeout(receive_timeout) .open() .unwrap(); sender.clear(ClearBuffer::All).unwrap(); receiver.clear(ClearBuffer::All).unwrap(); let expected_message = message.clone(); let receiver_thread = thread::spawn(move || { let chunk_timeout = send_period + marign; assert!(receiver.timeout() > 3 * chunk_timeout); let mut received_message = Vec::with_capacity(expected_message.len()); loop { let chunk_start = Instant::now(); let expected_chunk_until = chunk_start + chunk_timeout; let mut buffer = [0u8; 1024]; assert!(buffer.len() > expected_message.len()); // Try to read more data than we are expecting and expect some data to be available // after the send period (plus some margin). match receiver.read(&mut buffer) { Ok(read) => { assert!(expected_chunk_until > Instant::now()); assert!(read > 0); println!( "receive: {} bytes after waiting {} ms", read, (Instant::now() - chunk_start).as_millis() ); received_message.extend_from_slice(&buffer[..read]); } e => panic!("unexpected error {:?}", e), } if received_message.len() >= expected_message.len() { break; } } assert_eq!(expected_message, received_message); }); let sender_thread = thread::spawn(move || { let mut next = Instant::now(); for chunk in message.chunks(chunk_size) { sender.write_all(chunk).unwrap(); sender.flush().unwrap(); println!("send: {} bytes", chunk.len()); next += send_period; thread::sleep(next - Instant::now()); } }); sender_thread.join().unwrap(); receiver_thread.join().unwrap(); } #[rstest] #[case(b"a")] #[case(b"0123456789")] #[case(b"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_timeout_zero(hw_config: HardwareConfig, #[case] message: &[u8]) { let timeout = Duration::ZERO; let margin = Duration::from_millis(100); let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap(); let mut receiver = serialport::new(hw_config.port_2, 115200) .timeout(timeout) .open() .unwrap(); let mut buffer: [u8; 1024] = [0xff; 1024]; sender.clear(ClearBuffer::All).unwrap(); receiver.clear(ClearBuffer::All).unwrap(); sender.write_all(message).unwrap(); sender.flush().unwrap(); let flushed_at = Instant::now(); let expected_until = flushed_at + timeout + margin; let mut timeouts = 0usize; loop { match receiver.read(&mut buffer) { Ok(read) => { assert!(read > 0); println!( "read: {} bytes of {} after {} timeouts/{} ms", read, message.len(), timeouts, (Instant::now() - flushed_at).as_millis() ); assert_eq!(message[..read], buffer[..read]); break; } Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::TimedOut); timeouts += 1; } } assert!(expected_until > Instant::now()); } } #[rstest] #[case(Duration::from_millis(10))] #[case(Duration::from_millis(100))] #[case(Duration::from_millis(1000))] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_timeout_greater_zero(hw_config: HardwareConfig, #[case] timeout: Duration) { let margin = Duration::from_millis(100); let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap(); let mut receiver = serialport::new(hw_config.port_2, 115200) .timeout(timeout) .open() .unwrap(); let message = b"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; let mut buffer: [u8; 1024] = [0xff; 1024]; sender.clear(ClearBuffer::All).unwrap(); receiver.clear(ClearBuffer::All).unwrap(); sender.write_all(message).unwrap(); sender.flush().unwrap(); let flushed_at = Instant::now(); let read = receiver.read(&mut buffer).unwrap(); let read_at = Instant::now(); println!( "read: {} bytes of {} after {} ms", read, message.len(), (Instant::now() - flushed_at).as_millis() ); assert!(read > 0); assert!(flushed_at + timeout + margin > read_at); assert_eq!(buffer[..read], message[..read]); } /// Checks that reading data with a timeout of `Duration::MAX` returns some data and no error. It /// does not check the actual timeout for obvious reason. #[rstest] #[cfg_attr(feature = "ignore-hardware-tests", ignore)] fn test_timeout_max(hw_config: HardwareConfig) { let sleep = Duration::from_millis(3000); let margin = Duration::from_millis(500); let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap(); let mut receiver = serialport::new(hw_config.port_2, 115200) .timeout(Duration::MAX) .open() .unwrap(); let message = b"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; let mut buffer: [u8; 1024] = [0xff; 1024]; sender.clear(ClearBuffer::All).unwrap(); receiver.clear(ClearBuffer::All).unwrap(); let started_at = Instant::now(); let sender_thread = thread::spawn(move || { thread::sleep(sleep); sender.write_all(message).unwrap(); sender.flush().unwrap(); }); let read = receiver.read(&mut buffer).unwrap(); let read_at = Instant::now(); println!( "read: {} bytes of {} after {} ms", read, message.len(), (Instant::now() - started_at).as_millis() ); assert!(read > 0); assert!(read_at > started_at + sleep); assert!(read_at < started_at + sleep + margin); assert_eq!(buffer[..read], message[..read]); sender_thread.join().unwrap(); } serialport-4.7.0/tests/test_try_clone.rs000064400000000000000000000033461046102023000165730ustar 00000000000000#![cfg(unix)] extern crate serialport; use serialport::{SerialPort, TTYPort}; use std::io::{Read, Write}; // Test that cloning a port works as expected #[test] fn test_try_clone() { let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); // Create the clone in an inner scope to test that dropping a clone doesn't close the original // port { let mut clone = master.try_clone().expect("Failed to clone"); let bytes = [b'a', b'b', b'c', b'd', b'e', b'f']; clone.write_all(&bytes).unwrap(); let mut buffer = [0; 6]; slave.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, [b'a', b'b', b'c', b'd', b'e', b'f']); } // Second try to check that the serial device is not closed { let mut clone = master.try_clone().expect("Failed to clone"); let bytes = [b'g', b'h', b'i', b'j', b'k', b'l']; clone.write_all(&bytes).unwrap(); let mut buffer = [0; 6]; slave.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, [b'g', b'h', b'i', b'j', b'k', b'l']); } } // Test moving a cloned port into a thread #[test] fn test_try_clone_move() { use std::thread; let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); let mut clone = master.try_clone().expect("Failed to clone the slave"); let loopback = thread::spawn(move || { let bytes = [b'a', b'b', b'c', b'd', b'e', b'f']; clone.write_all(&bytes).unwrap(); }); let mut buffer = [0; 6]; slave.read_exact(&mut buffer).unwrap(); assert_eq!(buffer, [b'a', b'b', b'c', b'd', b'e', b'f']); // The thread should have already ended, but we'll make sure here anyways. loopback.join().unwrap(); } serialport-4.7.0/tests/test_tty.rs000064400000000000000000000126341046102023000154150ustar 00000000000000//! Tests for the `posix::TTYPort` struct. #![cfg(unix)] extern crate serialport; use std::io::{Read, Write}; use std::os::unix::prelude::*; use std::str; use std::time::Duration; use serialport::{SerialPort, TTYPort}; #[test] fn test_ttyport_pair() { // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe let (mut master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); master .set_timeout(Duration::from_millis(10)) .expect("Unable to set timeout on the master"); slave .set_timeout(Duration::from_millis(10)) .expect("Unable to set timeout on the slave"); // Test file descriptors. assert!( master.as_raw_fd() > 0, "Invalid file descriptor on master ptty" ); assert!( slave.as_raw_fd() > 0, "Invalid file descriptor on slae ptty" ); assert_ne!( master.as_raw_fd(), slave.as_raw_fd(), "master and slave ptty's share the same file descriptor." ); let msg = "Test Message"; let mut buf = [0u8; 128]; // Write the string on the master let nbytes = master .write(msg.as_bytes()) .expect("Unable to write bytes."); assert_eq!( nbytes, msg.len(), "Write message length differs from sent message." ); // Read it on the slave let nbytes = slave.read(&mut buf).expect("Unable to read bytes."); assert_eq!( nbytes, msg.len(), "Read message length differs from sent message." ); assert_eq!( str::from_utf8(&buf[..nbytes]).unwrap(), msg, "Received message does not match sent" ); } #[test] fn test_ttyport_timeout() { let result = std::sync::Arc::new(std::sync::Mutex::new(None)); let result_thread = result.clone(); std::thread::spawn(move || { // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe let (mut master, _slave) = TTYPort::pair().expect("Unable to create ptty pair"); master.set_timeout(Duration::new(1, 0)).unwrap(); let mut buffer = [0u8]; let read_res = master.read(&mut buffer); *result_thread.lock().unwrap() = Some(read_res); }); std::thread::sleep(std::time::Duration::new(2, 0)); let read_res = result.lock().unwrap(); match *read_res { Some(Ok(_)) => panic!("Received data without sending"), Some(Err(ref e)) => assert_eq!(e.kind(), std::io::ErrorKind::TimedOut), None => panic!("Read did not time out"), } } #[test] #[cfg(any(target_os = "ios", target_os = "macos"))] fn test_osx_pty_pair() { #![allow(unused_variables)] let (mut master, slave) = TTYPort::pair().expect("Unable to create ptty pair"); let (output_sink, output_stream) = std::sync::mpsc::sync_channel(1); let name = slave.name().unwrap(); master.write_all("12".as_bytes()).expect(""); let reader_thread = std::thread::spawn(move || { let mut port = TTYPort::open(&serialport::new(&name, 0)).expect("unable to open"); let mut buffer = [0u8; 2]; let amount = port.read_exact(&mut buffer); output_sink .send(String::from_utf8(buffer.to_vec()).expect("buffer not read as valid utf-8")) .expect("unable to send from thread"); }); reader_thread.join().expect("unable to join sink thread"); assert_eq!(output_stream.recv().unwrap(), "12"); } // On Mac this should work (in fact used to in b77768a) but now fails. It's not functionality that // should be required, and the ptys work otherwise. So going to just disable this test instead. #[test] #[cfg_attr(any(target_os = "ios", target_os = "macos"), ignore)] fn test_ttyport_set_standard_baud() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. // TODO: Convert this to a statement-level attribute once // https://github.com/rust-lang/rust/issues/15701 is on stable. // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe #![allow(unused_variables)] let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); slave.set_baud_rate(9600).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 9600); slave.set_baud_rate(57600).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 57600); slave.set_baud_rate(115_200).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 115_200); } // On mac this fails because you can't set nonstandard baud rates for these virtual ports #[test] #[cfg_attr( any( target_os = "ios", all(target_os = "linux", target_env = "musl"), target_os = "macos" ), ignore )] fn test_ttyport_set_nonstandard_baud() { // `master` must be used here as Dropping it causes slave to be deleted by the OS. // TODO: Convert this to a statement-level attribute once // https://github.com/rust-lang/rust/issues/15701 is on stable. // FIXME: Create a mutex across all tests for using `TTYPort::pair()` as it's not threadsafe #![allow(unused_variables)] let (master, mut slave) = TTYPort::pair().expect("Unable to create ptty pair"); slave.set_baud_rate(10000).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 10000); slave.set_baud_rate(60000).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 60000); slave.set_baud_rate(1_200_000).unwrap(); assert_eq!(slave.baud_rate().unwrap(), 1_200_000); }