diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3da8a5a..c6ac946 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -43,3 +43,38 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --all-targets + + check_configurations: + name: Check codebase + runs-on: ubuntu-latest + env: + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: "-D warnings" + strategy: + matrix: + target: + - "x86_64-unknown-linux-gnu" + - "aarch64-unknown-linux-gnu" + - "x86_64-pc-windows-msvc" + - "x86_64-apple-darwin" + - "x86_64-unknown-freebsd" + - "x86_64-unknown-netbsd" + with-rs4xx: ["", "--features rs4xx"] + with-unix: ["", "--features unix"] + with-windows: ["", "--features windows"] + steps: + - name: Checkout code + uses: actions/checkout@master + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ matrix.target }} + - name: Install toolchain + run: rustup target add ${{ matrix.target }} + - name: Check + run: cargo clippy --workspace --color=always --target ${{ matrix.target }} ${{ matrix.with-rs4xx }} ${{ matrix.with-unix }} ${{ matrix.with-windows }} + - name: Doc + run: cargo doc --workspace --color=always --target ${{ matrix.target }} ${{ matrix.with-rs4xx }} ${{ matrix.with-unix }} ${{ matrix.with-windows }} diff --git a/Cargo.toml b/Cargo.toml index 2365cf7..0497cc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,18 @@ unix = [] # The "windows" features enables windows specific extensions. windows = [] +# The "rs4xx" feature enables RS-485/RS-422 specific extensions on supported platforms. +rs4xx = [] + # Add #[doc(cfg(...))] annotations to platform specific items for better documentation (requires nightly toolchain). doc-cfg = [] serde = ["dep:serde"] +[[example]] +name = "rs485" +required-features = ["rs4xx"] + [dependencies] serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/check-targets b/check-targets index 8a51265..501192f 100755 --- a/check-targets +++ b/check-targets @@ -2,7 +2,6 @@ targets=( x86_64-pc-solaris - x86_64-sun-solaris x86_64-unknown-illumos aarch64-linux-android aarch64-unknown-linux-gnu @@ -24,10 +23,6 @@ targets=( i686-linux-android i686-unknown-linux-gnu i686-unknown-linux-musl - mips-unknown-linux-musl - mips64-unknown-linux-muslabi64 - mips64el-unknown-linux-muslabi64 - mipsel-unknown-linux-musl powerpc-unknown-linux-gnu powerpc64-unknown-linux-gnu powerpc64le-unknown-linux-gnu @@ -55,9 +50,9 @@ for target in "${targets[@]}"; do printf "Checking target: %s\n" "$target" rustup target add "$target" - cargo check --target "$target" + cargo clippy --target "$target" "$@" if (( $? != 0 )); then - printf "cargo check --target %s failed\n" "$target" + printf "cargo clippy --target %s failed\n" "$target" fi echo done diff --git a/examples/rs485.rs b/examples/rs485.rs new file mode 100644 index 0000000..304ff11 --- /dev/null +++ b/examples/rs485.rs @@ -0,0 +1,22 @@ +use serial2::rs4xx::Rs485Config; +use serial2::{SerialPort, Settings}; +use std::time::Duration; + +fn main() -> std::io::Result<()> { + let port_name = "/dev/ttyS5"; + let serial_port = SerialPort::open(port_name, |mut settings: Settings| { + settings.set_raw(); + settings.set_baud_rate(115200)?; + Ok(settings) + })?; + + let mut rs485_config = Rs485Config::new(); + rs485_config.set_bus_termination(true); + rs485_config.set_full_duplex(true); + serial_port.set_rs4xx_mode(&rs485_config.into())?; + + loop { + serial_port.write(b"test").unwrap(); + std::thread::sleep(Duration::from_millis(500)); + } +} diff --git a/src/lib.rs b/src/lib.rs index efd61f2..ca33e8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ //! The second argument to `open()` must be a type that implements [`IntoSettings`]. //! In the simplest case, it is enough to pass a `u32` for the baud rate. //! Doing that will also configure a character size of 8 bits with 1 stop bit and disables parity checks and flow control. -//! For full control over the applied settings, pass a closure that receives the the current [`Settings`] and return the desired settings. +//! For full control over the applied settings, pass a closure that receives the current [`Settings`] and return the desired settings. //! If you do, you will almost always want to call [`Settings::set_raw()`] before changing any other settings. //! //! The standard [`std::io::Read`] and [`std::io::Write`] traits are implemented for [`SerialPort`] and [`&SerialPort`][`SerialPort`]. @@ -70,3 +70,7 @@ mod settings; pub use settings::{CharSize, FlowControl, Parity, Settings, StopBits, TryFromError, COMMON_BAUD_RATES}; pub mod os; + +#[cfg(any(doc, feature = "rs4xx"))] +#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "rs4xx")))] +pub mod rs4xx; diff --git a/src/rs4xx.rs b/src/rs4xx.rs new file mode 100644 index 0000000..0477f54 --- /dev/null +++ b/src/rs4xx.rs @@ -0,0 +1,159 @@ +//! RS-422 and RS-485 configuration. + +use std::time::Duration; + +/// The mode of a transceiver. +/// +/// Some transceivers can be configured in different modes (RS-232, RS-422, RS-485) from software. +/// +/// Warning: kernel and driver support for your serial port may be incomplete or bugged. +/// Unsupported features may silently be ignored by the kernel or your device. +/// If your device seems to misbehave, consult the documentation of your platform and your device driver to see what features it supports. +/// +/// On Linux, also see the kernel documentation about [RS485 Serial Communication](https://www.kernel.org/doc/html/latest/driver-api/serial/serial-rs485.html). +#[derive(Debug, Clone)] +pub enum TransceiverMode { + /// The default mode. + /// + /// If the transceiver supports RS-232 and RS-422 or RS-485, the default mode is usually RS-232. + Default, + + /// RS-422 mode. + /// + /// Supported in the Linux kernel since version 6.8, + /// but at the time of writing there are no device drivers included with the Linux kernel that use it. + /// + /// Note that some device drivers may interpret full duplex RS-485 mode as RS-422 instead. + Rs422, + + /// RS-485 mode. + /// + /// In RS-485 mode, the kernel will automatically set the RTS (request-to-send) signal high before each transmission, + /// and low again after each transmission. + /// + /// For full-duplex (or 4-wire) RS-485 mode, set [`Rs485Config::set_full_duplex()`] to true. + /// Otherwise, the receiver will be disabled during transmissions to avoid reading back your own message. + Rs485(Rs485Config), +} + +/// RS-485 specific configuration options. +/// +/// Note that devices may silently ignore unsupported options instead of raising an error. +#[derive(Debug, Clone, Default)] +pub struct Rs485Config { + /// Enable full-duplex (or 4-wire) RS-485 mode. + /// + /// Enable this if your device is using different twisted pairs for transmitting and receiving data. + /// With this mode enabled, the receiver will be left enabled while transmitting data. + full_duplex: bool, + + /// Some transceivers allow enabling or disabling a termination resistor for the RS-485 bus. + /// + /// If set to true, enable the termination resistor. + /// + /// Note that this option may be silently ignored by devices that do not support it. + terminate_bus: bool, + + /// Time in milliseconds to delay after setting the RTS signal, before starting transmission. + /// + /// May be needed to give some devices on the bus time to activate their receiver. + delay_before_send: Duration, + + /// Time in milliseconds to delay after finishing a transmission, before clearing the RTS signal. + /// + /// May be needed to give some devices on the bus time to fully receive the message before they disable their receiver. + delay_after_send: Duration, + + /// Invert the RTS signal: set it low during transmissions and high after. + invert_rts: bool, +} + +impl Rs485Config { + /// Create a new RS-485 configuration with all options disabled and all delays set to zero. + pub fn new() -> Self { + Self::default() + } + + /// Enable or disable full-duplex (or 4-wire) mode. + /// + /// Enable this if your device is using different twisted pairs for transmitting and receiving data. + /// With this mode enabled, the receiver will be left enabled while transmitting data. + /// + /// Note that this option may be silently ignored by devices that do not support it. + pub fn set_full_duplex(&mut self, enable: bool) { + self.full_duplex = enable; + } + + /// Check if the full-duplex (or 4-wire) mode is enabled. + pub fn get_full_duplex(&self) -> bool { + self.full_duplex + } + + /// Enable or disable the bus termination resistor. + /// + /// Note that this option may be silently ignored by devices that do not support it. + pub fn set_bus_termination(&mut self, enable: bool) { + self.terminate_bus = enable; + } + + /// Check if the bus termination resistor is enabled. + pub fn get_bus_termination(&self) -> bool { + self.terminate_bus + } + + /// Set the time to delay after setting the RTS signal, before starting a transmission. + /// + /// This may be needed to give some devices on the bus time to activate their receiver. + /// + /// The precision will be truncated to whatever the platform supports. + /// On Linux, the delay supports millisecond precision. + /// + /// Note that this option may be silently ignored by devices that do not support it. + pub fn set_delay_before_send(&mut self, delay: Duration) { + self.delay_before_send = delay; + } + + /// Get the delay time after setting the RTS signal, before starting a transmission. + pub fn get_delay_before_send(&self) -> Duration { + self.delay_before_send + } + + /// Set the time to delay after setting the RTS signal, before starting transmission. + /// + /// This may be needed to give some devices on the bus time to fully receive the message before they disable their receiver. + /// + /// The precision will be truncated to whatever the platform supports. + /// On Linux, the delay supports millisecond precision. + /// + /// Note that this option may be silently ignored by devices that do not support it. + pub fn set_delay_after_send(&mut self, delay: Duration) { + self.delay_after_send = delay; + } + + /// Get the delay time after setting the RTS signal, before starting a transmission. + pub fn get_delay_after_send(&self) -> Duration { + self.delay_after_send + } + + /// Set whether to invert the level of the RTS signal. + /// + /// If enabled, the RTS signal will be set low during transmissions and high again after each transmission. + /// + /// Note that this option may be silently ignored by devices that do not support it. + pub fn set_invert_rts(&mut self, invert: bool) { + self.invert_rts = invert; + } + + /// Check if the level of the RTS signal is inverted. + /// + /// If enabled, the RTS signal will be set low during transmissions and high again after each transmission. + pub fn get_invert_rts(&self) -> bool { + self.invert_rts + } +} + +impl From for TransceiverMode { + fn from(other: Rs485Config) -> Self { + Self::Rs485(other) + } +} diff --git a/src/serial_port.rs b/src/serial_port.rs index 1402da8..cf47f04 100644 --- a/src/serial_port.rs +++ b/src/serial_port.rs @@ -4,6 +4,9 @@ use std::time::Duration; use crate::{sys, IntoSettings, Settings}; +#[cfg(any(doc, all(feature = "rs4xx", target_os = "linux")))] +use crate::rs4xx; + /// A serial port. pub struct SerialPort { inner: sys::SerialPort, @@ -351,7 +354,7 @@ impl SerialPort { /// Set the state of the Ready To Send line. /// /// If hardware flow control is enabled on the serial port, it is platform specific what will happen. - /// The function may fail with an error or it may silently be ignored. + /// The function may fail with an error, or it may silently be ignored. /// It may even succeed and interfere with the flow control. pub fn set_rts(&self, state: bool) -> std::io::Result<()> { self.inner.set_rts(state) @@ -368,7 +371,7 @@ impl SerialPort { /// Set the state of the Data Terminal Ready line. /// /// If hardware flow control is enabled on the serial port, it is platform specific what will happen. - /// The function may fail with an error or it may silently be ignored. + /// The function may fail with an error, or it may silently be ignored. pub fn set_dtr(&self, state: bool) -> std::io::Result<()> { self.inner.set_dtr(state) } @@ -395,6 +398,48 @@ impl SerialPort { pub fn read_cd(&self) -> std::io::Result { self.inner.read_cd() } + + /// Get the RS-4xx mode of the serial port transceiver. + /// + /// This is currently only supported on Linux. + /// + /// Not all serial ports can be configured in a different mode by software. + /// Some serial ports are always in RS-485 or RS-422 mode, + /// and some may have hardware switches or jumpers to configure the transceiver. + /// In those cases, this function will usually report an error or [`rs4xx::TransceiverMode::Default`], + /// even though the serial port is configured is RS-485 or RS-422 mode. + /// + /// Note that driver support for this feature is very limited and sometimes inconsistent. + /// Please read all the warnings in the [`rs4xx`] module carefully. + #[cfg(any(doc, all(feature = "rs4xx", target_os = "linux")))] + #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))] + pub fn get_rs4xx_mode(&self) -> std::io::Result { + #[cfg(doc)] + panic!("compiled with cfg(doc)"); + #[cfg(not(doc))] + sys::get_rs4xx_mode(&self.inner) + } + + /// Set the RS-4xx mode of the serial port transceiver. + /// + /// This is currently only supported on Linux. + /// + /// Not all serial ports can be configured in a different mode by software. + /// Some serial ports are always in RS-485 or RS-422 mode, + /// and some may have hardware switches or jumpers to configure the transceiver. + /// In that case, this function will usually return an error, + /// but the port can still be in RS-485 or RS-422 mode. + /// + /// Note that driver support for this feature is very limited and sometimes inconsistent. + /// Please read all the warnings in the [`rs4xx`] module carefully. + #[cfg(any(doc, all(feature = "rs4xx", target_os = "linux")))] + #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))] + pub fn set_rs4xx_mode(&self, mode: impl Into) -> std::io::Result<()> { + #[cfg(doc)] + panic!("compiled with cfg(doc)"); + #[cfg(not(doc))] + sys::set_rs4xx_mode(&self.inner, &mode.into()) + } } impl std::io::Read for SerialPort { @@ -456,7 +501,7 @@ impl From for std::os::unix::io::OwnedFd { impl From for SerialPort { fn from(value: std::os::unix::io::OwnedFd) -> Self { Self { - inner: sys::SerialPort::from_file(value.into()) + inner: sys::SerialPort::from_file(value.into()), } } } @@ -506,7 +551,7 @@ impl From for std::os::windows::io::OwnedHandle { impl From for SerialPort { fn from(value: std::os::windows::io::OwnedHandle) -> Self { Self { - inner: sys::SerialPort::from_file(value.into()) + inner: sys::SerialPort::from_file(value.into()), } } } @@ -532,7 +577,6 @@ impl std::os::windows::io::IntoRawHandle for SerialPort { } } - /// Convert an [`RawHandle`][std::os::windows::io::RawHandle] into a `SerialPort`. /// /// The file handle must have been created with the `FILE_FLAG_OVERLAPPED` flag for the serial port to function correctly. diff --git a/src/settings.rs b/src/settings.rs index bbded37..bd403ad 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -190,7 +190,6 @@ delegate_impl!(TryFrom for StopBits as u8); delegate_impl!(TryFrom for StopBits as u8); delegate_impl!(TryFrom for StopBits as u8); - /// The type of parity check for a serial port. /// ///
@@ -389,7 +388,7 @@ impl Settings { /// Set the baud rate to be configured. /// - /// This function returns an error if the platform does not support the requested band-width. + /// This function returns an error if the platform does not support the requested bandwidth. /// Note that the device itself may also not support the requested baud rate, even if the platform does. /// In that case [`SerialPort::set_configuration()`][crate::SerialPort::set_configuration] will return an error. pub fn set_baud_rate(&mut self, baud_rate: u32) -> std::io::Result<()> { @@ -449,7 +448,7 @@ impl Settings { /// On other Unix platforms, this is a `termios` struct. /// /// You can use this function to access Unix specific features of the serial port. - /// You code will not be cross platform anymore if you use this. + /// Your code will not be cross-platform anymore if you use this. #[cfg(any(doc, all(unix, feature = "unix")))] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "unix")))] pub fn as_termios(&self) -> &crate::os::unix::RawTermios { @@ -462,7 +461,7 @@ impl Settings { /// On other Unix platforms, this is a `termios` struct. /// /// You can use this function to access Unix specific features of the serial port. - /// You code will not be cross platform anymore if you use this. + /// Your code will not be cross-platform anymore if you use this. #[cfg(any(doc, all(unix, feature = "unix")))] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "unix")))] pub fn as_termios_mut(&mut self) -> &mut crate::os::unix::RawTermios { @@ -472,7 +471,7 @@ impl Settings { /// Get a reference to the raw `DCB` struct. /// /// You can use this function to access Windows specific features of the serial port. - /// You code will not be cross platform anymore if you use this. + /// Your code will not be cross-platform anymore if you use this. #[cfg(any(doc, all(windows, feature = "windows")))] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "windows")))] pub fn as_raw_dbc(&self) -> &crate::os::windows::DCB { @@ -482,7 +481,7 @@ impl Settings { /// Get a mutable reference to the raw `DCB` struct. /// /// You can use this function to access Windows specific features of the serial port. - /// You code will not be cross platform anymore if you use this. + /// Your code will not be cross-platform anymore if you use this. #[cfg(any(doc, all(windows, feature = "windows")))] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "windows")))] pub fn as_raw_dbc_mut(&mut self) -> &mut crate::os::windows::DCB { @@ -515,16 +514,14 @@ impl<'de> serde::Deserialize<'de> for CharSize { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = CharSize; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str(Self::Value::EXPECTED) } fn visit_u64(self, data: u64) -> Result { Self::Value::try_from(data) - .map_err(|e| E::invalid_value( - serde::de::Unexpected::Unsigned(e.unexpected), - &e.expected - )) + .map_err(|e| E::invalid_value(serde::de::Unexpected::Unsigned(e.unexpected), &e.expected)) } } @@ -545,16 +542,14 @@ impl<'de> serde::Deserialize<'de> for StopBits { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = StopBits; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str(Self::Value::EXPECTED) } fn visit_u64(self, data: u64) -> Result { Self::Value::try_from(data) - .map_err(|e| E::invalid_value( - serde::de::Unexpected::Unsigned(e.unexpected), - &e.expected - )) + .map_err(|e| E::invalid_value(serde::de::Unexpected::Unsigned(e.unexpected), &e.expected)) } } @@ -575,16 +570,14 @@ impl<'de> serde::Deserialize<'de> for Parity { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = Parity; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str(Self::Value::EXPECTED) } fn visit_str(self, data: &str) -> Result { Self::Value::try_from(data) - .map_err(|e| E::invalid_value( - serde::de::Unexpected::Str(e.unexpected), - &e.expected, - )) + .map_err(|e| E::invalid_value(serde::de::Unexpected::Str(e.unexpected), &e.expected)) } } @@ -605,16 +598,14 @@ impl<'de> serde::Deserialize<'de> for FlowControl { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = FlowControl; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str(Self::Value::EXPECTED) } fn visit_str(self, data: &str) -> Result { Self::Value::try_from(data) - .map_err(|e| E::invalid_value( - serde::de::Unexpected::Str(e.unexpected), - &e.expected, - )) + .map_err(|e| E::invalid_value(serde::de::Unexpected::Str(e.unexpected), &e.expected)) } } @@ -643,7 +634,7 @@ impl TryFromError { impl std::fmt::Display for TryFromError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "invalid value: {:?}, expected {}", self.unexpected, self. expected) + write!(f, "invalid value: {:?}, expected {}", self.unexpected, self.expected) } } diff --git a/src/sys/unix/bsd.rs b/src/sys/unix/bsd.rs index a8fbf06..e2a3c24 100644 --- a/src/sys/unix/bsd.rs +++ b/src/sys/unix/bsd.rs @@ -6,7 +6,6 @@ pub fn enumerate() -> std::io::Result> { use std::os::unix::fs::FileTypeExt; let serial_ports = std::fs::read_dir("/dev")? - .into_iter() .filter_map(|entry| { let entry = entry.ok()?; let kind = entry.metadata().ok()?.file_type(); diff --git a/src/sys/unix/linux.rs b/src/sys/unix/linux/mod.rs similarity index 97% rename from src/sys/unix/linux.rs rename to src/sys/unix/linux/mod.rs index 0803fc4..c0ffeba 100644 --- a/src/sys/unix/linux.rs +++ b/src/sys/unix/linux/mod.rs @@ -1,6 +1,12 @@ use cfg_if::cfg_if; use std::path::{Path, PathBuf}; +#[cfg(feature = "rs4xx")] +mod rs4xx; + +#[cfg(feature = "rs4xx")] +pub use rs4xx::*; + cfg_if! { if #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))] { pub const BAUD_RATES: [(u32, u32); 30] = [ diff --git a/src/sys/unix/linux/rs4xx.rs b/src/sys/unix/linux/rs4xx.rs new file mode 100644 index 0000000..13f52d5 --- /dev/null +++ b/src/sys/unix/linux/rs4xx.rs @@ -0,0 +1,162 @@ +use std::os::unix::io::{AsFd, AsRawFd}; +use std::time::Duration; + +use crate::rs4xx::{Rs485Config, TransceiverMode}; +use crate::sys::unix::{check, SerialPort}; + +/// Get the RS-485/422 mode of the serial port transceiver. +/// +/// Driver and device support is very flaky. +/// See all the warnings in the [`crate::rs4xx`] module. +pub fn get_rs4xx_mode(port: &SerialPort) -> std::io::Result { + let config = &SerialRs485::get_from_fd(&port.file)?; + Ok(config.into()) +} + +/// Set the RS-485/422 mode of the serial port transceiver. +/// +/// Driver and device support is very flaky. +/// See all the warnings in the [`crate::rs4xx`] module. +pub fn set_rs4xx_mode(port: &SerialPort, mode: &crate::rs4xx::TransceiverMode) -> std::io::Result<()> { + let config = SerialRs485::from(mode); + config.set_on_fd(&port.file) +} + +#[rustfmt::skip] +#[allow(dead_code)] +mod flags { + pub const SER_RS485_ENABLED: u32 = 1 << 0; + pub const SER_RS485_RTS_ON_SEND: u32 = 1 << 1; + pub const SER_RS485_RTS_AFTER_SEND: u32 = 1 << 2; + pub const SER_RS485_RX_DURING_TX: u32 = 1 << 4; + pub const SER_RS485_TERMINATE_BUS: u32 = 1 << 5; + pub const SER_RS485_ADDRB: u32 = 1 << 6; + pub const SER_RS485_ADDR_RECV: u32 = 1 << 7; + pub const SER_RS485_ADDR_DEST: u32 = 1 << 8; + pub const SER_RS485_MODE_RS422: u32 = 1 << 9; +} + +/// RS485 serial configuration +/// +/// Internally, this structure is the same as a [`struct serial_rs485`] as defined by the Linux kernel. +/// See . +#[derive(Debug, Default, Copy, Clone)] +#[repr(C)] +struct SerialRs485 { + flags: u32, + delay_rts_before_send_ms: u32, + delay_rts_after_send_ms: u32, + addr_recv: u8, + addr_dest: u8, + _padding0: [u8; 2], + _padding1: [u32; 4], +} + +impl SerialRs485 { + /// Create a [`SerialRs485`] struct with the specified flags and otherwise all zeroed. + fn new_with_flags(flags: u32) -> Self { + Self { + flags, + delay_rts_before_send_ms: 0, + delay_rts_after_send_ms: 0, + addr_recv: 0, + addr_dest: 0, + _padding0: [0; 2], + _padding1: [0; 4], + } + } + + /// Create a [`SerialRs485`] struct for RS-422 mode. + fn new_rs422() -> Self { + // Add the RX_DURING_TX_FLAG as fallback for devices that do not support the RS422 flag. + Self::new_with_flags(flags::SER_RS485_ENABLED | flags::SER_RS485_MODE_RS422 | flags::SER_RS485_RX_DURING_TX) + } + + /// Load settings from file descriptor + /// + /// Settings will be loaded from the file descriptor, which must be a + /// valid serial device support RS485 extensions + pub fn get_from_fd(fd: &impl AsFd) -> std::io::Result { + let fd = fd.as_fd().as_raw_fd(); + let mut conf = SerialRs485::new_with_flags(0); + unsafe { + check(libc::ioctl(fd, libc::TIOCGRS485, &mut conf))?; + } + Ok(conf) + } + + /// Apply settings to file descriptor + /// + /// Applies the constructed configuration a raw file-descriptor using + /// `ioctl`. + pub fn set_on_fd(&self, fd: &impl AsFd) -> std::io::Result<()> { + let fd = fd.as_fd().as_raw_fd(); + unsafe { + check(libc::ioctl(fd, libc::TIOCSRS485, self))?; + } + Ok(()) + } +} + +impl From<&'_ TransceiverMode> for SerialRs485 { + fn from(other: &TransceiverMode) -> Self { + match other { + TransceiverMode::Default => Self::new_with_flags(0), + TransceiverMode::Rs422 => Self::new_rs422(), + TransceiverMode::Rs485(config) => config.into(), + } + } +} + +impl From<&'_ Rs485Config> for SerialRs485 { + fn from(config: &Rs485Config) -> Self { + let mut flags = flags::SER_RS485_ENABLED; + if config.get_full_duplex() { + flags |= flags::SER_RS485_RX_DURING_TX; + } + if config.get_bus_termination() { + flags |= flags::SER_RS485_TERMINATE_BUS; + } + if config.get_invert_rts() { + flags |= flags::SER_RS485_RTS_AFTER_SEND; + } else { + flags |= flags::SER_RS485_RTS_ON_SEND; + } + + let delay_rts_before_send_ms = config + .get_delay_before_send() + .as_millis() + .try_into() + .unwrap_or(u32::MAX); + let delay_rts_after_send_ms = config.get_delay_after_send().as_millis().try_into().unwrap_or(u32::MAX); + + Self { + flags, + delay_rts_before_send_ms, + delay_rts_after_send_ms, + addr_recv: 0, + addr_dest: 0, + _padding0: [0; 2], + _padding1: [0; 4], + } + } +} + +impl From<&SerialRs485> for TransceiverMode { + fn from(other: &SerialRs485) -> Self { + if other.flags & flags::SER_RS485_ENABLED == 0 { + return TransceiverMode::Default; + } + if other.flags & flags::SER_RS485_MODE_RS422 != 0 { + return TransceiverMode::Rs422; + } + + let mut config = Rs485Config::new(); + config.set_full_duplex(other.flags & flags::SER_RS485_RX_DURING_TX != 0); + config.set_bus_termination(other.flags & flags::SER_RS485_TERMINATE_BUS != 0); + config.set_invert_rts(other.flags & flags::SER_RS485_RTS_ON_SEND == 0); + config.set_delay_before_send(Duration::from_millis(other.delay_rts_before_send_ms.into())); + config.set_delay_after_send(Duration::from_millis(other.delay_rts_after_send_ms.into())); + TransceiverMode::Rs485(config) + } +} diff --git a/src/sys/unix/mod.rs b/src/sys/unix/mod.rs index 0e4c4ed..b47c6cb 100644 --- a/src/sys/unix/mod.rs +++ b/src/sys/unix/mod.rs @@ -53,7 +53,7 @@ cfg_if! { #[derive(Clone)] pub struct Settings { - pub termios: RawTermios, + pub(crate) termios: RawTermios, } impl Settings { @@ -68,8 +68,8 @@ cfg_if! { fn set_on_file(&self, file: &mut std::fs::File) -> std::io::Result<()> { unsafe { check(libc::ioctl(file.as_raw_fd(), libc::TCSETSW2 as _, &self.termios))?; - Ok(()) } + Ok(()) } } } else { @@ -348,7 +348,7 @@ fn check_isize(ret: isize) -> std::io::Result { } } -/// Create an std::io::Error with custom message. +/// Create a std::io::Error with custom message. fn other_error(msg: E) -> std::io::Error where E: Into>, @@ -358,8 +358,8 @@ where #[cfg(any(doc, all(unix, feature = "unix")))] fn pts_name(master: &SerialPort) -> std::io::Result { - use std::os::unix::ffi::OsStringExt; use std::ffi::OsString; + use std::os::unix::ffi::OsStringExt; cfg_if! { if #[cfg(any( @@ -374,7 +374,7 @@ fn pts_name(master: &SerialPort) -> std::io::Result { unsafe { let name = libc::ptsname(master.file.as_raw_fd()); let name = std::ffi::CStr::from_ptr(name).to_bytes().to_vec(); - return Ok(OsString::from_vec(name).into()) + Ok(OsString::from_vec(name).into()) } } else { let mut name = Vec::with_capacity(256); @@ -402,6 +402,7 @@ fn pts_name(master: &SerialPort) -> std::io::Result { impl Settings { pub fn set_raw(&mut self) { unsafe { + #[allow(clippy::unnecessary_cast)] // not unnecessary for all targets libc::cfmakeraw(&mut self.termios as *mut _ as *mut libc::termios); self.termios.c_iflag |= libc::IGNBRK | libc::IGNPAR; self.termios.c_cc[libc::VMIN] = 1; @@ -467,7 +468,10 @@ impl Settings { ))] { unsafe { - return Ok(libc::cfgetospeed(&self.termios).try_into().unwrap()); + let baud_rate = libc::cfgetospeed(&self.termios); + #[allow(clippy::useless_conversion)] // Not useless on all platforms. + baud_rate.try_into() + .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, format!("baud rate out of range: {} > {}", baud_rate, u32::MAX))) } } else { #[cfg(all( @@ -607,13 +611,17 @@ impl PartialEq for Settings { _ => false, } } else { - let same = true; - let same = same && a.c_cflag == b.c_cflag; - let same = same && a.c_iflag == b.c_iflag; - let same = same && a.c_oflag == b.c_oflag; - let same = same && a.c_lflag == b.c_lflag; - let same = same && a.c_cc == b.c_cc; - same + { + let same = true; + let same = same && a.c_cflag == b.c_cflag; + let same = same && a.c_iflag == b.c_iflag; + let same = same && a.c_oflag == b.c_oflag; + let same = same && a.c_lflag == b.c_lflag; + let same = same && a.c_cc == b.c_cc; + + #[allow(clippy::let_and_return)] + same + } } } } diff --git a/src/sys/unix/solarish.rs b/src/sys/unix/solarish.rs index a6586e5..10ae6de 100644 --- a/src/sys/unix/solarish.rs +++ b/src/sys/unix/solarish.rs @@ -35,8 +35,8 @@ pub fn enumerate() -> std::io::Result> { // https://illumos.org/man/1M/ports // Let's hope Solaris is doing the same. // If only Oracle actually had navigatable documentation. - let cua = std::fs::read_dir("/dev/cua")?.into_iter(); - let term = std::fs::read_dir("/dev/cua")?.into_iter(); + let cua = std::fs::read_dir("/dev/cua")?; + let term = std::fs::read_dir("/dev/cua")?; let serial_ports = cua .chain(term) diff --git a/src/sys/windows/mod.rs b/src/sys/windows/mod.rs index a7b391c..c6806ae 100644 --- a/src/sys/windows/mod.rs +++ b/src/sys/windows/mod.rs @@ -82,7 +82,8 @@ impl SerialPort { // Timeout must be > 0 and < u32::MAX, so clamp it. // For more details, see: // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts#remarks - let timeout_ms = timeout.as_millis() + let timeout_ms = timeout + .as_millis() .try_into() .unwrap_or(u32::MAX) .clamp(1, u32::MAX - 1); @@ -105,10 +106,7 @@ impl SerialPort { pub fn set_write_timeout(&mut self, timeout: Duration) -> std::io::Result<()> { unsafe { let mut timeouts = std::mem::zeroed(); - let timeout_ms = timeout - .as_millis() - .try_into() - .unwrap_or(u32::MAX); + let timeout_ms = timeout.as_millis().try_into().unwrap_or(u32::MAX); check_bool(commapi::GetCommTimeouts(self.file.as_raw_handle(), &mut timeouts))?; timeouts.WriteTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutConstant = timeout_ms; diff --git a/tests/pair.rs b/tests/pair.rs index 749010b..5487c40 100644 --- a/tests/pair.rs +++ b/tests/pair.rs @@ -1,7 +1,7 @@ #![cfg(unix)] -use serial2::SerialPort; use assert2::{assert, let_assert}; +use serial2::SerialPort; #[test] fn open_pair() {