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}