You dont have javascript enabled! Please enable it!

Practical case: ESP32 Local Network Monitor

Practical case: ESP32 Local Network Monitor — hero

Objective and use case

What you’ll build: A standalone Field Technician’s Local Network Monitor using an ESP32 configured as a Software Access Point (SoftAP). It broadcasts a secure, localized Wi-Fi network to serve a real-time, air-gapped diagnostic dashboard directly to a smartphone or tablet.

Why it matters / Use cases

  • Infrastructure-independent diagnostics: Allows technicians to connect directly to equipment in offline environments like remote agricultural fields, deep industrial basements, or new construction sites.
  • Safe machine fault logging: Interfaces with industrial fault relays so personnel can safely monitor machine states via browser, avoiding physical exposure to high-voltage control panels.
  • Isolated human-machine interface (HMI): Delivers a highly secure, air-gapped configuration portal that cannot be accessed from the public internet, drastically reducing cybersecurity attack surfaces.
  • Access control monitoring: Acts as a localized, temporary monitor for server rack doors or secure gates, logging open/close states instantly.

Expected outcome

  • The ESP32 reliably broadcasts a WPA2-secured Wi-Fi network (SSID: ESP32-FieldMonitor) with a client connection time of <2 seconds.
  • A fully mobile-responsive web dashboard loads without external internet, rendering diagnostic UI elements.
  • Real-time hardware status and fault logs update on the client browser with <50ms network latency.

Audience: Industrial IoT Developers, Field Technicians, Maintenance Engineers; Level: Intermediate

Architecture/flow: ESP32 (SoftAP Mode) → Broadcasts WPA2 SSID → Technician Smartphone Connects → ESP32 Web Server → Serves HTML/JS Dashboard & Streams Real-time GPIO Data.

Educational validation note

Before publication, this case passed the Prometeo automated validation gate with status PASS. For this ESP32 DevKitC profile, the project was checked as a PlatformIO project: the validator extracted platformio.ini and src/main.cpp, created a temporary project and ran pio run against platform = espressif32, board = esp32dev and framework = arduino. It also checked article structure, copy/paste-safe ASCII command options, and unsupported stacks such as direct ESP-IDF or non-scoped ESP32 boards.

Published validation evidence

  • Automatic result: PASS.
  • Parsed structure: 4 sections, 2 tables and 2 code blocks detected before publication.
  • Checked code: 1 PlatformIO config + 1 ESP32 source/pio run.
  • Supported catalog: the article text was checked against Prometeo’s validation-capable device profiles, and unsupported stacks block publication.
  • Report findings: no blocking findings.

This validation confirms syntax and tool compatibility for the published code, but it does not replace physical testing on your exact ESP32 DevKitC board, wiring, power supply and local WiFi environment.

Educational safety note

This project is an educational prototype, not a certified product. Before powering the setup, verify the pinout of your exact ULX3S board revision, keep FPGA I/O signals at 3.3 V, never connect 5 V directly to I/O pins, disconnect power before changing wiring, and use suitable external supplies for loads, motors or servos while sharing ground only when the wiring requires it.

Conceptual block diagram

High-level view: what enters the system, what each block processes, and what comes out.

Functional architecture

ESP32 (SoftAP Mode)

Broadcasts WPA2 SSID

Technician Smartphone Connects

ESP32 Web Server

Serves HTML/JS Dashboard & Streams Real-t…

Conceptual signal and responsibility flow between device blocks.

Validation path

Source code

PlatformIO build

Flash

Serial monitor

Conceptual summary of the tools used to check the published material.

Prerequisites

To successfully complete this tutorial, you will need:
* Basic understanding of C++ programming and microcontroller GPIO concepts.
* PlatformIO IDE installed (either as a Visual Studio Code extension or via the Command Line Interface).
* Basic knowledge of networking concepts, specifically Wi-Fi Access Points (SSID, WPA2) and IP addressing.
* A web browser on a Wi-Fi enabled device (smartphone, tablet, or laptop) to view the dashboard.


Materials

  • Microcontroller: ESP32 DevKitC (Exact model: ESP32 DevKitC V4, typically equipped with the ESP32-WROOM-32 module).
  • Input Device: Pushbutton or dry contact switch (this will simulate an external machine fault relay or door contact).
  • Visual Indicator: Standard 5mm Status LED (e.g., Blue or Green).
  • Passive Components: 1x 330Ω resistor (current limiting for the LED).
  • Prototyping: Standard standard breadboard and male-to-male jumper wires.
  • Connection: Micro-USB cable (must support both power and data transfer; charging-only cables will fail to flash the device).

(Note: Depending on your specific ESP32 DevKitC manufacturer, you may need to install CP210x or CH34x USB-to-UART drivers on your operating system to allow PlatformIO to recognize the device).


Setup/Connection

The hardware setup utilizes the ESP32’s internal pull-up resistors for the contact input, minimizing the need for external passive components. When the contact switch is open, the internal resistor pulls the pin HIGH. When the switch is closed, it connects the pin to Ground (LOW).

Wiring Table:

ComponentESP32 DevKitC PinConnection / DestinationDescription
Contact SwitchGPIO 18Terminal 1 of SwitchConfigured as INPUT_PULLUP.
Contact SwitchGNDTerminal 2 of SwitchPulls GPIO 18 LOW when closed.
Status LEDGPIO 19LED Anode (Long leg)Configured as OUTPUT.
Status LEDGNDLED Cathode (Short leg) via 330Ω ResistorCompletes the LED circuit safely.

Important Hardware Notes:
1. Ensure you are using a 330Ω resistor in series with the Status LED to prevent drawing excessive current from the ESP32’s GPIO pin, which could damage the microcontroller.
2. Do not connect the contact switch to any external voltage source. It must act as a “dry contact” (a simple mechanical closure to Ground).


Validated Code

The project requires two files within your PlatformIO workspace. The platformio.ini file configures the build environment, while src/main.cpp contains the application logic.

PlatformIO Configuration

Create or overwrite the platformio.ini file in the root of your project directory with the following configuration. This ensures the correct board definition and serial monitor baud rate are used.

; platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

Application Source Code

Create or overwrite the src/main.cpp file with the following complete, compilable code.

Public preview of the validated file. The complete source is shown to members and in PDF/Print.

// src/main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>

// Hardware Pin Definitions
#define CONTACT_PIN 18
#define STATUS_LED_PIN 19

// SoftAP Network Credentials
const char* ssid = "ESP32-FieldMonitor";
const char* password = "admin1234"; // WPA2 requires a minimum of 8 characters

// Initialize the WebServer on port 80
WebServer server(80);

// Global state variables
int currentContactState = HIGH;
int lastContactState = HIGH;

// HTML Dashboard stored in Program Memory (PROGMEM) to save RAM
const char dashboard_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Field Monitor Dashboard</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      text-align: center;
      background-color: #e9ecef;
      margin: 0;
      padding: 20px;
    }
    .card {
      background: white;
      max-width: 400px;
      margin: 40px auto;
      padding: 30px;
      border-radius: 12px;
      box-shadow: 0 8px 16px rgba(0,0,0,0.1);
    }
    h2 {
      color: #343a40;
      margin-top: 0;
    }
    .status-box {
      font-size: 1.8em;
      font-weight: bold;
      margin: 20px 0;
      padding: 20px;
      border-radius: 8px;
      transition: background-color 0.3s, color 0.3s;
    }
    .loading { background-color: #f8f9fa; color: #6c757d; border: 2px dashed #6c757d; }
    .closed { background-color: #d4edda; color: #155724; border: 2px solid #28a745; }
    .open { background-color: #f8d7da; color: #721c24; border: 2px solid #dc3545; }
    .footer {
      margin-top: 20px;
      font-size: 0.85em;
      color: #6c757d;
    }
  </style>
</head>
<body>
  <div class="card">
    <h2>Machine Contact Status</h2>
    <div id="contact-state" class="status-box loading">Awaiting Data...</div>
    <div class="footer">Auto-refreshing every 500ms via JSON API</div>
  </div>

  <script>
    // Asynchronous function to fetch status from the ESP32
    function fetchStatus() {
      fetch('/api/status')
        .then(response => {
          if (!response.ok) {
            throw new Error('Network response was not ok');
          }
          return response.json();
        })
        .then(data => {
          const statusDiv = document.getElementById('contact-state');
          // data.contact is 0 when closed (LOW) due to INPUT_PULLUP
          if(data.contact === 0) {
            statusDiv.innerHTML = "CONTACT CLOSED";
            statusDiv.className = "status-box closed";
          } else {
            statusDiv.innerHTML = "CONTACT OPEN";
            statusDiv.className = "status-box open";
          }
// ... continues for members in the complete validated source ...

🔒 Part of the validated code is premium. With the 7-day pass or the monthly membership you can view the complete validated source.

// src/main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>

// Hardware Pin Definitions
#define CONTACT_PIN 18
#define STATUS_LED_PIN 19

// SoftAP Network Credentials
const char* ssid = "ESP32-FieldMonitor";
const char* password = "admin1234"; // WPA2 requires a minimum of 8 characters

// Initialize the WebServer on port 80
WebServer server(80);

// Global state variables
int currentContactState = HIGH;
int lastContactState = HIGH;

// HTML Dashboard stored in Program Memory (PROGMEM) to save RAM
const char dashboard_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Field Monitor Dashboard</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      text-align: center;
      background-color: #e9ecef;
      margin: 0;
      padding: 20px;
    }
    .card {
      background: white;
      max-width: 400px;
      margin: 40px auto;
      padding: 30px;
      border-radius: 12px;
      box-shadow: 0 8px 16px rgba(0,0,0,0.1);
    }
    h2 {
      color: #343a40;
      margin-top: 0;
    }
    .status-box {
      font-size: 1.8em;
      font-weight: bold;
      margin: 20px 0;
      padding: 20px;
      border-radius: 8px;
      transition: background-color 0.3s, color 0.3s;
    }
    .loading { background-color: #f8f9fa; color: #6c757d; border: 2px dashed #6c757d; }
    .closed { background-color: #d4edda; color: #155724; border: 2px solid #28a745; }
    .open { background-color: #f8d7da; color: #721c24; border: 2px solid #dc3545; }
    .footer {
      margin-top: 20px;
      font-size: 0.85em;
      color: #6c757d;
    }
  </style>
</head>
<body>
  <div class="card">
    <h2>Machine Contact Status</h2>
    <div id="contact-state" class="status-box loading">Awaiting Data...</div>
    <div class="footer">Auto-refreshing every 500ms via JSON API</div>
  </div>

  <script>
    // Asynchronous function to fetch status from the ESP32
    function fetchStatus() {
      fetch('/api/status')
        .then(response => {
          if (!response.ok) {
            throw new Error('Network response was not ok');
          }
          return response.json();
        })
        .then(data => {
          const statusDiv = document.getElementById('contact-state');
          // data.contact is 0 when closed (LOW) due to INPUT_PULLUP
          if(data.contact === 0) {
            statusDiv.innerHTML = "CONTACT CLOSED";
            statusDiv.className = "status-box closed";
          } else {
            statusDiv.innerHTML = "CONTACT OPEN";
            statusDiv.className = "status-box open";
          }
        })
        .catch(error => {
          console.error('Error fetching status:', error);
          const statusDiv = document.getElementById('contact-state');
          statusDiv.innerHTML = "CONNECTION LOST";
          statusDiv.className = "status-box loading";
        });
    }

    // Poll the API every 500 milliseconds
    setInterval(fetchStatus, 500);

    // Initial fetch immediately on load
    window.onload = fetchStatus;
  </script>
</body>
</html>
)rawliteral";

// Route Handler: Serve the main HTML dashboard
void handleRoot() {
  server.send(200, "text/html", dashboard_html);
  Serial.println("Dashboard accessed by a client.");
}

// Route Handler: Serve the JSON API for the dashboard to consume
void handleApiStatus() {
  // Construct a simple JSON string manually
  String jsonPayload = "{\"contact\": " + String(currentContactState) + "}";
  server.send(200, "application/json", jsonPayload);
}

// Route Handler: Handle 404 Not Found
void handleNotFound() {
  server.send(404, "text/plain", "404: Not Found");
}

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  delay(1000); // Allow serial to stabilize
  Serial.println("\n--- ESP32 Field Monitor Initialization ---");

  // Configure Hardware Pins
  pinMode(CONTACT_PIN, INPUT_PULLUP);
  pinMode(STATUS_LED_PIN, OUTPUT);

  // Read initial state
  currentContactState = digitalRead(CONTACT_PIN);
  lastContactState = currentContactState;

  // Set initial LED state (ON when contact is closed/LOW)
  digitalWrite(STATUS_LED_PIN, (currentContactState == LOW) ? HIGH : LOW);

  // Configure Wi-Fi in Access Point (SoftAP) mode
  Serial.print("Configuring Access Point...");
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.println(" Ready!");
  Serial.print("SoftAP SSID: ");
  Serial.println(ssid);
  Serial.print("SoftAP IP Address: ");
  Serial.println(IP);

  // Define Web Server Routing
  server.on("/", HTTP_GET, handleRoot);
  server.on("/api/status", HTTP_GET, handleApiStatus);
  server.onNotFound(handleNotFound);

  // Start the Web Server
  server.begin();
  Serial.println("HTTP Web Server started.");
}

void loop() {
  // Handle incoming HTTP client requests
  server.handleClient();

  // Read the physical contact state
  currentContactState = digitalRead(CONTACT_PIN);

  // Detect state changes to update the LED and log to Serial
  if (currentContactState != lastContactState) {
    // Debounce delay (basic implementation)
    delay(50);
    currentContactState = digitalRead(CONTACT_PIN);

    if (currentContactState != lastContactState) {
      if (currentContactState == LOW) {
        Serial.println("EVENT: Contact CLOSED (Active).");
        digitalWrite(STATUS_LED_PIN, HIGH); // Turn LED ON
      } else {
        Serial.println("EVENT: Contact OPEN (Inactive).");
        digitalWrite(STATUS_LED_PIN, LOW);  // Turn LED OFF
      }
      lastContactState = currentContactState;
    }
  }
}


Build/Flash/Run commands

Use the PlatformIO Core CLI to compile, upload, and monitor your project. Open your terminal in the project’s root directory (where platformio.ini is located) and execute the following commands.

CommandPurpose
pio runCompiles the project and verifies all dependencies and syntax.
pio run --target uploadCompiles and flashes the compiled firmware to the ESP32 DevKitC.
pio device monitorOpens the serial monitor to view runtime logs at 115200 baud.

Execution Workflow:
1. Connect the ESP32 DevKitC to your computer via the micro-USB cable.
2. Run pio run to ensure the code compiles without syntax errors.
3. Run pio run --target upload. (If the upload fails to connect, press and hold the BOOT button on the ESP32 DevKitC when you see “Connecting…” in the terminal).
4. Run pio device monitor to observe the initialization sequence and verify the SoftAP IP address.


Step-by-step Validation

Follow these checkpoints to ensure the prototype operates exactly as intended.

  1. Verify Serial Initialization
    • Action: Observe the terminal output immediately after running pio device monitor or pressing the EN (Reset) button on the ESP32.
    • Expected observation: The terminal prints “— ESP32 Field Monitor Initialization —“, followed by the SSID ESP32-FieldMonitor and the IP 192.168.4.1.
    • Pass condition: The ESP32 does not crash or enter a reboot loop.
  2. Verify SoftAP Broadcast
    • Action: Open the Wi-Fi settings on a smartphone or laptop.
    • Expected observation: A network named ESP32-FieldMonitor appears in the list of available networks.
    • Pass condition: You can successfully connect to the network using the password admin1234.
  3. Verify Web Dashboard Loading
    • Action: Open a web browser on the connected device and navigate to http://192.168.4.1.
    • Expected observation: The “Field Monitor Dashboard” loads, showing a styled card. The serial monitor logs “Dashboard accessed by a client.”
    • Pass condition: The UI renders correctly without broken CSS.
  4. Verify Physical Input and LED Output
    • *Action

Find this product and/or books on this topic on Amazon

Go to Amazon

As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.

Quick Quiz

Question 1: What is the primary hardware component used to build the Local Network Monitor?




Question 2: How is the ESP32 configured to broadcast a localized Wi-Fi network?




Question 3: Why is the diagnostic dashboard considered 'air-gapped'?




Question 4: What is one major safety benefit of using this device for machine fault logging?




Question 5: Which of the following is a mentioned use case for the Local Network Monitor?




Question 6: What type of environments is the infrastructure-independent diagnostic feature designed for?




Question 7: How does the device help reduce cybersecurity attack surfaces?




Question 8: Which security protocol is used for the broadcasted Wi-Fi network?




Question 9: What device is typically used to view the real-time diagnostic dashboard?




Question 10: What can the device log instantly when acting as an access control monitor?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Telecommunications Electronics Engineer and Computer Engineer (official degrees in Spain).

Follow me:
Scroll to Top