Skip to content

14. Interface and application programming

Group assignment:

  • Compare as many tool options as possible

To see our group assignment click here

Individual assignment:

  • Write an application that interfaces a user with input and/or output device(s) on a board that you made.

What I learned from the group assignment

Working on this group assignment helped me understand the trade-offs between different tools for building an interface that talks to an electronic board over serial. Comparing Python (Tkinter + PySerial + matplotlib) with JavaScript (Node.js/serialport and the Web Serial API) showed me that Python is faster to prototype with and excellent for plotting sensor data, but produces a less modern-looking interface and requires every user to install Python and its libraries locally.

The Web Serial API, on the other hand, needs no installation at all — just a Chromium-based browser — which makes it much easier to share with others, even though it isn't supported everywhere (no Firefox or Safari support).

This comparison directly influenced my choice for the individual assignment: since I wanted a dashboard accessible from any device without installing anything, I went with a web-based interface (HTML/JS + WebSocket) hosted directly on the ESP32, combining the flexibility of JavaScript with the simplicity of a browser-only client.

Project Introduction

The system is based on an ESP32 acting as an asynchronous web server. The interface, accessible from any web browser (PC or smartphone), allows the user to:

  • Monitor temperature and humidity in real time using a DHT11 sensor

  • React instantly to physical events (such as notifications generated when a push button is pressed)

  • Remotely control an actuator (LED ON/OFF control)

  • Secure the environment by defining a critical temperature threshold that triggers both visual and hardware alerts


Development Architecture

The development is based on a clear separation between physical data processing (Arduino/ESP32) and the presentation layer (Web Interface). Unlike a traditional web server where the page must reload to update information, this project uses a persistent connection through the WebSocket protocol.

A. Communication Flow (Logical Diagram)

The diagram below illustrates how data flows between the physical components and the web browser:

Image

  1. Sensor (Input): The DHT11 sends raw environmental data to the ESP32.

  2. ESP32 (The Hub): The ESP32 converts the sensor values into JSON format, a lightweight text-based data structure.

  3. The Bridge (WebSocket): The ESP32 continuously pushes the JSON data to the web page without waiting for a request from the client.

  4. Interface (Output): JavaScript receives the JSON object, decodes the values, and updates the gauges and visual elements dynamically.

B. Web Page Development (Frontend)

The interface is designed to be intelligent and responsive. The frontend development is divided into three layers:

1. Structure (HTML5)

HTML is used to create:

  • Temperature and humidity gauge containers

  • LED control button

  • Notification and logging area

2. Style (Tailwind CSS)

The project uses Tailwind CSS, a utility-first framework, to ensure that the dashboard remains visually attractive and fully responsive on:

  • Smartphones

  • Tablets

  • Desktop computers

3. Logic (JavaScript)

JavaScript manages the dynamic behavior of the dashboard:

  • WebSocket connection management
  • Real-time data decoding
  • Threshold management using a user-defined slider
  • Visual notifications and alerts
  • Background color changes when critical values are exceeded

AI-Assisted Development

To save time and improve efficiency, I used artificial intelligence during the development process.
For anyone who wishes to reproduce or extend this project, I have included below the exact prompt that was used.

Here is the exact prompt I formulated. The reader can copy and paste it to generate a result similar to mine.

Act as a Full-Stack IoT expert. Create a complete ESP32 project including:

A Web Interface (HTML/CSS/JS):

- Modern Dark Mode design using Tailwind CSS
- Two gauges (Temperature and Humidity) using Chart.js
- A slider to define a temperature alert threshold
- A log area to display real-time notifications from a physical push button
- A control button to drive an LED (GPIO 2)
- WebSocket connection (ws://[ESP32_IP]/ws) with automatic reconnection handling

The Arduino Code (C++):

- Libraries: ESPAsyncWebServer.h, ArduinoJson.h, and DHT.h
- DHT11 connected to GPIO 27
- Push button connected to GPIO 14 using INPUT_PULLUP
- Logic: If Temperature > Threshold (received from the web interface), blink the LED rapidly
- Send a JSON packet every 3 seconds:
  {"temp": float, "hum": float, "btn": "OFF/ON"}
- Receive a JSON packet for threshold and LED control:
  {"threshold": float, "led_cmd": int}

Provide the HTML code in one block and the Arduino code in another.

To develop both the web interface code and the ESP32 firmware, we used Kimi AI, an artificial intelligence assistant specialized in code generation, reasoning, and technical assistance.

As you can see, in just a few minutes, we obtained the following result:

Image

Image

Image

Wiring diagram

Component Component pin ESP32 pin
DHT11 VCC 3V3
DHT11 GND GND
DHT11 DATA GPIO 23
LED Anode GPIO 15
LED Cathode GND
push button Pin 1 GPIO 12
push button Pin 2 GND

Image

Project code

/*
 * ============================================================================
 * Fab Academy 2026 - Interface & Application Programming
 * Projet IoT ESP32 : DHT11 + Bouton + LED + WebSocket + JSON
 * VERSION ENRICHIE : JavaScript avancé avec graphique historique
 * ============================================================================
 */

#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#include "DHT.h"

// ========================== CONFIGURATION RESEAU ==========================
const char* SSID_WIFI = "XXXXXXXXXXXXXXX";
const char* PASS_WIFI = "XXXXXXXXXXXXXXX";

// ========================== CONFIGURATION PINS ==========================
#define PIN_DHT       23    // DHT11 Data
#define PIN_BUTTON    12    // Bouton poussoir (INPUT_PULLUP)
#define PIN_LED       15     // LED integree

#define DHT_TYPE      DHT11

// ========================== TIMINGS ==========================
#define INTERVAL_DHT_READ     2000  
#define INTERVAL_WS_SEND      3000  
#define INTERVAL_BLINK_ALERT  200   

// ========================== OBJETS GLOBAUX ==========================
DHT dht(PIN_DHT, DHT_TYPE);
WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

// ========================== VARIABLES D'ETAT ==========================
float currentTemperature  = 0.0;
float currentHumidity     = 0.0;
float temperatureThreshold = 30.0; 

bool ledManualState     = false;    
bool isAlertActive      = false;    
bool buttonPressed      = false;    
bool lastButtonState    = false;    

unsigned long lastDHTRead   = 0;
unsigned long lastWsSend    = 0;
unsigned long lastBlink     = 0;

// ========================== PAGE WEB EMBARQUEE ==========================
const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fab Academy - IoT Dashboard</title>
</head>
<body>
    <!-- Le contenu HTML complet continue ici -->
</body>
</html>
)rawliteral";

// ========================== FONCTIONS WEBSOCKET ==========================
void handleWebSocketMessage(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {

    if (type == WStype_TEXT) {

        Serial.printf("[WS] Message du client #%u: %s\n", num, payload);

        StaticJsonDocument<256> doc;

        DeserializationError error = deserializeJson(doc, (char*)payload);

        if (error) {
            Serial.printf("[ERR] JSON invalide: %s\n", error.c_str());
            return;
        }

        // Reception du seuil de temperature
        if (doc.containsKey("threshold")) {

            temperatureThreshold = doc["threshold"];

            Serial.printf("[CFG] Nouveau seuil: %.1f C\n",
                          temperatureThreshold);
        }

        // Reception commande LED
        if (doc.containsKey("led_cmd")) {

            ledManualState = (doc["led_cmd"] == 1);

            Serial.printf("[CFG] LED manuelle: %s\n",
                          ledManualState ? "ON" : "OFF");
        }
    }

    else if (type == WStype_CONNECTED) {

        Serial.printf("[WS] Client #%u connecte depuis %s\n",
                      num,
                      webSocket.remoteIP(num).toString().c_str());
    }

    else if (type == WStype_DISCONNECTED) {

        Serial.printf("[WS] Client #%u deconnecte\n", num);
    }
}

// ========================== FONCTIONS CAPTEURS ==========================
bool readDHTSensor() {

    float t = dht.readTemperature();
    float h = dht.readHumidity();

    if (isnan(t) || isnan(h)) {

        Serial.println("[ERR] Echec lecture DHT11");
        return false;
    }

    currentTemperature = t;
    currentHumidity = h;

    // Verification du seuil
    isAlertActive = (currentTemperature > temperatureThreshold);

    return true;
}

// ========================== GESTION BOUTON ==========================
void checkButton() {

    bool currentState = (digitalRead(PIN_BUTTON) == LOW);

    // Detection appui bouton
    if (currentState && !lastButtonState) {

        buttonPressed = true;

        Serial.println("[BTN] Bouton presse !");

        StaticJsonDocument<100> doc;
        doc["btn"] = "ON";

        char buffer[100];

        size_t len = serializeJson(doc, buffer);

        webSocket.broadcastTXT(buffer, len);
    }

    else if (!currentState && lastButtonState) {

        buttonPressed = false;
    }

    lastButtonState = currentState;
}

// ========================== GESTION LED ==========================
void updateLED() {

    // Clignotement automatique si alerte
    if (isAlertActive) {

        if (millis() - lastBlink >= INTERVAL_BLINK_ALERT) {

            lastBlink = millis();

            digitalWrite(PIN_LED,
                         !digitalRead(PIN_LED));
        }
    }

    // Sinon mode manuel
    else {

        digitalWrite(PIN_LED,
                     ledManualState ? HIGH : LOW);
    }
}

// ========================== ENVOI DONNEES JSON ==========================
void broadcastData() {

    StaticJsonDocument<256> doc;

    doc["temp"] = currentTemperature;
    doc["hum"] = currentHumidity;
    doc["is_alert"] = isAlertActive;
    doc["led"] = (digitalRead(PIN_LED) == HIGH);

    char buffer[256];

    size_t len = serializeJson(doc, buffer);

    webSocket.broadcastTXT(buffer, len);

    Serial.printf("[TX] %s\n", buffer);
}

// ========================== HANDLERS HTTP ==========================
void handleRoot() {

    server.send_P(200,
                  "text/html",
                  INDEX_HTML);
}

void handleNotFound() {

    server.send(404,
                "text/plain",
                "Not Found");
}

// ========================== SETUP ==========================
void setup() {

    Serial.begin(115200);

    delay(1000);

    Serial.println("\n========================================");
    Serial.println("  Fab Academy 2026 - IoT Dashboard");
    Serial.println("  Version JS ENRICHIE");
    Serial.println("========================================\n");

    // Configuration des broches
    pinMode(PIN_LED, OUTPUT);
    pinMode(PIN_BUTTON, INPUT_PULLUP);

    dht.begin();

    // Connexion WiFi
    WiFi.begin(SSID_WIFI, PASS_WIFI);

    Serial.print("[WIFI] Connexion");

    while (WiFi.status() != WL_CONNECTED) {

        delay(500);
        Serial.print(".");
    }

    Serial.println();

    Serial.print("[WIFI] Connecte ! IP: ");

    Serial.println(WiFi.localIP());

    // Serveur HTTP
    server.on("/", HTTP_GET, handleRoot);

    server.onNotFound(handleNotFound);

    server.begin();

    Serial.println("[HTTP] Serveur demarre sur port 80");

    // Serveur WebSocket
    webSocket.begin();

    webSocket.onEvent(handleWebSocketMessage);

    Serial.println("[WS] WebSocket demarre sur port 81");

    Serial.println("\n>>> Ouvrez http://" +
                   WiFi.localIP().toString() +
                   " dans votre navigateur");
}

// ========================== LOOP ==========================
void loop() {

    unsigned long now = millis();

    // Lecture DHT11
    if (now - lastDHTRead >= INTERVAL_DHT_READ) {

        lastDHTRead = now;

        if (readDHTSensor()) {

            Serial.printf("[DHT] Temp: %.1f C | Hum: %.1f%% | Alert: %s\n",
                         currentTemperature,
                         currentHumidity,
                         isAlertActive ? "OUI" : "NON");
        }
    }

    // Verification bouton
    checkButton();

    // Gestion LED
    updateLED();

    // Envoi WebSocket
    if (now - lastWsSend >= INTERVAL_WS_SEND) {

        lastWsSend = now;

        broadcastData();
    }

    // Gestion serveur
    server.handleClient();

    webSocket.loop();

    delay(10);
}

RESULT

Image

General Explanation of the Code

1. Library Inclusion

#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#include "DHT.h"

These libraries provide the main functionalities of the project:

  • WiFi.h → Connects the ESP32 to the Wi-Fi network
  • WebServer.h → Creates the HTTP web server
  • WebSocketsServer.h → Enables real-time communication between the ESP32 and the web interface
  • ArduinoJson.h → Handles JSON data exchange
  • DHT.h → Communicates with the DHT11 temperature and humidity sensor

2. Network and Pin Configuration

These variables store the Wi-Fi credentials used by the ESP32 to connect to the local network.

const char* SSID_WIFI = "XXXXXXXXXXXXXXX";
const char* PASS_WIFI = "XXXXXXXXXXXXXXX";

These lines define the GPIO pins connected to: DHT11 sensor, push button and LED

#define PIN_DHT 23
#define PIN_BUTTON 12
#define PIN_LED 15

3. Global Objects

DHT dht(PIN_DHT, DHT_TYPE);
WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

The code creates:

  • A DHT sensor object
  • An HTTP server running on port 80
  • A WebSocket server running on port 81

The WebSocket server is responsible for instant bidirectional communication between the ESP32 and the browser.


4. System Variables

float currentTemperature = 0.0;
float currentHumidity = 0.0;
float temperatureThreshold = 30.0;

These variables store:

  • Current temperature
  • Current humidity
  • Alert threshold defined by the user

The system also uses Boolean variables to manage:

  • LED state
  • Alert state
  • Button state

5. Embedded Web Interface

const char INDEX_HTML[] PROGMEM

The complete HTML/CSS/JavaScript dashboard is stored directly inside the ESP32 memory.

This web page allows the user to:

  • Visualize sensor values in real time
  • Control the LED remotely
  • Change the alert threshold
  • Receive notifications from the physical button
  • Display historical graphs

6. WebSocket Communication

void handleWebSocketMessage(...)

This function manages messages received from the web interface.

The ESP32 receives JSON commands such as:

{
  "threshold": 35
}

or

{
  "led_cmd": 1
}

The ESP32 then updates:

  • The temperature threshold
  • The LED state

7. Sensor Reading

bool readDHTSensor()

This function reads:

  • Temperature
  • Humidity

from the DHT11 sensor.

It also checks whether the measured temperature exceeds the threshold:

isAlertActive = (currentTemperature > temperatureThreshold);

8. Button Management

void checkButton()

The ESP32 continuously monitors the push button.

When the button is pressed:

  • A JSON message is created
  • The message is sent instantly to all connected web clients via WebSocket

This creates real-time interaction between the physical system and the interface.


9. LED Control Logic

void updateLED()

The LED operates in two modes:

Normal Mode

The LED follows the manual command received from the web interface.

Alert Mode

If the temperature exceeds the threshold, the LED blinks automatically to indicate danger.


10. JSON Data Broadcasting

void broadcastData()

The ESP32 periodically sends sensor data to the web dashboard in JSON format:

{
  "temp": 27.5,
  "hum": 65,
  "is_alert": false,
  "led": true
}

The JavaScript interface receives these values and updates the dashboard instantly.


11. Setup Function

void setup()

The setup function initializes the entire system:

  • Serial Monitor
  • GPIO pins
  • DHT11 sensor
  • Wi-Fi connection
  • HTTP server
  • WebSocket server

After initialization, the ESP32 displays its IP address in the Serial Monitor.


12. Main Loop

void loop()

The loop continuously performs several tasks:

  • Read the DHT11 sensor
  • Check the push button
  • Update the LED behavior
  • Send WebSocket data
  • Handle HTTP requests
  • Handle WebSocket communication

This creates a fully interactive IoT supervision system operating in real time.

Result After Uploading the Code

The following result was obtained after uploading the program to the ESP32 board.

Files

Download Code files