serial2_tokio/lib.rs
1//! Serial port communication for [`tokio`] using [`serial2`].
2//!
3//! The `serial2-tokio` crate provides a cross-platform interface to serial ports.
4//! It aims to provide a simpler interface than other alternatives.
5//!
6//! Currently supported features:
7//! * Simple interface: one [`SerialPort`] struct for all supported platforms.
8//! * List available ports.
9//! * Custom baud rates on all supported platforms except Solaris and Illumos.
10//! * Concurrent reads and writes from multiple tasks, even on Windows.
11//! * Purge the OS buffers (useful to discard read noise when the line should have been silent, for example).
12//! * Read and control individual modem status lines to use them as general purpose I/O.
13//! * Cross platform configuration of serial port settings:
14//! * Baud rate
15//! * Character size
16//! * Stop bits
17//! * Parity checks
18//! * Flow control
19//! * Read/write timeouts
20//!
21//! You can open and configure a serial port in one go with [`SerialPort::open()`].
22//! The second argument to `open()` must be a type that implements [`IntoSettings`].
23//! In the simplest case, it is enough to pass a `u32` for the baud rate.
24//! Doing that will also configure a character size of 8 bits with 1 stop bit and disables parity checks and flow control.
25//! For full control over the applied settings, pass a closure that receives the the current [`Settings`] and return the desired settings.
26//! If you do, you will almost always want to call [`Settings::set_raw()`] before changing any other settings.
27//!
28//! The [`SerialPort`] struct implements the standard [`tokio::io::AsyncRead`] and [`tokio::io::AsyncWrite`] traits,
29//! as well as [`read()`][`SerialPort::read()`] and [`write()`][`SerialPort::write()`] functions that take `&self` instead of `&mut self`.
30//! This allows you to use the serial port concurrently from multiple tasks.
31//!
32//! The [`SerialPort::available_ports()`] function can be used to get a list of available serial ports on supported platforms.
33//!
34//! # Example
35//! This example opens a serial port and echoes back everything that is read.
36//!
37//! ```no_run
38//! # async fn example() -> std::io::Result<()> {
39//! use serial2_tokio::SerialPort;
40//!
41//! // On Windows, use something like "COM1" or "COM15".
42//! let port = SerialPort::open("/dev/ttyUSB0", 115200)?;
43//! let mut buffer = [0; 256];
44//! loop {
45//! let read = port.read(&mut buffer).await?;
46//! port.write_all(&buffer[..read]).await?;
47//! }
48//! # }
49//! ```
50
51#![cfg_attr(feature = "doc-cfg", feature(doc_cfg))]
52
53#![warn(missing_docs)]
54#![warn(private_interfaces)]
55#![warn(private_bounds)]
56
57use std::io::{IoSliceMut, IoSlice};
58use std::path::{Path, PathBuf};
59use std::pin::Pin;
60use std::task::Poll;
61
62mod inner;
63
64pub use serial2::{
65 COMMON_BAUD_RATES,
66 CharSize,
67 FlowControl,
68 IntoSettings,
69 KeepSettings,
70 Parity,
71 Settings,
72 StopBits,
73 TryFromError,
74};
75
76#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "rs4xx")))]
77#[cfg(any(feature = "doc", feature = "rs4xx"))]
78pub use serial2::rs4xx;
79
80use tokio::io::{AsyncRead, AsyncWrite};
81
82/// An asynchronous serial port for Tokio.
83pub struct SerialPort {
84 inner: inner::SerialPort,
85}
86
87impl SerialPort {
88 /// Open and configure a serial port by path or name.
89 ///
90 /// On Unix systems, the `name` parameter must be a path to a TTY device.
91 /// On Windows, it must be the name of a COM device, such as COM1, COM2, etc.
92 ///
93 /// The second argument is used to configure the serial port.
94 /// For simple cases, you pass a `u32` for the baud rate.
95 /// See [`IntoSettings`] for more information.
96 ///
97 /// The library automatically uses the win32 device namespace on Windows, so COM ports above COM9 are supported out of the box.
98 ///
99 /// # Example
100 /// ```no_run
101 /// # use serial2::SerialPort;
102 /// # fn main() -> std::io::Result<()> {
103 /// SerialPort::open("/dev/ttyUSB0", 115200)?;
104 /// # Ok(())
105 /// # }
106 /// ```
107 pub fn open(path: impl AsRef<Path>, settings: impl IntoSettings) -> std::io::Result<Self> {
108 let inner = serial2::SerialPort::open(path, settings)?;
109 let inner = inner::SerialPort::wrap(inner)?;
110 Ok(Self {
111 inner,
112 })
113 }
114
115 /// Open a connected pair of pseudo-terminals.
116 #[cfg(any(feature = "doc", all(unix, feature = "unix")))]
117 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "unix")))]
118 pub fn pair() -> std::io::Result<(Self, Self)> {
119 let (a, b) = serial2::SerialPort::pair()?;
120 let a = Self { inner: inner::SerialPort::wrap(a)? };
121 let b = Self { inner: inner::SerialPort::wrap(b)? };
122 Ok((a, b))
123 }
124
125 /// Get a list of available serial ports.
126 ///
127 /// Not currently supported on all platforms.
128 /// On unsupported platforms, this function always returns an error.
129 pub fn available_ports() -> std::io::Result<Vec<PathBuf>> {
130 serial2::SerialPort::available_ports()
131 }
132
133 /// Configure (or reconfigure) the serial port.
134 pub fn set_configuration(&mut self, settings: &Settings) -> std::io::Result<()> {
135 self.inner.with_raw_mut(|raw| raw.set_configuration(settings))
136 }
137
138 /// Get the current configuration of the serial port.
139 ///
140 /// This function can fail if the underlying syscall fails,
141 /// or if the serial port configuration can't be reported using [`Settings`].
142 pub fn get_configuration(&self) -> std::io::Result<Settings> {
143 self.inner.with_raw(|raw| raw.get_configuration())
144 }
145
146 /// Try to clone the serial port handle.
147 ///
148 /// The cloned object refers to the same serial port.
149 ///
150 /// Mixing reads and writes on different handles to the same serial port from different threads may lead to unexpect results.
151 /// The data may end up interleaved in unpredictable ways.
152 pub fn try_clone(&self) -> std::io::Result<Self> {
153 let inner = self.inner.try_clone()?;
154 Ok(Self { inner })
155 }
156
157 /// Read bytes from the serial port.
158 ///
159 /// This is identical to [`AsyncReadExt::read()`][tokio::io::AsyncReadExt::read], except that this function takes a const reference `&self`.
160 /// This allows you to use the serial port concurrently from multiple tasks.
161 ///
162 /// Note that there are no guarantees about which task receives what data when multiple tasks are reading from the serial port.
163 /// You should normally limit yourself to a single reading task and a single writing task.
164 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
165 pub async fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
166 self.inner.read(buf).await
167 }
168
169 /// Read bytes from the serial port into a slice of buffers.
170 ///
171 /// Note that there are no guarantees about which task receives what data when multiple tasks are reading from the serial port.
172 /// You should normally limit yourself to a single reading task and a single writing task.
173 pub async fn read_vectored(&self, buf: &mut [IoSliceMut<'_>]) -> std::io::Result<usize> {
174 self.inner.read_vectored(buf).await
175 }
176
177 /// Check if the implementation supports vectored reads.
178 ///
179 /// If this returns false, then [`Self::read_vectored()`] will only use the first buffer of the given slice.
180 /// All platforms except for Windows support vectored reads.
181 pub fn is_read_vectored(&self) -> bool {
182 self.inner.is_read_vectored()
183 }
184
185 /// Write bytes to the serial port.
186 ///
187 /// This is identical to [`AsyncWriteExt::write()`][tokio::io::AsyncWriteExt::write], except that this function takes a const reference `&self`.
188 /// This allows you to use the serial port concurrently from multiple tasks.
189 ///
190 /// Note that data written to the same serial port from multiple tasks may end up interleaved at the receiving side.
191 /// You should normally limit yourself to a single reading task and a single writing task.
192 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
193 pub async fn write(&self, buf: &[u8]) -> std::io::Result<usize> {
194 self.inner.write(buf).await
195 }
196
197 /// Write all bytes to the serial port.
198 ///
199 /// This will continue to call [`Self::write()`] until the entire buffer has been written,
200 /// or an I/O error occurs.
201 ///
202 /// This is identical to [`AsyncWriteExt::write_all()`][tokio::io::AsyncWriteExt::write_all], except that this function takes a const reference `&self`.
203 /// This allows you to use the serial port concurrently from multiple tasks.
204 ///
205 /// Note that data written to the same serial port from multiple tasks may end up interleaved at the receiving side.
206 /// You should normally limit yourself to a single reading task and a single writing task.
207 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
208 pub async fn write_all(&self, buf: &[u8]) -> std::io::Result<()> {
209 let mut written = 0;
210 while written < buf.len() {
211 written += self.write(&buf[written..]).await?;
212 }
213 Ok(())
214 }
215
216 /// Write bytes to the serial port from a slice of buffers.
217 ///
218 /// This is identical to [`AsyncWriteExt::write_vectored()`][tokio::io::AsyncWriteExt::write_vectored], except that this function takes a const reference `&self`.
219 /// This allows you to use the serial port concurrently from multiple tasks.
220 ///
221 /// Note that data written to the same serial port from multiple tasks may end up interleaved at the receiving side.
222 /// You should normally limit yourself to a single reading task and a single writing task.
223 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
224 pub async fn write_vectored(&self, buf: &[IoSlice<'_>]) -> std::io::Result<usize> {
225 self.inner.write_vectored(buf).await
226 }
227
228 /// Check if the implementation supports vectored writes.
229 ///
230 /// If this returns false, then [`Self::write_vectored()`] will only use the first buffer of the given slice.
231 /// All platforms except for Windows support vectored writes.
232 pub fn is_write_vectored(&self) -> bool {
233 self.inner.is_write_vectored()
234 }
235
236 /// Discard the kernel input and output buffers for the serial port.
237 ///
238 /// When you write to a serial port, the data may be put in a buffer by the OS to be transmitted by the actual device later.
239 /// Similarly, data received on the device can be put in a buffer by the OS untill you read it.
240 /// This function clears both buffers: any untransmitted data and received but unread data is discarded by the OS.
241 pub fn discard_buffers(&self) -> std::io::Result<()> {
242 self.inner.with_raw(|raw| raw.discard_buffers())
243 }
244
245 /// Discard the kernel input buffers for the serial port.
246 ///
247 /// Data received on the device can be put in a buffer by the OS untill you read it.
248 /// This function clears that buffer: received but unread data is discarded by the OS.
249 ///
250 /// This is particularly useful when communicating with a device that only responds to commands that you send to it.
251 /// If you discard the input buffer before sending the command, you discard any noise that may have been received after the last command.
252 pub fn discard_input_buffer(&self) -> std::io::Result<()> {
253 self.inner.with_raw(|raw| raw.discard_input_buffer())
254 }
255
256 /// Discard the kernel output buffers for the serial port.
257 ///
258 /// When you write to a serial port, the data is generally put in a buffer by the OS to be transmitted by the actual device later.
259 /// This function clears that buffer: any untransmitted data is discarded by the OS.
260 pub fn discard_output_buffer(&self) -> std::io::Result<()> {
261 self.inner.with_raw(|raw| raw.discard_input_buffer())
262 }
263
264 /// Set the state of the Ready To Send line.
265 ///
266 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
267 /// The function may fail with an error or it may silently be ignored.
268 /// It may even succeed and interfere with the flow control.
269 pub fn set_rts(&self, state: bool) -> std::io::Result<()> {
270 self.inner.with_raw(|raw| raw.set_rts(state))
271 }
272
273 /// Read the state of the Clear To Send line.
274 ///
275 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
276 /// The function may fail with an error, it may return a bogus value, or it may return the actual state of the CTS line.
277 pub fn read_cts(&self) -> std::io::Result<bool> {
278 self.inner.with_raw(|raw| raw.read_cts())
279 }
280
281 /// Set the state of the Data Terminal Ready line.
282 ///
283 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
284 /// The function may fail with an error or it may silently be ignored.
285 pub fn set_dtr(&self, state: bool) -> std::io::Result<()> {
286 self.inner.with_raw(|raw| raw.set_dtr(state))
287 }
288
289 /// Read the state of the Data Set Ready line.
290 ///
291 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
292 /// The function may fail with an error, it may return a bogus value, or it may return the actual state of the DSR line.
293 pub fn read_dsr(&self) -> std::io::Result<bool> {
294 self.inner.with_raw(|raw| raw.read_dsr())
295 }
296
297 /// Read the state of the Ring Indicator line.
298 ///
299 /// This line is also sometimes also called the RNG or RING line.
300 pub fn read_ri(&self) -> std::io::Result<bool> {
301 self.inner.with_raw(|raw| raw.read_ri())
302 }
303
304 /// Read the state of the Carrier Detect (CD) line.
305 ///
306 /// This line is also called the Data Carrier Detect (DCD) line
307 /// or the Receive Line Signal Detect (RLSD) line.
308 pub fn read_cd(&self) -> std::io::Result<bool> {
309 self.inner.with_raw(|raw| raw.read_cd())
310 }
311
312 /// Set or clear the break state of the serial port.
313 ///
314 /// The serial port will hold the data line in a logical low state while the break state is enabled.
315 /// This can be detected as a break condition on the other side of the line.
316 pub fn set_break(&self, enable: bool) -> std::io::Result<()> {
317 self.inner.with_raw(|raw| raw.set_break(enable))
318 }
319
320 /// Get the RS-4xx mode of the serial port transceiver.
321 ///
322 /// This is currently only supported on Linux.
323 ///
324 /// Not all serial ports can be configured in a different mode by software.
325 /// Some serial ports are always in RS-485 or RS-422 mode,
326 /// and some may have hardware switches or jumpers to configure the transceiver.
327 /// In those cases, this function will usually report an error or [`rs4xx::TransceiverMode::Default`],
328 /// even though the serial port is configured is RS-485 or RS-422 mode.
329 ///
330 /// Note that driver support for this feature is very limited and sometimes inconsistent.
331 /// Please read all the warnings in the [`rs4xx`] module carefully.
332 #[cfg(any(feature = "doc", all(feature = "rs4xx", target_os = "linux")))]
333 #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))]
334 pub fn get_rs4xx_mode(&self) -> std::io::Result<rs4xx::TransceiverMode> {
335 self.inner.with_raw(|raw| raw.get_rs4xx_mode())
336 }
337
338 /// Set the RS-4xx mode of the serial port transceiver.
339 ///
340 /// This is currently only supported on Linux.
341 ///
342 /// Not all serial ports can be configured in a different mode by software.
343 /// Some serial ports are always in RS-485 or RS-422 mode,
344 /// and some may have hardware switches or jumpers to configure the transceiver.
345 /// In that case, this function will usually return an error,
346 /// but the port can still be in RS-485 or RS-422 mode.
347 ///
348 /// Note that driver support for this feature is very limited and sometimes inconsistent.
349 /// Please read all the warnings in the [`rs4xx`] module carefully.
350 #[cfg(any(feature = "doc", all(feature = "rs4xx", target_os = "linux")))]
351 #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))]
352 pub fn set_rs4xx_mode(&self, mode: impl Into<rs4xx::TransceiverMode>) -> std::io::Result<()> {
353 self.inner.with_raw(|raw| raw.set_rs4xx_mode(mode))
354 }
355}
356
357impl AsyncRead for SerialPort {
358 fn poll_read(
359 self: Pin<&mut Self>,
360 cx: &mut std::task::Context<'_>,
361 buf: &mut tokio::io::ReadBuf<'_>,
362 ) -> Poll<std::io::Result<()>> {
363 self.get_mut().inner.poll_read(cx, buf)
364 }
365}
366
367impl AsyncWrite for SerialPort {
368 fn poll_write(
369 self: Pin<&mut Self>,
370 cx: &mut std::task::Context<'_>,
371 buf: &[u8],
372 ) -> Poll<std::io::Result<usize>> {
373 self.get_mut().inner.poll_write(cx, buf)
374 }
375
376 fn poll_write_vectored(
377 self: Pin<&mut Self>,
378 cx: &mut std::task::Context<'_>,
379 bufs: &[IoSlice<'_>],
380 ) -> Poll<Result<usize, std::io::Error>> {
381 self.get_mut().inner.poll_write_vectored(cx, bufs)
382 }
383
384 fn poll_flush(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), std::io::Error>> {
385 // We can't do `tcdrain()` asynchronously :(
386 Poll::Ready(Ok(()))
387 }
388
389 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Result<(), std::io::Error>> {
390 self.get_mut().inner.poll_shutdown(cx)
391 }
392}
393
394impl std::fmt::Debug for SerialPort {
395 #[inline]
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 std::fmt::Debug::fmt(&self.inner, f)
398 }
399}