HTTP Client
Next, we'll write a small client that retrieves data over an HTTP connection to the internet.
For demonstration purposes we use reqwless to create the request and parse the response. embassy-net provides TCP/IP and DNS.
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_stdprojects) - For more information about the crate, see the official documentation
- It does not require heap allocation (which is a requirement for some
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 timg0 = TimerGroup::new(peripherals.TIMG0);
let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
esp_rtos::start(timg0.timer0, sw_int.software_interrupt0);
✅ Configure Wi-Fi using Station Mode
let station_config = Config::Station(
StationConfig::default()
.with_ssid(SSID)
.with_password(PASSWORD.into()),
);
✅ Create a Client with your Wi-Fi credentials and default configuration. Look for a suitable constructor in the documentation.
println!("Starting Wi-Fi");
let (mut controller, interfaces) = esp_radio::wifi::new(
peripherals.WIFI,
ControllerConfig::default().with_initial_config(station_config),
)
.unwrap();
println!("Wi-Fi configured and started");
let wifi_interface = interfaces.station;
let config = embassy_net::Config::dhcpv4(Default::default());
let rng = Rng::new();
let seed = (rng.random() as u64) << 32 | rng.random() as u64;
let (stack, runner) = embassy_net::new(
wifi_interface,
config,
mk_static!(StackResources<3>, StackResources::<3>::new()),
seed,
);
println!("Scanning for access points");
let scan_config = ScanConfig::default().with_max(10);
let result = controller.scan_async(&scan_config).await.unwrap();
for ap in result {
println!("{:?}", ap);
}
spawner.spawn(connection(controller).unwrap());
spawner.spawn(net_task(runner).unwrap());
stack.wait_config_up().await;
if let Some(config) = stack.config_v4() {
println!("Got IP: {}", config.address);
}
let tcp_client = TcpClient::new(
stack,
mk_static!(
TcpClientState<1, 1500, 1500>,
TcpClientState::<1, 1500, 1500>::new()
),
);
let dns_client = DnsSocket::new(stack);
loop {
println!("Making HTTP request");
let mut client = HttpClient::new(&tcp_client, &dns_client);
let mut rx_buf = [0u8; 4096];
let request = client
.request(Method::GET, "http://www.mobile-j.de/")
.await
.unwrap();
let mut request = request.headers(&[("Connection", "close")]);
let response = request.send(&mut rx_buf).await.unwrap();
match response.body().read_to_end().await {
Ok(data) => {
if let Ok(body) = core::str::from_utf8(data) {
println!("Body: {}", body);
}
}
Err(err) => println!("Body error: {:?}", err),
}
Timer::after(Duration::from_secs(5)).await;
}
}
#[embassy_executor::task]
async fn connection(mut controller: WifiController<'static>) {
loop {
println!("Connecting to Wi-Fi...");
match controller.connect_async().await {
Ok(info) => {
println!("Wi-Fi connected to {:?}", info);
let info = controller.wait_for_disconnect_async().await.ok();
println!("Disconnected: {:?}", info);
}
Err(err) => println!("Failed to connect to Wi-Fi: {:?}", err),
}
Timer::after(Duration::from_secs(5)).await;
}
}
#[embassy_executor::task]
async fn net_task(mut runner: Runner<'static, Interface<'static>>) {
runner.run().await
}
....
✅ Start the Wi-Fi controller, scan the available networks, and try to connect to the one we set.
println!("Scanning for access points");
let scan_config = ScanConfig::default().with_max(10);
let result = controller.scan_async(&scan_config).await.unwrap();
for ap in result {
println!("{:?}", ap);
}
spawner.spawn(connection(controller).unwrap());
spawner.spawn(net_task(runner).unwrap());
✅ Then we obtain the assigned IP
stack.wait_config_up().await;
if let Some(config) = stack.config_v4() {
println!("Got IP: {}", config.address);
}
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, create an HttpClient, issue a GET request, send it, and read the response body. The Host header is derived from the URL, so we only add Connection: close.
let mut client = HttpClient::new(&tcp_client, &dns_client);
let mut rx_buf = [0u8; 4096];
let request = client
.request(Method::GET, "http://www.mobile-j.de/")
.await
.unwrap();
let mut request = request.headers(&[("Connection", "close")]);
let response = request.send(&mut rx_buf).await.unwrap();
match response.body().read_to_end().await {
Ok(data) => {
if let Ok(body) = core::str::from_utf8(data) {
println!("Body: {}", body);
}
}
Err(err) => println!("Body error: {:?}", err),
}
✅ Finally, wait a bit before making the next request.
Timer::after(Duration::from_secs(5)).await;
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:
- Press F1, select
Wokwi: Select Config Fileand chooseintro/http-client/wokwi.toml- Edit the
wokwi.tomlfile to select between exercise and solution simulation
- Edit the
- Build you project
- Press F1 again and select
Wokwi: Start Simulator
- Press F1, select