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
use std::sync::Arc;

#[cfg(feature = "tokio")]
use tokio_serial::{DataBits, FlowControl, Parity, SerialPortBuilder, SerialStream, StopBits};

use unicom::{Backend, BoxedConnect, BoxedConnection, BoxedConnector, Connector, Error, Url};

/// Serial port backend
///
/// Support connecting to devices via serial ports
#[derive(Clone)]
pub struct SerialPort {
    name: String,
    description: String,
}

impl Default for SerialPort {
    fn default() -> Self {
        Self {
            name: "serial-port".into(),
            description: "Support for serial port connections.".into(),
        }
    }
}

impl Backend for SerialPort {
    fn name(&self) -> &str {
        &self.name
    }

    fn description(&self) -> &str {
        &self.description
    }

    fn connector(&self, url: &Url) -> Option<BoxedConnector> {
        if (url.scheme() == "serial"
            || url.scheme() == "console"
            || url.scheme() == "tty"
            || url.scheme() == "com")
            && !url.has_host()
            && url.path() != "/"
        {
            let builder = tokio_serial::new(url.path(), 115200);
            let builder =
                if let Some(baud_rate) = url.fragment().and_then(|val| val.parse::<u32>().ok()) {
                    builder.baud_rate(baud_rate)
                } else {
                    builder
                };
            let builder = url
                .query_pairs()
                .fold(builder, |builder, (name, val)| match &*name {
                    "baud" | "baudrate" | "baud_rate" => {
                        if let Ok(baud_rate) = val.parse::<u32>() {
                            builder.baud_rate(baud_rate)
                        } else {
                            builder
                        }
                    }
                    "data" | "databits" | "data_bits" => match &*val {
                        "5" | "five" => builder.data_bits(DataBits::Five),
                        "6" | "six" => builder.data_bits(DataBits::Six),
                        "7" | "seven" => builder.data_bits(DataBits::Seven),
                        "8" | "eight" => builder.data_bits(DataBits::Eight),
                        _ => builder,
                    },
                    "flow" | "flowctrl" | "flowcontrol" | "flow_ctrl" | "flow_control" => {
                        match &*val {
                            "none" | "no" | "off" => builder.flow_control(FlowControl::None),
                            "soft" | "sw" | "software" => {
                                builder.flow_control(FlowControl::Software)
                            }
                            "hard" | "hw" | "hardware" => {
                                builder.flow_control(FlowControl::Hardware)
                            }
                            _ => builder,
                        }
                    }
                    "parity" => match &*val {
                        "none" | "no" => builder.parity(Parity::None),
                        "odd" | "od" => builder.parity(Parity::Odd),
                        "even" | "evn" | "ev" => builder.parity(Parity::Even),
                        _ => builder,
                    },
                    "stop" | "stopbits" | "stop_bits" => match &*val {
                        "1" | "one" => builder.stop_bits(StopBits::One),
                        "2" | "two" => builder.stop_bits(StopBits::Two),
                        _ => builder,
                    },
                    _ => builder,
                });
            let url = url.clone();
            Some(Arc::new(SerialConnector { url, builder }))
        } else {
            None
        }
    }
}

#[derive(Clone)]
struct SerialConnector {
    url: Url,
    builder: SerialPortBuilder,
}

impl Connector for SerialConnector {
    fn url(&self) -> &Url {
        &self.url
    }

    fn connect(&self) -> BoxedConnect {
        let this = self.clone();
        Box::pin(async move {
            let mut stm = SerialStream::open(&this.builder)
                .map_err(|err| Error::FailedConnect(err.to_string()))?;
            #[cfg(unix)]
            stm.set_exclusive(true)
                .map_err(|err| Error::FailedConnect(err.to_string()))?;
            Ok(Box::new(stm) as BoxedConnection)
        })
    }
}