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}