DEV Community

Cover image for Edge IoT with Rust on ESP: Ping!
Omar Hiari
Omar Hiari

Posted on

Edge IoT with Rust on ESP: Ping!

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:

  1. Configure and Connect to WiFi.

  2. Configure and perform a Ping.

  3. 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 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};
Enter fullscreen mode Exit fullscreen mode

πŸŽ› 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();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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(),
);
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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),
}
Enter fullscreen mode Exit fullscreen mode

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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 πŸ‘‡.

If you found 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

Top comments (0)