ESP32 Web Server LED Control - Zephyr RTOS

In this tutorial I will show you how to create Web Server using ESP32 Module based on Zephyr RTOS. You will learn how to create a HTML Web Page with Buttons for 2 LEDs. We will control LEDs based on pressed buttons.


Zephyr RTOS ESP32 LED Control using Web Server



What is Zephyr RTOS?

Zephyr is small RTOS (Real Time Operating System) which is designed for Resource-Constrained and Embedded Devices. It is provided with open-source permissively licensed using the Apache 2.0 license.

For a Linux developer, Zephyr RTOS offers a smooth transition into embedded systems by leveraging familiar Linux tools (like Kconfig, Device Tree) for powerful, scalable, and secure IoT development. 

It supports multiple architectures like,

  • ARCv2 (EM and HS) and ARCv3 (HS6X)
  • ARMv6-M, ARMv7-M, and ARMv8-M (Cortex-M)
  • ARMv7-A and ARMv8-A (Cortex-A, 32- and 64-bit)
  • ARMv7-R, ARMv8-R (Cortex-R, 32- and 64-bit)
  • Intel x86 (32- and 64-bit)
  • MIPS (MIPS32 Release 1 specification)
  • Renesas RX
  • RISC-V (32- and 64-bit)
  • SPARC V8
  • Tensilica Xtensa

As of today, it already has support added for nearly 914 boards, 206 shields and support growing is each day by day.

Key Features,

  • A small kernel
  • Highly configurable / Modular for flexibility
  • Supports Variety of Boards with Different CPU Architecture
  • Implements Memory Protection
  • Compile-time resource definition reduces code size
  • Consistent Device Driver Support
  • Uses Device tree Concept from Linux
  • Has Native Support for Multiple Protocols
  • Host PC Development support for Linux, macOS and Windows OS
  • Native sim allows to run as Linux application for Simulation purposes.


HTTP

HTTP which stands for Hypertext Transfer Protocol is the foundation of the World Wide Web (WWW) is used to load Web Pages including our WebSite. It is standard which is used between Web browser and Web Server, It defines rules to transfer data.

It works on Client server model, Client requests data from Server. 

Features

  • Stateless
    Server is not required to store any information between requests. 
  • Connectionless
    It is considered connectionless as connection is dropped after every data transfers.
  • Media Independent
    Multiple type of data can be shared using HTTP.
  • Client-Server Model
    Follows client-server model for requesting and serving data.
  • Requests Methods
    Supports multiple methods like GET, POST, PUT and DELETE for different actions.


Prerequisite

Before proceeding further make sure you have installed Zephyr OS.  I have created a tutorial for installing Zephyr RTOS SDK on Windows at,

Zephyr RTOS Installation Step



Hardware Used

In this tutorial we are using following hardware,
  1. ESP32-C6 Dev Kit (You can use any ESP32 based board with this code)ESP32-C3-DevKitC-02 is an entry-level development board based on ESP32-C3-WROOM-02 or ESP32-C3-WROOM-02U, general-purpose modules with 4 MB SPI flash. This board integrates complete Wi-Fi and Bluetooth® Low Energy functions.
  2. 5mm Through Hole LEDs
    Through Hole Red and GreenLED
  3. 470 Ohm Through Hole Resistors
    470 Ohm through hole resistor
  4. Connecting Wires
    Connecting Wires, Jumper Wires
  5. Bread Board
    Bread Board for Connecting Circuit


Full Project Code

You can directly download and use whole project from below GitHub link.

GitHub Repo Link



How to use Build and Flash Project using Zephyr RTOS

  1. First of all, Setup Zephyr RTOS on your host pc. To setup Zephyr RTOS on windows system I have created tutorial at,

    Zephyr RTOS Installation Step

  2. Download full project code from provided GitHub repo link above.
  3. Copy downloaded project folder "ESP32_Zephyr_WebServer_LED_Control" into Zephyr RTOS folder at path "zephyrproject\zephyr\applications\" .
    Full path for reference, "C:\Users\embetronics\zephyrproject\zephyr\applications\ESP32_Zephyr_WebServer_LED_Control"
  4. To build project use below command,
    west build -p=auto -b esp32c3_devkitm .\applications\ESP32_Zephyr_WebServer_LED_Control\
  5. Flash build project and open com port monitor using below commands,
    west flash
    west espressif monitor


Working Of Code

  1. Connect 2 LEDs with Board on GPIO Pins 2 and 3 through 470 Ohm series resistor.
  2. Build and Flash provided project on board using steps mentioned in above steps.
  3. As soon as program starts running on board, It will create Wi-Fi Access Point with below credentials.
    SSID : EmbeTronics
    PASS : 12345678
  4. From Laptop or Mobile, Connect with above Access Point using provided credentials. As soon as device is connected with access point of ESP32, It will automatically open webpage in browser.
  5. If Webpage didn't open automatically in Web Browser, Use IP Address "192.168.4.1" to open Web Page manually.
  6. From Webpage you can control both LEDs by pressing On and Off Button.


Explanation of Code

In this section I will describe and explain each block of code,

main.c

First we will include all required header files. Header files for Zephyr RTOS Kernel, WiFi, HTTP, DHCP Server and Logging are included.
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/wifi_mgmt.h>
#include <zephyr/net/dhcpv4_server.h>
#include <zephyr/net/http/service.h>
#include <zephyr/drivers/gpio.h>

To use logging module with Zephyr RTOS, We have to set a unique module name and register it using LOG_MODULE_REGISTER API.
LOG_MODULE_REGISTER(MAIN);

To get a node identifier for a LED properties we have defined in /aliases node in device tree overlay below code is used.
/* The devicetree node identifier for the "led0" alias. */
#define RED_NODE 	DT_ALIAS(redled)
#define GREEN_NODE 	DT_ALIAS(greenled)

Below code is used to get status initializer for led nodes from given device tree identifier.
static const struct gpio_dt_spec redled = GPIO_DT_SPEC_GET(RED_NODE, gpios);
static const struct gpio_dt_spec greenled = GPIO_DT_SPEC_GET(GREEN_NODE, gpios);

Macro used for printing mac address is defined.
#define MACSTR "%02X:%02X:%02X:%02X:%02X:%02X"

Mask value which is used to subscribe different events of Wi-Fi Station mode and Access Point mode is defined.
#define NET_EVENT_WIFI_MASK                                                                        \
	(NET_EVENT_WIFI_CONNECT_RESULT | NET_EVENT_WIFI_DISCONNECT_RESULT |                        \
	 NET_EVENT_WIFI_AP_ENABLE_RESULT | NET_EVENT_WIFI_AP_DISABLE_RESULT |                      \
	 NET_EVENT_WIFI_AP_STA_CONNECTED | NET_EVENT_WIFI_AP_STA_DISCONNECTED)

Below structures are used to handle network interfaces for Wi-Fi Station mode and Access Point mode.
static struct net_if *ap_iface;
static struct net_if *sta_iface;

Below structures are used to handle connection request parameters for Wi-Fi Station mode and Access Point mode.
static struct wifi_connect_req_params ap_config;
static struct wifi_connect_req_params sta_config;

Structure used to register a callback function for network management event is defined.
static struct net_mgmt_event_callback cb;

Below code checks whether necessary definitions are done for Wi-Fi Station mode and Access Point mode.
/* Check necessary definitions */

BUILD_ASSERT(sizeof(CONFIG_WIFI_SAMPLE_AP_SSID) > 1,
	     "CONFIG_WIFI_SAMPLE_AP_SSID is empty. Please set it in conf file.");

BUILD_ASSERT(sizeof(CONFIG_WIFI_SAMPLE_SSID) > 1,
	     "CONFIG_WIFI_SAMPLE_SSID is empty. Please set it in conf file.");

#if WIFI_SAMPLE_DHCPV4_START
BUILD_ASSERT(sizeof(CONFIG_WIFI_SAMPLE_AP_IP_ADDRESS) > 1,
	     "CONFIG_WIFI_SAMPLE_AP_IP_ADDRESS is empty. Please set it in conf file.");

BUILD_ASSERT(sizeof(CONFIG_WIFI_SAMPLE_AP_NETMASK) > 1,
	     "CONFIG_WIFI_SAMPLE_AP_NETMASK is empty. Please set it in conf file.");

#endif

Below code is used to embed binary data of HTML Webpage directly into program as array
static uint8_t root_html_gz[] = {
#include "root.html.gz.inc"
};

Handler function "default_handler" is called when WebPage other than "/" (root) is requested. It also changes LED gpio status based on received URL requests.
static int default_handler(struct http_client_ctx *client, enum http_data_status status,
                   const struct http_request_ctx *request_ctx,
                   struct http_response_ctx *response_ctx, void *user_data)
{
	// LOG_INF("%s,%d, req %s", __func__, __LINE__, client->url_buffer);

	if(strstr(client->url_buffer,"GREEN_LED_ON"))
	{
		gpio_pin_set_dt(&greenled,1);
	}
	else if(strstr(client->url_buffer,"GREEN_LED_OFF"))
	{
		gpio_pin_set_dt(&greenled,0);
	}
	else if(strstr(client->url_buffer,"RED_LED_ON"))
	{
		gpio_pin_set_dt(&redled,1);
	}
	else if(strstr(client->url_buffer,"RED_LED_OFF"))
	{
		gpio_pin_set_dt(&redled,0);
	}

    if (status == HTTP_SERVER_DATA_FINAL) {
        response_ctx->status = HTTP_200_OK;
        response_ctx->body = root_html_gz;
        response_ctx->body_len = sizeof(root_html_gz) - 1;
        response_ctx->final_chunk = true;
    }

    return 0;
}

Using below code, HTTP service is defined with registering dynamic handler function.
static struct http_resource_detail_dynamic default_detail = {
    .common = {
        .type = HTTP_RESOURCE_TYPE_DYNAMIC,
        .bitmask_of_supported_http_methods = BIT(HTTP_GET),
    },
    .cb = default_handler,
    .user_data = NULL,
};

HTTP_SERVICE_DEFINE(http_service, "0.0.0.0", &http_service_port, 1, 10, NULL, &default_detail, NULL);

Static HTML Web Page is defined using below code.
struct http_resource_detail_static root_html_gz_resource_detail = {
    .common = {
        .type = HTTP_RESOURCE_TYPE_STATIC,
        .bitmask_of_supported_http_methods = BIT(HTTP_GET),
        // .content_encoding = "gzip",
    },
    .static_data = root_html_gz,
    .static_data_len = sizeof(root_html_gz),
};

HTTP_RESOURCE_DEFINE(root_html_gz_resource, http_service, "/",&root_html_gz_resource_detail);

wifi_event_handler function handles events for Wi-Fi for Station and Access Point Mode related to connect, and disconnect.
static void wifi_event_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event,
			       struct net_if *iface)
{
	switch (mgmt_event) {
	case NET_EVENT_WIFI_CONNECT_RESULT: {
		LOG_INF("Connected to %s", CONFIG_WIFI_SAMPLE_SSID);
		break;
	}
	case NET_EVENT_WIFI_DISCONNECT_RESULT: {
		LOG_INF("Disconnected from %s", CONFIG_WIFI_SAMPLE_SSID);
		break;
	}
	case NET_EVENT_WIFI_AP_ENABLE_RESULT: {
		LOG_INF("AP Mode is enabled. Waiting for station to connect");
		break;
	}
	case NET_EVENT_WIFI_AP_DISABLE_RESULT: {
		LOG_INF("AP Mode is disabled.");
		break;
	}
	case NET_EVENT_WIFI_AP_STA_CONNECTED: {
		struct wifi_ap_sta_info *sta_info = (struct wifi_ap_sta_info *)cb->info;

		LOG_INF("station: " MACSTR " joined ", sta_info->mac[0], sta_info->mac[1],
			sta_info->mac[2], sta_info->mac[3], sta_info->mac[4], sta_info->mac[5]);
		break;
	}
	case NET_EVENT_WIFI_AP_STA_DISCONNECTED: {
		struct wifi_ap_sta_info *sta_info = (struct wifi_ap_sta_info *)cb->info;

		LOG_INF("station: " MACSTR " leave ", sta_info->mac[0], sta_info->mac[1],
			sta_info->mac[2], sta_info->mac[3], sta_info->mac[4], sta_info->mac[5]);
		break;
	}
	default:
		break;
	}
}

Below function enables and initializes DHCP server on Access Point Interface.
#if CONFIG_WIFI_SAMPLE_DHCPV4_START
static void enable_dhcpv4_server(void)
{
	static struct net_in_addr addr;
	static struct net_in_addr netmaskAddr;

	if (net_addr_pton(NET_AF_INET, CONFIG_WIFI_SAMPLE_AP_IP_ADDRESS, &addr)) {
		LOG_ERR("Invalid address: %s", CONFIG_WIFI_SAMPLE_AP_IP_ADDRESS);
		return;
	}

	if (net_addr_pton(NET_AF_INET, CONFIG_WIFI_SAMPLE_AP_NETMASK, &netmaskAddr)) {
		LOG_ERR("Invalid netmask: %s", CONFIG_WIFI_SAMPLE_AP_NETMASK);
		return;
	}

	net_if_ipv4_set_gw(ap_iface, &addr);

	if (net_if_ipv4_addr_add(ap_iface, &addr, NET_ADDR_DHCP, 0) == NULL) {
		LOG_ERR("unable to set IP address for AP interface");
	}

	if (!net_if_ipv4_set_netmask_by_addr(ap_iface, &addr, &netmaskAddr)) {
		LOG_ERR("Unable to set netmask for AP interface: %s",
			 CONFIG_WIFI_SAMPLE_AP_NETMASK);
	}

	addr.s4_addr[3] += 10; /* Starting IPv4 address for DHCPv4 address pool. */

	if (net_dhcpv4_server_start(ap_iface, &addr) != 0) {
		LOG_ERR("DHCP server is not started for desired IP");
		return;
	}

	LOG_INF("DHCPv4 server started...\n");
}
#endif

Below function is used to enable access point interface, using provided SSID Name and Password. It also enables DHCP server calling enable_dhcpv4_server function.
static int enable_ap_mode(void)
{
	if (!ap_iface) {
		LOG_INF("AP: is not initialized");
		return -EIO;
	}

	LOG_INF("Turning on AP Mode");
	ap_config.ssid = (const uint8_t *)CONFIG_WIFI_SAMPLE_AP_SSID;
	ap_config.ssid_length = sizeof(CONFIG_WIFI_SAMPLE_AP_SSID) - 1;
	ap_config.psk = (const uint8_t *)CONFIG_WIFI_SAMPLE_AP_PSK;
	ap_config.psk_length = sizeof(CONFIG_WIFI_SAMPLE_AP_PSK) - 1;
	ap_config.channel = WIFI_CHANNEL_ANY;
	ap_config.band = WIFI_FREQ_BAND_2_4_GHZ;

	if (sizeof(CONFIG_WIFI_SAMPLE_AP_PSK) == 1) {
		ap_config.security = WIFI_SECURITY_TYPE_NONE;
	} else {

		ap_config.security = WIFI_SECURITY_TYPE_PSK;
	}

#if CONFIG_WIFI_SAMPLE_DHCPV4_START
	enable_dhcpv4_server();
#endif

	int ret = net_mgmt(NET_REQUEST_WIFI_AP_ENABLE, ap_iface, &ap_config,
			   sizeof(struct wifi_connect_req_params));
	if (ret) {
		LOG_ERR("NET_REQUEST_WIFI_AP_ENABLE failed, err: %d", ret);
	}

	return ret;
}

connect_to_wifi function initializes Wi-Fi in Station mode and Connects to provided SSID and Password.
static int connect_to_wifi(void)
{
	if (!sta_iface) {
		LOG_INF("STA: interface no initialized");
		return -EIO;
	}

	sta_config.ssid = (const uint8_t *)CONFIG_WIFI_SAMPLE_SSID;
	sta_config.ssid_length = sizeof(CONFIG_WIFI_SAMPLE_SSID) - 1;
	sta_config.psk = (const uint8_t *)CONFIG_WIFI_SAMPLE_PSK;
	sta_config.psk_length = sizeof(CONFIG_WIFI_SAMPLE_PSK) - 1;
	sta_config.security = WIFI_SECURITY_TYPE_PSK;
	sta_config.channel = WIFI_CHANNEL_ANY;
	sta_config.band = WIFI_FREQ_BAND_2_4_GHZ;

	LOG_INF("Connecting to SSID: %s\n", sta_config.ssid);

	int ret = net_mgmt(NET_REQUEST_WIFI_CONNECT, sta_iface, &sta_config,
			   sizeof(struct wifi_connect_req_params));
	if (ret) {
		LOG_ERR("Unable to Connect to (%s)", CONFIG_WIFI_SAMPLE_SSID);
	}

	return ret;
}

main function performs initialization for GPIO and Wi-Fi Interface.
  • Initialize GPIO pins for both LEDs in Output mode, using device node from Device tree overlay.
  • Register Wi-Fi event handler function.
  • Initialize Wi-Fi Access Point and Station Mode.
  • Start http server.
int main(void)
{
	// k_sleep(K_SECONDS(5));

	/* Init LED GPIO Pins */
	int ret;

	if (!gpio_is_ready_dt(&redled)) {
		LOG_ERR("RED LED gpio not ready !!");
		return 0;
	}

	if (!gpio_is_ready_dt(&greenled)) {
		LOG_ERR("GREEN LED gpio not ready !!");
		return 0;
	}

	ret = gpio_pin_configure_dt(&redled, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return 0;
	}

	ret = gpio_pin_configure_dt(&greenled, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return 0;
	}

	// ret = gpio_pin_toggle_dt(&redled);
	// ret = gpio_pin_toggle_dt(&greenled);
	// k_sleep(K_SECONDS(2));
	// ret = gpio_pin_toggle_dt(&redled);
	// ret = gpio_pin_toggle_dt(&greenled);
	// k_sleep(K_SECONDS(2));
	// ret = gpio_pin_toggle_dt(&redled);
	// ret = gpio_pin_toggle_dt(&greenled);


	net_mgmt_init_event_callback(&cb, wifi_event_handler, NET_EVENT_WIFI_MASK);
	net_mgmt_add_event_callback(&cb);

	/* Get AP interface in AP-STA mode. */
	ap_iface = net_if_get_wifi_sap();

	/* Get STA interface in AP-STA mode. */
	sta_iface = net_if_get_wifi_sta();

	enable_ap_mode();
	connect_to_wifi();

	http_server_start();

	return 0;
}


Device Logs

ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fc8c540,len:0x7454
load:0x40380000,len:0xc530
SHA-256 comparison failed:
Calculated: cb0ed82a57c19be673d7b9a8a85823ddaa5e0b6eb06a3a3443f0314522230864
Expected: 0000000040c60000000000000000000000000000000000000000000000000000
Attempting to boot anyway...
entry 0x4038328c
I (75) soc_init: ESP Simple boot
I (75) soc_init: compile time Jan  3 2026 12:58:13
I (75) soc_init: chip revision: v0.3
I (75) flash_init: SPI Speed      : 80MHz
I (78) flash_init: SPI Mode       : DIO
I (82) flash_init: SPI Flash Size : 4MB
I (85) boot: DRAM       : lma=00000020h vma=3fc8c540h size=07454h ( 29780)
I (91) boot: IRAM       : lma=0000747ch vma=40380000h size=0c530h ( 50480)
I (97) boot: IROM       : lma=00020000h vma=42000000h size=670e8h (422120)
I (103) boot: DROM      : lma=00090000h vma=3c070000h size=0e898h ( 59544)
I (121) boot: libc heap size 87 kB.
I (121) spi_flash: detected chip: generic
I (121) spi_flash: flash io: dio
[00:00:00.003,000] <inf> wifi_init: rx ba win: 6
*** Booting Zephyr OS build v4.3.0-2760-g72360f821a61 ***
[00:00:00.005,000] <inf> MAIN: Turning on AP Mode
[00:00:00.005,000] <dbg> net_dhcpv4_server: net_dhcpv4_server_start: Started DHCPv4 server, address pool:
[00:00:00.005,000] <dbg> net_dhcpv4_server: net_dhcpv4_server_start:     0: 192.168.4.11
[00:00:00.005,000] <dbg> net_dhcpv4_server: net_dhcpv4_server_start:     1: 192.168.4.12
[00:00:00.005,000] <dbg> net_dhcpv4_server: net_dhcpv4_server_start:     2: 192.168.4.13
[00:00:00.005,000] <dbg> net_dhcpv4_server: net_dhcpv4_server_start:     3: 192.168.4.14
[00:00:00.006,000] <inf> MAIN: DHCPv4 server started...

[00:00:00.912,000] <inf> MAIN: AP Mode is enabled. Waiting for station to connect
[00:00:00.912,000] <inf> MAIN: Connecting to SSID: SAMPLE_SSID

[00:00:03.750,000] <inf> MAIN: Disconnected from SAMPLE_SSID
[00:00:10.620,000] <inf> MAIN: station: 28:C5:D2:4D:0C:2E joined
[00:00:11.361,000] <dbg> net_dhcpv4_server: dhcpv4_handle_discover: DHCPv4 processing Discover - reserved 192.168.4.11
[00:00:12.377,000] <dbg> net_dhcpv4_server: dhcpv4_handle_request: DHCPv4 processing Request - allocated 192.168.4.11


Video

You can watch video for working of project at below link.




Wrapping Up

In todays, Tutorial you have learned how to control LEDs from web browser using ESP32 Board based on Zephyr RTOS.

Please leave a comment if you have a question or you found this helpful.


Other tutorial and projects for ESP32





Tags

esp32 web server led control,
Esp32 web server led control tutorial,
Esp32 web server led control example,
Esp32 web server led control arduino,
ESP32 web server example,
Esp32 web server led control programming


Post a Comment

0 Comments