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}