drogue_esp8266/
protocol.rs

1use core::fmt;
2use core::fmt::{Debug, Write};
3use drogue_network::addr::{IpAddr, Ipv4Addr, SocketAddr};
4use heapless::{
5    String,
6    consts::{
7        U128,
8    }
9};
10
11#[derive(Debug)]
12pub struct ResolverAddresses {
13    pub resolver1: Ipv4Addr,
14    pub resolver2: Option<Ipv4Addr>,
15}
16
17/// Type of socket connection.
18#[derive(Debug)]
19pub enum ConnectionType {
20    TCP,
21    UDP,
22}
23
24/// Mode of the Wi-Fi stack
25#[derive(Debug)]
26pub enum WiFiMode {
27    /// Station mode, aka client
28    Station,
29    /// Access point mode
30    SoftAccessPoint,
31    /// Access point + station mode
32    SoftAccessPointAndStation,
33}
34
35/// Commands to be sent to the ESP board.
36#[derive(Debug)]
37pub enum Command<'a> {
38    QueryFirmwareInfo,
39    SetMode(WiFiMode),
40    JoinAp { ssid: &'a str, password: &'a str },
41    QueryIpAddress,
42    StartConnection(usize, ConnectionType, SocketAddr),
43    CloseConnection(usize),
44    Send { link_id: usize, len: usize },
45    Receive { link_id: usize, len: usize },
46    QueryDnsResolvers,
47    SetDnsResolvers(ResolverAddresses),
48    GetHostByName{ hostname: &'a str},
49}
50
51impl<'a> Command<'a> {
52    pub fn as_bytes(&self) -> String<U128> {
53        match self {
54            Command::QueryFirmwareInfo => String::from("AT+GMR"),
55            Command::QueryIpAddress => String::from("AT+CIPSTA_CUR?"),
56            Command::SetMode(mode)=> match mode {
57                WiFiMode::Station => String::from("AT+CWMODE_CUR=1"),
58                WiFiMode::SoftAccessPoint => String::from("AT+CWMODE_CUR=2"),
59                WiFiMode::SoftAccessPointAndStation => String::from("AT+CWMODE_CUR=3"),
60            }
61            Command::JoinAp { ssid, password } => {
62                let mut s = String::from("AT+CWJAP_CUR=\"");
63                s.push_str(ssid).unwrap();
64                s.push_str("\",\"").unwrap();
65                s.push_str(password).unwrap();
66                s.push_str("\"").unwrap();
67                s
68            }
69            Command::StartConnection(link_id, connection_type, socket_addr) => {
70                let mut s = String::from("AT+CIPSTART=");
71                write!(s, "{},", link_id).unwrap();
72                match connection_type {
73                    ConnectionType::TCP => {
74                        write!(s, "\"TCP\"").unwrap();
75                    }
76                    ConnectionType::UDP => {
77                        write!(s, "\"UDP\"").unwrap();
78                    }
79                }
80                write!(s, ",").unwrap();
81                match socket_addr.ip() {
82                    IpAddr::V4(ip) => {
83                        let octets = ip.octets();
84                        write!(
85                            s,
86                            "\"{}.{}.{}.{}\",{}",
87                            octets[0],
88                            octets[1],
89                            octets[2],
90                            octets[3],
91                            socket_addr.port()
92                        )
93                        .unwrap();
94                    }
95                    IpAddr::V6(_) => panic!("IPv6 not supported"),
96                }
97                s as String<U128>
98            }
99            Command::CloseConnection(link_id) => {
100                let mut s = String::from("AT+CIPCLOSE=");
101                write!(s, "{}", link_id).unwrap();
102                s
103            }
104            Command::Send { link_id, len } => {
105                let mut s = String::from("AT+CIPSEND=");
106                write!(s, "{},{}", link_id, len).unwrap();
107                s
108            }
109            Command::Receive { link_id, len } => {
110                let mut s = String::from("AT+CIPRECVDATA=");
111                write!(s, "{},{}", link_id, len).unwrap();
112                s
113            }
114            Command::QueryDnsResolvers => {
115                String::from("AT+CIPDNS_CUR?")
116            }
117            Command::SetDnsResolvers(addr) => {
118                let mut s = String::from("AT+CIPDNS_CUR=1,");
119                write!(s, "\"{}\"", addr.resolver1).unwrap();
120                if let Some(resolver2) = addr.resolver2 {
121                    write!(s, ",\"{}\"", resolver2 ).unwrap()
122                }
123                s
124            }
125            Command::GetHostByName { hostname } => {
126                let mut s = String::from("AT+CIPDOMAIN=");
127                write!(s, "\"{}\"", hostname).unwrap();
128                s
129            }
130        }
131    }
132}
133
134/// Responses (including unsolicited) which may be parsed from the board.
135#[allow(clippy::large_enum_variant)]
136pub enum Response {
137    None,
138    Ok,
139    Error,
140    FirmwareInfo(FirmwareInfo),
141    ReadyForData,
142    ReceivedDataToSend(usize),
143    SendOk,
144    SendFail,
145    DataAvailable { link_id: usize, len: usize },
146    DataReceived([u8; crate::BUFFER_LEN], usize),
147    WifiConnected,
148    WifiConnectionFailure(WifiConnectionFailure),
149    WifiDisconnect,
150    GotIp,
151    IpAddresses(IpAddresses),
152    Connect(usize),
153    Closed(usize),
154    Resolvers(ResolverAddresses),
155    IpAddress(IpAddr),
156    DnsFail,
157    UnlinkFail,
158}
159
160impl Debug for Response {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        match self {
163            Response::None => f.write_str("None"),
164            Response::Ok => f.write_str("Ok"),
165            Response::Error => f.write_str("Error"),
166            Response::FirmwareInfo(v) => f.debug_tuple("FirmwareInfo").field(v).finish(),
167            Response::ReadyForData => f.write_str("ReadyForData"),
168            Response::ReceivedDataToSend(len) => f.debug_tuple("ReceivedDataToSend").field(len).finish(),
169            Response::SendOk =>f.write_str("SendOk"),
170            Response::SendFail => f.write_str("SendFail"),
171            Response::DataAvailable { link_id, len } => f
172                .debug_struct("DataAvailable")
173                .field("link_id", link_id)
174                .field("len", len)
175                .finish(),
176            //Response::DataReceived(d, l) => dump_data("DataReceived", d, *l, f),
177            Response::DataReceived(_, _) => f.write_str("DataReceived"),
178            Response::WifiConnected => f.write_str("WifiConnected"),
179            Response::WifiConnectionFailure(v) => {
180                f.debug_tuple("WifiConnectionFailure").field(v).finish()
181            }
182            Response::WifiDisconnect => f.write_str("WifiDisconnect"),
183            Response::GotIp => f.write_str("GotIp"),
184            Response::IpAddresses(v) => f.debug_tuple("IpAddresses").field(v).finish(),
185            Response::Connect(v) => f.debug_tuple("Connect").field(v).finish(),
186            Response::Closed(v) => f.debug_tuple("Closed").field(v).finish(),
187            Response::IpAddress( v) => f.debug_tuple( "IpAddress").field(v).finish(),
188            Response::Resolvers(v) => f.debug_tuple( "Resolvers").field(v).finish(),
189            Response::DnsFail => f.write_str("DNS Fail"),
190            Response::UnlinkFail => f.write_str("UnlinkFail"),
191        }
192    }
193}
194
195/// IP addresses for the board, including its own address, netmask and gateway.
196#[derive(Debug)]
197pub struct IpAddresses {
198    pub ip: Ipv4Addr,
199    pub gateway: Ipv4Addr,
200    pub netmask: Ipv4Addr,
201}
202
203/// Version information for the ESP board.
204#[derive(Debug)]
205pub struct FirmwareInfo {
206    pub major: u8,
207    pub minor: u8,
208    pub patch: u8,
209    pub build: u8,
210}
211
212/// Reasons for Wifi access-point join failures.
213#[derive(Debug)]
214pub enum WifiConnectionFailure {
215    Timeout,
216    WrongPassword,
217    CannotFindTargetAp,
218    ConnectionFailed,
219}
220
221
222
223impl From<u8> for WifiConnectionFailure {
224    fn from(code: u8) -> Self {
225        match code {
226            1 => WifiConnectionFailure::Timeout,
227            2 => WifiConnectionFailure::WrongPassword,
228            3 => WifiConnectionFailure::CannotFindTargetAp,
229            _ => WifiConnectionFailure::ConnectionFailed,
230        }
231    }
232}
233
234/// Dump some data, which is stored in a buffer with a length indicator.
235///
236/// The output will contain the field name, the data as string (only 7bits) and the raw bytes
237/// in hex encoding.
238#[allow(dead_code)]
239fn dump_data(name: &str, data: &[u8], len: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240    let data = &data[0..len];
241
242    f.write_str(name)?;
243    f.write_char('(')?;
244
245    f.write_fmt(format_args!("{}; '", len))?;
246
247    for d in data {
248        if *d == 0 {
249            f.write_str("\\0")?;
250        } else if *d <= 0x7F {
251            f.write_char(*d as char)?;
252        } else {
253            f.write_char('\u{FFFD}')?;
254        }
255    }
256
257    f.write_str("'; ")?;
258    f.write_fmt(format_args!("{:X?}", data))?;
259    f.write_char(')')?;
260
261    Ok(())
262}
263
264#[cfg(test)]
265mod test {
266    use super::*;
267    use arrayvec::ArrayString;
268    use core::fmt::Write;
269
270    #[test]
271    fn test_debug_no_value() {
272        let mut buf = ArrayString::<[u8; 20]>::new();
273
274        write!(&mut buf, "{:?}", Response::Ok).expect("Can't write");
275        assert_eq!(&buf, "Ok");
276    }
277
278    #[test]
279    fn test_debug_simple_value() {
280        let mut buf = ArrayString::<[u8; 20]>::new();
281
282        write!(&mut buf, "{:?}", Response::Connect(1)).expect("Can't write");
283        assert_eq!(&buf, "Connect(1)");
284    }
285
286    #[test]
287    fn test_debug_data() {
288        let mut buf = ArrayString::<[u8; 256]>::new();
289        let data = b"FOO\0BAR";
290
291        let mut array = [0u8; 128];
292        for (&x, p) in data.iter().zip(array.iter_mut()) {
293            *p = x;
294        }
295
296        write!(&mut buf, "{:?}", Response::DataReceived(array, data.len())).expect("Can't write");
297        assert_eq!(
298            &buf,
299            "DataReceived(7; 'FOO\\0BAR'; [46, 4F, 4F, 0, 42, 41, 52])"
300        );
301    }
302}