ESP32 Static/Fixed IP Address using ESP-IDF

In this guide ESP-IDF guide, we will show you how to set a static IP address for your ESP32 development board. We will use an example sketch provided by ESP-IDF and explain APIs used to assign static IPs. Fixing an IP address resolves the issue of IP address is changed or a new IP address assigned by the router whenever the ESP32 board is restarted. For example, if you are running a web server or a web client on ESP32, having a static/fixed IP address will be helpful. You can follow this tutorial to assign a static/fixed IP address using ESP-IDF.

Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

ESP32 Static IP Address using ESP-IDF

Let’s show you how to assign a static IP address for our ESP32 board using ESP-IDF. We will use the example sketch uploaded on GitHub at the following link (https://github.com/espressif/esp-idf/tree/master/examples/protocols/static_ip).

This uses the ESP-IDF examples under the protocols tab named ‘static_ip.’

ESP32 Static IP Example

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory. We have named our project ‘ESP_IDF_STATIC_IP.’ For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.

ESP32 Static IP Address using ESP-IDF Project 1

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens our project that we created, inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer. Create a new file named ‘Kconfig.projbuild‘ under the main folder. Copy the file from this destination in this newly created file.

ESP32 Static IP Address using ESP-IDF Project 2

Project Configuration

Lets first head over to the menuconfig. Click the icon shown below. It opens the ESP-IDF SDK Configuration Editor.

ESP32 Static IP Address using ESP-IDF Project 3

Scroll down and open the Example Configuration. Here we can set the configuration parameters for this example according to our needs. This includes the Wi-Fi SSID, Wi-Fi password, Maximum retry, Static IP address, Static netmask address, Static gateway address, Choose DNS server and Enable DNS resolve test. Here you can view that by default, ESP-IDF is using maximum retry number as 5, Static IP address as 192.168.4.2, Static netmask address as 255.255.255.0, Static gateway address as 192.168.4.1 and DNS server as gateway address.

You can alter these parameters according to your preference and then click the Save button found at the top. Set the following parameters:

  • Wi-Fi SSID, Wi-Fi Password, Maximum retry number
  • Static IP Address
  • Static Netmask Address
  • Static Gateway Address
  • For Choose DNS server option, you can either select ‘Use gateway address as DNS server’ or ‘Set manual value as DNS server’.
  • If you select the option of configuring the DNS server with a manual value, then you will be asked to specify the Main DNS server address and the Backup DNS server address.
  • You can tick Enable DNS resolve test and specify the hostname to resolve.

We will assign the static IP address 192.168.4.1 to our ESP32 board which is the default address specified.

ESP32 Static IP Address using ESP-IDF Project 4

ESP32 Static IP Code

The main folder contains the source code meaning the main.c file will be found here. Inside the main.c we will write our program code. Go to main > main.c and open it. We will define the functions and the program code here. Copy the code from this link here.

This is our main.c file.

/* Static IP Example
   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include <netdb.h>
#include "nvs_flash.h"

/* The examples use configuration that you can set via project configuration menu
   If you'd rather not, just change the below entries to strings with
   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_WIFI_SSID             CONFIG_EXAMPLE_WIFI_SSID
#define EXAMPLE_WIFI_PASS             CONFIG_EXAMPLE_WIFI_PASSWORD
#define EXAMPLE_MAXIMUM_RETRY         CONFIG_EXAMPLE_MAXIMUM_RETRY
#define EXAMPLE_STATIC_IP_ADDR        CONFIG_EXAMPLE_STATIC_IP_ADDR
#define EXAMPLE_STATIC_NETMASK_ADDR   CONFIG_EXAMPLE_STATIC_NETMASK_ADDR
#define EXAMPLE_STATIC_GW_ADDR        CONFIG_EXAMPLE_STATIC_GW_ADDR
#ifdef CONFIG_EXAMPLE_STATIC_DNS_AUTO
#define EXAMPLE_MAIN_DNS_SERVER       EXAMPLE_STATIC_GW_ADDR
#define EXAMPLE_BACKUP_DNS_SERVER     "0.0.0.0"
#else
#define EXAMPLE_MAIN_DNS_SERVER       CONFIG_EXAMPLE_STATIC_DNS_SERVER_MAIN
#define EXAMPLE_BACKUP_DNS_SERVER     CONFIG_EXAMPLE_STATIC_DNS_SERVER_BACKUP
#endif
#ifdef CONFIG_EXAMPLE_STATIC_DNS_RESOLVE_TEST
#define EXAMPLE_RESOLVE_DOMAIN        CONFIG_EXAMPLE_STATIC_RESOLVE_DOMAIN
#endif
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;

/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static const char *TAG = "static_ip";

static int s_retry_num = 0;

static esp_err_t example_set_dns_server(esp_netif_t *netif, uint32_t addr, esp_netif_dns_type_t type)
{
    if (addr && (addr != IPADDR_NONE)) {
        esp_netif_dns_info_t dns;
        dns.ip.u_addr.ip4.addr = addr;
        dns.ip.type = IPADDR_TYPE_V4;
        ESP_ERROR_CHECK(esp_netif_set_dns_info(netif, type, &dns));
    }
    return ESP_OK;
}

static void example_set_static_ip(esp_netif_t *netif)
{
    if (esp_netif_dhcpc_stop(netif) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to stop dhcp client");
        return;
    }
    esp_netif_ip_info_t ip;
    memset(&ip, 0 , sizeof(esp_netif_ip_info_t));
    ip.ip.addr = ipaddr_addr(EXAMPLE_STATIC_IP_ADDR);
    ip.netmask.addr = ipaddr_addr(EXAMPLE_STATIC_NETMASK_ADDR);
    ip.gw.addr = ipaddr_addr(EXAMPLE_STATIC_GW_ADDR);
    if (esp_netif_set_ip_info(netif, &ip) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set ip info");
        return;
    }
    ESP_LOGD(TAG, "Success to set static ip: %s, netmask: %s, gw: %s", EXAMPLE_STATIC_IP_ADDR, EXAMPLE_STATIC_NETMASK_ADDR, EXAMPLE_STATIC_GW_ADDR);
    ESP_ERROR_CHECK(example_set_dns_server(netif, ipaddr_addr(EXAMPLE_MAIN_DNS_SERVER), ESP_NETIF_DNS_MAIN));
    ESP_ERROR_CHECK(example_set_dns_server(netif, ipaddr_addr(EXAMPLE_BACKUP_DNS_SERVER), ESP_NETIF_DNS_BACKUP));
}

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
        example_set_static_ip(arg);
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "static ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_netif_init());

    ESP_ERROR_CHECK(esp_event_loop_create_default());

    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        sta_netif,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        sta_netif,
                                                        &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_WIFI_SSID,
            .password = EXAMPLE_WIFI_PASS,
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
	     .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 EXAMPLE_WIFI_SSID, EXAMPLE_WIFI_PASS);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_WIFI_SSID, EXAMPLE_WIFI_PASS);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }
#ifdef CONFIG_EXAMPLE_STATIC_DNS_RESOLVE_TEST
    struct addrinfo *address_info;
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    int res = getaddrinfo(EXAMPLE_RESOLVE_DOMAIN, NULL, &hints, &address_info);
    if (res != 0 || address_info == NULL) {
        ESP_LOGE(TAG, "couldn't get hostname for :%s: "
                      "getaddrinfo() returns %d, addrinfo=%p", EXAMPLE_RESOLVE_DOMAIN, res, address_info);
    } else {
        if (address_info->ai_family == AF_INET) {
            struct sockaddr_in *p = (struct sockaddr_in *)address_info->ai_addr;
            ESP_LOGI(TAG, "Resolved IPv4 address: %s", ipaddr_ntoa((const ip_addr_t*)&p->sin_addr.s_addr));
        }
#if CONFIG_LWIP_IPV6
        else if (address_info->ai_family == AF_INET6) {
            struct sockaddr_in6 *p = (struct sockaddr_in6 *)address_info->ai_addr;
            ESP_LOGI(TAG, "Resolved IPv6 address: %s", ip6addr_ntoa((const ip6_addr_t*)&p->sin6_addr));
        }
#endif
    }
#endif
    /* The event will not be processed after unregister */
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);
}

void app_main(void)
{
    //Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();
}

How the Code Works?

Firstly, we will start by including the necessary libraries that includes the FreeRTOS libraries to generate delays and create tasks, esp_wifi.h to enable Wi-Fi connectivity, esp_event.h to monitor the Wi-Fi events and esp_log.h as the logging library.

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include <netdb.h>
#include "nvs_flash.h"

Then we have defined the variables that constitute the example configuration parameters that we previously set. These variables will access the values that we set in the menuconfig previously so the values are already defined. However, if you want to define these parameters in the main.c file, then define them inside double quotation marks. For example: #define EXAMPLE_WIFI_SSID “PTCL-08”

#define EXAMPLE_WIFI_SSID             CONFIG_EXAMPLE_WIFI_SSID
#define EXAMPLE_WIFI_PASS             CONFIG_EXAMPLE_WIFI_PASSWORD
#define EXAMPLE_MAXIMUM_RETRY         CONFIG_EXAMPLE_MAXIMUM_RETRY
#define EXAMPLE_STATIC_IP_ADDR        CONFIG_EXAMPLE_STATIC_IP_ADDR
#define EXAMPLE_STATIC_NETMASK_ADDR   CONFIG_EXAMPLE_STATIC_NETMASK_ADDR
#define EXAMPLE_STATIC_GW_ADDR        CONFIG_EXAMPLE_STATIC_GW_ADDR
#ifdef CONFIG_EXAMPLE_STATIC_DNS_AUTO
#define EXAMPLE_MAIN_DNS_SERVER       EXAMPLE_STATIC_GW_ADDR
#define EXAMPLE_BACKUP_DNS_SERVER     "0.0.0.0"
#else
#define EXAMPLE_MAIN_DNS_SERVER       CONFIG_EXAMPLE_STATIC_DNS_SERVER_MAIN
#define EXAMPLE_BACKUP_DNS_SERVER     CONFIG_EXAMPLE_STATIC_DNS_SERVER_BACKUP
#endif
#ifdef CONFIG_EXAMPLE_STATIC_DNS_RESOLVE_TEST
#define EXAMPLE_RESOLVE_DOMAIN        CONFIG_EXAMPLE_STATIC_RESOLVE_DOMAIN
#endif

Next, we declare a variable ‘s_wifi-event_group’ to hold the created FreeRTOS event group. This FreeRTOS event group is used to signal when we are connected to Wi-Fi.

static EventGroupHandle_t s_wifi_event_group;

Then we define two bits for the Wi-Fi events that are of importance to us. These include the bit for a successful Wi-Fi connection with an AP and another bit for a failed Wi-Fi connection with an AP after the maximum number of retries are over.

#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

This code uses Informational Logging. The log function takes in two arguments. The first argument is the tag and the second argument is a formatted string. Therefore this global variable will be useful while calling the ESP_LOGI() functions. Here, “static ip” is the tag that will be used while logging.

static const char *TAG = "static_ip";

Set DNS Server and Static IP Address

The example_set_dns_server() function will be responsible for setting the DNS server by calling the esp_netif_set_dns_info() function. The esp_netif_set_dns_info() function takes in three parameters. The first parameter is the handle to the esp-netif instance. The second parameter is the type of the DNS server (ESP_NETIF_DNS_MAIN, ESP_NETIF_DNS_BACKUP, ESP_NETIF_DNS_FALLBACK) that is to be set. The third parameter is the DNS server address that is to be set.

static esp_err_t example_set_dns_server(esp_netif_t *netif, uint32_t addr, esp_netif_dns_type_t type)
{
    if (addr && (addr != IPADDR_NONE)) {
        esp_netif_dns_info_t dns;
        dns.ip.u_addr.ip4.addr = addr;
        dns.ip.type = IPADDR_TYPE_V4;
        ESP_ERROR_CHECK(esp_netif_set_dns_info(netif, type, &dns));
    }
    return ESP_OK;
}

The example_set_static_ip() function is will assign the static IP. It first stops the DHCP client if it is enabled for this interface by using esp_netif_dhcpc_stop() that takes in the handle to the esp-netif instance as a parameter inside it. If the function esp_netif_dhcpc_stop() does not return ESP_OK then it prints “Failed to stop dhcp client” in the terminal. After that, it accesses the values for the static IP address, static netmask address and static gateway address already defined by the user and calls esp_netif_set_ip_info(). This function will be used to set the static IP address on the interface. This function takes in two parameters. The first parameter is the handle to the esp-netif instance and the second parameter is the information of IP which is set on the particular interface. If all goes well, the terminal will print the static IP address, netmask address and the gateway address.

static void example_set_static_ip(esp_netif_t *netif)
{
    if (esp_netif_dhcpc_stop(netif) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to stop dhcp client");
        return;
    }
    esp_netif_ip_info_t ip;
    memset(&ip, 0 , sizeof(esp_netif_ip_info_t));
    ip.ip.addr = ipaddr_addr(EXAMPLE_STATIC_IP_ADDR);
    ip.netmask.addr = ipaddr_addr(EXAMPLE_STATIC_NETMASK_ADDR);
    ip.gw.addr = ipaddr_addr(EXAMPLE_STATIC_GW_ADDR);
    if (esp_netif_set_ip_info(netif, &ip) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set ip info");
        return;
    }
    ESP_LOGD(TAG, "Success to set static ip: %s, netmask: %s, gw: %s", EXAMPLE_STATIC_IP_ADDR, EXAMPLE_STATIC_NETMASK_ADDR, EXAMPLE_STATIC_GW_ADDR);
    ESP_ERROR_CHECK(example_set_dns_server(netif, ipaddr_addr(EXAMPLE_MAIN_DNS_SERVER), ESP_NETIF_DNS_MAIN));
    ESP_ERROR_CHECK(example_set_dns_server(netif, ipaddr_addr(EXAMPLE_BACKUP_DNS_SERVER), ESP_NETIF_DNS_BACKUP));
}

event_handler()

Then we have the event_handler() function which handles the Wi-Fi events. This acts as a callback function when either Wi-Fi event or IP event occurs.

The IP event occurs when the ESP32 board connects with our access point and router assigns an IP address to it. IP events can be one of the following types. But here we are using only on that is IP_EVENT_STA_GOT_IP.

/** IP event declarations */
typedef enum {
    IP_EVENT_STA_GOT_IP,               /*!< station got IP from connected AP */
    IP_EVENT_STA_LOST_IP,              /*!< station lost IP and the IP is reset to 0 */
    IP_EVENT_AP_STAIPASSIGNED,         /*!< soft-AP assign an IP to a connected station */
    IP_EVENT_GOT_IP6,                  /*!< station or ap or ethernet interface v6IP addr is preferred */
    IP_EVENT_ETH_GOT_IP,               /*!< ethernet got IP from connected AP */
    IP_EVENT_ETH_LOST_IP,              /*!< ethernet lost IP and the IP is reset to 0 */
    IP_EVENT_PPP_GOT_IP,               /*!< PPP interface got IP */
    IP_EVENT_PPP_LOST_IP,              /*!< PPP interface lost IP */
} ip_event_t;

Firstly, when the Wi-Fi events (WIFI_EVENT and WIFI_EVENT_STA_START) occur, we connect the ESP32 with the AP. We use the function esp_wifi_connect() to connect the board with the AP.

If the Wi-Fi events WIFI_EVENT and WIFI_EVENT_STA_CONNECTED occurred, then example_set_static_ip(arg) function will be called. This will assign a static IP address to the ESP32 board. If the ESP32 board is unable to connect to an AP within the maximum number of retries set, then it again tries to connect with the AP and logs “retry to connect to the AP”. However, if the ESP32 is unable to establish connection with the AP after the maximum number of retries are over, then it logs “connect to the AP fail” and sets the WIFI_FAIL_BIT.

Now, if the ESP32 board is able to successfully connect with an AP, then it suggests that both the IP events (IP_EVENT and IP_EVENT_STA_GOT_IP) occurred. In this case, it prints the ip address as an output. Moreover, it also sets the WIFI_CONNECTED_BIT.

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
        example_set_static_ip(arg);
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "static ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

wifi_init_sta()

The wifi_init_sta() function will be called inside the app_main() function to initialize Wi-Fi in station mode for ESP32.

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_netif_init());

    ESP_ERROR_CHECK(esp_event_loop_create_default());

    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        sta_netif,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        sta_netif,
                                                        &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_WIFI_SSID,
            .password = EXAMPLE_WIFI_PASS,
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
	     .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 EXAMPLE_WIFI_SSID, EXAMPLE_WIFI_PASS);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_WIFI_SSID, EXAMPLE_WIFI_PASS);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }
#ifdef CONFIG_EXAMPLE_STATIC_DNS_RESOLVE_TEST
    struct addrinfo *address_info;
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    int res = getaddrinfo(EXAMPLE_RESOLVE_DOMAIN, NULL, &hints, &address_info);
    if (res != 0 || address_info == NULL) {
        ESP_LOGE(TAG, "couldn't get hostname for :%s: "
                      "getaddrinfo() returns %d, addrinfo=%p", EXAMPLE_RESOLVE_DOMAIN, res, address_info);
    } else {
        if (address_info->ai_family == AF_INET) {
            struct sockaddr_in *p = (struct sockaddr_in *)address_info->ai_addr;
            ESP_LOGI(TAG, "Resolved IPv4 address: %s", ipaddr_ntoa((const ip_addr_t*)&p->sin_addr.s_addr));
        }
#if CONFIG_LWIP_IPV6
        else if (address_info->ai_family == AF_INET6) {
            struct sockaddr_in6 *p = (struct sockaddr_in6 *)address_info->ai_addr;
            ESP_LOGI(TAG, "Resolved IPv6 address: %s", ip6addr_ntoa((const ip6_addr_t*)&p->sin6_addr));
        }
#endif
    }
#endif
    /* The event will not be processed after unregister */
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);
}

We start off by creating a FreeRTOS event group to monitor the connection. This returns a handle to the event group.

s_wifi_event_group = xEventGroupCreate();

The ESP-IDF provides a useful macro called ESP_ERROR_CHECK() that will be used to check whether an error occurred or not. If the function that we pass inside it does not return ESP_OK, then we assert and log the line.

Here we are initializing lwIP through the function esp_netif_init() and create an IwIP task which is also known as a TCP/IP task. lwIP is a TCP/IP library stack provided by ESP-IDF that is used to perform various protocols such as TCP, UDP, DHCP etc.

ESP_ERROR_CHECK(esp_netif_init());

Next, we create a default event loop which enables the system events to be sent to the event task.

ESP_ERROR_CHECK(esp_event_loop_create_default());

Moreover, the esp_netif_create_default_wifi_sta() is used for Wi-Fi default initialization for station mode. This API is used to initialize as well as register event handlers for the default interface which is station in our case. It creates a network interface instance binding Wi-Fi driver and TCP/IP stack. When a station is in the process of connecting to an AP, various processes automatically get handled through this function.

  esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
  assert(sta_netif);

Then we initialize the Wi-Fi allocate resource for the Wi-Fi driver. It is responsible for initiating the Wi-Fi task. It takes in a single parameter cfg which is a pointer to the already initialized Wi-Fi configuration structure which is set to WIFI_INIT_CONFIG_DEFAULT() so that the initialization of the configuration is at default values. Here, wifi_init_config_t is a structure that denotes the Wi-Fi stack configuration parameters that are passed inside the esp_wifi_init() function.

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

Next, we create two event handlers called ‘instance_any_id’ for all Wi-Fi events and ‘instance_any_ip’ for when the station obtains the IP.

esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;

Next, we have two registers for the event handler instances that we set up. This way the application task can register for a callback that can listen for events for Wi-Fi and TCP/IP. The first event is ‘ESP_EVENT_ANY_ID‘ and the second event is ‘IP_EVENT_STA_GOT_IP.’ Therefore it will listen to all Wi-Fi events (ESP_EVENT_ANY_ID) and an IP event when the station got its IP address (IP_EVENT_STA_GOT_IP). Hence, the callback event_handler function will be called if any of these two events occur.

    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        sta_netif,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        sta_netif,
                                                        &instance_got_ip));

Other wifi events which we can use in our code are:

typedef enum {
    WIFI_EVENT_STA_CONNECTED,            /**< ESP32 station connected to AP */
    WIFI_EVENT_STA_DISCONNECTED,         /**< ESP32 station disconnected from AP */
    WIFI_EVENT_AP_START,                 /**< ESP32 soft-AP start */
    WIFI_EVENT_AP_STOP,                  /**< ESP32 soft-AP stop */
} mdns_used_event_t;

In this part, we set up the SSID and password of the network we want our ESP32 to connect with, as well as assign the security setting.

wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
	    .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };

Moreover, we set Wi-Fi mode as station. To set the Wi-Fi mode as station, we use the function esp_wifi_set_mode() and pass the WiFI_MODE_STA as a parameter inside it.

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );

Then we assign our network’s SSID and password. This way the Wi-Fi driver gets configured with the AP network credentials and Wi-Fi mode. Then, we start the Wi-Fi connection at our assigned configuration.

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );

At this point the information log prints “wifi_init_sta finished.” We are done with the process of initializing the Wi-Fi in station mode.

ESP_LOGI(TAG, "wifi_init_sta finished.");

Next the application moves to a blocked state whereby it waits for either of the bits in the event group to be set. This happens after the Wi-Fi is initialized and started. The application remains in the blocked state until either the Wi-Fi gets connected which is monitored by WIFI_CONNECTED_BIT or the W-Fi fails to connect within the maximum number of retries which is monitored by WIFI_FAIL_BIT.

Now if the event_handler() function sets the WIFI_CONNECTED_BIT which suggests that the ESP32 was able to successfully connect with an AP, then the informational log will print that it got connected to the SSID and password of the specified AP. If on the other hand, the event_handler() function sets the WIFI_FAIL_BIT which suggests that the ESP32 was not able to connected to the AP even after the maximum number of retries have been reached, then the informational log will print that it failed to connect to the SSID and password of the specified AP.

 EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);


    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }

Moreover, we unregister the two handlers from the system event loop for IP_EVENT (base event) and WIFI_EVENT (base event). The esp_vent_handler_instance_unregister() function takes in three parameters. The first is the event base. The second is the event id. The third is the instance object. Moreover, we also delete the event group ‘s_wifi_event_group’ using the vEventGroupDelete() function. It was created before by using a call to xEventGroupCreate().

    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);

app_main()

Inside the app_main() function, we will first initialize NVS by calling the nvs_flash_init() function. Then we will check if the NVS partition does not contain any empty pages or if it contains data in a format which is different from the current version of code. If any of these condition is true then we will erase the NVS flash using nvs_flash_erase() and initialize it again.

Then we will print “ESP_WIFI_MODE_STA” in the terminal and call the wifi_init_sta() function to initialize the Wi-Fi for station mode.

void app_main(void)
{
    //Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();
}

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor
ESP32 Static IP Address using ESP-IDF Project Flash Chip

After the code flashes successfully, you can view all the informational logs.

We can first see “ESP_WIFI_MODE_STA” printed in the terminal. Followed by “wifi_init_sta finished” which denotes that the Station Wi-Fi is initialized. Then we can view the esp_netif_handlers which includes the IP, mask, and gw addresses. Then we get the log of the static IP address 192.168.4.2 that gets assigned to the board. This shows that the ESP32 successfully connected with the AP and is assigned a static IP address.

ESP32 Static IP Address using ESP-IDF

Further readings:

2 thoughts on “ESP32 Static/Fixed IP Address using ESP-IDF”

  1. I tried this code and had a problem inside the event_handler function

    example_set_static_ip(arg);

    arg can’t be converted from void* to esp_netif_t

    to solve that, i had to declare a global variable
    esp_netif_t *sta_netif;

    and then, inside the event_handler function change the call to the example_set_static from arg to sta_netif

    example_set_static_ip(sta_netif);

    hope this help someone, I spend several days traying to make this code work.

    Reply
  2. I started with your example i used in my code and it is nice to see such a simple explaination.

    Further, my ESP32 is creating AP for clients to connect. DHCP is stopped and static IP is already assigned to ESP32 AP. Now client when connects to the AP of ESP32, i want to assign a static IP to the client by checking its MAC address.
    for example the static IP assigned to ESP32 AP is 192.168.10.10 and the client connected(which is actually trying to connect) and identified by event ID = WIFI_EVENT_AP_STACONNECTED should be set with static IP 192.168.10.11 and if more clients will be connecting, the last octect should be incremented and set their IP. 192.168.10.12, 192.168.10.13 and so on.
    Could you please help us with another code example for this scenerio?

    Reply

Leave a Comment