flipdot_testing/odk.rs
1use std::time::Duration;
2
3use serial_core::prelude::*;
4use thiserror::Error;
5
6use flipdot_core::{Frame, Message, SignBus};
7
8/// Errors related to [`Odk`]s.
9#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum OdkError {
12 /// The sign bus failed to process a message.
13 #[error("Sign bus failed to process message")]
14 Bus {
15 /// The underlying bus error.
16 #[from]
17 source: Box<dyn std::error::Error + Send + Sync>,
18 },
19
20 /// Failure reading/writing data.
21 #[error("ODK communication error")]
22 Communication {
23 /// The underlying communication error.
24 #[from]
25 source: flipdot_core::FrameError,
26 },
27}
28
29/// Connects to a real ODK over the specified serial port and uses it to drive a [`SignBus`].
30///
31/// Typically this will be used to drive a [`VirtualSignBus`](crate::VirtualSignBus) in order to study the bus traffic
32/// or inspect the pages of pixel data sent by the ODK.
33///
34/// # Examples
35///
36/// ```no_run
37/// use flipdot_core::PageFlipStyle;
38/// use flipdot_serial::SerialSignBus;
39/// use flipdot_testing::{Address, Odk, VirtualSign, VirtualSignBus};
40///
41/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
42/// #
43/// // Populate bus with signs from addresses 2 to 126
44/// // (which seems to be the possible range for actual signs).
45/// let signs = (2..127).map(Address).map(|addr| VirtualSign::new(addr, PageFlipStyle::Manual));
46/// let bus = VirtualSignBus::new(signs);
47///
48/// // Hook up ODK to virtual bus.
49/// let port = serial::open("/dev/ttyUSB0")?;
50/// let mut odk = Odk::try_new(port, bus)?;
51/// loop {
52/// // ODK communications are forwarded to/from the virtual bus.
53/// odk.process_message()?;
54/// }
55/// #
56/// # Ok(()) }
57/// ```
58#[derive(Debug, PartialEq, Eq, Hash)]
59pub struct Odk<P: SerialPort, B: SignBus> {
60 port: P,
61 bus: B,
62}
63
64impl<P: SerialPort, B: SignBus> Odk<P, B> {
65 /// Create a new `Odk` that connects the specified serial port and bus.
66 ///
67 /// # Errors
68 ///
69 /// Returns the underlying [`serial_core::Error`] if the serial port cannot be configured.
70 ///
71 /// # Examples
72 ///
73 /// ```no_run
74 /// # use flipdot_core::PageFlipStyle;
75 /// # use flipdot_serial::SerialSignBus;
76 /// # use flipdot_testing::{Address, Odk, VirtualSign, VirtualSignBus};
77 /// #
78 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
79 /// #
80 /// let bus = VirtualSignBus::new(vec![VirtualSign::new(Address(3), PageFlipStyle::Manual)]);
81 /// let port = serial::open("COM3")?;
82 /// let odk = Odk::try_new(port, bus)?;
83 /// #
84 /// # Ok(()) }
85 /// ```
86 ///
87 /// Note: You would typically use the `env_logger` crate and run with
88 /// `RUST_LOG=debug` to watch the bus messages go by.
89 pub fn try_new(mut port: P, bus: B) -> Result<Self, serial_core::Error> {
90 flipdot_serial::configure_port(&mut port, Duration::from_secs(10))?;
91 Ok(Odk { port, bus })
92 }
93
94 /// Reads the next frame from the ODK over the serial port, forwards it
95 /// to the attached bus, and sends the response, if any, back to the ODK.
96 ///
97 /// # Errors
98 ///
99 /// Returns:
100 /// * [`OdkError::Communication`] if there was an error reading or writing the data.
101 /// * [`OdkError::Bus`] if the bus failed to process the message.
102 ///
103 /// # Examples
104 ///
105 /// ```no_run
106 /// # use flipdot_core::PageFlipStyle;
107 /// # use flipdot_serial::SerialSignBus;
108 /// # use flipdot_testing::{Address, Odk, VirtualSign, VirtualSignBus};
109 /// #
110 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
111 /// #
112 /// let bus = VirtualSignBus::new(vec![VirtualSign::new(Address(3), PageFlipStyle::Manual)]);
113 /// let port = serial::open("/dev/ttyUSB0")?;
114 /// let mut odk = Odk::try_new(port, bus)?;
115 /// loop {
116 /// odk.process_message()?;
117 /// }
118 /// #
119 /// # Ok(()) }
120 /// ```
121 pub fn process_message(&mut self) -> Result<(), OdkError> {
122 let response = {
123 let frame = Frame::read(&mut self.port)?;
124 let message = Message::from(frame);
125 self.bus.process_message(message)?
126 };
127
128 if let Some(message) = response {
129 let frame = Frame::from(message);
130 frame.write(&mut self.port)?;
131 }
132
133 Ok(())
134 }
135}