Skip to main content

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}