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
use anyhow::{ensure, Result};
use embedded_svc::{http::client::Client as HttpClient, io::Write};
use esp_idf_svc::http::client::{Configuration, EspHttpConnection};

use crate::wifi::Connection;

/// Represents an HTTP client that interacts with a server over Wi-Fi.
///
/// This struct provides methods to send HTTP requests, such as POST requests, using the ESP-IDF framework.
/// It owns an active Wi-Fi connection for the duration of its lifetime.
pub struct Client<'a> {
    client: HttpClient<EspHttpConnection>,
    wifi: Connection<'a>,
}

impl<'a> Client<'a> {
    /// Creates a new `Client` instance with the given Wi-Fi connection.
    ///
    /// # Arguments
    ///
    /// * `wifi` - An active Wi-Fi connection.
    ///
    /// # Returns
    ///
    /// A new `Client` ready to send HTTP requests.
    ///
    /// # Errors
    ///
    /// Returns an error if the HTTP client cannot be initialized.
    pub fn new(wifi: Connection<'a>) -> Result<Self> {
        let client =
            HttpClient::wrap(EspHttpConnection::new(&Configuration::default())?);
        Ok(Self { client, wifi })
    }

    /// Sends a POST request to the specified URL with an optional payload.
    ///
    /// # Arguments
    ///
    /// * `url` - The URL to send the POST request to.
    /// * `payload` - An optional byte slice containing the payload to send.
    ///
    /// # Returns
    ///
    /// The HTTP status code of the response.
    ///
    /// # Errors
    ///
    /// Returns an error if the Wi-Fi is not connected, the request fails, or the response status is not in the success range.
    pub fn post(&mut self, url: &str, payload: Option<&[u8]>) -> Result<u16> {
        ensure!(self.wifi.is_on()?, "WIFI is off");

        let payload = payload.unwrap_or(b"");
        let content_length_header = format!("{}", payload.len());
        let headers = [
            ("content-type", "text/plain"),
            ("content-length", &*content_length_header),
        ];

        let mut request = self.client.post(url, &headers)?;
        request.write_all(payload)?;
        request.flush()?;

        let response = request.submit()?;
        let status = response.status();
        ensure!(
            (200..300).contains(&status),
            "Request failed with status: {}",
            status
        );

        Ok(status)
    }
}