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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! Serial port wrapper to support platform-specific functionality
//!
//! Since we support flashing using a Raspberry Pi's built-in UART, we must be
//! able to abstract over the differences between this setup and when using a
//! serial port as one normally would, ie.) via USB.

use std::io::Read;

use miette::{Context, Result};
#[cfg(feature = "raspberry")]
use rppal::gpio::{Gpio, OutputPin};
use serialport::{FlowControl, SerialPort, SerialPortInfo};

use crate::error::Error;

/// Errors relating to the configuration of a serial port
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum SerialConfigError {
    #[cfg(feature = "raspberry")]
    #[error("You need to specify both DTR and RTS pins when using an internal UART peripheral")]
    MissingDtrRtsForInternalUart,
    #[cfg(feature = "raspberry")]
    #[error("GPIO {0} is not available")]
    GpioUnavailable(u8),
}

/// Wrapper around SerialPort where platform-specific modifications can be
/// implemented.
pub struct Interface {
    /// Hardware serial port used for communication
    pub serial_port: Box<dyn SerialPort>,
    /// Data Transmit Ready pin
    #[cfg(feature = "raspberry")]
    pub dtr: Option<OutputPin>,
    /// Ready To Send pin
    #[cfg(feature = "raspberry")]
    pub rts: Option<OutputPin>,
}

/// Set the level of a GPIO
#[cfg(feature = "raspberry")]
fn write_gpio(gpio: &mut OutputPin, level: bool) {
    if level {
        gpio.set_high();
    } else {
        gpio.set_low();
    }
}

/// Open a serial port
fn open_port(port_info: &SerialPortInfo) -> Result<Box<dyn SerialPort>> {
    serialport::new(&port_info.port_name, 115_200)
        .flow_control(FlowControl::None)
        .open()
        .map_err(Error::from)
        .wrap_err_with(|| format!("Failed to open serial port {}", port_info.port_name))
}

impl Interface {
    #[cfg(feature = "raspberry")]
    pub fn new(port_info: &SerialPortInfo, dtr: Option<u8>, rts: Option<u8>) -> Result<Self> {
        if port_info.port_type == serialport::SerialPortType::Unknown
            && (dtr.is_none() || rts.is_none())
        {
            // Assume internal UART, which has no DTR pin and usually no RTS either.
            return Err(Error::from(SerialConfigError::MissingDtrRtsForInternalUart).into());
        }

        let gpios = Gpio::new().unwrap();

        let rts = if let Some(gpio) = rts {
            match gpios.get(gpio) {
                Ok(pin) => Some(pin.into_output()),
                Err(_) => return Err(Error::from(SerialConfigError::GpioUnavailable(gpio)).into()),
            }
        } else {
            None
        };

        let dtr = if let Some(gpio) = dtr {
            match gpios.get(gpio) {
                Ok(pin) => Some(pin.into_output()),
                Err(_) => return Err(Error::from(SerialConfigError::GpioUnavailable(gpio)).into()),
            }
        } else {
            None
        };

        Ok(Self {
            serial_port: open_port(port_info)?,
            rts,
            dtr,
        })
    }

    #[cfg(not(feature = "raspberry"))]
    pub fn new(port_info: &SerialPortInfo, _dtr: Option<u8>, _rts: Option<u8>) -> Result<Self> {
        Ok(Self {
            serial_port: open_port(port_info)?,
        })
    }

    /// Set the level of the DTR pin
    pub fn write_data_terminal_ready(&mut self, pin_state: bool) -> serialport::Result<()> {
        #[cfg(feature = "raspberry")]
        if let Some(gpio) = self.dtr.as_mut() {
            write_gpio(gpio, pin_state);
            return Ok(());
        }

        self.serial_port.write_data_terminal_ready(pin_state)
    }

    /// Set the level of the RTS pin
    pub fn write_request_to_send(&mut self, pin_state: bool) -> serialport::Result<()> {
        #[cfg(feature = "raspberry")]
        if let Some(gpio) = self.rts.as_mut() {
            write_gpio(gpio, pin_state);
            return Ok(());
        }

        self.serial_port.write_request_to_send(pin_state)
    }

    /// Turn an [Interface] into a [SerialPort]
    pub fn into_serial(self) -> Box<dyn SerialPort> {
        self.serial_port
    }

    /// Turn an [Interface] into a `&`[SerialPort]
    pub fn serial_port(&self) -> &dyn SerialPort {
        self.serial_port.as_ref()
    }

    /// Turn an [Interface] into a  `&mut `[SerialPort]
    pub fn serial_port_mut(&mut self) -> &mut dyn SerialPort {
        self.serial_port.as_mut()
    }
}

// Note(dbuga): this `impl` is necessary because using `dyn SerialPort` as `dyn
// Read` requires trait_upcasting which isn't stable yet.
impl Read for Interface {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.serial_port.read(buf)
    }
}