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 /// Get a list of available serial ports.
116 ///
117 /// Not currently supported on all platforms.
118 /// On unsupported platforms, this function always returns an error.
119 pub fn available_ports() -> std::io::Result<Vec<PathBuf>> {
120 serial2::SerialPort::available_ports()
121 }
122
123 /// Configure (or reconfigure) the serial port.
124 pub fn set_configuration(&mut self, settings: &Settings) -> std::io::Result<()> {
125 self.inner.with_raw_mut(|raw| raw.set_configuration(settings))
126 }
127
128 /// Get the current configuration of the serial port.
129 ///
130 /// This function can fail if the underlying syscall fails,
131 /// or if the serial port configuration can't be reported using [`Settings`].
132 pub fn get_configuration(&self) -> std::io::Result<Settings> {
133 self.inner.with_raw(|raw| raw.get_configuration())
134 }
135
136 /// Try to clone the serial port handle.
137 ///
138 /// The cloned object refers to the same serial port.
139 ///
140 /// Mixing reads and writes on different handles to the same serial port from different threads may lead to unexpect results.
141 /// The data may end up interleaved in unpredictable ways.
142 pub fn try_clone(&self) -> std::io::Result<Self> {
143 let inner = self.inner.try_clone()?;
144 Ok(Self { inner })
145 }
146
147 /// Read bytes from the serial port.
148 ///
149 /// This is identical to [`AsyncReadExt::read()`][tokio::io::AsyncReadExt::read], except that this function takes a const reference `&self`.
150 /// This allows you to use the serial port concurrently from multiple tasks.
151 ///
152 /// Note that there are no guarantees about which task receives what data when multiple tasks are reading from the serial port.
153 /// You should normally limit yourself to a single reading task and a single writing task.
154 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
155 pub async fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
156 self.inner.read(buf).await
157 }
158
159 /// Read bytes from the serial port into a slice of buffers.
160 ///
161 /// Note that there are no guarantees about which task receives what data when multiple tasks are reading from the serial port.
162 /// You should normally limit yourself to a single reading task and a single writing task.
163 pub async fn read_vectored(&self, buf: &mut [IoSliceMut<'_>]) -> std::io::Result<usize> {
164 self.inner.read_vectored(buf).await
165 }
166
167 /// Check if the implementation supports vectored reads.
168 ///
169 /// If this returns false, then [`Self::read_vectored()`] will only use the first buffer of the given slice.
170 /// All platforms except for Windows support vectored reads.
171 pub fn is_read_vectored(&self) -> bool {
172 self.inner.is_read_vectored()
173 }
174
175 /// Write bytes to the serial port.
176 ///
177 /// This is identical to [`AsyncWriteExt::write()`][tokio::io::AsyncWriteExt::write], except that this function takes a const reference `&self`.
178 /// This allows you to use the serial port concurrently from multiple tasks.
179 ///
180 /// Note that data written to the same serial port from multiple tasks may end up interleaved at the receiving side.
181 /// You should normally limit yourself to a single reading task and a single writing task.
182 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
183 pub async fn write(&self, buf: &[u8]) -> std::io::Result<usize> {
184 self.inner.write(buf).await
185 }
186
187 /// Write all bytes to the serial port.
188 ///
189 /// This will continue to call [`Self::write()`] until the entire buffer has been written,
190 /// or an I/O error occurs.
191 ///
192 /// This is identical to [`AsyncWriteExt::write_all()`][tokio::io::AsyncWriteExt::write_all], except that this function takes a const reference `&self`.
193 /// This allows you to use the serial port concurrently from multiple tasks.
194 ///
195 /// Note that data written to the same serial port from multiple tasks may end up interleaved at the receiving side.
196 /// You should normally limit yourself to a single reading task and a single writing task.
197 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
198 pub async fn write_all(&self, buf: &[u8]) -> std::io::Result<()> {
199 let mut written = 0;
200 while written < buf.len() {
201 written += self.write(&buf[written..]).await?;
202 }
203 Ok(())
204 }
205
206 /// Write bytes to the serial port from a slice of buffers.
207 ///
208 /// This is identical to [`AsyncWriteExt::write_vectored()`][tokio::io::AsyncWriteExt::write_vectored], except that this function takes a const reference `&self`.
209 /// This allows you to use the serial port concurrently from multiple tasks.
210 ///
211 /// Note that data written to the same serial port from multiple tasks may end up interleaved at the receiving side.
212 /// You should normally limit yourself to a single reading task and a single writing task.
213 #[cfg_attr(not(feature = "doc"), allow(rustdoc::broken_intra_doc_links))]
214 pub async fn write_vectored(&self, buf: &[IoSlice<'_>]) -> std::io::Result<usize> {
215 self.inner.write_vectored(buf).await
216 }
217
218 /// Check if the implementation supports vectored writes.
219 ///
220 /// If this returns false, then [`Self::write_vectored()`] will only use the first buffer of the given slice.
221 /// All platforms except for Windows support vectored writes.
222 pub fn is_write_vectored(&self) -> bool {
223 self.inner.is_write_vectored()
224 }
225
226 /// Discard the kernel input and output buffers for the serial port.
227 ///
228 /// 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.
229 /// Similarly, data received on the device can be put in a buffer by the OS untill you read it.
230 /// This function clears both buffers: any untransmitted data and received but unread data is discarded by the OS.
231 pub fn discard_buffers(&self) -> std::io::Result<()> {
232 self.inner.with_raw(|raw| raw.discard_buffers())
233 }
234
235 /// Discard the kernel input buffers for the serial port.
236 ///
237 /// Data received on the device can be put in a buffer by the OS untill you read it.
238 /// This function clears that buffer: received but unread data is discarded by the OS.
239 ///
240 /// This is particularly useful when communicating with a device that only responds to commands that you send to it.
241 /// If you discard the input buffer before sending the command, you discard any noise that may have been received after the last command.
242 pub fn discard_input_buffer(&self) -> std::io::Result<()> {
243 self.inner.with_raw(|raw| raw.discard_input_buffer())
244 }
245
246 /// Discard the kernel output buffers for the serial port.
247 ///
248 /// 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.
249 /// This function clears that buffer: any untransmitted data is discarded by the OS.
250 pub fn discard_output_buffer(&self) -> std::io::Result<()> {
251 self.inner.with_raw(|raw| raw.discard_input_buffer())
252 }
253
254 /// Set the state of the Ready To Send line.
255 ///
256 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
257 /// The function may fail with an error or it may silently be ignored.
258 /// It may even succeed and interfere with the flow control.
259 pub fn set_rts(&self, state: bool) -> std::io::Result<()> {
260 self.inner.with_raw(|raw| raw.set_rts(state))
261 }
262
263 /// Read the state of the Clear To Send line.
264 ///
265 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
266 /// The function may fail with an error, it may return a bogus value, or it may return the actual state of the CTS line.
267 pub fn read_cts(&self) -> std::io::Result<bool> {
268 self.inner.with_raw(|raw| raw.read_cts())
269 }
270
271 /// Set the state of the Data Terminal Ready line.
272 ///
273 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
274 /// The function may fail with an error or it may silently be ignored.
275 pub fn set_dtr(&self, state: bool) -> std::io::Result<()> {
276 self.inner.with_raw(|raw| raw.set_dtr(state))
277 }
278
279 /// Read the state of the Data Set Ready line.
280 ///
281 /// If hardware flow control is enabled on the serial port, it is platform specific what will happen.
282 /// The function may fail with an error, it may return a bogus value, or it may return the actual state of the DSR line.
283 pub fn read_dsr(&self) -> std::io::Result<bool> {
284 self.inner.with_raw(|raw| raw.read_dsr())
285 }
286
287 /// Read the state of the Ring Indicator line.
288 ///
289 /// This line is also sometimes also called the RNG or RING line.
290 pub fn read_ri(&self) -> std::io::Result<bool> {
291 self.inner.with_raw(|raw| raw.read_ri())
292 }
293
294 /// Read the state of the Carrier Detect (CD) line.
295 ///
296 /// This line is also called the Data Carrier Detect (DCD) line
297 /// or the Receive Line Signal Detect (RLSD) line.
298 pub fn read_cd(&self) -> std::io::Result<bool> {
299 self.inner.with_raw(|raw| raw.read_cd())
300 }
301
302 /// Set or clear the break state of the serial port.
303 ///
304 /// The serial port will hold the data line in a logical low state while the break state is enabled.
305 /// This can be detected as a break condition on the other side of the line.
306 pub fn set_break(&self, enable: bool) -> std::io::Result<()> {
307 self.inner.with_raw(|raw| raw.set_break(enable))
308 }
309
310 /// Get the RS-4xx mode of the serial port transceiver.
311 ///
312 /// This is currently only supported on Linux.
313 ///
314 /// Not all serial ports can be configured in a different mode by software.
315 /// Some serial ports are always in RS-485 or RS-422 mode,
316 /// and some may have hardware switches or jumpers to configure the transceiver.
317 /// In those cases, this function will usually report an error or [`rs4xx::TransceiverMode::Default`],
318 /// even though the serial port is configured is RS-485 or RS-422 mode.
319 ///
320 /// Note that driver support for this feature is very limited and sometimes inconsistent.
321 /// Please read all the warnings in the [`rs4xx`] module carefully.
322 #[cfg(any(feature = "doc", all(feature = "rs4xx", target_os = "linux")))]
323 #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))]
324 pub fn get_rs4xx_mode(&self) -> std::io::Result<rs4xx::TransceiverMode> {
325 self.inner.with_raw(|raw| raw.get_rs4xx_mode())
326 }
327
328 /// Set the RS-4xx mode of the serial port transceiver.
329 ///
330 /// This is currently only supported on Linux.
331 ///
332 /// Not all serial ports can be configured in a different mode by software.
333 /// Some serial ports are always in RS-485 or RS-422 mode,
334 /// and some may have hardware switches or jumpers to configure the transceiver.
335 /// In that case, this function will usually return an error,
336 /// but the port can still be in RS-485 or RS-422 mode.
337 ///
338 /// Note that driver support for this feature is very limited and sometimes inconsistent.
339 /// Please read all the warnings in the [`rs4xx`] module carefully.
340 #[cfg(any(feature = "doc", all(feature = "rs4xx", target_os = "linux")))]
341 #[cfg_attr(feature = "doc-cfg", doc(cfg(all(feature = "rs4xx", target_os = "linux"))))]
342 pub fn set_rs4xx_mode(&self, mode: impl Into<rs4xx::TransceiverMode>) -> std::io::Result<()> {
343 self.inner.with_raw(|raw| raw.set_rs4xx_mode(mode))
344 }
345}
346
347impl AsyncRead for SerialPort {
348 fn poll_read(
349 self: Pin<&mut Self>,
350 cx: &mut std::task::Context<'_>,
351 buf: &mut tokio::io::ReadBuf<'_>,
352 ) -> Poll<std::io::Result<()>> {
353 self.get_mut().inner.poll_read(cx, buf)
354 }
355}
356
357impl AsyncWrite for SerialPort {
358 fn poll_write(
359 self: Pin<&mut Self>,
360 cx: &mut std::task::Context<'_>,
361 buf: &[u8],
362 ) -> Poll<std::io::Result<usize>> {
363 self.get_mut().inner.poll_write(cx, buf)
364 }
365
366 fn poll_write_vectored(
367 self: Pin<&mut Self>,
368 cx: &mut std::task::Context<'_>,
369 bufs: &[IoSlice<'_>],
370 ) -> Poll<Result<usize, std::io::Error>> {
371 self.get_mut().inner.poll_write_vectored(cx, bufs)
372 }
373
374 fn poll_flush(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), std::io::Error>> {
375 // We can't do `tcdrain()` asynchronously :(
376 Poll::Ready(Ok(()))
377 }
378
379 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Result<(), std::io::Error>> {
380 self.get_mut().inner.poll_shutdown(cx)
381 }
382}
383
384impl std::fmt::Debug for SerialPort {
385 #[inline]
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 std::fmt::Debug::fmt(&self.inner, f)
388 }
389}