Skip to content

Commit

Permalink
Merge pull request #29 from omelia-iliffe/rs485
Browse files Browse the repository at this point in the history
add rs485 support
  • Loading branch information
de-vri-es authored Apr 23, 2024
2 parents 4abda51 + 61cb642 commit 5e41c45
Show file tree
Hide file tree
Showing 15 changed files with 488 additions and 58 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
9 changes: 2 additions & 7 deletions check-targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

targets=(
x86_64-pc-solaris
x86_64-sun-solaris
x86_64-unknown-illumos
aarch64-linux-android
aarch64-unknown-linux-gnu
Expand All @@ -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
Expand Down Expand Up @@ -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
22 changes: 22 additions & 0 deletions examples/rs485.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down Expand Up @@ -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;
159 changes: 159 additions & 0 deletions src/rs4xx.rs
Original file line number Diff line number Diff line change
@@ -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<Rs485Config> for TransceiverMode {
fn from(other: Rs485Config) -> Self {
Self::Rs485(other)
}
}
54 changes: 49 additions & 5 deletions src/serial_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -395,6 +398,48 @@ impl SerialPort {
pub fn read_cd(&self) -> std::io::Result<bool> {
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<rs4xx::TransceiverMode> {
#[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<rs4xx::TransceiverMode>) -> 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 {
Expand Down Expand Up @@ -456,7 +501,7 @@ impl From<SerialPort> for std::os::unix::io::OwnedFd {
impl From<std::os::unix::io::OwnedFd> 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()),
}
}
}
Expand Down Expand Up @@ -506,7 +551,7 @@ impl From<SerialPort> for std::os::windows::io::OwnedHandle {
impl From<std::os::windows::io::OwnedHandle> 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()),
}
}
}
Expand All @@ -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.
Expand Down
Loading

0 comments on commit 5e41c45

Please sign in to comment.