esp_01/
lib.rs

1#![no_std]
2
3use embedded_hal as hal;
4
5use nb::block;
6
7use core::cmp::min;
8use core::fmt::{self};
9use arrayvec::{CapacityError, ArrayString};
10use itoa;
11
12mod serial;
13mod timing;
14
15pub use timing::{LongTimer, Second, Millisecond};
16
17/**
18    Maximum length of an AT response (Length of message + CRLF)
19
20    longest message: `WIFI GOT IP\r\n`
21*/
22const AT_RESPONSE_BUFFER_SIZE: usize = 13;
23
24/**
25  Possible responses from an esp8266 AT command.
26
27  This does not contain all possible responses but it does contain
28  ever response that can be received from the commands sent in this crates
29*/
30#[derive(Debug, PartialEq)]
31pub enum ATResponse {
32    Ok,
33    Error,
34    Busy,
35    WiFiGotIp,
36}
37
38/**
39  Error type for esp communication.
40
41  `R` and `T` are the error types of the serial module
42*/
43#[derive(Debug)]
44pub enum Error<R, T, P> {
45    /// Serial transmission errors
46    TxError(T),
47    /// Serial reception errors
48    RxError(R),
49    // Digital pin errors
50    PinError(P),
51    /// Invalid or unexpected data received from the device
52    UnexpectedResponse(ATResponse),
53    /// Errors from the formating of messages
54    Fmt(fmt::Error),
55    /// Error indicating an ArrayString wasn't big enough
56    Capacity(CapacityError)
57}
58impl<R,T, P> From<fmt::Error> for Error<R,T, P> {
59    fn from(other: fmt::Error) -> Error<R,T, P> {
60        Error::Fmt(other)
61    }
62}
63impl<R,T,ErrType,P> From<CapacityError<ErrType>> for Error<R,T,P> {
64    fn from(other: CapacityError<ErrType>) -> Error<R,T,P> {
65        Error::Capacity(other.simplify())
66    }
67}
68
69/**
70    Indicates what step in the data transmission that the sensor is in. Used
71    in `TransmissionError` for reporting information about where things went wrong
72*/
73#[derive(Debug)]
74pub enum TransmissionStep {
75    Connect,
76    Send,
77    Close
78}
79/**
80  Error indicating failure to transmit a message.
81*/
82#[derive(Debug)]
83pub struct TransmissionError<R, T, P> {
84    step: TransmissionStep,
85    cause: Error<R, T, P>
86}
87
88impl<R, T, P> TransmissionError<R, T, P> {
89    pub fn try_step<RetType>(step: TransmissionStep, cause: Result<RetType, Error<R, T, P>>) 
90        -> Result<RetType, Self>
91    {
92        cause.map_err(|e| {
93            Self {
94                step,
95                cause: e
96            }
97        })
98    }
99}
100
101
102pub enum ConnectionType {
103    Tcp,
104    Udp
105}
106impl ConnectionType {
107    pub fn as_str(&self) -> &str {
108        match *self {
109            ConnectionType::Tcp => "TCP",
110            ConnectionType::Udp => "UDP"
111        }
112    }
113}
114
115
116macro_rules! return_type {
117    ($ok:ty) => {
118        Result<$ok, Error<serial::Error<Rx::Error>, Tx::Error, Rst::Error>>
119    }
120}
121
122macro_rules! transmission_return_type {
123    ($ok:ty) => {
124        Result<$ok, TransmissionError<serial::Error<Rx::Error>, Tx::Error, Rst::Error>>
125    }
126}
127
128
129////////////////////////////////////////////////////////////////////////////////
130////////////////////////////////////////////////////////////////////////////////
131
132const STARTUP_TIMEOUT: Second = Second(10);
133const DEFAULT_TIMEOUT: Second = Second(5);
134
135
136/**
137  Struct for interracting with an esp8266 wifi module over USART
138*/
139pub struct Esp8266<Tx, Rx, Timer, Rst>
140where Tx: hal::serial::Write<u8>,
141      Rx: hal::serial::Read<u8>,
142      Timer: LongTimer,
143      Rst: hal::digital::v2::OutputPin
144{
145    tx: Tx,
146    rx: Rx,
147    timer: Timer,
148    chip_enable_pin: Rst
149}
150
151impl<Tx, Rx, Timer, Rst> Esp8266<Tx, Rx, Timer, Rst>
152where Tx: hal::serial::Write<u8>,
153      Rx: hal::serial::Read<u8>,
154      Timer: LongTimer,
155      Rst: hal::digital::v2::OutputPin,
156{
157    /**
158      Sets up the esp8266 struct and configures the device for future use
159
160      `tx` and `rx` are the pins used for serial communication, `timer` is
161      a hardware timer for dealing with things like serial timeout and
162      `chip_enable_pin` is a pin which must be connected to the CHIP_EN pin
163      of the device
164    */
165    pub fn new(tx: Tx, rx: Rx, timer: Timer, chip_enable_pin: Rst)
166        -> return_type!(Self)
167    {
168        let mut result = Self {tx, rx, timer, chip_enable_pin};
169
170        result.reset()?;
171
172        Ok(result)
173    }
174
175    pub fn send_data(
176        &mut self,
177        connection_type: ConnectionType,
178        address: &str,
179        port: u16,
180        data: &str
181    ) -> transmission_return_type!(())
182    {
183        // Send a start connection message
184        let tcp_start_result = self.start_tcp_connection(connection_type, address, port);
185        TransmissionError::try_step(TransmissionStep::Connect, tcp_start_result)?;
186
187        TransmissionError::try_step(TransmissionStep::Send, self.transmit_data(data))?;
188
189        TransmissionError::try_step(TransmissionStep::Close, self.close_connection())
190    }
191
192    pub fn close_connection(&mut self) -> return_type!(()) {
193        self.send_at_command("+CIPCLOSE")?;
194        self.wait_for_ok(DEFAULT_TIMEOUT.into())
195    }
196
197    /**
198      Turns off the device by setting chip_enable to 0
199    */
200    pub fn power_down(&mut self) -> return_type!(()) {
201        self.chip_enable_pin.set_low().map_err(Error::PinError)
202    }
203
204    /**
205      Resets the device by setting chip_enable to 0 and then back to 1
206    */
207    pub fn reset(&mut self) -> return_type!(()) {
208        self.power_down()?;
209        self.timer.start(Millisecond(10));
210        block!(self.timer.wait()).unwrap();
211        self.power_up()
212    }
213
214    /**
215      Turns the device back on by setting chip_enable to high
216    */
217    pub fn power_up(&mut self) -> return_type!(()) {
218        self.chip_enable_pin.set_high().map_err(Error::PinError)?;
219
220        // The esp01 sends a bunch of garbage over the serial port before starting properly,
221        // therefore we need to retry this until we get valid data or time out
222        let mut error_count = 0;
223        loop {
224            match self.wait_for_got_ip(STARTUP_TIMEOUT.into()) {
225                Ok(()) => break,
226                e @ Err(Error::RxError(serial::Error::TimedOut)) => return e,
227                e => {
228                    if error_count < 255 {
229                        error_count += 1;
230                        continue
231                    }
232                    else {
233                        return e
234                    }
235                }
236            }
237        }
238
239        // Turn off echo on the device and wait for it to process that command
240        self.send_at_command("E0")?;
241        self.wait_for_ok(DEFAULT_TIMEOUT.into())?;
242
243        Ok(())
244    }
245
246    pub fn pull_some_current(&mut self) -> return_type!(()) {
247        self.chip_enable_pin.set_high().map_err(Error::PinError)?;
248
249        self.timer.start(Millisecond(500));
250        block!(self.timer.wait()).unwrap();
251        self.chip_enable_pin.set_low().map_err(Error::PinError)
252    }
253
254    fn transmit_data(&mut self, data: &str) -> return_type!(()) {
255        self.start_transmission(data.len())?;
256        self.wait_for_prompt(DEFAULT_TIMEOUT.into())?;
257        self.send_raw(data.as_bytes())?;
258        self.wait_for_ok(DEFAULT_TIMEOUT.into())
259    }
260
261    fn start_tcp_connection (
262        &mut self,
263        connection_type: ConnectionType,
264        address: &str,
265        port: u16
266    ) -> return_type!(())
267    {
268        // Length of biggest u16:
269        const PORT_STRING_LENGTH: usize = 5;
270        let mut port_str = ArrayString::<[_;PORT_STRING_LENGTH]>::new();
271        // write!(&mut port_str, "{}", port)?;
272        itoa::fmt(&mut port_str, port)?;
273
274        self.send_raw("AT+CIPSTART=\"".as_bytes())?;
275        self.send_raw(connection_type.as_str().as_bytes())?;
276        self.send_raw("\",\"".as_bytes())?;
277        self.send_raw(address.as_bytes())?;
278        self.send_raw("\",".as_bytes())?;
279        self.send_raw(port_str.as_bytes())?;
280        self.send_raw("\r\n".as_bytes())?;
281        self.wait_for_ok(DEFAULT_TIMEOUT.into())
282    }
283
284    fn start_transmission(&mut self, message_length: usize) -> return_type!(()) {
285        // You can only send 2048 bytes per packet 
286        assert!(message_length < 2048);
287        let mut length_buffer = ArrayString::<[_; 4]>::new();
288        // write!(&mut length_buffer, "{}", message_length)?;
289        itoa::fmt(&mut length_buffer, message_length)?;
290
291        self.send_raw(b"AT+CIPSEND=")?;
292        self.send_raw(length_buffer.as_bytes())?;
293        self.send_raw(b"\r\n")?;
294        Ok(())
295    }
296
297    /**
298      Sends the "AT${command}" to the device
299    */
300    fn send_at_command(&mut self, command: &str) -> return_type!(()) {
301        self.send_raw(b"AT")?;
302        self.send_raw(command.as_bytes())?;
303        self.send_raw(b"\r\n")?;
304        Ok(())
305    }
306
307    fn wait_for_at_response(
308        &mut self,
309        expected_response: &ATResponse,
310        timeout: Millisecond
311    ) -> return_type!(()) {
312        let mut buffer = [0; AT_RESPONSE_BUFFER_SIZE];
313        let response = serial::read_until_message(
314            &mut self.rx,
315            &mut self.timer,
316            timeout,
317            &mut buffer,
318            &parse_at_response
319        );
320
321        match response {
322            Ok(ref resp) if resp == expected_response => {
323                Ok(())
324            },
325            Ok(other) => {
326                Err(Error::UnexpectedResponse(other))
327            }
328            Err(e) => {
329                Err(Error::RxError(e))
330            }
331        }
332    }
333
334    fn wait_for_ok(&mut self, timeout: Millisecond) -> return_type!(()) {
335        self.wait_for_at_response(&ATResponse::Ok, timeout)
336    }
337    fn wait_for_got_ip(&mut self, timeout: Millisecond) -> return_type!(()) {
338        self.wait_for_at_response(&ATResponse::WiFiGotIp, timeout)
339    }
340
341    fn wait_for_prompt(&mut self, timeout: Millisecond) -> return_type!(()) {
342        let mut buffer = [0; 1];
343        let result = serial::read_until_message(
344            &mut self.rx,
345            &mut self.timer,
346            timeout,
347            &mut buffer,
348            &|buf, _ptr| {
349                if buf[0] == '>' as u8 {
350                    Some(())
351                }
352                else {
353                    None
354                }
355            }
356        );
357        match result {
358            Ok(_) => Ok(()),
359            Err(e) => Err(Error::RxError(e))
360        }
361    }
362
363    fn send_raw(&mut self, bytes: &[u8]) -> return_type!(()) {
364        match serial::write_all(&mut self.tx, bytes) {
365            Ok(_) => Ok(()),
366            Err(e) => Err(Error::TxError(e))
367        }
368    }
369}
370
371/**
372  Parses `buffer` as an AT command response returning the type if it
373  is a valid AT response and `None` otherwise
374*/
375pub fn parse_at_response(buffer: &[u8], offset: usize) -> Option<ATResponse> {
376    if compare_circular_buffer(buffer, offset, "OK\r\n".as_bytes()) {
377        Some(ATResponse::Ok)
378    }
379    else if compare_circular_buffer(buffer, offset, "ERROR\r\n".as_bytes()) {
380        Some(ATResponse::Error)
381    }
382    else if compare_circular_buffer(buffer, offset, "busy p...\r\n".as_bytes()) {
383        Some(ATResponse::Busy)
384    }
385    else if compare_circular_buffer(buffer, offset, "WIFI GOT IP\r\n".as_bytes()) {
386        Some(ATResponse::WiFiGotIp)
387    }
388    else {
389        None
390    }
391}
392
393/**
394  Compares the content of a circular buffer with another buffer. The comparison
395  is done 'from the back' and if one buffer is longer than the other, only the
396  content of the shared bytes is compared.
397
398  `offset` is the index of the first byte of the circular buffer
399  ```
400  [4,5,0,1,2,3]
401       ^- offset
402  ```
403*/
404pub fn compare_circular_buffer(
405    circular_buffer: &[u8],
406    offset: usize,
407    comparison: &[u8]
408) -> bool
409{
410    let comparison_length = min(circular_buffer.len(), comparison.len());
411    for i in 0..comparison_length {
412        // Addition of circular_buffer.len() because % is remainder, not mathematical modulo
413        // https://stackoverflow.com/questions/31210357/is-there-a-modulus-not-remainder-function-operation/31210691
414        let circular_index = (circular_buffer.len() + offset - 1 - i) % circular_buffer.len();
415        let comparison_index = comparison.len() - 1 - i;
416        if circular_buffer[circular_index] != comparison[comparison_index] {
417            return false;
418        }
419    }
420    true
421}
422