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.
Introduction
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.
If you find this post useful, and if Embedded Rust interests you, stay in the know by subscribing to The Embedded Rustacean newsletter:
Subscribe Now to The Embedded Rustacean
π Knowledge Pre-requisites
To understand the content of this post, you need the following:
Basic knowledge of coding in Rust.
Basic familiarity with the network stack.
πΎ Software Setup
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.
π Hardware Setup
Materials
π¨βπ¨ Software Design
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.
π¨βπ» Code Implementation
π₯ Crate Imports
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 theFromStr
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};
π Initialization/Configuration Code
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!
π± Application Code
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 default
Configuration
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!
π±Full Application Code
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 {}
}
Conclusion
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 π.
Top comments (0)