Edge IoT with Rust on ESP: Ping!
Tags: esp32, rust, iot, tutorial
Published: 2024-02-10 18:07:12

> ***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:

{% cta  http://www.theembeddedrustacean.com/subscribe %} Subscribe Now to The Embedded Rustacean{% endcta%} 

### **📚 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**](https://github.com/apollolabsdev/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**](https://wokwi.com/projects/389360760159060993).

### **🛠 Hardware Setup**

#### Materials

* [**ESP32-C3-DevKitM**](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitm-1.html)
    
    ![DevKit](https://cdn.hashnode.com/res/hashnode/image/upload/v1681796942083/da81fb7b-1f90-4593-a848-11c53a87821d.jpeg)
    

## **👨‍🎨 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.
    

```rust
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:

```rust
let peripherals = Peripherals::take().unwrap();
```

2️⃣ **Configure and Connect to WiFi**: this involves the same steps that were done in the [wifi post](https://apollolabsblog.hashnode.dev/edge-iot-with-rust-on-esp-connecting-wifi).

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](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_netif.html) for more about ESP Netif). Following that we create an `ping` handle as follows:

```rust
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:

```rust
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:

```rust
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:

```rust
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:

```rust
pub struct Summary {
    pub transmitted: u32,
    pub received: u32,
    pub time: Duration,
}
```

Consequently, the `Result` is processed as follows:

```rust
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**](https://github.com/apollolabsdev/ESP32C3) git repo. Also, the Wokwi project can be accessed [**here**](https://wokwi.com/projects/389360760159060993).

```rust
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 👇.

#### If you found this post useful, and if Embedded Rust interests you, stay in the know by subscribing to The Embedded Rustacean newsletter:

{% cta  http://www.theembeddedrustacean.com/subscribe %} Subscribe Now to The Embedded Rustacean{% endcta%}