cat_dev_serial/underlying/
mod.rs

1mod sys;
2
3use crate::underlying::sys::RawSyncSerialPort;
4use std::{
5	io::{
6		Error as IoError, ErrorKind as IoErrorKind, IoSlice, IoSliceMut, Read, Result as IoResult,
7		Write,
8	},
9	path::{Path, PathBuf},
10	time::Duration,
11};
12use valuable::Valuable;
13
14#[cfg(unix)]
15use crate::underlying::sys::DEFAULT_TIMEOUT_MS;
16
17/// A serial port that you can interact with synchronously.
18#[derive(Debug, Valuable)]
19pub struct SyncSerialPort {
20	inner: RawSyncSerialPort,
21}
22
23impl SyncSerialPort {
24	/// Get a list of available serial ports.
25	///
26	/// ## Errors
27	///
28	/// - If the platform is not supported.
29	/// - If we get an error from the OS listing ports.
30	pub fn available_ports() -> IoResult<Vec<PathBuf>> {
31		RawSyncSerialPort::enumerate()
32	}
33
34	/// Open and configure a serial port by path or name.
35	///
36	/// On Unix systems, the `name` parameter must be a path to a TTY device.
37	/// On Windows, it must be the name of a COM device, such as COM1, COM2, etc.
38	///
39	/// The library automatically uses the win32 device namespace on Windows,
40	/// so COM ports above COM9 are supported out of the box.
41	///
42	/// ## Errors
43	///
44	/// If we cannot open the
45	pub fn new(name: impl AsRef<Path>) -> IoResult<Self> {
46		Ok(Self {
47			inner: RawSyncSerialPort::new(name)?,
48		})
49	}
50
51	/// Try to clone the serial port handle.
52	///
53	/// The cloned object refers to the same serial port.
54	///
55	/// Mixing reads and writes on different handles to the same serial port
56	/// from different threads may lead to unexpect results. The data may end
57	/// up interleaved in unpredictable ways.
58	///
59	/// ## Errors
60	///
61	/// If we cannot clone the underlying file descriptor/handle.
62	pub fn try_clone(&self) -> IoResult<Self> {
63		Ok(Self {
64			inner: self.inner.try_clone()?,
65		})
66	}
67
68	/// Read bytes from the serial port.
69	///
70	/// This is identical to [`std::io::Read::read()`], except that this function
71	/// takes a const reference `&self`. This allows you to use the serial port
72	/// concurrently from multiple threads.
73	///
74	/// Note that there are no guarantees on which thread receives what data
75	/// when multiple threads are reading from the serial port. You should
76	/// normally limit yourself to a single reading thread and a single
77	/// writing thread.
78	///
79	/// ## Errors
80	///
81	/// If we get an error back from the OS.
82	pub fn read(&self, buff: &mut [u8]) -> IoResult<usize> {
83		self.inner.read(buff)
84	}
85
86	/// Read the exact number of bytes required to fill the buffer from the
87	/// serial port.
88	///
89	/// This will repeatedly call `read()` until the entire buffer is filled.
90	/// Errors of the type [`std::io::ErrorKind::Interrupted`] are silently
91	/// ignored. Any other errors (including timeouts) will be returned
92	/// immediately.
93	///
94	/// If this function returns an error, it may already have read some data
95	/// from the serial port into the provided buffer.
96	///
97	/// This function is identical to [`std::io::Read::read_exact()`], except
98	/// that this function takes a const reference `&self`. This allows you to
99	/// use the serial port concurrently from multiple threads.
100	///
101	/// Note that there are no guarantees on which thread receives what data when
102	/// multiple threads are reading from the serial port. You should normally
103	/// limit yourself to a single reading thread and a single writing thread.
104	///
105	/// ## Errors
106	///
107	/// If we get an error back from the OS.
108	pub fn read_exact(&self, buff: &mut [u8]) -> IoResult<()> {
109		let mut working_buff = buff;
110
111		while !working_buff.is_empty() {
112			match self.read(working_buff) {
113				Ok(0) => {
114					return Err(IoError::new(
115						IoErrorKind::UnexpectedEof,
116						"Failed to fill whole buffer",
117					));
118				}
119				Ok(read) => working_buff = &mut working_buff[read..],
120				Err(cause) => {
121					if cause.kind() == IoErrorKind::Interrupted {
122						continue;
123					}
124
125					return Err(cause);
126				}
127			}
128		}
129
130		Ok(())
131	}
132
133	/// If this implementation supports vectored reads.
134	#[must_use]
135	pub const fn can_read_vectored() -> bool {
136		RawSyncSerialPort::can_read_vectored()
137	}
138
139	/// Read bytes from the serial port into a slice of buffers.
140	///
141	/// This is identical to [`std::io::Read::read_vectored()`], except that this
142	/// function takes a const reference `&self`. This allows you to use the
143	/// serial port concurrently from multiple threads.
144	///
145	/// Note that there are no guarantees on which thread receives what data when
146	/// multiple threads are reading from the serial port. You should normally
147	/// limit yourself to a single reading thread and a single writing thread.
148	///
149	/// ## Errors
150	///
151	/// If we get an error back from the OS.
152	pub fn read_vectored(&self, buff: &mut [IoSliceMut<'_>]) -> IoResult<usize> {
153		self.inner.read_vectored(buff)
154	}
155
156	/// Write bytes to the serial port.
157	///
158	/// This is identical to [`std::io::Write::write()`], except that this
159	/// function takes a const reference `&self`. This allows you to use the
160	/// serial port concurrently from multiple threads.
161	///
162	/// Note that data written to the same serial port from multiple threads may
163	/// end up interleaved at the receiving side. You should normally limit
164	/// yourself to a single reading thread and a single writing thread.
165	///
166	/// ## Errors
167	///
168	/// If we get an error back from the OS.
169	pub fn write(&self, buff: &[u8]) -> IoResult<usize> {
170		self.inner.write(buff)
171	}
172
173	/// Write all bytes to the serial port.
174	///
175	/// This will continue to call [`Self::write()`] until the entire buffer has
176	/// been written, or an I/O error occurs.
177	///
178	/// This is identical to [`std::io::Write::write_all()`], except that this
179	/// function takes a const reference `&self`. This allows you to use the
180	/// serial port concurrently from multiple threads.
181	///
182	/// Note that data written to the same serial port from multiple threads may
183	/// end up interleaved at the receiving side. You should normally limit
184	/// yourself to a single reading thread and a single writing thread.
185	///
186	/// ## Errors
187	///
188	/// If we get an error back from the OS.
189	pub fn write_all(&self, buff: &[u8]) -> IoResult<()> {
190		let mut working_buff = buff;
191
192		while !working_buff.is_empty() {
193			match self.write(working_buff) {
194				Ok(0) => {
195					return Err(IoError::new(
196						IoErrorKind::WriteZero,
197						"failed to write whole buffer",
198					));
199				}
200				Ok(n) => working_buff = &working_buff[n..],
201				Err(cause) => {
202					if cause.kind() == IoErrorKind::Interrupted {
203						continue;
204					}
205
206					return Err(cause);
207				}
208			}
209		}
210
211		Ok(())
212	}
213
214	/// If this implementation supports vectored writes.
215	#[must_use]
216	pub const fn can_write_vectored() -> bool {
217		RawSyncSerialPort::can_write_vectored()
218	}
219
220	/// Write bytes to the serial port from a slice of buffers.
221	///
222	/// This is identical to [`std::io::Write::write_vectored()`], except that
223	/// this function takes a const reference `&self`. This allows you to use
224	/// the serial port concurrently from multiple threads.
225	///
226	/// Note that data written to the same serial port from multiple threads may
227	/// end up interleaved at the receiving side. You should normally limit
228	/// yourself to a single reading thread and a single writing thread.
229	///
230	/// ## Errors
231	///
232	/// If we get an error back from the OS.
233	pub fn write_vectored(&self, buff: &[IoSlice<'_>]) -> IoResult<usize> {
234		self.inner.write_vectored(buff)
235	}
236
237	/// Flush all data queued to be written.
238	///
239	/// This will block until the OS buffer has been fully transmitted.
240	///
241	/// This is identical to [`std::io::Write::flush()`], except that this
242	/// function takes a const reference `&self`.
243	///
244	/// ## Errors
245	///
246	/// If we get an error back from the OS.
247	pub fn flush(&self) -> IoResult<()> {
248		self.inner.flush_output()
249	}
250
251	/// Get the read timeout for the serial port.
252	///
253	/// The timeout gotten by this function is an upper bound on individual calls
254	/// to [`std::io::Read::read()`]. Other platform specific time-outs may
255	/// trigger before this timeout does.
256	///
257	/// ## Errors
258	///
259	/// If we get an error back from the OS.
260	pub fn get_read_timeout(&self) -> IoResult<Duration> {
261		self.inner.get_read_timeout()
262	}
263
264	/// Get the write timeout for the serial port.
265	///
266	/// The timeout gotten by this function is an upper bound on individual calls
267	/// to [`std::io::Write::write()`]. Other platform specific time-outs may
268	/// trigger before this timeout does.
269	///
270	/// ## Errors
271	///
272	/// If we get an error back from the OS.
273	pub fn get_write_timeout(&self) -> IoResult<Duration> {
274		self.inner.get_write_timeout()
275	}
276
277	/// Set the read timeout for the serial port.
278	///
279	/// The timeout set by this function is an upper bound on individual calls
280	/// to [`std::io::Read::read()`]. Other platform specific time-outs may
281	/// trigger before this timeout does.
282	///
283	/// ## Errors
284	///
285	/// If we get an error back from the OS.
286	pub fn set_read_timeout(&mut self, new_timeout: Duration) -> IoResult<()> {
287		self.inner.set_read_timeout(new_timeout)
288	}
289
290	/// Set the read timeout for the serial port.
291	///
292	/// The timeout set by this function is an upper bound on individual calls
293	/// to [`std::io::Write::write()`]. Other platform specific time-outs may
294	/// trigger before this timeout does.
295	///
296	/// ## Errors
297	///
298	/// If we get an error back from the OS.
299	pub fn set_write_timeout(&mut self, new_timeout: Duration) -> IoResult<()> {
300		self.inner.set_write_timeout(new_timeout)
301	}
302
303	/// Discard the kernel input and output buffers for the serial port.
304	///
305	/// When you write to a serial port, the data may be put in a buffer
306	/// by the OS to be transmitted by the actual device later. Similarly, data
307	/// received on the device can be put in a buffer by the OS untill you read
308	/// it. This function clears both buffers: any untransmitted data and
309	/// received but unread data is discarded by the OS.
310	///
311	/// ## Errors
312	///
313	/// If we get an error back from the OS.
314	pub fn discard_buffers(&self) -> IoResult<()> {
315		self.inner.discard_buffers(true, true)
316	}
317
318	/// Discard the kernel input buffers for the serial port.
319	///
320	/// Data received on the device can be put in a buffer by the OS untill you
321	/// read it. This function clears that buffer: received but unread data
322	/// is discarded by the OS.
323	///
324	/// This is particularly useful when communicating with a device that only
325	/// responds to commands that you send to it. If you discard the input
326	/// buffer before sending the command, you discard any noise that may
327	/// have been received after the last command.
328	///
329	/// ## Errors
330	///
331	/// If we get an error back from the OS.
332	pub fn discard_input_buffer(&self) -> IoResult<()> {
333		self.inner.discard_buffers(true, false)
334	}
335
336	/// Discard the kernel output buffers for the serial port.
337	///
338	/// When you write to a serial port, the data is generally put in a buffer
339	/// by the OS to be transmitted by the actual device later. This function
340	/// clears that buffer: any untransmitted data is discarded by the OS.
341	///
342	/// ## Errors
343	///
344	/// If we get an error back from the OS.
345	pub fn discard_output_buffer(&self) -> IoResult<()> {
346		self.inner.discard_buffers(false, true)
347	}
348
349	/// Set the state of the Ready To Send line.
350	///
351	/// If hardware flow control is enabled on the serial port, it is platform
352	/// specific what will happen. The function may fail with an error or it
353	/// may silently be ignored. It may even succeed and interfere with the
354	/// flow control.
355	///
356	/// ## Errors
357	///
358	/// If we get an error back from the OS.
359	pub fn set_rts(&self, state: bool) -> IoResult<()> {
360		self.inner.set_rts(state)
361	}
362
363	/// Read the state of the Clear To Send line.
364	///
365	/// If hardware flow control is enabled on the serial port, it is platform
366	/// specific what will happen. The function may fail with an error, it may
367	/// return a bogus value, or it may return the actual state of the CTS line.
368	///
369	/// ## Errors
370	///
371	/// If we get an error back from the OS.
372	pub fn read_cts(&self) -> IoResult<bool> {
373		self.inner.read_cts()
374	}
375
376	/// Set the state of the Data Terminal Ready line.
377	///
378	/// If hardware flow control is enabled on the serial port, it is platform
379	/// specific what will happen. The function may fail with an error or it
380	/// may silently be ignored.
381	///
382	/// ## Errors
383	///
384	/// If we get an error back from the OS.
385	pub fn set_dtr(&self, state: bool) -> IoResult<()> {
386		self.inner.set_dtr(state)
387	}
388
389	/// Read the state of the Data Set Ready line.
390	///
391	/// If hardware flow control is enabled on the serial port, it is platform
392	/// specific what will happen. The function may fail with an error, it may
393	/// return a bogus value, or it may return the actual state of the DSR line.
394	///
395	/// ## Errors
396	///
397	/// If we get an error back from the OS.
398	pub fn read_dsr(&self) -> IoResult<bool> {
399		self.inner.read_dsr()
400	}
401
402	/// Read the state of the Ring Indicator line.
403	///
404	/// This line is also sometimes also called the RNG or RING line.
405	///
406	/// ## Errors
407	///
408	/// If we get an error back from the OS.
409	pub fn read_ri(&self) -> IoResult<bool> {
410		self.inner.read_ri()
411	}
412
413	/// Read the state of the Carrier Detect (CD) line.
414	///
415	/// This line is also called the Data Carrier Detect (DCD) line
416	/// or the Receive Line Signal Detect (RLSD) line.
417	///
418	/// ## Errors
419	///
420	/// If we get an error back from the OS.
421	pub fn read_cd(&self) -> IoResult<bool> {
422		self.inner.read_cd()
423	}
424}
425
426impl Read for SyncSerialPort {
427	fn read(&mut self, buff: &mut [u8]) -> IoResult<usize> {
428		SyncSerialPort::read(self, buff)
429	}
430	fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> IoResult<usize> {
431		SyncSerialPort::read_vectored(self, bufs)
432	}
433}
434
435impl Read for &'_ SyncSerialPort {
436	fn read(&mut self, buff: &mut [u8]) -> IoResult<usize> {
437		SyncSerialPort::read(self, buff)
438	}
439	fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> IoResult<usize> {
440		SyncSerialPort::read_vectored(self, bufs)
441	}
442}
443
444impl Write for SyncSerialPort {
445	fn write(&mut self, buff: &[u8]) -> IoResult<usize> {
446		SyncSerialPort::write(self, buff)
447	}
448
449	fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> IoResult<usize> {
450		SyncSerialPort::write_vectored(self, bufs)
451	}
452
453	fn flush(&mut self) -> IoResult<()> {
454		SyncSerialPort::flush(self)
455	}
456}
457
458impl Write for &'_ SyncSerialPort {
459	fn write(&mut self, buff: &[u8]) -> IoResult<usize> {
460		SyncSerialPort::write(self, buff)
461	}
462
463	fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> IoResult<usize> {
464		SyncSerialPort::write_vectored(self, bufs)
465	}
466
467	fn flush(&mut self) -> IoResult<()> {
468		SyncSerialPort::flush(self)
469	}
470}
471
472#[cfg(unix)]
473impl From<SyncSerialPort> for std::os::unix::io::OwnedFd {
474	fn from(value: SyncSerialPort) -> Self {
475		value.inner.fd.into()
476	}
477}
478
479#[cfg(unix)]
480impl From<std::os::unix::io::OwnedFd> for SyncSerialPort {
481	fn from(value: std::os::unix::io::OwnedFd) -> Self {
482		Self {
483			inner: RawSyncSerialPort {
484				fd: value.into(),
485				read_timeout_ms: DEFAULT_TIMEOUT_MS,
486				write_timeout_ms: DEFAULT_TIMEOUT_MS,
487			},
488		}
489	}
490}
491
492#[cfg(unix)]
493impl std::os::unix::io::AsFd for SyncSerialPort {
494	fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> {
495		self.inner.fd.as_fd()
496	}
497}
498
499#[cfg(unix)]
500impl std::os::unix::io::AsRawFd for SyncSerialPort {
501	fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
502		self.inner.fd.as_raw_fd()
503	}
504}
505
506#[cfg(unix)]
507impl std::os::unix::io::IntoRawFd for SyncSerialPort {
508	fn into_raw_fd(self) -> std::os::unix::prelude::RawFd {
509		self.inner.fd.into_raw_fd()
510	}
511}
512
513#[cfg(unix)]
514impl std::os::unix::io::FromRawFd for SyncSerialPort {
515	unsafe fn from_raw_fd(fd: std::os::unix::prelude::RawFd) -> Self {
516		Self {
517			inner: RawSyncSerialPort {
518				fd: unsafe { std::fs::File::from_raw_fd(fd) },
519				read_timeout_ms: DEFAULT_TIMEOUT_MS,
520				write_timeout_ms: DEFAULT_TIMEOUT_MS,
521			},
522		}
523	}
524}
525
526#[cfg(target_os = "windows")]
527impl From<SyncSerialPort> for std::os::windows::io::OwnedHandle {
528	fn from(value: SyncSerialPort) -> Self {
529		value.inner.fd.into()
530	}
531}
532
533#[cfg(target_os = "windows")]
534impl From<std::os::windows::io::OwnedHandle> for SyncSerialPort {
535	fn from(value: std::os::windows::io::OwnedHandle) -> Self {
536		Self {
537			inner: RawSyncSerialPort { fd: value.into() },
538		}
539	}
540}
541
542#[cfg(target_os = "windows")]
543impl std::os::windows::io::AsHandle for SyncSerialPort {
544	fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
545		self.inner.fd.as_handle()
546	}
547}
548
549#[cfg(target_os = "windows")]
550impl std::os::windows::io::AsRawHandle for SyncSerialPort {
551	fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
552		self.inner.fd.as_raw_handle()
553	}
554}
555
556#[cfg(target_os = "windows")]
557impl std::os::windows::io::IntoRawHandle for SyncSerialPort {
558	fn into_raw_handle(self) -> std::os::windows::io::RawHandle {
559		self.inner.fd.into_raw_handle()
560	}
561}
562
563#[cfg(target_os = "windows")]
564impl std::os::windows::io::FromRawHandle for SyncSerialPort {
565	unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
566		Self {
567			inner: RawSyncSerialPort {
568				fd: unsafe { std::fs::File::from_raw_handle(handle) },
569			},
570		}
571	}
572}