flipdot_serial/
serial_sign_bus.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::error::Error;
use std::thread;
use std::time::Duration;

use log::debug;
use serial_core::prelude::*;

use flipdot_core::{Frame, Message, SignBus, State};

use crate::serial_port;

/// An implementation of [`SignBus`] that communicates with one or more signs over serial.
///
/// Messages and responses are logged using the [`log`] crate for debugging purposes. Consuming binaries
/// typically use the [`env_logger`] crate and can be run with the `RUST_LOG=debug` environment variable
/// to watch the bus messages go by.
///
/// # Examples
///
/// ```no_run
/// use flipdot_serial::SerialSignBus;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// #
/// let port = serial::open("/dev/ttyUSB0")?;
/// let bus = SerialSignBus::try_new(port)?;
/// // Can now connect a Sign to the bus.
/// #
/// # Ok(()) }
/// ```
///
/// [`log`]: https://crates.io/crates/log
/// [`env_logger`]: https://crates.io/crates/env_logger
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct SerialSignBus<P: SerialPort> {
    port: P,
}

impl<P: SerialPort> SerialSignBus<P> {
    /// Creates a new `SerialSignBus` that communicates over the specified serial port.
    ///
    /// # Errors
    ///
    /// Returns the underlying [`serial_core::Error`] if the serial port cannot be configured.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use flipdot_serial::SerialSignBus;
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// #
    /// let port = serial::open("COM3")?;
    /// let bus = SerialSignBus::try_new(port)?;
    /// #
    /// # Ok(()) }
    /// ```
    pub fn try_new(mut port: P) -> Result<Self, serial_core::Error> {
        serial_port::configure_port(&mut port, Duration::from_secs(5))?;
        Ok(SerialSignBus { port })
    }

    /// Returns a reference to the underlying serial port.
    pub fn port(&self) -> &P {
        &self.port
    }
}

impl<P: SerialPort> SignBus for SerialSignBus<P> {
    /// Handles a bus message by sending it to the serial port and reading a response if necessary.
    fn process_message<'a>(&mut self, message: Message<'_>) -> Result<Option<Message<'a>>, Box<dyn Error + Send + Sync>> {
        debug!("Bus message: {}", message);

        let response_expected = response_expected(&message);
        let delay = delay_after_send(&message);

        let frame = Frame::from(message);
        frame.write(&mut self.port)?;

        if let Some(duration) = delay {
            thread::sleep(duration);
        }

        if response_expected {
            let frame = Frame::read(&mut self.port)?;
            let message = Message::from(frame);
            debug!(" Sign reply: {}", message);

            if let Some(duration) = delay_after_receive(&message) {
                thread::sleep(duration);
            }

            Ok(Some(message))
        } else {
            Ok(None)
        }
    }
}

/// Determines whether we need to listen for a response to the given message.
fn response_expected(message: &Message<'_>) -> bool {
    // A sign is only expected to reply to messages that query its state or request
    // that it perform an operation.
    matches!(
        *message,
        Message::Hello(_) | Message::QueryState(_) | Message::RequestOperation(_, _)
    )
}

/// Returns the length of time to delay after sending a message.
fn delay_after_send(message: &Message<'_>) -> Option<Duration> {
    match *message {
        // When sending data, this delay is necessary to avoid overloading the receiving sign.
        Message::SendData(_, _) => Some(Duration::from_millis(30)),
        _ => None,
    }
}

/// Returns the length of time to delay after receiving a response.
fn delay_after_receive(message: &Message<'_>) -> Option<Duration> {
    match *message {
        // When loading or showing a page, we wait for the sign to finish the operation, which can take
        // a second or more depending on how many dots need to flip. This delay prevents us from spamming
        // the sign with status requests.
        Message::ReportState(_, State::PageLoadInProgress) | Message::ReportState(_, State::PageShowInProgress) => {
            Some(Duration::from_millis(100))
        }
        _ => None,
    }
}