Module 7 : WiFi, HTTP, and MQTT

OpenSSL

This guide provides instructions on using OpenSSL to:

  1. Test SSL/TLS connections with a server.
  2. Generate a new private key and a self-signed certificate for client authentication.

1. Testing SSL/TLS Connection with openssl s_client

The openssl s_client command is a tool for checking SSL/TLS connections with servers. It’s commonly used to verify if a server's certificate is valid and to view the server’s full certificate chain.

Command:

openssl s_client -connect {Host}:{Port} -showcerts

Explanation:

Steps:

  1. Finding Server Common Name

Open your browser and find the settings like below then click on "Connection is secure".

Click on "Certificate is valid"

Here you can find the server's common name which will be our host which is typicode.com. If it is a web server, then you need to add www. in front of the CN. Hence, our host is www.typicode.com

  1. Finding your Port

For the port, you need to know which protocol you are using.

Ports that we will be using in this module :

So in this example, if we want to connect and hit the API from https://jsonplaceholder.typicode.com, our {Host}:{Port} combination will be www.typicode.com:443

  1. Run the Command
openssl s_client -connect www.typicode.com:443 -showcerts

Then you need to scroll down and get the LAST certificate.

This will be your server's root CA.

2. Generating a New RSA Key and Self-Signed Certificate with openssl req

This command generates a new private key and a self-signed certificate, which can be used for client authentication.

Command:

openssl req -newkey rsa:2048 -nodes -keyout client_key.pem -x509 -days 365 -out client_cert.pem

Explanation:

Steps:

  1. Run the Command

Enter the command in your terminal to generate the private key and certificate.

  1. Provide Certificate Details

Note, for testing purposes of this module, you can skip this step and fill blanks in all details.

You’ll be prompted to enter information like Country, State, Organization, Common Name (CN), and Email Address. These fields will be included in the certificate’s Subject field. The Common Name (CN) field is important, as it typically contains the hostname of the server or a unique identifier for the client in a client certificate setup. Check Output Files:

  1. After completion, you should find two new files

client_key.pem: This is your private key. Keep it secure, as it identifies you in a client-server interaction.

client_cert.pem: This is your self-signed certificate, which can be provided to a server for authentication if client certificate verification is set up.

You can open them with notepad to see the certificate

Example Codes

WiFi Events

The ESP32 WiFi library provides several events that allow you to monitor the WiFi connection status and respond to changes in network conditions. These events can be handled using event handlers in your code to manage WiFi connections more effectively.

ARDUINO_EVENT_WIFI_STA_GOT_IP

#include <WiFi.h>

const char* ssid = "ssid";
const char* password = "password";

void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info){
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
}

void setup(){
    Serial.begin(115200);

    WiFi.onEvent(WiFiGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);

    WiFi.begin(ssid, password);
    
    Serial.println();
    Serial.println();
    Serial.println("Wait for WiFi... ");
}

void loop(){
    delay(1000);
}

NTP Server

The ESP32 can be configured to fetch the current time and date from NTP servers over the internet. This is particularly useful in projects requiring accurate timestamps, scheduling, or clock synchronization. With the built-in WiFi capabilities of the ESP32, connecting to an NTP server is simple.

Using the configTime() function, the ESP32 can communicate with an NTP server to get the current time and date. This function takes the timezone offset and NTP server URLs as parameters. Once configured, the ESP32 will maintain the time internally, even if it disconnects from WiFi.

#include <WiFi.h>
#include <time.h>

const char* ssid     = "ssid";
const char* password = "password";

const char* ntpServer = "id.pool.ntp.org";
const long  gmtOffset_sec = 25200; // GMT offset in seconds (e.g., +25200 for WIB because 7 * 3600)

void setup(){
    Serial.begin(115200);

    // Connect to Wi-Fi
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected.");
    
    // Init and get the time
    configTime(gmtOffset_sec, 0, ntpServer);
    printLocalTime();
}

void loop(){
    delay(1000);
    printLocalTime();
}

void printLocalTime(){
    struct tm timeinfo;
    if(!getLocalTime(&timeinfo)){
        Serial.println("Failed to obtain time");
        return;
    }
    Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
    Serial.print("Day of week: ");
    Serial.println(&timeinfo, "%A");
    Serial.print("Month: ");
    Serial.println(&timeinfo, "%B");
    Serial.print("Day of Month: ");
    Serial.println(&timeinfo, "%d");
    Serial.print("Year: ");
    Serial.println(&timeinfo, "%Y");
    Serial.print("Hour: ");
    Serial.println(&timeinfo, "%H");
    Serial.print("Hour (12 hour format): ");
    Serial.println(&timeinfo, "%I");
    Serial.print("Minute: ");
    Serial.println(&timeinfo, "%M");
    Serial.print("Second: ");
    Serial.println(&timeinfo, "%S");
}

HTTPClient

The HTTPClient library on the ESP32 enables you to perform HTTP requests (GET, POST, PUT, DELETE) with ease. This is especially useful for applications where the ESP32 interacts with web servers, APIs, or cloud services over HTTP or HTTPS. The library provides a straightforward API for making HTTP requests, handling responses, and managing errors.

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>

const char ssid[] = "ssid";
const char password[] = "password";
const char *server_cert = R"(-----BEGIN CERTIFICATE-----
Get the server's root CA by running the command below

openssl s_client -connect {Host}:{Port} -showcerts
-----END CERTIFICATE-----)";

const char *client_cert = R"(-----BEGIN CERTIFICATE-----
Generate client certificates by running the command below

openssl req -newkey rsa:2048 -nodes -keyout client_key.pem -x509 -days 365 -out client_cert.pem

Fill client_cert with client_cert.pem
-----END CERTIFICATE-----)";

const char *client_key = R"(-----BEGIN PRIVATE KEY-----
Fill client_key with client_key.pem
-----END PRIVATE KEY-----)";

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    Serial.printf("Connecting to WiFi with SSID : %s\n", ssid);
    while(!WiFi.isConnected());
    Serial.println("Connection succesful");

    delay(1000);
}

void loop() {
    WiFiClientSecure *client = new WiFiClientSecure;
    if(client) {
        client->setCACert(server_cert);
        client->setCertificate(client_cert);
        client->setPrivateKey(client_key);

        {
            // Add a scoping block for HTTPClient https to make sure it is destroyed before WiFiClientSecure *client is 
            HTTPClient https;
    
            Serial.print("[HTTPS] begin...\n");
            if (https.begin(*client, "https://httpbin.org/get")) {  // HTTPS
                Serial.print("[HTTPS] GET...\n");
                // start connection and send HTTP header
                int httpCode = https.GET();
        
                // httpCode will be negative on error
                if (httpCode > 0) {
                // HTTP header has been send and Server response header has been handled
                    Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
            
                    // file found at server
                    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
                        String payload = https.getString();
                        Serial.println(payload);
                    }
                } else {
                    Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
                }
                https.end();
            } else {
                Serial.printf("[HTTPS] Unable to connect\n");
            }

            // End extra scoping block
        }
    
        delete client;
    } else {
        Serial.println("Unable to create client");
    }

    Serial.println();
    Serial.println("Waiting 10s before the next round...");
    delay(10000);
}

ArduinoJSON

ArduinoJSON is a library for the Arduino/ESP32 to serialize and de-serialize JSON documents. Below is an example of JSON de-serializing from the endpoint https://httpbin.org/get

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char ssid[] = "ssid";
const char password[] = "password";
const char *server_cert = R"(-----BEGIN CERTIFICATE-----
Get the server's root CA by running the command below

openssl s_client -connect {Host}:{Port} -showcerts
-----END CERTIFICATE-----)";

const char *client_cert = R"(-----BEGIN CERTIFICATE-----
Generate client certificates by running the command below

openssl req -newkey rsa:2048 -nodes -keyout client_key.pem -x509 -days 365 -out client_cert.pem

Fill client_cert with client_cert.pem
-----END CERTIFICATE-----)";

const char *client_key = R"(-----BEGIN PRIVATE KEY-----
Fill client_key with client_key.pem
-----END PRIVATE KEY-----)";

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    Serial.printf("Connecting to WiFi with SSID : %s\n", ssid);
    while (!WiFi.isConnected());
    Serial.println("Connection successful");

    delay(1000);
}

void loop() {
    WiFiClientSecure *client = new WiFiClientSecure;
    if (client) {
        client->setCACert(server_cert);
        client->setCertificate(client_cert);
        client->setPrivateKey(client_key);

        {
            // Add a scoping block for HTTPClient https to ensure it is destroyed before WiFiClientSecure *client is
            HTTPClient https;
    
            Serial.print("[HTTPS] begin...\n");
            if (https.begin(*client, "https://httpbin.org/get")) {  // HTTPS
                Serial.print("[HTTPS] GET...\n");
                // start connection and send HTTP header
                int httpCode = https.GET();
        
                // httpCode will be negative on error
                if (httpCode > 0) {
                    // HTTP header has been sent and Server response header has been handled
                    Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
            
                    // file found at server
                    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
                        String payload = https.getString();
                        Serial.println(payload);

                        // Parse JSON payload
                        DynamicJsonDocument doc(1024);
                        DeserializationError error = deserializeJson(doc, payload);

                        if (!error) {
                            // Extract "origin" and "url" values
                            const char* origin = doc["origin"];
                            const char* url = doc["url"];

                            // Print extracted values
                            Serial.printf("Origin: %s\n", origin);
                            Serial.printf("URL: %s\n", url);
                        } else {
                            Serial.print("JSON parsing failed: ");
                            Serial.println(error.c_str());
                        }
                    }
                } else {
                    Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
                }
                https.end();
            } else {
                Serial.printf("[HTTPS] Unable to connect\n");
            }

            // End extra scoping block
        }
    
        delete client;
    } else {
        Serial.println("Unable to create client");
    }

    Serial.println();
    Serial.println("Waiting 10s before the next round...");
    delay(10000);
}

MQTT (PubSubClient)

Basic MQTT example

This sketch demonstrates the basic capabilities of the library. It connects to an MQTT server then:

It will reconnect to the server if the connection is lost using a blocking reconnect function.

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>

const char ssid[] = "ssid";
const char password[] = "password";
const char *server_cert = R"(-----BEGIN CERTIFICATE-----
Get the server's root CA by running the command below

openssl s_client -connect {Host}:{Port} -showcerts
-----END CERTIFICATE-----)";

const char *client_cert = R"(-----BEGIN CERTIFICATE-----
Generate client certificates by running the command below

openssl req -newkey rsa:2048 -nodes -keyout client_key.pem -x509 -days 365 -out client_cert.pem

Fill client_cert with client_cert.pem
-----END CERTIFICATE-----)";

const char *client_key = R"(-----BEGIN PRIVATE KEY-----
Fill client_key with client_key.pem
-----END PRIVATE KEY-----)";

const char mqttServer[] = "broker.hivemq.com";
const int mqttPort = 8883;

void callback(char* topic, byte* payload, unsigned int length) {
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    for (int i=0;i<length;i++) {
        Serial.print((char)payload[i]);
    }
    Serial.println();
}

WiFiClientSecure wifiClient;
PubSubClient client(wifiClient);

void reconnect() {
    // Loop until we're reconnected
    while (!client.connected()) {
        Serial.print("Attempting MQTT connection...");
        // Attempt to connect
        if (client.connect("arduinoClient")) {
            Serial.println("connected");
            // Once connected, publish an announcement...
            client.publish("outTopic","hello world");
            // ... and resubscribe
            client.subscribe("inTopic");
        } else {
            Serial.print("failed, rc=");
            Serial.print(client.state());
            Serial.println(" try again in 5 seconds");
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
}

void setup()
{
    Serial.begin(115200);

    client.setServer(mqttServer, mqttPort);
    client.setCallback(callback);

    WiFi.begin(ssid, password);

    wifiClient.setCACert(server_cert);
    wifiClient.setCertificate(client_cert);
    wifiClient.setPrivateKey(client_key);

    delay(1000);
}

void loop()
{
    if (!client.connected()) {
        reconnect();
    }
    client.loop();
}

Module 7: WiFi, HTTP(S), & MQTT(S)

Basics of WiFi Networking

WiFi Standards and Protocols

Wi-Fi is a technology that allows devices to connect to a wireless network and exchange data. Wi-Fi is based on the IEEE 802.11 standard, which defines the physical and data link layers of a network. There are several versions of the IEEE 802.11 standard, such as 802.11a, 802.11b, 802.11g, 802.11n, 802.11ac, and 802.11ax. Each version has different characteristics, such as frequency bands, data rates, modulation schemes, and range.

The ESP32 supports the following Wi-Fi standards:

The ESP32 also supports the following Wi-Fi protocols:

Network Types and Their Relevance

There are three main types of Wi-Fi networks that the ESP32 can operate in:

  1. Infrastructure: The most common type of Wi-Fi network, where the ESP32 connects to an access point (AP) like a router or hotspot. The AP acts as a bridge between the ESP32 and the internet or other devices on the same network. The AP also assigns an IP address to the ESP32 and manages network traffic.
  2. Ad-hoc: In this Wi-Fi network type, the ESP32 connects directly to another device without needing an AP. The ESP32 and the other device form a peer-to-peer network and assign IP addresses to each other. This network type is useful for temporary or local communication, such as file sharing or gaming.
  3. Wi-Fi Direct: In this Wi-Fi network type, the ESP32 connects to another device that supports Wi-Fi Direct, such as a smartphone or printer. The ESP32 and the other device form a one-to-one network and communicate using established protocols like P2P (Peer-to-Peer) or Miracast. This network type is useful for specific applications like streaming or printing.

The ESP32 can operate in any of these network types, depending on the configured mode:

  1. Station mode (STA): The ESP32 acts as a station and connects to an AP. This is the default mode for the ESP32.
  2. Access Point mode (AP): The ESP32 acts as an AP, allowing other stations to connect to it. The ESP32 can also provide internet access to connected stations using NAT (Network Address Translation) or routing.
  3. Station/AP coexistence mode (STA+AP): The ESP32 operates as both a station and an AP simultaneously. The ESP32 can connect to another AP as a station and provide a separate network as an AP. This mode is useful for extending the range of an existing network or creating a bridge between two networks.
  4. NAN mode (NAN): The ESP32 acts as a node in a NAN (Neighbor Awareness Networking) network. NAN is a new Wi-Fi standard that allows devices to discover and communicate with each other without an AP. NAN is designed for low-power and proximity-based applications, such as social networking or location-based services.

IP Addressing, DHCP, DNS, and Other Network Fundamentals

An IP address is a unique identifier assigned to a device on a network. IP addresses consist of four numbers separated by dots, like 192.168.1.100. Each number can range from 0 to 255. IP addresses can be static or dynamic. A static IP address remains constant, while a dynamic IP address is assigned by a DHCP (Dynamic Host Configuration Protocol) server and may change over time.

A DHCP server is a device that manages IP address allocation on a network. A DHCP server can be an AP, router, or dedicated server. The DHCP server assigns an IP address to a device upon request and releases it when the device disconnects or its lease expires. The DHCP server also provides other network information, such as subnet mask, gateway, and DNS server.

A subnet mask is a number that defines the size and structure of a network. It consists of four numbers separated by dots, such as 255.255.255.0. Each number can be 255 or 0. The subnet mask specifies which part of an IP address belongs to the network and which part belongs to the host. For example, if the subnet mask is 255.255.255.0, the first three numbers of the IP address represent the network, while the last number represents the host.

A gateway is a device that connects two or more networks and routes traffic between them. A gateway can be an AP, router, or dedicated device. The gateway has an IP address on each network it connects. For example, if a gateway connects a local network (192.168.1.0/24) to the internet (0.0.0.0/0), it has the IP address 192.168.1.1 on the local network and 1.2.3.4 on the internet.

A DNS server is a device that translates domain names into IP addresses. A domain name is a human-readable identifier for a website or service on the internet, such as www.bing.com. The DNS server maintains a database of domain names and their corresponding IP addresses. A DNS server can be an AP, router, or dedicated server. A DNS server can also cache previous query results to speed up resolution.

The ESP32 can obtain an IP address and other network information from a DHCP server when connected to an AP as a station. The ESP32 can also act as a DHCP server when operating as an AP and assign IP addresses to connected stations. The ESP32 can use a DNS server to resolve domain names to IP addresses when accessing the internet or other services.

Wi-Fi Capabilities of ESP32

Hardware Capabilities

The ESP32 supports the following Wi-Fi features:

Dual-Mode Functionality and Use Cases

The ESP32 can operate in two Wi-Fi modes: station mode and access point mode. In station mode, the ESP32 connects to an existing Wi-Fi network as a client. In access point mode, the ESP32 creates its own Wi-Fi network and allows other devices to join as clients. The ESP32 can also operate in both modes simultaneously, creating a Wi-Fi repeater or bridge between two networks.

The dual-mode functionality of the ESP32 enables various use cases, such as:

ESP32 Security Protocols: Support for WPA, WPA2, WPA3

The ESP32 supports various security protocols to protect Wi-Fi communication from unauthorized access and attacks. The security protocols include:

The ESP32 supports WPA, WPA2, and WPA3 in both station and access point modes. It can also operate in mixed mode, allowing the ESP32 to accept connections from devices that support different security protocols. For example, the ESP32 can create a Wi-Fi network supporting both WPA2 and WPA3, enabling devices that support either protocol to join the network.

The ESP32 also supports Protected Management Frames (PMF), a feature that encrypts and authenticates management frames, such as deauthentication, disassociation, and robust management frames. PMF prevents attacks using forged or spoofed management frames to disrupt Wi-Fi connections or perform man-in-the-middle attacks. PMF can be configured as optional, required, or disabled in both station and access point modes.

The ESP32 supports Wi-Fi Enterprise, a secure authentication mechanism for enterprise wireless networks. It uses a RADIUS server for user authentication before connecting to an access point. The authentication process is based on 802.1X policies and comes with various Extended Authentication Protocol (EAP) methods, such as TLS, TTLS, PEAP, and EAP-FAST. The ESP32 only supports Wi-Fi Enterprise in station mode.

Configuring ESP32 as a Wi-Fi Station (STA)

The ESP32 can operate as a Wi-Fi station, meaning it can connect to an existing Wi-Fi network as a client device. This mode is useful for accessing the internet or other network services. In this section, we'll look at how to configure the ESP32 as a Wi-Fi station and manage Wi-Fi connection events and system responses.

Connecting to an Existing Wi-Fi Network

#include <WiFi.h>
#define WIFI_SSID "my_wifi"
#define WIFI_PASS "my_password"

void setup() {
  // Initialize serial monitor
  Serial.begin(115200);
  
  // Connect to Wi-Fi network
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  
  // Wait until connected or timeout
  uint8_t timeout = 30;
  while (WiFi.status() != WL_CONNECTED && timeout--) {
    delay(1000);
    Serial.print(".");
  }

  // Check connection status
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Connected to Wi-Fi");
    Serial.println("IP Address: " + WiFi.localIP().toString());
  } else {
    Serial.println("Failed to connect to Wi-Fi");
  }
}

void loop() {
  // Do nothing
}

Managing Wi-Fi Connection Events and System Responses

The ESP32 can handle various Wi-Fi connection events and system responses using the WiFiEvent class. This class allows the ESP32 to register callback functions for different types of events, such as:

To use the WiFiEvent class, we need to follow this code:

#include <WiFi.h>
#define WIFI_SSID "my_wifi"
#define WIFI_PASS "my_password"

// Callback function for WiFiEventStationModeConnected event
void onStationModeConnected(WiFiEventStationModeConnected info) {
  Serial.println("Connected to AP");
  Serial.println("SSID: " + info.ssid);
  Serial.println("BSSID: " + info.bssid);
  Serial.println("Channel: " + String(info.channel));
}

// Callback function for WiFiEventStationModeDisconnected event
void onStationModeDisconnected(WiFiEventStationModeDisconnected info) {
  Serial.println("Disconnected from AP");
  Serial.println("SSID: " + info.ssid);
  Serial.println("BSSID: " + info.bssid);
  Serial.println("Reason: " + String(info.reason));
}

// Callback function for WiFiEventStationModeGotIP event
void onStationModeGotIP(WiFiEventStationModeGotIP info) {
  Serial.println("Obtained IP Address");
  Serial.println("IP: " + info.ip.toString());
  Serial.println("Mask: " + info.mask.toString());
  Serial.println("Gateway: " + info.gw.toString());
}

// Callback function for WiFiEventStationModeAuthModeChanged event
void onStationModeAuthModeChanged(WiFiEventStationModeAuthModeChanged info) {
  Serial.println("Authentication mode changed");
  Serial.println("Old mode: " + String(info.oldMode));
  Serial.println("New mode: " + String(info.newMode));
}

// Callback function for WiFiEventStationModeDHCPTimeout event
void onStationModeDHCPTimeout() {
  Serial.println("DHCP Timeout");
}

void setup() {
  // Initialize serial monitor
  Serial.begin(115200);
  
  // Register event handlers
  WiFi.onEvent(onStationModeConnected, SYSTEM_EVENT_STA_CONNECTED);
  WiFi.onEvent(onStationModeDisconnected, SYSTEM_EVENT_STA_DISCONNECTED);
  WiFi.onEvent(onStationModeGotIP, SYSTEM_EVENT_STA_GOT_IP);
  WiFi.onEvent(onStationModeAuthModeChanged, SYSTEM_EVENT_STA_AUTHMODE_CHANGE);
  WiFi.onEvent(onStationModeDHCPTimeout, SYSTEM_EVENT_STA_DHCP_TIMEOUT);
  
  // Connect to Wi-Fi network
  WiFi.begin(WIFI_SSID, WIFI_PASS);
}

void loop() {
  // Do nothing
}

Example Output:

Connected to AP
SSID: my_wifi
BSSID: 00:11:22:33:44:55
Channel: 6

Obtaining IP address
IP: 192.168.1.100
Mask: 255.255.255.0
Gateway: 192.168.1.1

Disconnected from AP
SSID: my_wifi
BSSID: 00:11:22:33:44:55
Reason: 200

Secure Storage and Management of Wi-Fi Credentials

The ESP32 can securely store and manage Wi-Fi credentials using the Preferences library. The Preferences library allows the ESP32 to save and retrieve key-value pairs in the non-volatile storage (NVS) partition. The Preferences library also supports encryption of stored data using the AES-256 algorithm.

To use the Preferences library, follow the code below:

#include <WiFi.h>
#include <Preferences.h>

#define WIFI_SSID "my_wifi"
#define WIFI_PASS "my_password"

// Create a Preferences object
Preferences preferences;

// Define a 32-byte encryption key
uint8_t enc_key[32] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                        0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
                        0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
                        0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20 };

void setup()
{
    // Initialize serial monitor
    Serial.begin(115200);

    // Open a namespace called "wifi"
    preferences.begin("wifi", false); // false = read/write

    // Enable encryption with the encryption key
    preferences.setEncryptionKey(enc_key);

    // Store Wi-Fi SSID and password
    preferences.putString("ssid", WIFI_SSID);
    preferences.putString("pass", WIFI_PASS);

    // Close the namespace
    preferences.end();
}

To retrieve the SSID and Wi-Fi password from the NVS partition using the getString method, you can optionally enable encryption using the setEncryptionKey method. Here’s an example:

// Define a 32-byte encryption key
uint8_t enc_key[32] = { /* ... */ };

void setup()
{
    // Initialize serial monitor
    Serial.begin(115200);

    // Open a namespace called "wifi"
    preferences.begin("wifi", true);

    // Enable encryption with the encryption key
    preferences.setEncryptionKey(enc_key);

    // Retrieve Wi-Fi SSID and password
    String ssid = preferences.getString("ssid", "");
    String pass = preferences.getString("pass", "");

    // Close the namespace
    preferences.end();

    // Connect to the Wi-Fi network
    WiFi.begin(ssid.c_str(), pass.c_str());

    // Wait until connected or timeout
    uint8_t timeout = 30;
    while (WiFi.status() != WL_CONNECTED && timeout--)
    {
        delay(1000);
        Serial.print(".");
    }

    // Check connection status
    if (WiFi.status() == WL_CONNECTED)
    {
        Serial.println("Connected to Wi-Fi");
        Serial.println("IP Address: " + WiFi.localIP().toString());
    }
    else
    {
        Serial.println("Failed to connect to Wi-Fi");
    }
}

void loop()
{
    // Do nothing
}

Configuring ESP32 as a Wi-Fi Access Point (AP)

The ESP32 can also operate as a Wi-Fi access point, meaning it can create its own Wi-Fi network and allow other devices to join as clients. This mode is useful for creating a local network without an internet connection or router. In this section, we will look at how to set up the ESP32 as a Wi-Fi access point and how to manage connected client devices and their data flow.

Procedure to Initialize a Local Wi-Fi Network

To initialize a local Wi-Fi network with the ESP32 as an access point, follow these steps:

#include <WiFi.h>

#define WIFI_SSID "ESP32-Access-Point"
#define WIFI_PASS "123456789"

// Set the web server port to 80
WiFiServer server(80);

void setup()
{
    // Initialize serial monitor
    Serial.begin(115200);

    // Set Wi-Fi mode to AP
    WiFi.mode(WIFI_AP);

    // Create Wi-Fi network
    // WiFi.softAP(ssid, password, channel, hidden, max_connection)
    WiFi.softAP(WIFI_SSID, WIFI_PASS, 1, false, 4);

    // Print the IP address of the AP
    Serial.print("AP IP Address: ");
    Serial.println(WiFi.softAPIP());

    // Start web server
    server.begin();
}

void loop()
{
    // Listen for incoming clients
    WiFiClient client = server.available();
    if (client)
    {
        // If a new client connects, print a message
        Serial.println("New client.");
        
        // Create a String to hold incoming data
        String request = "";

        // Loop while the client is connected
        while (client.connected())
        {
            // If there are bytes to read from the client
            if (client.available())
            {
                // Read a byte and add it to the request
                char c = client.read();
                request += c;

                // If the byte is a newline character
                if (c == '\n')
                {
                    // If the request is empty, the client has sent an empty line
                    // This is the end of the HTTP request, so send a response
                    if (request.length() == 0)
                    {
                        // HTTP header always starts with response code and content type
                        // Then a blank line
                        client.println("HTTP/1.1 200 OK");
                        client.println("Content-type:text/plain");
                        client.println();
                        
                        // Send the response body
                        client.println("Hello from ESP32 AP!");
                        
                        // Break out of the loop
                        break;
                    }
                    else
                    {
                        // Clear the request
                        request = "";
                    }
                }
            }
        }

        // Close the connection
        client.stop();
        Serial.println("Client disconnected.");
    }
}

The ESP32 access point can be configured with various parameters, such as SSID, password, channel, SSID visibility, and maximum number of connections. These parameters can be passed as arguments to the WiFi.softAP method, as shown below:

// WiFi.softAP(ssid, password, channel, hidden, max_connection)

// Create an open Wi-Fi network with the default channel (1), SSID visibility (true), and maximum connections (4)
WiFi.softAP("ESP32-AP", NULL);

// Create a secure Wi-Fi network with the default channel (1), SSID visibility (true), and maximum connections (4)
WiFi.softAP("ESP32-AP", "123456789");

// Create a secure Wi-Fi network with a custom channel (6), SSID visibility (true), and maximum connections (4)
WiFi.softAP("ESP32-AP", "123456789", 6);

// Create a secure Wi-Fi network with a custom channel (6), SSID visibility (false), and maximum connections (4)
WiFi.softAP("ESP32-AP", "123456789", 6, false);

// Create a secure Wi-Fi network with a custom channel (6), SSID visibility (false), and maximum connections (2)
WiFi.softAP("ESP32-AP", "123456789", 6, false, 2);

HTTP/HTTPS Introduction

Overview of HTTP and HTTPS

Importance in IoT Communication

Internet of Things (IoT) refers to a network of physical devices, such as sensors, actuators, cameras, and microcontrollers, that are connected to the internet. HTTP/HTTPS is important for IoT communication because it allows IoT devices to interact with web servers and cloud platforms (e.g., Google Cloud, AWS, Microsoft Azure). HTTP/HTTPS enables IoT devices to send data to the cloud, receive commands or updates, or access web APIs.

Differences and Transition from HTTP to HTTPS

HTTP and HTTPS have several key differences:

Many websites have switched to HTTPS for better security and privacy. Some web browsers also mark HTTP sites as not secure to warn users.

Basics of Web Communication

Request-Response Model

The request-response model describes how web communication works. It is based on the idea that a client (e.g., web browser) sends a request to a server (e.g., web server), and the server responds.

  1. Request: Consists of:

    • Request Line: Specifies the method, URL, and HTTP version.
    • Request Headers: Additional information about the request (e.g., host, content type).
    • Request Body: Data that the client wants to send to the server (e.g., form inputs, files).
  2. Response: Consists of:

    • Status Line: Specifies the HTTP version, status code, and status message.
    • Response Headers: Additional information about the response (e.g., content type, content length).
    • Response Body: Data that the server sends back to the client (e.g., web pages, images).

HTTP Methods: GET, POST, PUT, DELETE, and Use Cases

Status Codes and Their Meaning

Status codes are numbers that indicate the result of a request. They are grouped as follows:

HTTP Communication with ESP32

Creating a Basic HTTP Client

To use the ESP32 as an HTTP client, we need to include the following libraries:

#include <WiFi.h>
#include <HTTPClient.h>

The WiFi.h library allows us to connect to a Wi-Fi network, and the HTTPClient.h library allows us to make HTTP requests.

We also need to declare the Wi-Fi SSID and password, as well as the hostname and pathname of the server we want to communicate with. For example:

const char* WIFI_SSID = "YOUR_WIFI_SSID"; // change this
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; // change this
String HOST_NAME = "http://YOUR_DOMAIN.com"; // change this
String PATH_NAME = "/products/arduino"; // change this

In the setup() function, we need to initialize the serial monitor and connect to the Wi-Fi network. We can use the WiFi.begin() and WiFi.status() functions to do this. For example:

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println();
  Serial.println("Connected to Wi-Fi");
  Serial.println("IP Address: ");
  Serial.println(WiFi.localIP());
}

In the loop() function, we need to create an HTTP client object and use the begin() method to specify the URL we want to request. For example:

void loop() {
  HTTPClient http;
  http.begin(HOST_NAME + PATH_NAME);
  // further code will follow
}

Sending an HTTP GET Request to an API

To send an HTTP GET request to an API, we use the GET() method of the HTTP client object. This method returns an integer representing the status code of the response. For example:

int httpCode = http.GET();

We can use the httpCode variable to check if the request was successful. If httpCode is positive, it means the server responded with a valid status code. If httpCode is negative, it means there was an error in the request. For example:

if (httpCode > 0) {
  Serial.print("HTTP GET request completed, code: ");
  Serial.println(httpCode);
} else {
  Serial.print("HTTP GET request failed, error: ");
  Serial.println(http.errorToString(httpCode));
}

If the request is successful, we can use the getString() method of the HTTP client object to get the response content as a string. This method returns a string containing the data sent by the server. For example:

if (httpCode == HTTP_CODE_OK) {
  String payload = http.getString();
  Serial.println("HTTP Response: ");
  Serial.println(payload);
}

The response content may contain various types of data, depending on the API. For example, it could contain JSON, XML, HTML, plain text, or binary data. We need to parse the response content according to the data type we expect. For instance, if the response contains JSON data, we need to use a JSON parsing library to extract the values we need.

Sending an HTTP POST Request

To send an HTTP POST request to the server, we use the POST() method of the HTTP client object. This method takes a string as an argument, representing the data we want to send in the request body. For example:

String data = "temperature=26&humidity=70"; // example data
int httpCode = http.POST(data);

We can use the same httpCode variable to check if the request was successful, as we did with the GET request. If the request is successful, we can also use the same getString() method to get the response content as a string, just like we did with the GET request.

The data we send in the request body can be in various formats, depending on the server. For example, it could be in query string format, JSON format, XML format, or binary format. We need to format the data according to the server’s expectations. We may also need to set the appropriate content type header for the request, using the addHeader() method of the HTTP client object. For example:

http.addHeader("Content-Type", "application/json"); // example header
String data = "{\"temperature\":26,\"humidity\":70}"; // example data in JSON format
int httpCode = http.POST(data);

Parsing JSON Responses and Error Handling

To parse JSON responses, we need to include the following library:

#include <ArduinoJson.h>

The ArduinoJson.h library allows us to parse and generate JSON data. We can use the deserializeJson() function to parse a JSON string into a JSON object. For example:

String payload = http.getString(); // get the response content as a string
StaticJsonDocument<200> doc; // create a JSON document object with a capacity of 200 bytes
deserializeJson(doc, payload); // parse the JSON string into the JSON document object

We can use the doc variable to access values within the JSON object, using the [] operator. For example, if the JSON object contains a key called "temperature" and a key called "humidity," we can retrieve their values as follows:

int temperature = doc["temperature"]; // get the value of "temperature" as an integer
int humidity = doc["humidity"]; // get the value of "humidity" as an integer
Serial.print("Temperature: ");
Serial.println(temperature);
Serial.print("Humidity: ");
Serial.println(humidity);

We can also use the doc variable to check if the JSON object contains a specific key, using the containsKey() method. For example, if we want to check if the JSON object contains a key called "error," we can do as follows:

if (doc.containsKey("error")) {
  Serial.println("Error in JSON response");
}

We can also use the doc variable to check if JSON parsing was successful, using the isNull() method. For example, if JSON parsing fails, the doc variable will be null, and we can handle it as follows:

if (doc.isNull()) {
  Serial.println("Invalid JSON response");
}

Code Snippets for GET and POST Requests

The following code snippets show how to make GET and POST requests using the ESP32 as an HTTP client. This code assumes that the server is running on the same network as the ESP32, with a server IP address of 192.168.1.100. It also assumes that the server responds with JSON data containing keys "temperature" and "humidity".

GET Request

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* WIFI_SSID = "YOUR_WIFI_SSID"; // change this
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; // change this
String HOST_NAME = "http://192.168.1.100"; // change this
String PATH_NAME = "/get_data";

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println();
  Serial.println("Connected to Wi-Fi");
  Serial.println("IP Address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  HTTPClient http;
  http.begin(HOST_NAME + PATH_NAME);
  
  int httpCode = http.GET();
  if (httpCode > 0) {
    Serial.print("HTTP GET request completed, code: ");
    Serial.println(httpCode);
    
    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.println("HTTP Response: ");
      Serial.println(payload);
      
      StaticJsonDocument<200> doc;
      deserializeJson(doc, payload);
      
      if (doc.isNull()) {
        Serial.println("Invalid JSON response");
      } else {
        if (doc.containsKey("error")) {
          Serial.println("Error in JSON response");
        } else {
          int temperature = doc["temperature"];
          int humidity = doc["humidity"];
          Serial.print("Temperature: ");
          Serial.println(temperature);
          Serial.print("Humidity: ");
          Serial.println(humidity);
        }
      }
    }
  } else {
    Serial.print("HTTP GET request failed, error: ");
    Serial.println(http.errorToString(httpCode));
  }
  
  http.end();
  delay(5000);
}

POST Request

The following code demonstrates how to make a POST request using the ESP32 as an HTTP client. This code assumes that the server is running on the same network as the ESP32, with the server IP address 192.168.1.100. The server is expected to respond with JSON data containing the keys "temperature" and "humidity".

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* WIFI_SSID = "YOUR_WIFI_SSID"; // change this
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; // change this
String HOST_NAME = "http://192.168.1.100"; // change this
String PATH_NAME = "/post_data";

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println();
  Serial.println("Connected to Wi-Fi");
  Serial.println("IP Address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  HTTPClient http;
  http.begin(HOST_NAME + PATH_NAME);
  http.addHeader("Content-Type", "application/json"); // set content-type to JSON

  StaticJsonDocument<100> doc; // create a JSON document with a 100-byte capacity
  doc["temperature"] = 26; // set the "temperature" key value to 26
  doc["humidity"] = 70; // set the "humidity" key value to 70

  String data; // create a string to store JSON data
  serializeJson(doc, data); // serialize the JSON document into the string

  int httpCode = http.POST(data); // send POST request with JSON data
  if (httpCode > 0) {
    Serial.print("HTTP POST request done, code: ");
    Serial.println(httpCode);
    
    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.println("HTTP Response: ");
      Serial.println(payload);
      
      StaticJsonDocument<200> doc;
      deserializeJson(doc, payload);
      
      if (doc.isNull()) {
        Serial.println("Invalid JSON response");
      } else {
        if (doc.containsKey("error")) {
          Serial.println("Error in JSON response");
        } else {
          int temperature = doc["temperature"];
          int humidity = doc["humidity"];
          Serial.print("Temperature: ");
          Serial.println(temperature);
          Serial.print("Humidity: ");
          Serial.println(humidity);
        }
      }
    }
  } else {
    Serial.print("HTTP POST request failed, error: ");
    Serial.println(http.errorToString(httpCode));
  }
  
  http.end();
  delay(5000);
}

Creating an HTTP Server with ESP32

Configuring ESP32 as an HTTP Server

To use ESP32 as an HTTP server, we need to include the following libraries:

#include <WiFi.h>
#include <WebServer.h>

The WiFi.h library allows us to connect to a Wi-Fi network, and the WebServer.h library enables us to create and handle HTTP requests.

We also need to declare the Wi-Fi SSID, Wi-Fi password, and the port number for the web server. For example:

const char* WIFI_SSID = "YOUR_WIFI_SSID"; // change this
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; // change this
const int SERVER_PORT = 80; // default port for HTTP

In the setup() function, we need to initialize the serial monitor and connect to the Wi-Fi network. We can use the WiFi.begin() and WiFi.status() functions to do this. For example:

void setup() {
  Serial.begin(115200);
  Serial.print("Connecting to ");
  Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println();
  Serial.println("Connected to Wi-Fi");
  Serial.println("IP Address: ");
  Serial.println(WiFi.localIP());
}

Next, we need to create a web server object and pass the port number as an argument. For example:

WebServer server(SERVER_PORT); // create a web server on port 80

Then, we define routes and handlers for the web server. A route is a path or URL that the client requests from the server. A handler is a function that executes when a specific route is requested. We can use the on() method from the web server object to define routes and handlers. For example:

server.on("/", handleRoot); // calls the 'handleRoot' function when the root route is requested

The handleRoot function is a custom function that we need to define later. It will handle requests for the root route, which is the default route when we access the web server.

Finally, we start the web server using the begin() method from the web server object. For example:

server.begin(); // start the web server
Serial.println("Web server started");

To define routes and handlers for the web server, we need to write custom functions for each route. These functions should have the following structure:

void handleRoute() {
  // code to handle the request for the route
}

The handleRoute function must have the same name as the one we pass to the on() method. For example, if we define a route like this:

server.on("/about", handleAbout);

We need to write a function like this:

void handleAbout() {
  // code to handle the request for the /about route
}

Within functions, we can use various methods and properties of the web server object to handle requests. Some of the most useful methods and properties are:

To serve HTML and CSS content from the ESP32, we need to write HTML and CSS code as a string in our sketch. For example, we can write a simple HTML code like this:

String HTML = "<!DOCTYPE html>"
              "<html>"
              "<head>"
              "<style>"
              "h1 {color: blue;}"
              "</style>"
              "</head>"
              "<body>"
              "<h1>Hello, world!</h1>"
              "</body>"
              "</html>";

Then, we can send the HTML code as a response to the client using the server.send() method. We need to specify the content type as "text/html" to inform the browser how to interpret the content. For example, we can write a function to handle the root route like this:

void handleRoot() {
  server.send(200, "text/html", HTML); // send the HTML code as a response
}

We can also use variables or expressions within our HTML code to make it dynamic. For example, we can use the millis() function to display the current time on the web page. We can do this by combining the HTML code with the millis() function using the + operator. For example:

String HTML = "<!DOCTYPE html>"
              "<html>"
              "<head>"
              "<style>"
              "h1 {color: blue;}"
              "</style>"
              "</head>"
              "<body>"
              "<h1>Hello, world!</h1>"
              "<p>The current time is " + String(millis()) + " milliseconds.</p>"
              "</body>"
              "</html>";

Implementing HTTPS with SSL/TLS

Explanation of SSL/TLS Encryption

SSL stands for Secure Sockets Layer, and TLS stands for Transport Layer Security. Both are cryptographic protocols that provide encryption and authentication for data transmitted over the internet. They are commonly used to secure web communications, like HTTPS, as well as other protocols, such as SMTP, FTP, and MQTT.

SSL/TLS encryption works by establishing a secure connection between a client (such as a web browser) and a server (such as a web server) through a process called an SSL/TLS handshake. The handshake involves the following steps:

  1. The client sends a ClientHello message to the server, indicating the SSL/TLS version, supported cipher suites, and a random number.
  2. The server responds with a ServerHello message, selecting the SSL/TLS version, cipher suite, and another random number. The server also sends its certificate, which contains its public key and identity information, signed by a trusted authority.
  3. The client verifies the server's certificate and, optionally, sends its own certificate if the server requests it. The client also generates a premaster secret, which is a random number, and encrypts it with the server's public key. The client then sends the encrypted premaster secret to the server.
  4. The server decrypts the premaster secret using its private key. Both the client and server use the premaster secret and random numbers to generate a master secret, which is a shared secret key. They also use the master secret to generate a session key, which is a symmetric key used to encrypt and decrypt data.
  5. The client sends a Finished message to the server, containing a hash of previous messages, encrypted with the session key. The server does the same, sending a Finished message to the client.
  6. The client and server can now exchange data securely, using the session key to encrypt and decrypt data.

Using WiFiClientSecure.h for Secure Server Communication

To simplify this explanation, using WiFiClientSecure is essentially the same as using a regular WiFiClient. However, there is one key difference to note. Before you initiate a connection to the target server, you need to define the server's certificate in a constant variable, for example, named server_cert. After that, you can run client.setCACert(server_cert) on the client. Once this step is completed, you will use WiFiClientSecure in a similar way to how you would use a regular WiFiClient. This means all operations you perform afterward—such as sending and receiving data—will be done over a secure connection, as it is authenticated with the correct certificate.

Introduction to MQTT and MQTTS

Overview of MQTT in IoT

MQTT (Message Queuing Telemetry Transport) is a lightweight publish/subscribe messaging protocol designed for low-bandwidth, high-latency, and unreliable networks. It is widely used in the Internet of Things (IoT) to enable communication between devices and applications.

MQTT operates on a broker and client principle. The broker is a central server that manages message distribution among clients. Clients are devices or applications connected to the broker and exchange messages using topics. A topic is a hierarchical string that identifies the content and scope of a message. For example, home/temperature is a topic representing temperature data for a home.

Clients can publish messages to a topic or subscribe to a topic to receive messages from the broker. The broker ensures that messages are delivered to subscribed clients according to the Quality of Service (QoS) level specified by the publisher. The QoS level indicates message delivery guarantees. MQTT has three QoS levels:

  1. QoS 0: At most once delivery. Messages are delivered at most once or not at all. This QoS level is the fastest and simplest but does not provide reliability. Suitable for scenarios where occasional message loss is acceptable, such as sensor data or telemetry.
  2. QoS 1: At least once delivery. Messages are delivered at least once, but they may be delivered more than once. This level ensures messages are received by the broker and subscribed clients but may introduce duplicate messages. Suitable for scenarios where message loss is unacceptable, but duplicates can be tolerated or filtered, such as alerts or notifications.
  3. QoS 2: Exactly once delivery. Messages are delivered exactly once. This is the most reliable and complex level but also introduces more overhead and latency. Suitable for scenarios where both message loss and duplication are unacceptable, such as financial transactions or commands.

MQTT is a simple and flexible protocol that can be implemented on various platforms and devices. It has many advantages for IoT applications, such as:

  1. Low overhead: MQTT packet headers are only 2 bytes, which minimizes network bandwidth and resource consumption.
  2. Scalability: Brokers can handle millions of simultaneous connections and messages from clients, allowing large-scale IoT deployments.
  3. Security: MQTT supports Transport Layer Security (TLS) encryption and authentication, which protects transmitted data from eavesdropping and tampering.
  4. Interoperability: MQTT is based on the standard TCP/IP stack, making it compatible with any network infrastructure and device. It also supports various data formats, such as JSON, XML, or binary, making integration with different applications and services easy.

Advantages of MQTT for ESP32 Devices

Using MQTT with ESP32 devices has several advantages, such as:

Comparing MQTT and MQTTS (MQTT over SSL/TLS)

MQTT is a protocol that operates on the TCP/IP stack, providing reliable and ordered data delivery. However, TCP/IP does not offer security or encryption for data, making it vulnerable to various attacks, such as:

To protect data from these attacks, MQTT can be used with SSL/TLS, a protocol that provides security and encryption for data. SSL/TLS stands for Secure Sockets Layer/Transport Layer Security and is widely used to secure internet communications. SSL/TLS works by establishing a secure channel between the sender and receiver, which involves the following steps:

MQTT over SSL/TLS, also known as MQTTS, is a variant of MQTT that uses SSL/TLS to secure data. MQTTS has the same features and functionality as MQTT, except it uses a different port number (8883 instead of 1883) and a different URI scheme (mqtts instead of mqtt) to indicate SSL/TLS usage. MQTTS offers several advantages over MQTT, such as:

Basics of MQTT Protocol

Topics

A topic is a hierarchical string that identifies the content and scope of a message. Topics are used by the broker to filter and distribute messages among clients. A topic consists of one or more topic levels, separated by forward slashes (/). For example, home/temperature is a topic with two levels: home and temperature.

Topics are case-sensitive and can contain UTF-8 encoded characters, except:

Wildcard characters are used to create topic filters, allowing clients to subscribe to multiple topics with a single subscription. There are two types of wildcards:

Wildcards can only be used in topic filters, not in topic names. Topic filters must follow these rules:

Broker

The broker is a central server that manages MQTT communication among clients. It is responsible for:

Brokers can be hosted on cloud platforms, such as AWS IoT Core, Azure IoT Hub, or Google Cloud IoT Core, or on local machines, such as a Raspberry Pi, using MQTT broker software like Mosquitto, EMQ X, or HiveMQ.

Clients

A client is a device or application that connects to a broker and exchanges messages using topics. Clients can be publishers, subscribers, or both. Publishers are clients that publish messages to topics, while subscribers are clients that subscribe to topics to receive messages from the broker. Clients can publish or subscribe to multiple topics simultaneously.

Clients can use various MQTT client libraries or tools to connect to the broker and perform MQTT operations, such as:

Publish/Subscribe Mechanism

MQTT uses a publish/subscribe mechanism to enable communication between clients and the broker. The publish/subscribe mechanism works as follows:

Quality of Service (QoS) Levels: 0, 1, and 2

Retained Messages

A retained message is a message stored by the broker for a topic and delivered to new subscribers when they subscribe to that topic. A publisher can mark a message as retained by setting the retain flag to 1 in the fixed header. The broker will store the last retained message for each topic and replace it with a new one if the publisher publishes another retained message to the same topic. A publisher can also delete the retained message by publishing a message with a null payload and retain flag set to 1 to the same topic.

Retained messages are useful for providing the latest status or information about a topic to new subscribers without waiting for the publisher to publish a new message.

Last Will

The last will is a message sent by the broker on behalf of a client if the client disconnects unexpectedly. A client can specify a last will message when connecting to the broker by providing the topic, payload, QoS level, and retain flag of the message. The broker will store the last will message until the client disconnects normally or the keep-alive interval expires. If the client disconnects abnormally, the broker publishes the last will message to the topic and delivers it to subscribers according to the QoS level and retain flag.

The last will message is useful for notifying other clients about the status or reason for the client’s disconnection and for taking appropriate action.

MQTT Communication with ESP32

Connecting to the MQTT Broker

To communicate with an MQTT broker, ESP32 needs to use an MQTT client library compatible with the Arduino IDE. One of the most popular and easy-to-use libraries is the PubSubClient library by Nick O'Leary. This library provides a simple and convenient way to connect, publish, and subscribe to MQTT brokers and topics without additional libraries or dependencies.

To install the PubSubClient library, follow these steps:

  1. Open Arduino IDE and go to Sketch > Include Library > Manage Libraries
  2. Search for "PubSubClient" and select the latest version
  3. Click "Install" and wait for the installation to complete
  4. Close the Library Manager window

Code Example

// Include Wi-Fi and MQTT libraries
#include <WiFi.h>
#include <PubSubClient.h>

// Define Wi-Fi and MQTT credentials
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
const char* mqtt_server = "your_mqtt_broker_address";

// Create Wi-Fi client object
WiFiClient wifiClient;

// Create PubSubClient object
PubSubClient mqttClient(wifiClient);

// Connect to Wi-Fi network
void setup_wifi() {
    Serial.print("Connecting to ");
    Serial.println(ssid);

    // Connect to Wi-Fi network
    WiFi.begin(ssid, password);

    // Wait for connection to establish
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(1000);
    }

    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());
}

// Connect to MQTT broker
void reconnect() {
    while (!mqttClient.connected()) {
        Serial.print("Attempting MQTT connection...");
        String clientId = "ESP32Client-";
        clientId += String(random(0xffff), HEX);

        // Try connecting to the broker
        if (mqttClient.connect(clientId.c_str())) {
            Serial.println("connected");
            mqttClient.subscribe("esp32/output");
        } else {
            Serial.print("failed, rc=");
            Serial.print(mqttClient.state());
            Serial.println(" try again in 5 seconds");
            delay(5000);
        }
    }
}

// Setup function runs once when ESP32 starts
void setup() {
    Serial.begin(115200);
    setup_wifi();
    mqttClient.setServer(mqtt_server, 1883);
    mqttClient.setCallback(callback);
}

// Loop function runs repeatedly after setup
void loop() {
    if (!mqttClient.connected()) {
        reconnect();
    }
    mqttClient.loop();
}