Build a Real-Time Clock Web Server with ESP32 and Rust

Zacchaeus Oluwole
5 min readOct 1, 2023

--

Introduction

In this article, we will explore how to create a real-time clock web server using ESP32, one of the most popular microcontrollers, and Rust programming language. Rust, known for its focus on safety and performance, offers an excellent environment for embedded systems development. We will walk through the code and concepts behind creating a simple web server that displays the current time in real-time.

Prerequisites

Before we begin, make sure you have the following:

  • Basic knowledge of Rust programming language.
  • ESP32 development board and related peripherals.
  • Rust installed on your development machine.
  • Basic familiarity with WiFi and http-server.
  • Basic understanding of HTML, CSS, and JavaScript.

Hardware Setup

  1. ESP32
  1. Micro USB Cable

Breaking down the code step by step

1. Importing Dependencies

use std::{
thread::sleep,
time::Duration,
};
use embedded_svc::{http::Method, io::Write};
use anyhow::Result;
use esp_idf_hal::peripherals::Peripherals;

use esp_idf_svc::{
wifi::EspWifi,
nvs::EspDefaultNvsPartition,
eventloop::EspSystemEventLoop,
http::server::{Configuration, EspHttpServer},
};

use embedded_svc::wifi::{ClientConfiguration, Configuration as wifiConfiguration};

use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use log::*;

The code imports various Rust modules and external dependencies necessary for handling threads, time, error handling, HTTP methods, I/O operations, and working with ESP32 hardware and peripherals.

2. Initialization and Wi-Fi Connection

fn main() -> Result<()> {
// Initialization code
esp_idf_sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
info!("Hello, world!");

let peripherals = Peripherals::take().unwrap();
let sys_loop = EspSystemEventLoop::take().unwrap();
let nvs = EspDefaultNvsPartition::take().unwrap();

// Wi-Fi configuration and connection code
let mut wifi_driver = EspWifi::new(
peripherals.modem,
sys_loop,
Some(nvs)
).unwrap();

wifi_driver.set_configuration(&wifiConfiguration::Client(ClientConfiguration{
ssid: "mySSID".into(),
password: "myPASSWORD".into(),
..Default::default()
})).unwrap();

wifi_driver.start().unwrap();
wifi_driver.connect().unwrap();
while !wifi_driver.is_connected().unwrap(){
let config = wifi_driver.get_configuration().unwrap();
println!("Waiting for station {:?} ", config);
}
println!("Should be connected now");

}
  • Peripherals::take() obtains access to the ESP32 peripherals.
  • EspSystemEventLoop::take() and EspDefaultNvsPartition::take() acquire the system event loop and non-volatile storage, respectively.
  • An EspWifi instance is created and configured with SSID and password.
  • The code waits until the Wi-Fi connection is established before proceeding further.

3. HTTP Server Setup and Request Handling

fn main() -> Result<()> {
// ... (Wi-Fi connection code)

// Set the HTTP server
let mut server = EspHttpServer::new(&Configuration::default())?;
// http://<sta ip>/ handler
server.fn_handler("/", Method::Get, |request| {
let html = index_html();
let mut response = request.into_ok_response()?;
response.write_all(html.as_bytes())?;
Ok(())
})?;
// ... (loop code)
}
  • An EspHttpServer instance is created with default configuration.
  • server.fn_handler("/", Method::Get, |request| { ... }) sets up a handler for HTTP GET requests at the root ("/") path.
  • Inside the handler, the index_html() function generates the HTML response and sends it back to the client.

4. HTML, CSS, and JavaScript

fn index_html() -> String {
format!(
r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>esp32 clock app webserver</title>
<style>
body {{
font-family: Arial, sans-serif;
text-align: center;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
height: 100vh;
}}

#left-section {{
flex: 1;
background-color: green;
color: white;
display: flex;
align-items: center;
justify-content: center;
}}

#middle-section {{
flex: 1;
background-color: white;
color: green;
align-items: center;
justify-content: center;
}}

#right-section {{
flex: 1;
background-color: green;
color: white;
display: flex;
align-items: center;
justify-content: center;
}}

#clock {{
font-size: 3rem;
}}
#mw {{
flex: 1;
background-color: white;
color: green;
align-items: center;
justify-content: center;
}}
</style>
</head>
<body>
<div id="left-section">
<h1></h1>
</div>
<div id="middle-section">
<h1>Clock App Web Server on ESP32</h1>
<div id="clock"></div>
<div id="mw"><h1>Happy Independence To Nigeria</h1></div>
<div id="mw"><h1>By Zacchaeus Oluwole</h1></div>
</div>
<div id="right-section">
<h1></h1>
</div>

<script>
function updateClock() {{
const clockElement = document.getElementById('clock');
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');

const timeString = `${{hours}}:${{minutes}}:${{seconds}}`;
clockElement.textContent = timeString;
}}

// Update the clock every second
setInterval(updateClock, 1000);

// Initial call to set the clock
updateClock();
</script>
</body>
</html>
"#
)
}
  • The index_html() function generates the HTML content for the web page. It includes a simple layout with three sections: left, middle, and right.
  • CSS styles define the appearance of the sections and clock.
  • JavaScript inside the HTML updates the clock every second by fetching the current time from the client’s browser.

5. Main Loop

fn main() -> Result<()> {
// ... (HTTP server setup code)

loop {
println!("IP info: {:?} ", wifi_driver.sta_netif().get_ip_info().unwrap());
sleep(Duration::new(10, 0));
}
}
  • The program enters an infinite loop, printing the IP information of the connected ESP32 device every 10 seconds.

This Rust code sets up an ESP32 microcontroller as a web server. It establishes a Wi-Fi connection, handles HTTP requests at the root path, and serves an HTML page with a real-time clock. The code structure demonstrates the integration of Rust with ESP32 hardware and showcases the simplicity of creating embedded web applications.

You can get the complete code here

Customization for Nigeria’s Independence Day

The color scheme of green, white, and green, utilized in the web layout, holds a special significance. Today marks Nigeria’s Independence Day, and these colors are a representation of the Nigerian flag. The two green stripes sandwiching the white stripe signify Nigeria’s agricultural wealth, while the white stripe represents peace. By incorporating these colors into the web design, this project is dedicated to celebrating Nigeria’s Independence Day. It’s a tribute to the people of Nigeria, symbolizing unity, peace, and national pride. I am a Nigerian. 🙏🥰

Conclusion

In this article, we’ve explored how to create a real-time clock web server using ESP32 and Rust. By leveraging Rust’s safety features and the capabilities of ESP32, we’ve built a simple yet functional web application. This project serves as a foundation for more complex embedded web applications. Feel free to modify and expand upon this example to create your own unique projects.

If you’d like to see the result in action, check out this YouTube video demonstrating the Real-Time Clock web server on an ESP32 board. Happy coding!

--

--

Zacchaeus Oluwole

Software & IoT developer || Dart/Flutter & Rust 🦀 || Mobile, Desktop, Web || Backend, Embedded Systems, Network || 🧑‍💻