Edge IoT with Rust on ESP: Ping!

Search for a command to run...

No comments yet. Be the first to comment.
A community giveaway program by The Embedded Rustacean Γ Seeed Studio to grow the embedded Rust ecosystem

Introduction Another year comes to an end; as always, it is an opportunity to reflect on the achievements of the past year and look forward to the next. It's been over 3.5 years since I embarked on my embedded Rust journey, and it only gets more exci...

Introduction Another year comes to an end, as such, this is an opportunity to reflect on the achievements of 2024 and look forward to 2025. It's been over 2.5 years since I embarked on my embedded Rust journey, and it remains as exciting as ever. If ...

Introduction π On Friday, May 17th, 2024, the release of Simplified Embedded Rust was announced marking a new kind of book in the embedded Rust learning space. I'm happy to say that this book, design

This post is the sixth of a multi-part series where I'm exploring the use of Bluetooth Low Energy along embedded Rust on the ESP32. Information in this post might rely on knowledge presented in past p

This blog post is the seventh of a multi-part series of posts where I explore various peripherals in the ESP32C3 using standard library embedded Rust and the esp-idf-svc. Please be aware that certain concepts in newer posts could depend on concepts in prior posts.
Prior posts include (in order of publishing):
Ping is a networking utility used to test the reachability of a host on an Internet Protocol (IP) network and to measure the round-trip time for messages sent from the originating host to a destination computer and back. Essentially, it sends a small packet of data to a specified address and waits for a response. If the destination host is reachable, it sends a response back, allowing the sender to determine the status and latency of the connection. Ping is widely used for troubleshooting network connectivity issues and measuring network performance.
The esp-idf-svc provides an EspPing abstraction to perform ping operations. In this post, a simple ping application will be created using ping::EspPing. I'll be pinging the Google DNS IP.
To understand the content of this post, you need the following:
Basic knowledge of coding in Rust.
Basic familiarity with the network stack.
All the code presented in this post is available on the apollolabs ESP32C3 git repo. Note that if the code on the git repo is slightly different then it means that it was modified to enhance the code quality or accommodate any HAL/Rust updates.
Additionally, the full project (code and simulation) is available on Wokwi here.
Ping utilizes the Internet Control Message Protocol (ICMP) in the network layer of the network stack. ICMP is a protocol used by network devices, like routers and servers, to communicate error messages and operational information back to the sender. Ping uses ICMP Echo Request and Echo Reply messages to determine the reachability and latency of a target host.
When you initiate a ping from your device, it constructs an ICMP Echo Request packet containing a small amount of data and sends it to the destination IP address. The destination device, if reachable, receives the packet and responds with an ICMP Echo Reply packet, indicating that it has received the ping request. This exchange of ICMP messages allows ping to determine if the destination host is reachable and measure the round-trip time (RTT) it takes for the packet to travel to the destination and back.
In this post, we are going to configure the ESP to ping the Google DNS IP (8.8.8.8). The steps include the following:
Configure and Connect to WiFi.
Configure and perform a Ping.
Print Results.
In this implementation, the following crates are required:
The esp_idf_hal crate to import the peripherals.
The esp_idf_svc crate to import the device services (wifi in particular).
The std::str crate to import the FromStr abstraction.
use std::str::FromStr;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::ipv4::Ipv4Addr;
use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::ping::{Configuration as PingConfiguration, EspPing};
use esp_idf_svc::wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi};
1οΈβ£ Obtain a handle for the device peripherals: Similar to all past blog posts, in embedded Rust, as part of the singleton design pattern, we first have to take the device peripherals. This is done using the take() method. Here I create a device peripheral handler named peripherals as follows:
let peripherals = Peripherals::take().unwrap();
2οΈβ£ Configure and Connect to WiFi: this involves the same steps that were done in the wifi post.
3οΈβ£ Create Handle and Configure Ping: Within esp_idf_svc::ping::EspPing there exists an new abstraction. new takes a single interface_index parameter that is a u32 type. interface_index represents the Netif index. Passing a value of 0 means that is no interface index (click here for more about ESP Netif). Following that we create an ping handle as follows:
let mut ping = EspPing::new(0_u32);
That's it for Configuration!
1οΈβ£ Perform a Ping: Within the EspPing abstraction, there is a ping method with the following signature:
pub fn ping(
&mut self,
ip: Ipv4Addr,
conf: &Configuration
) -> Result<Summary, EspError>
There are two arguments that need to be passed, an IP address of type Ipv4Addr and and configuration. Ipv4Addr has a from_str associated method that allows the passing of a slice using the dot decimal notation. Configuration on the other hand is a struct containing the configuration parameters and is defined as follows:
pub struct Configuration {
pub count: u32,
pub interval: Duration,
pub timeout: Duration,
pub data_size: u32,
pub tos: u8,
}
where count is the number of probes/messages to be sent, interval is the Duration between probes, timeout defines the waitΒ Duration to wait for an echo, data_size is the size of each message, and tos defines the type of service where 0 defines normal service also referred to as best effort. In this post, the defaultConfiguration will be used. Consequently, a ping is initiated as follows:
let ping_res = ping.ping(
Ipv4Addr::from_str("8.8.8.8").unwrap(),
&PingConfiguration::default(),
);
2οΈβ£ Process the Ping Response: from the signature shown earlier, ping returns a Result wrapped in a Summary. Summary has the following members:
pub struct Summary {
pub transmitted: u32,
pub received: u32,
pub time: Duration,
}
Consequently, the Result is processed as follows:
match ping_res {
Ok(summary) => println!(
"Transmitted: {}, Recieved: {} Time: {:?}",
summary.transmitted, summary.received, summary.time
),
Err(e) => println!("{:?}", e),
}
Thats it!
Here is the full code for the implementation described in this post. You can additionally find the full project and others available on the apollolabs ESP32C3 git repo. Also, the Wokwi project can be accessed here.
use std::str::FromStr;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::ipv4::Ipv4Addr;
use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::ping::{Configuration as PingConfiguration, EspPing};
use esp_idf_svc::wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi};
fn main() -> anyhow::Result<()> {
// Take Peripherals
let peripherals = Peripherals::take().unwrap();
let sysloop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;
let mut wifi = BlockingWifi::wrap(
EspWifi::new(peripherals.modem, sysloop.clone(), Some(nvs))?,
sysloop,
)?;
wifi.set_configuration(&Configuration::Client(ClientConfiguration {
ssid: "Wokwi-GUEST".try_into().unwrap(),
bssid: None,
auth_method: AuthMethod::None,
password: "".try_into().unwrap(),
channel: None,
}))?;
println!("Connecting to WiFi");
// Start Wifi
wifi.start()?;
// Connect Wifi
wifi.connect()?;
// Wait until the network interface is up
wifi.wait_netif_up()?;
// This line is for Wokwi only so that the console output is formatted correctly
println!("\x1b[20h");
println!("Wifi Connected");
println!("Pinging Google DNS (8.8.8.8)");
let mut ping = EspPing::new(0_u32);
let ping_res = ping.ping(
Ipv4Addr::from_str("8.8.8.8").unwrap(),
&PingConfiguration::default(),
);
match ping_res {
Ok(summary) => println!(
"Transmitted: {}, Recieved: {} Time: {:?}",
summary.transmitted, summary.received, summary.time
),
Err(e) => println!("{:?}", e),
}
loop {}
}
Ping is a popular tool used by network devices to determine device reachability and latency information. This post introduced how to set up Ping on a ESP32C3 using Rust and the esp_idf_svc. Have any questions? Share your thoughts in the comments below π.