1use cu29::bundle_resources;
2use cu29::prelude::*;
3use cu29::resource::{ResourceBundle, ResourceManager};
4use embedded_io::{Read as EmbeddedRead, Write as EmbeddedWrite};
5#[cfg(feature = "embedded-io-07")]
6use embedded_io_07 as embedded_io07;
7use serialport::{Parity as SerialParity, StopBits as SerialStopBits};
8use std::string::String;
9
10pub const SERIAL0_DEV_KEY: &str = "serial0_dev";
11pub const SERIAL0_BAUDRATE_KEY: &str = "serial0_baudrate";
12pub const SERIAL0_PARITY_KEY: &str = "serial0_parity";
13pub const SERIAL0_STOPBITS_KEY: &str = "serial0_stopbits";
14pub const SERIAL0_TIMEOUT_MS_KEY: &str = "serial0_timeout_ms";
15
16pub const SERIAL1_DEV_KEY: &str = "serial1_dev";
17pub const SERIAL1_BAUDRATE_KEY: &str = "serial1_baudrate";
18pub const SERIAL1_PARITY_KEY: &str = "serial1_parity";
19pub const SERIAL1_STOPBITS_KEY: &str = "serial1_stopbits";
20pub const SERIAL1_TIMEOUT_MS_KEY: &str = "serial1_timeout_ms";
21
22pub const SERIAL2_DEV_KEY: &str = "serial2_dev";
23pub const SERIAL2_BAUDRATE_KEY: &str = "serial2_baudrate";
24pub const SERIAL2_PARITY_KEY: &str = "serial2_parity";
25pub const SERIAL2_STOPBITS_KEY: &str = "serial2_stopbits";
26pub const SERIAL2_TIMEOUT_MS_KEY: &str = "serial2_timeout_ms";
27
28pub const SERIAL3_DEV_KEY: &str = "serial3_dev";
29pub const SERIAL3_BAUDRATE_KEY: &str = "serial3_baudrate";
30pub const SERIAL3_PARITY_KEY: &str = "serial3_parity";
31pub const SERIAL3_STOPBITS_KEY: &str = "serial3_stopbits";
32pub const SERIAL3_TIMEOUT_MS_KEY: &str = "serial3_timeout_ms";
33
34pub const SERIAL4_DEV_KEY: &str = "serial4_dev";
35pub const SERIAL4_BAUDRATE_KEY: &str = "serial4_baudrate";
36pub const SERIAL4_PARITY_KEY: &str = "serial4_parity";
37pub const SERIAL4_STOPBITS_KEY: &str = "serial4_stopbits";
38pub const SERIAL4_TIMEOUT_MS_KEY: &str = "serial4_timeout_ms";
39
40pub const SERIAL5_DEV_KEY: &str = "serial5_dev";
41pub const SERIAL5_BAUDRATE_KEY: &str = "serial5_baudrate";
42pub const SERIAL5_PARITY_KEY: &str = "serial5_parity";
43pub const SERIAL5_STOPBITS_KEY: &str = "serial5_stopbits";
44pub const SERIAL5_TIMEOUT_MS_KEY: &str = "serial5_timeout_ms";
45
46pub const I2C0_DEV_KEY: &str = "i2c0_dev";
47pub const I2C1_DEV_KEY: &str = "i2c1_dev";
48pub const I2C2_DEV_KEY: &str = "i2c2_dev";
49
50pub const GPIO_OUT0_PIN_KEY: &str = "gpio_out0_pin";
51pub const GPIO_OUT1_PIN_KEY: &str = "gpio_out1_pin";
52pub const GPIO_OUT2_PIN_KEY: &str = "gpio_out2_pin";
53pub const GPIO_IN0_PIN_KEY: &str = "gpio_in0_pin";
54pub const GPIO_IN1_PIN_KEY: &str = "gpio_in1_pin";
55pub const GPIO_IN2_PIN_KEY: &str = "gpio_in2_pin";
56
57pub const DEFAULT_SERIAL_BAUDRATE: u32 = 115_200;
58pub const DEFAULT_SERIAL_TIMEOUT_MS: u64 = 50;
59pub const DEFAULT_SERIAL_PARITY: SerialParity = SerialParity::None;
60pub const DEFAULT_SERIAL_STOPBITS: SerialStopBits = SerialStopBits::One;
61
62pub struct Exclusive<T>(T);
68
69impl<T> Exclusive<T> {
70 pub const fn new(inner: T) -> Self {
71 Self(inner)
72 }
73
74 pub fn into_inner(self) -> T {
75 self.0
76 }
77
78 pub fn get_mut(&mut self) -> &mut T {
79 &mut self.0
80 }
81}
82
83unsafe impl<T: Send> Sync for Exclusive<T> {}
86
87impl<T: std::io::Read> std::io::Read for Exclusive<T> {
88 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
89 self.0.read(buf)
90 }
91}
92
93impl<T: std::io::Write> std::io::Write for Exclusive<T> {
94 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
95 self.0.write(buf)
96 }
97
98 fn flush(&mut self) -> std::io::Result<()> {
99 self.0.flush()
100 }
101}
102
103#[cfg(feature = "embedded-io-07")]
104impl<T: embedded_io07::ErrorType> embedded_io07::ErrorType for Exclusive<T> {
105 type Error = T::Error;
106}
107
108#[cfg(feature = "embedded-io-07")]
109impl<T: embedded_io07::Read> embedded_io07::Read for Exclusive<T> {
110 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
111 self.0.read(buf)
112 }
113}
114
115#[cfg(feature = "embedded-io-07")]
116impl<T: embedded_io07::Write> embedded_io07::Write for Exclusive<T> {
117 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
118 self.0.write(buf)
119 }
120
121 fn flush(&mut self) -> Result<(), Self::Error> {
122 self.0.flush()
123 }
124}
125
126impl<T> embedded_hal::i2c::ErrorType for Exclusive<T>
127where
128 T: embedded_hal::i2c::ErrorType,
129{
130 type Error = T::Error;
131}
132
133impl<T> embedded_hal::i2c::I2c for Exclusive<T>
134where
135 T: embedded_hal::i2c::I2c,
136{
137 fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
138 self.0.read(address, read)
139 }
140
141 fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
142 self.0.write(address, write)
143 }
144
145 fn write_read(
146 &mut self,
147 address: u8,
148 write: &[u8],
149 read: &mut [u8],
150 ) -> Result<(), Self::Error> {
151 self.0.write_read(address, write, read)
152 }
153
154 fn transaction(
155 &mut self,
156 address: u8,
157 operations: &mut [embedded_hal::i2c::Operation<'_>],
158 ) -> Result<(), Self::Error> {
159 self.0.transaction(address, operations)
160 }
161}
162
163impl<T> embedded_hal::digital::ErrorType for Exclusive<T>
164where
165 T: embedded_hal::digital::ErrorType,
166{
167 type Error = T::Error;
168}
169
170impl<T> embedded_hal::digital::OutputPin for Exclusive<T>
171where
172 T: embedded_hal::digital::OutputPin,
173{
174 fn set_low(&mut self) -> Result<(), Self::Error> {
175 self.0.set_low()
176 }
177
178 fn set_high(&mut self) -> Result<(), Self::Error> {
179 self.0.set_high()
180 }
181}
182
183impl<T> embedded_hal::digital::StatefulOutputPin for Exclusive<T>
184where
185 T: embedded_hal::digital::StatefulOutputPin,
186{
187 fn is_set_high(&mut self) -> Result<bool, Self::Error> {
188 self.0.is_set_high()
189 }
190
191 fn is_set_low(&mut self) -> Result<bool, Self::Error> {
192 self.0.is_set_low()
193 }
194}
195
196impl<T> embedded_hal::digital::InputPin for Exclusive<T>
197where
198 T: embedded_hal::digital::InputPin,
199{
200 fn is_high(&mut self) -> Result<bool, Self::Error> {
201 self.0.is_high()
202 }
203
204 fn is_low(&mut self) -> Result<bool, Self::Error> {
205 self.0.is_low()
206 }
207}
208
209pub struct LinuxSerialPort {
210 inner: Exclusive<Box<dyn serialport::SerialPort>>,
211}
212
213impl LinuxSerialPort {
214 pub fn new(inner: Box<dyn serialport::SerialPort>) -> Self {
215 Self {
216 inner: Exclusive::new(inner),
217 }
218 }
219
220 pub fn open(dev: &str, baudrate: u32, timeout_ms: u64) -> std::io::Result<Self> {
221 let config = SerialSlotConfig {
222 dev: dev.to_string(),
223 baudrate,
224 parity: DEFAULT_SERIAL_PARITY,
225 stop_bits: DEFAULT_SERIAL_STOPBITS,
226 timeout_ms,
227 };
228 Self::open_with_config(&config)
229 }
230
231 pub fn open_with_config(config: &SerialSlotConfig) -> std::io::Result<Self> {
232 let port = serialport::new(config.dev.as_str(), config.baudrate)
233 .parity(config.parity)
234 .stop_bits(config.stop_bits)
235 .timeout(std::time::Duration::from_millis(config.timeout_ms))
236 .open()?;
237 Ok(Self::new(port))
238 }
239}
240
241impl std::io::Read for LinuxSerialPort {
242 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
243 self.inner.read(buf)
244 }
245}
246
247impl std::io::Write for LinuxSerialPort {
248 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
249 self.inner.write(buf)
250 }
251
252 fn flush(&mut self) -> std::io::Result<()> {
253 self.inner.flush()
254 }
255}
256
257impl embedded_io::ErrorType for LinuxSerialPort {
258 type Error = std::io::Error;
259}
260
261impl EmbeddedRead for LinuxSerialPort {
262 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
263 std::io::Read::read(self, buf)
264 }
265}
266
267impl EmbeddedWrite for LinuxSerialPort {
268 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
269 std::io::Write::write(self, buf)
270 }
271
272 fn flush(&mut self) -> Result<(), Self::Error> {
273 std::io::Write::flush(self)
274 }
275}
276
277#[cfg(target_os = "linux")]
278pub type LinuxI2c = Exclusive<linux_embedded_hal::I2cdev>;
279#[cfg(target_os = "linux")]
280pub type LinuxOutputPin = Exclusive<rppal::gpio::OutputPin>;
281#[cfg(target_os = "linux")]
282pub type LinuxInputPin = Exclusive<rppal::gpio::InputPin>;
283
284pub struct LinuxResources;
285
286bundle_resources!(
287 LinuxResources:
288 Serial0,
289 Serial1,
290 Serial2,
291 Serial3,
292 Serial4,
293 Serial5,
294 I2c0,
295 I2c1,
296 I2c2,
297 GpioOut0,
298 GpioOut1,
299 GpioOut2,
300 GpioIn0,
301 GpioIn1,
302 GpioIn2
303);
304
305const LINUX_RESOURCE_SLOT_NAMES: &[&str] = &[
306 "serial0",
307 "serial1",
308 "serial2",
309 "serial3",
310 "serial4",
311 "serial5",
312 "i2c0",
313 "i2c1",
314 "i2c2",
315 "gpio_out0",
316 "gpio_out1",
317 "gpio_out2",
318 "gpio_in0",
319 "gpio_in1",
320 "gpio_in2",
321];
322
323struct SerialSlot {
324 id: LinuxResourcesId,
325 dev_key: &'static str,
326 baudrate_key: &'static str,
327 parity_key: &'static str,
328 stopbits_key: &'static str,
329 timeout_ms_key: &'static str,
330}
331
332#[derive(Clone, Debug)]
333pub struct SerialSlotConfig {
334 pub dev: String,
335 pub baudrate: u32,
336 pub parity: SerialParity,
337 pub stop_bits: SerialStopBits,
338 pub timeout_ms: u64,
339}
340
341const SERIAL_SLOTS: &[SerialSlot] = &[
342 SerialSlot {
343 id: LinuxResourcesId::Serial0,
344 dev_key: SERIAL0_DEV_KEY,
345 baudrate_key: SERIAL0_BAUDRATE_KEY,
346 parity_key: SERIAL0_PARITY_KEY,
347 stopbits_key: SERIAL0_STOPBITS_KEY,
348 timeout_ms_key: SERIAL0_TIMEOUT_MS_KEY,
349 },
350 SerialSlot {
351 id: LinuxResourcesId::Serial1,
352 dev_key: SERIAL1_DEV_KEY,
353 baudrate_key: SERIAL1_BAUDRATE_KEY,
354 parity_key: SERIAL1_PARITY_KEY,
355 stopbits_key: SERIAL1_STOPBITS_KEY,
356 timeout_ms_key: SERIAL1_TIMEOUT_MS_KEY,
357 },
358 SerialSlot {
359 id: LinuxResourcesId::Serial2,
360 dev_key: SERIAL2_DEV_KEY,
361 baudrate_key: SERIAL2_BAUDRATE_KEY,
362 parity_key: SERIAL2_PARITY_KEY,
363 stopbits_key: SERIAL2_STOPBITS_KEY,
364 timeout_ms_key: SERIAL2_TIMEOUT_MS_KEY,
365 },
366 SerialSlot {
367 id: LinuxResourcesId::Serial3,
368 dev_key: SERIAL3_DEV_KEY,
369 baudrate_key: SERIAL3_BAUDRATE_KEY,
370 parity_key: SERIAL3_PARITY_KEY,
371 stopbits_key: SERIAL3_STOPBITS_KEY,
372 timeout_ms_key: SERIAL3_TIMEOUT_MS_KEY,
373 },
374 SerialSlot {
375 id: LinuxResourcesId::Serial4,
376 dev_key: SERIAL4_DEV_KEY,
377 baudrate_key: SERIAL4_BAUDRATE_KEY,
378 parity_key: SERIAL4_PARITY_KEY,
379 stopbits_key: SERIAL4_STOPBITS_KEY,
380 timeout_ms_key: SERIAL4_TIMEOUT_MS_KEY,
381 },
382 SerialSlot {
383 id: LinuxResourcesId::Serial5,
384 dev_key: SERIAL5_DEV_KEY,
385 baudrate_key: SERIAL5_BAUDRATE_KEY,
386 parity_key: SERIAL5_PARITY_KEY,
387 stopbits_key: SERIAL5_STOPBITS_KEY,
388 timeout_ms_key: SERIAL5_TIMEOUT_MS_KEY,
389 },
390];
391
392#[cfg(target_os = "linux")]
393struct I2cSlot {
394 id: LinuxResourcesId,
395 dev_key: &'static str,
396}
397
398#[cfg(target_os = "linux")]
399const I2C_SLOTS: &[I2cSlot] = &[
400 I2cSlot {
401 id: LinuxResourcesId::I2c0,
402 dev_key: I2C0_DEV_KEY,
403 },
404 I2cSlot {
405 id: LinuxResourcesId::I2c1,
406 dev_key: I2C1_DEV_KEY,
407 },
408 I2cSlot {
409 id: LinuxResourcesId::I2c2,
410 dev_key: I2C2_DEV_KEY,
411 },
412];
413
414struct GpioSlot {
415 id: LinuxResourcesId,
416 key: &'static str,
417}
418
419const GPIO_OUT_SLOTS: &[GpioSlot] = &[
420 GpioSlot {
421 id: LinuxResourcesId::GpioOut0,
422 key: GPIO_OUT0_PIN_KEY,
423 },
424 GpioSlot {
425 id: LinuxResourcesId::GpioOut1,
426 key: GPIO_OUT1_PIN_KEY,
427 },
428 GpioSlot {
429 id: LinuxResourcesId::GpioOut2,
430 key: GPIO_OUT2_PIN_KEY,
431 },
432];
433
434const GPIO_IN_SLOTS: &[GpioSlot] = &[
435 GpioSlot {
436 id: LinuxResourcesId::GpioIn0,
437 key: GPIO_IN0_PIN_KEY,
438 },
439 GpioSlot {
440 id: LinuxResourcesId::GpioIn1,
441 key: GPIO_IN1_PIN_KEY,
442 },
443 GpioSlot {
444 id: LinuxResourcesId::GpioIn2,
445 key: GPIO_IN2_PIN_KEY,
446 },
447];
448
449impl ResourceBundle for LinuxResources {
450 fn build(
451 bundle: cu29::resource::BundleContext<Self>,
452 config: Option<&ComponentConfig>,
453 manager: &mut ResourceManager,
454 ) -> CuResult<()> {
455 for slot in SERIAL_SLOTS {
456 let Some(serial_config) = read_serial_slot_config(config, slot)? else {
457 continue; };
459 match LinuxSerialPort::open_with_config(&serial_config) {
460 Ok(serial) => {
461 manager.add_owned(bundle.key(slot.id), serial)?;
462 }
463 Err(err) => {
464 warning!(
465 "LinuxResources: skipping serial slot {} (dev {}): {}",
466 slot_name(slot.id),
467 serial_config.dev,
468 err.to_string()
469 );
470 }
471 }
472 }
473
474 #[cfg(target_os = "linux")]
475 for slot in I2C_SLOTS {
476 let Some(dev) = get_string(config, slot.dev_key)? else {
477 continue; };
479 match linux_embedded_hal::I2cdev::new(&dev) {
480 Ok(i2c) => {
481 manager.add_owned(bundle.key(slot.id), Exclusive::new(i2c))?;
482 }
483 Err(err) => {
484 warning!(
485 "LinuxResources: skipping i2c slot {} (dev {}): {}",
486 slot_name(slot.id),
487 dev,
488 err.to_string()
489 );
490 }
491 }
492 }
493
494 #[cfg(target_os = "linux")]
495 {
496 let mut configured_gpio_out: std::vec::Vec<(LinuxResourcesId, u8)> =
497 std::vec::Vec::new();
498 let mut configured_gpio_in: std::vec::Vec<(LinuxResourcesId, u8)> =
499 std::vec::Vec::new();
500
501 for slot in GPIO_OUT_SLOTS {
502 if let Some(pin) = get_u8(config, slot.key)? {
503 configured_gpio_out.push((slot.id, pin));
504 }
505 }
506 for slot in GPIO_IN_SLOTS {
507 if let Some(pin) = get_u8(config, slot.key)? {
508 configured_gpio_in.push((slot.id, pin));
509 }
510 }
511
512 if !configured_gpio_out.is_empty() || !configured_gpio_in.is_empty() {
513 let gpio = rppal::gpio::Gpio::new().map_err(|err| {
514 CuError::new_with_cause("Failed to initialize GPIO subsystem", err)
515 })?;
516
517 for (slot_id, pin) in configured_gpio_out {
518 match gpio.get(pin) {
519 Ok(pin) => {
520 manager.add_owned(
521 bundle.key(slot_id),
522 Exclusive::new(pin.into_output()),
523 )?;
524 }
525 Err(err) => {
526 warning!(
527 "LinuxResources: skipping gpio output slot {} (pin {}): {}",
528 slot_name(slot_id),
529 pin,
530 err.to_string()
531 );
532 }
533 }
534 }
535
536 for (slot_id, pin) in configured_gpio_in {
537 match gpio.get(pin) {
538 Ok(pin) => {
539 manager
540 .add_owned(bundle.key(slot_id), Exclusive::new(pin.into_input()))?;
541 }
542 Err(err) => {
543 warning!(
544 "LinuxResources: skipping gpio input slot {} (pin {}): {}",
545 slot_name(slot_id),
546 pin,
547 err.to_string()
548 );
549 }
550 }
551 }
552 }
553 }
554
555 #[cfg(not(target_os = "linux"))]
556 {
557 for slot in GPIO_OUT_SLOTS {
558 if let Some(pin) = get_u8(config, slot.key)? {
559 warning!(
560 "LinuxResources: requested gpio output slot {} on pin {} but GPIO is only supported on Linux",
561 slot_name(slot.id),
562 pin
563 );
564 }
565 }
566 for slot in GPIO_IN_SLOTS {
567 if let Some(pin) = get_u8(config, slot.key)? {
568 warning!(
569 "LinuxResources: requested gpio input slot {} on pin {} but GPIO is only supported on Linux",
570 slot_name(slot.id),
571 pin
572 );
573 }
574 }
575 }
576
577 Ok(())
578 }
579}
580
581fn read_serial_slot_config(
582 config: Option<&ComponentConfig>,
583 slot: &SerialSlot,
584) -> CuResult<Option<SerialSlotConfig>> {
585 let Some(dev) = get_string(config, slot.dev_key)? else {
586 return Ok(None); };
588 let baudrate = get_u32(config, slot.baudrate_key)?.unwrap_or(DEFAULT_SERIAL_BAUDRATE);
589 let parity = get_serial_parity(config, slot.parity_key)?.unwrap_or(DEFAULT_SERIAL_PARITY);
590 let stop_bits =
591 get_serial_stop_bits(config, slot.stopbits_key)?.unwrap_or(DEFAULT_SERIAL_STOPBITS);
592 let timeout_ms = get_u64(config, slot.timeout_ms_key)?.unwrap_or(DEFAULT_SERIAL_TIMEOUT_MS);
593
594 Ok(Some(SerialSlotConfig {
595 dev,
596 baudrate,
597 parity,
598 stop_bits,
599 timeout_ms,
600 }))
601}
602
603fn get_serial_parity(
604 config: Option<&ComponentConfig>,
605 key: &str,
606) -> CuResult<Option<SerialParity>> {
607 let Some(raw) = get_string(config, key)? else {
608 return Ok(None);
609 };
610 Ok(Some(parse_serial_parity_value(raw.as_str())?))
611}
612
613fn get_serial_stop_bits(
614 config: Option<&ComponentConfig>,
615 key: &str,
616) -> CuResult<Option<SerialStopBits>> {
617 let Some(raw) = get_u8(config, key)? else {
618 return Ok(None);
619 };
620 Ok(Some(parse_serial_stop_bits_value(raw)?))
621}
622
623fn parse_serial_parity_value(raw: &str) -> CuResult<SerialParity> {
624 let normalized = raw.trim().to_ascii_lowercase();
625 match normalized.as_str() {
626 "none" => Ok(SerialParity::None),
627 "odd" => Ok(SerialParity::Odd),
628 "even" => Ok(SerialParity::Even),
629 _ => Err(CuError::from(format!(
630 "Invalid parity '{raw}'. Expected one of: none, odd, even"
631 ))),
632 }
633}
634
635fn parse_serial_stop_bits_value(raw: u8) -> CuResult<SerialStopBits> {
636 match raw {
637 1 => Ok(SerialStopBits::One),
638 2 => Ok(SerialStopBits::Two),
639 _ => Err(CuError::from(format!(
640 "Invalid stopbits value '{raw}'. Expected 1 or 2"
641 ))),
642 }
643}
644
645fn slot_name(id: LinuxResourcesId) -> &'static str {
646 LINUX_RESOURCE_SLOT_NAMES[id as usize]
647}
648
649fn get_string(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<String>> {
650 match config {
651 Some(cfg) => Ok(cfg.get::<String>(key)?.filter(|value| !value.is_empty())),
652 None => Ok(None),
653 }
654}
655
656fn get_u8(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<u8>> {
657 match config {
658 Some(cfg) => Ok(cfg.get::<u8>(key)?),
659 None => Ok(None),
660 }
661}
662
663fn get_u32(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<u32>> {
664 match config {
665 Some(cfg) => Ok(cfg.get::<u32>(key)?),
666 None => Ok(None),
667 }
668}
669
670fn get_u64(config: Option<&ComponentConfig>, key: &str) -> CuResult<Option<u64>> {
671 match config {
672 Some(cfg) => Ok(cfg.get::<u64>(key)?),
673 None => Ok(None),
674 }
675}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 #[test]
682 fn parse_serial_parity_value_accepts_expected_inputs() {
683 assert!(matches!(
684 parse_serial_parity_value("none").unwrap(),
685 SerialParity::None
686 ));
687 assert!(matches!(
688 parse_serial_parity_value("Odd").unwrap(),
689 SerialParity::Odd
690 ));
691 assert!(matches!(
692 parse_serial_parity_value("EVEN").unwrap(),
693 SerialParity::Even
694 ));
695 }
696
697 #[test]
698 fn parse_serial_parity_value_rejects_invalid_input() {
699 assert!(parse_serial_parity_value("mark").is_err());
700 }
701
702 #[test]
703 fn parse_serial_stop_bits_value_accepts_expected_inputs() {
704 assert!(matches!(
705 parse_serial_stop_bits_value(1).unwrap(),
706 SerialStopBits::One
707 ));
708 assert!(matches!(
709 parse_serial_stop_bits_value(2).unwrap(),
710 SerialStopBits::Two
711 ));
712 }
713
714 #[test]
715 fn parse_serial_stop_bits_value_rejects_invalid_input() {
716 assert!(parse_serial_stop_bits_value(0).is_err());
717 assert!(parse_serial_stop_bits_value(3).is_err());
718 }
719
720 #[cfg(feature = "embedded-io-07")]
721 struct MockIo {
722 rx: [u8; 4],
723 rx_len: usize,
724 tx: [u8; 4],
725 tx_len: usize,
726 }
727
728 #[cfg(feature = "embedded-io-07")]
729 impl MockIo {
730 fn new(rx: &[u8]) -> Self {
731 let mut buf = [0_u8; 4];
732 buf[..rx.len()].copy_from_slice(rx);
733 Self {
734 rx: buf,
735 rx_len: rx.len(),
736 tx: [0; 4],
737 tx_len: 0,
738 }
739 }
740 }
741
742 #[cfg(feature = "embedded-io-07")]
743 impl embedded_io_07::ErrorType for MockIo {
744 type Error = core::convert::Infallible;
745 }
746
747 #[cfg(feature = "embedded-io-07")]
748 impl embedded_io_07::Read for MockIo {
749 fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
750 let n = core::cmp::min(buf.len(), self.rx_len);
751 buf[..n].copy_from_slice(&self.rx[..n]);
752 Ok(n)
753 }
754 }
755
756 #[cfg(feature = "embedded-io-07")]
757 impl embedded_io_07::Write for MockIo {
758 fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
759 let n = core::cmp::min(buf.len(), self.tx.len());
760 self.tx[..n].copy_from_slice(&buf[..n]);
761 self.tx_len = n;
762 Ok(n)
763 }
764
765 fn flush(&mut self) -> Result<(), Self::Error> {
766 Ok(())
767 }
768 }
769
770 #[cfg(feature = "embedded-io-07")]
771 #[test]
772 fn exclusive_forwards_embedded_io_07_traits() {
773 let mut wrapped = Exclusive::new(MockIo::new(&[1, 2, 3]));
774
775 let mut rx = [0_u8; 4];
776 let read = embedded_io_07::Read::read(&mut wrapped, &mut rx).unwrap();
777 assert_eq!(read, 3);
778 assert_eq!(&rx[..3], &[1, 2, 3]);
779
780 let written = embedded_io_07::Write::write(&mut wrapped, &[9, 8]).unwrap();
781 assert_eq!(written, 2);
782 embedded_io_07::Write::flush(&mut wrapped).unwrap();
783
784 let inner = wrapped.into_inner();
785 assert_eq!(&inner.tx[..inner.tx_len], &[9, 8]);
786 }
787}