HTTP Client

Next, we'll write a small client that retrieves data over an HTTP connection to the internet.

For demonstration purposes we implement the http client ourselves. Usually you want to use e.g. reqwless or edge-net

Before jumping to the exercise, let's explore how Wi-Fi works in no_std Rust for Espressif devices.

Wi-Fi Ecosystem

Wi-Fi support comes in the esp-wifi crate. The esp-wifi is home to the Wi-Fi, Bluetooth and ESP-NOW driver implementations for no_std Rust. Check the repository README for current support, limitations and usage details.

There are some other relevant crates, on which esp-wifi depends on:

  • smol-tcp: Event-driven TCP/IP stack implementation.
    • It does not require heap allocation (which is a requirement for some no_std projects)
    • For more information about the crate, see the official documentation

Additionally when using async, embassy-net is relevant.

Setup

✅ Go to intro/http-client directory.

✅ Open the prepared project skeleton in intro/http-client.

✅ Add your network credentials: Set the SSID and PASSWORD environment variables.

intro/http-client/examples/http-client.rs contains the solution. You can run it with the following command:

cargo run --release --example http-client

✅ Read the Optimization Level section of the esp-wifi README.

Exercise

✅ Bump the clock frequency at which the target operates to its maximum. Consider using ClockControl::configure or ClockControl::max

✅ Create a timer and initialize the Wi-Fi

    let timer = SystemTimer::new(peripherals.SYSTIMER).alarm0;
    let init = initialize(
        EspWifiInitFor::Wifi,
        timer,
        Rng::new(peripherals.RNG),
        system.radio_clock_control,
        &clocks,
    )
    .unwrap();

✅ Configure Wi-Fi using Station Mode

    let wifi = peripherals.WIFI;
    let mut socket_set_entries: [SocketStorage; 3] = Default::default();
    let (iface, device, mut controller, sockets) =
        create_network_interface(&init, wifi, WifiStaDevice, &mut socket_set_entries).unwrap();

✅ Create a Client with your Wi-Fi credentials and default configuration. Look for a suitable constructor in the documentation.

    let client_config = Configuration::Client(ClientConfiguration {
    ....
    });

    let res = controller.set_configuration(&client_config);
    println!("Wi-Fi set_configuration returned {:?}", res);

✅ Start the Wi-Fi controller, scan the available networks, and try to connect to the one we set.

    controller.start().unwrap();
    println!("Is wifi started: {:?}", controller.is_started());

    println!("Start Wifi Scan");
    let res: Result<(heapless::Vec<AccessPointInfo, 10>, usize), WifiError> = controller.scan_n();
    if let Ok((res, _count)) = res {
        for ap in res {
            println!("{:?}", ap);
        }
    }

    println!("{:?}", controller.get_capabilities());
    println!("Wi-Fi connect: {:?}", controller.connect());

    // Wait to get connected
    println!("Wait to get connected");
    loop {
        let res = controller.is_connected();
        match res {
            Ok(connected) => {
                if connected {
                    break;
                }
            }
            Err(err) => {
                println!("{:?}", err);
                loop {}
            }
        }
    }
    println!("{:?}", controller.is_connected());

✅ Then we obtain the assigned IP

    // Wait for getting an ip address
    let wifi_stack = WifiStack::new(iface, device, sockets, current_millis);
    println!("Wait to get an ip address");
    loop {
        wifi_stack.work();

        if wifi_stack.is_iface_up() {
            println!("got ip {:?}", wifi_stack.get_ip_info());
            break;
        }
    }

If the connection succeeds, we proceed with the last part, making the HTTP request.

By default, only unencrypted HTTP is available, which limits our options of hosts to connect to. We're going to use www.mobile-j.de/.

To make an HTTP request, we first need to open a socket, and write to it the GET request,

✅ Open a socket with the following IPv4 address 142.250.185.115 and port 80. See IpAddress::Ipv4 documentation.

write the following message to the socket and flush it: b"GET / HTTP/1.0\r\nHost: www.mobile-j.de\r\n\r\n"

✅ Then we wait for the response and read it out.

        let wait_end = current_millis() + 20 * 1000;
        loop {
            let mut buffer = [0u8; 512];
            if let Ok(len) = socket.read(&mut buffer) {
                let to_print = unsafe { core::str::from_utf8_unchecked(&buffer[..len]) };
                print!("{}", to_print);
            } else {
                break;
            }

            if current_millis() > wait_end {
                println!("Timeout");
                break;
            }
        }
        println!();

✅ Finally, we will close the socket and wait

        socket.disconnect();

        let wait_end = current_millis() + 5 * 1000;
        while current_millis() < wait_end {
            socket.work();
        }

Simulation

This project is available for simulation through two methods:

  • Wokwi projects:
    • Exercise: Currently not available
    • Solution: Currently not available
  • Wokwi files are also present in the project folder to simulate it with Wokwi VS Code extension:
    1. Press F1, select Wokwi: Select Config File and choose intro/http-client/wokwi.toml
      • Edit the wokwi.toml file to select between exercise and solution simulation
    2. Build you project
    3. Press F1 again and select Wokwi: Start Simulator