flipdot_serial/
serial_sign_bus.rs

1use std::error::Error;
2use std::thread;
3use std::time::Duration;
4
5use log::debug;
6use serial_core::prelude::*;
7
8use flipdot_core::{Frame, Message, SignBus, State};
9
10use crate::serial_port;
11
12/// An implementation of [`SignBus`] that communicates with one or more signs over serial.
13///
14/// Messages and responses are logged using the [`log`] crate for debugging purposes. Consuming binaries
15/// typically use the [`env_logger`] crate and can be run with the `RUST_LOG=debug` environment variable
16/// to watch the bus messages go by.
17///
18/// # Examples
19///
20/// ```no_run
21/// use flipdot_serial::SerialSignBus;
22///
23/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
24/// #
25/// let port = serial::open("/dev/ttyUSB0")?;
26/// let bus = SerialSignBus::try_new(port)?;
27/// // Can now connect a Sign to the bus.
28/// #
29/// # Ok(()) }
30/// ```
31///
32/// [`log`]: https://crates.io/crates/log
33/// [`env_logger`]: https://crates.io/crates/env_logger
34#[derive(Debug, Eq, PartialEq, Hash)]
35pub struct SerialSignBus<P: SerialPort> {
36    port: P,
37}
38
39impl<P: SerialPort> SerialSignBus<P> {
40    /// Creates a new `SerialSignBus` that communicates over the specified serial port.
41    ///
42    /// # Errors
43    ///
44    /// Returns the underlying [`serial_core::Error`] if the serial port cannot be configured.
45    ///
46    /// # Examples
47    ///
48    /// ```no_run
49    /// # use flipdot_serial::SerialSignBus;
50    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
51    /// #
52    /// let port = serial::open("COM3")?;
53    /// let bus = SerialSignBus::try_new(port)?;
54    /// #
55    /// # Ok(()) }
56    /// ```
57    pub fn try_new(mut port: P) -> Result<Self, serial_core::Error> {
58        serial_port::configure_port(&mut port, Duration::from_secs(5))?;
59        Ok(SerialSignBus { port })
60    }
61
62    /// Returns a reference to the underlying serial port.
63    pub fn port(&self) -> &P {
64        &self.port
65    }
66}
67
68impl<P: SerialPort> SignBus for SerialSignBus<P> {
69    /// Handles a bus message by sending it to the serial port and reading a response if necessary.
70    fn process_message<'a>(&mut self, message: Message<'_>) -> Result<Option<Message<'a>>, Box<dyn Error + Send + Sync>> {
71        debug!("Bus message: {}", message);
72
73        let response_expected = response_expected(&message);
74        let delay = delay_after_send(&message);
75
76        let frame = Frame::from(message);
77        frame.write(&mut self.port)?;
78
79        if let Some(duration) = delay {
80            thread::sleep(duration);
81        }
82
83        if response_expected {
84            let frame = Frame::read(&mut self.port)?;
85            let message = Message::from(frame);
86            debug!(" Sign reply: {}", message);
87
88            if let Some(duration) = delay_after_receive(&message) {
89                thread::sleep(duration);
90            }
91
92            Ok(Some(message))
93        } else {
94            Ok(None)
95        }
96    }
97}
98
99/// Determines whether we need to listen for a response to the given message.
100fn response_expected(message: &Message<'_>) -> bool {
101    // A sign is only expected to reply to messages that query its state or request
102    // that it perform an operation.
103    matches!(
104        *message,
105        Message::Hello(_) | Message::QueryState(_) | Message::RequestOperation(_, _)
106    )
107}
108
109/// Returns the length of time to delay after sending a message.
110fn delay_after_send(message: &Message<'_>) -> Option<Duration> {
111    match *message {
112        // When sending data, this delay is necessary to avoid overloading the receiving sign.
113        Message::SendData(_, _) => Some(Duration::from_millis(30)),
114        _ => None,
115    }
116}
117
118/// Returns the length of time to delay after receiving a response.
119fn delay_after_receive(message: &Message<'_>) -> Option<Duration> {
120    match *message {
121        // When loading or showing a page, we wait for the sign to finish the operation, which can take
122        // a second or more depending on how many dots need to flip. This delay prevents us from spamming
123        // the sign with status requests.
124        Message::ReportState(_, State::PageLoadInProgress) | Message::ReportState(_, State::PageShowInProgress) => {
125            Some(Duration::from_millis(100))
126        }
127        _ => None,
128    }
129}