Week 11 — Networking and Communications


For this week, I used the custom PCB I had already developed in earlier weeks. The board includes an ESP32-C3, four push buttons, and an antenna for wireless communication. My goal was to turn this board into a wireless input node: when a local push button is pressed, the ESP32 reads the input and sends the button press data over Wi-Fi to a web application running on my laptop.

This project combines:

  • a local input device: four push buttons on my PCB
  • a wireless communication node: ESP32-C3
  • a network address: Wi-Fi connection and server URL
  • a remote output interface: a browser-based web app on my laptop

Final Result

My final system works as follows:

  1. The ESP32-C3 connects to my local Wi-Fi network.
  2. The ESP32 continuously monitors the four push buttons on my custom PCB.
  3. When a button is pressed, the ESP32 sends an HTTP POST request containing the button label and GPIO number.
  4. A Flask server running on my laptop receives the data.
  5. A browser-based web app updates and shows the latest message and message history.

This allowed me to demonstrate a fully working wireless node with local input and remote output.


System Architecture

The overall system is shown below:

[Push Buttons on PCB]
        ↓
 [ESP32-C3 on custom PCB]
        ↓   Wi-Fi / HTTP POST
   [Flask Server on Laptop]
        ↓
   [Browser-Based Web App]

Planning the Networking Approach

At the beginning of the week, I decided to use wireless networking rather than a wired bus. Since my board already uses an ESP32-C3 and includes an antenna, Wi-Fi was the most natural choice.

I wanted a setup that would be:

  • simple to implement
  • easy to debug
  • easy to demonstrate live
  • clear to document for the assignment

Because of that, I chose the following architecture:

  • ESP32-C3 as client
  • Flask server on my laptop
  • browser page as remote output
  • HTTP POST requests for communication

This was a good balance between technical clarity and practical reliability.

Hardware Used

The hardware side of the project was based on my custom board from previous weeks.

Main components

  • custom PCB
  • ESP32-C3
  • 4 push buttons
  • onboard or connected antenna for Wi-Fi communication
  • USB cable for power

The USB cable was only used to power the board during testing. The communication itself was wireless.


PCB and Setup

Below is the PCB used for this week. It includes the ESP32, buttons, and antenna.

Custom PCB with ESP32, buttons, and antenna

Below is the general testing setup. In this setup, the ESP32 board is not connected to the laptop for communication. The cable is only used for power.

General setup showing wireless communication arrangement

Antenna Placement and Positioning

Since this week depends on wireless communication, antenna placement matters. I made sure that:

  • the antenna side of the board was exposed to open space
  • the antenna area was not blocked by metal objects
  • the USB cable was used only for power, not data transfer during the demonstration

In practice, I oriented the board so the antenna had a clear path to the room rather than being blocked by the desk or my laptop body. For this small-distance local Wi-Fi setup, this was sufficient for a stable connection.

Software Architecture

The software side had two main parts:

  1. ESP32 firmware
  2. Python Flask server + browser interface

Communication format

Each button press is sent as a JSON object in the following structure:

{
  "node_id": "button_node_1",
  "button": "Option A",
  "gpio": 2
}

This format makes the communication explicit and readable. It also allowed me to identify the sending node and the specific button pressed.


Step 1 — Setting Up the Laptop Server

To receive data from the ESP32, I created a small Flask web server on my laptop.

Installing Flask

Since my Python installation was managed externally, I used a virtual environment.

python3 -m venv venv
source venv/bin/activate
python3 -m pip install flask

Running the server

I created a Python file called app.py and ran it with:

python app.py

At first, port 5000 was unavailable, so I changed the Flask server to run on port 5050 instead. Once the server started, it showed the following addresses:

  • http://127.0.0.1:5050
  • http://192.168.1.106:5050

The second one is the important local network address, since the ESP32 needs to send data to my laptop over Wi-Fi.

Finding the local IP address

To determine my laptop’s local IP, I used the terminal command:

ipconfig getifaddr en0

This returned:

192.168.1.106

That address was then used in the ESP32 code as the server destination.


Code

Flask Server Code

The Flask server receives button press messages, stores the latest event, keeps a short history, and serves a browser page that updates automatically.

from flask import Flask, request, jsonify, render_template_string
from datetime import datetime

app = Flask(__name__)

latest_data = {
    "node_id": "none",
    "button": "none",
    "gpio": "-",
    "time": "-"
}

history = []

HTML_PAGE = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>ESP32 Button Monitor</title>
    <script>
        async function refreshData() {
            const latestResponse = await fetch('/latest');
            const latest = await latestResponse.json();

            document.getElementById("node").textContent = latest.node_id;
            document.getElementById("button").textContent = latest.button;
            document.getElementById("gpio").textContent = latest.gpio;
            document.getElementById("time").textContent = latest.time;

            const historyResponse = await fetch('/history');
            const history = await historyResponse.json();

            const list = document.getElementById("history");
            list.innerHTML = "";

            history.slice().reverse().forEach(item => {
                const li = document.createElement("li");
                li.textContent = `${item.time} | ${item.node_id} | ${item.button} | GPIO ${item.gpio}`;
                list.appendChild(li);
            });
        }

        setInterval(refreshData, 1000);
        window.onload = refreshData;
    </script>
</head>
<body style="font-family: Arial, sans-serif; padding: 30px;">
    <h1>ESP32 Button Monitor</h1>

    <h2>Latest Message</h2>
    <p><strong>Node ID:</strong> <span id="node">-</span></p>
    <p><strong>Button:</strong> <span id="button">-</span></p>
    <p><strong>GPIO:</strong> <span id="gpio">-</span></p>
    <p><strong>Time:</strong> <span id="time">-</span></p>

    <h2>Message History</h2>
    <ul id="history"></ul>
</body>
</html>
"""

@app.route("/")
def index():
    return render_template_string(HTML_PAGE)

@app.route("/button", methods=["POST"])
def button():
    global latest_data, history

    data = request.get_json(silent=True)

    if not data:
        return jsonify({"status": "error", "message": "No JSON received"}), 400

    latest_data = {
        "node_id": data.get("node_id", "unknown"),
        "button": data.get("button", "unknown"),
        "gpio": data.get("gpio", "unknown"),
        "time": datetime.now().strftime("%H:%M:%S")
    }

    history.append(latest_data.copy())

    if len(history) > 20:
        history = history[-20:]

    print("Received:", latest_data)

    return jsonify({
        "status": "ok",
        "received": latest_data
    })

@app.route("/latest")
def latest():
    return jsonify(latest_data)

@app.route("/history")
def get_history():
    return jsonify(history)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5050, debug=True)

Step 2 — Building the Web Interface

The browser interface is intentionally simple. It shows:

  • latest received node ID
  • latest button pressed
  • corresponding GPIO pin
  • time received
  • recent message history

At the start, before any signal was received, the interface looked empty as expected.

Initial empty web app

This was helpful because it gave me a clear before/after demonstration: first the empty app, then live updates appearing when I pressed the buttons.


Step 3 — Programming the ESP32 Node

The second major part of the project was the ESP32 code. The firmware had to:

  • connect to Wi-Fi
  • define the button pins
  • detect button presses
  • send the correct message to the Flask server

Button Mapping

The four buttons on the PCB were mapped as follows:

  • GPIO2 → Option A
  • GPIO6 → Option B
  • GPIO4 → Option C
  • GPIO3 → Option D

ESP32 Firmware

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

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

// Replace with your computer's local IP address
const char* serverURL = "http://192.168.1.106:5050/button";

int buttons[] = {2, 6, 4, 3};
const char* labels[] = {"Option A", "Option B", "Option C", "Option D"};
int lastState[4];

const char* nodeID = "button_node_1";

void connectWiFi() {
  WiFi.begin(ssid, password);
  Serial.print("Connecting to Wi-Fi");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.println("Wi-Fi connected!");
  Serial.print("ESP32 IP: ");
  Serial.println(WiFi.localIP());
}

void sendButtonPress(const char* label, int gpio) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    http.begin(serverURL);
    http.addHeader("Content-Type", "application/json");

    String json = "{";
    json += "\"node_id\":\"" + String(nodeID) + "\",";
    json += "\"button\":\"" + String(label) + "\",";
    json += "\"gpio\":" + String(gpio);
    json += "}";

    int httpResponseCode = http.POST(json);

    Serial.print("Sent: ");
    Serial.print(json);
    Serial.print(" | Response code: ");
    Serial.println(httpResponseCode);

    http.end();
  } else {
    Serial.println("Wi-Fi disconnected, cannot send data.");
  }
}

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

  Serial.println("\nESP32-C3 Button Node");
  Serial.println("Press any button (GPIO 2, 6, 4, 3)...\n");

  for (int i = 0; i < 4; i++) {
    pinMode(buttons[i], INPUT_PULLUP);
    lastState[i] = HIGH;
  }

  connectWiFi();
}

void loop() {
  for (int i = 0; i < 4; i++) {
    int current = digitalRead(buttons[i]);

    if (current == LOW && lastState[i] == HIGH) {
      delay(10);
      if (digitalRead(buttons[i]) == LOW) {
        Serial.print(labels[i]);
        Serial.println(" pressed!");
        sendButtonPress(labels[i], buttons[i]);
        lastState[i] = LOW;
      }
    }

    if (current == HIGH && lastState[i] == LOW) {
      lastState[i] = HIGH;
    }
  }
}

How the ESP32 Code Works

1. Wi-Fi connection

The connectWiFi() function connects the board to the local Wi-Fi network and prints the ESP32 IP address to the serial monitor.

2. Input setup

The four button GPIO pins are set as INPUT_PULLUP. This means:

  • default state = HIGH
  • pressed state = LOW

This is a clean and common way to read buttons using internal pull-up resistors.

3. Button press detection

In the main loop, the ESP32 constantly checks each button. A press is detected when the signal changes from:

  • previous state: HIGH
  • current state: LOW

I also added a small debounce delay of 10 ms to prevent false triggering.

4. Sending data

When a valid press is detected, the ESP32 creates a JSON string and sends it to the laptop server using an HTTP POST request.

5. Preventing repeated sends while held down

The lastState[] array keeps track of previous button states so that the same press is not transmitted repeatedly while the button remains physically held down.


Demonstration Video

The following video shows the final system in action. In the video, I press a button on the PCB and the command is sent wirelessly to the web app.


Network Addressing

This project also demonstrates the addressing aspect of the assignment.

Network-level addressing

The ESP32 and laptop are on the same local Wi-Fi network. The laptop server is addressed using its local IP:

192.168.1.106

and the full server endpoint is:

http://192.168.1.106:5050/button

Node-level identification

In addition to the IP-based network address, each message includes:

"node_id": "button_node_1"

This means the system can be extended later to multiple wireless nodes, each with a unique ID.

Problems Encountered and Fixes

During the setup process, I ran into several practical issues.

1. Python package installation problem

When installing Flask, pip was not directly available and the system Python environment was externally managed. I solved this by creating a virtual environment and installing Flask there.

2. Port 5000 already in use

Initially, Flask could not start on port 5000 because that port was already being used by another service on macOS. I fixed this by moving the server to port 5050.

3. Finding the correct server address

The ESP32 cannot use 127.0.0.1, because that refers to itself rather than my laptop. I had to find my laptop’s local IP address and use that in the serverURL.

4. Matching communication and hardware

I made sure that the server endpoint, Wi-Fi credentials, and button mapping all matched the actual physical board and test environment.


What I Learned

This week helped me understand how to connect embedded hardware to a networked software system in a clear end-to-end way.

I learned how to:

  • use the ESP32 as a Wi-Fi-enabled node
  • send structured JSON data over HTTP
  • run a lightweight local server on a laptop
  • build a browser-based monitoring interface
  • connect physical inputs to a remote digital output
  • think about network addressing at both the device and message level

Most importantly, I built a complete system where a physical user action on a custom PCB directly affects a remote web interface in real time.

Reflection

This assignment was a very good bridge between electronics, embedded programming, and networking. Instead of treating the PCB as an isolated board, I turned it into a connected node that can interact with another system over Wi-Fi.

This feels especially relevant to my final project direction, since it shows how local hardware inputs can be transmitted to a computer-based application. The same general architecture could later be expanded with:

  • multiple nodes
  • more complex message types
  • display outputs
  • game or quiz interactions
  • live browser control panels

Overall, I successfully completed the assignment by creating a wireless ESP32-based node with local button inputs and a remote web-based output interface.


Group assignment

You can check our group project here


Files