Real‑Time System Telemetry Dashboard with FireBeetle ESP32P4

Build a real‑time system telemetry dashboard that shows your PC’s CPU, RAM, and network stats in a dark, single‑page web UI served by a DFRobot FireBeetle ESP32‑P4. Your PC sends newline‑terminated JSON over USB serial; the ESP32 parses it, blinks an onboard LED (pin 3) when new data arrives, and serves a responsive AJAX dashboard that updates every 5 seconds and includes a raw JSON console.

STEP 1
Hardware
  • DFRobot FireBeetle ESP32‑P4 board (or compatible ESP32‑P4 board
  • USB cable (for power, programming, and serial data)
  • Optional: small breadboard / jumper wires if you want to wire external LED or buttons (onboard LED uses pin 3)

Display / UI

  • No OLED required for the web UI version — everything is in the browser.

Software on PC

  • Python 3.8+
  • Python packages: pyserial, psutil (install with pip install pyserial psutil)

Arduino IDE

  • Latest Arduino IDE or VS Code with PlatformIO
  • ESP32 board support installed (Espressif package)
  • Libraries: Arduino_JSON (install via Library Manager)

Network

  • Local Wi‑Fi network credentials (SSID and password) for the ESP32 to host the webserver
STEP 2
Get PCBs for Your Projects Manufactured

Ā 

When working on open-source hardware projects, high-quality PCB assembly is critical for turning designs into real products. We’ve found that pcbassemblage.com deliver reliable prototyping and small-batch production, with fast turnaround and strict quality control to support makers and engineers.

Ā 

For projects targeting the European market, local and dependable PCB manufacturing can greatly improve efficiency. PCB production in Europe includes flexible PCB fabrication, assembly, and quick delivery, making it a great solution for open-source hardware developers who value stability and short lead times.

STEP 3
Wiring and hardware setup

Power and data

  • - Connect the FireBeetle to your PC using the USB cable. This provides power and the serial link for telemetry.
  • - The sketch uses the onboard LED on pin 3. No extra wiring required unless you want an external LED — then connect LED + resistor to pin 3 and GND.

Notes

  • - Close Arduino Serial Monitor before running the Python sender (only one app can open the COM port at a time)
  • - Ensure the FireBeetle is selected as the target board in Arduino IDE and the correct COM port is chosen.
STEP 4
Arduino sketch (webserver with AJAX dashboard)

What it does:

  • - Connects to Wi‑Fi using your SSID and password.
  • - Listens on USB serial (9600 baud) for newline‑terminated JSON from the PC.
  • - Parses JSON and stores the latest values.
  • - Serves a dark themed HTML dashboard at http:///.
  • - Provides /data JSON endpoint used by the page’s AJAX to update UI every 5 seconds.
  • - Blinks onboard LED on pin 3 when new data arrives.

Paste and upload this sketch (replace YOUR_SSID and YOUR_PASSWORD):

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

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

WebServer server(80);

String host, os, boot, ramUsed, ramTotal, ip, mac;
double cpu = 0, ramPct = 0;

#define LED_PIN 3   // onboard LED pin

// Serve the main HTML page
void handleRoot() {
  String html = R"rawliteral(
  <!DOCTYPE html>
  <html>
  <head>
    <meta charset="UTF-8">
    <title>ESP32-P4 Telemetry Dashboard</title>
    <style>
      body{background:#121212;color:#eee;font-family:Arial;text-align:center;}
      h1{color:#4cafef;}
      .card{background:#1e1e1e;padding:20px;margin:20px;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,0.5);}
      .bar{height:20px;background:#333;border-radius:10px;overflow:hidden;margin-top:10px;}
      .fill{height:100%;background:#4caf50;text-align:right;color:#fff;padding-right:5px;}
      .gauge{width:120px;height:60px;background:#333;border-radius:120px 120px 0 0;overflow:hidden;margin:20px auto;position:relative;}
      .needle{width:4px;height:60px;background:#4cafef;position:absolute;bottom:0;left:58px;transform-origin:bottom center;}
    </style>
    <script>
      async function fetchData(){
        const res = await fetch('/data');
        const data = await res.json();
        document.getElementById('host').innerText = data.host;
        document.getElementById('os').innerText = data.os;
        document.getElementById('boot').innerText = data.boot_time;
        document.getElementById('cpu').innerText = data.cpu_percent + '%';
        document.getElementById('cpuFill').style.width = data.cpu_percent + '%';
        document.getElementById('cpuNeedle').style.transform = 'rotate('+(data.cpu_percent/100*180)+'deg)';
        document.getElementById('ram').innerText = data.ram.used + ' / ' + data.ram.total + ' ('+data.ram.percent+'%)';
        document.getElementById('ramFill').style.width = data.ram.percent + '%';
        document.getElementById('ramNeedle').style.transform = 'rotate('+(data.ram.percent/100*180)+'deg)';
        document.getElementById('ip').innerText = data.network.ip;
        document.getElementById('mac').innerText = data.network.mac;
        // Show raw JSON in console widget
        document.getElementById('console').innerText = JSON.stringify(data, null, 2);
      }
      setInterval(fetchData, 5000); // auto refresh every 5s
      window.onload = fetchData;
    </script>
  </head>
  <body>
    <h1>ESP32-P4 Telemetry Dashboard</h1>
    <div class="card">
      <h2>System Info</h2>
      <p><b>Host:</b> <span id="host"></span></p>
      <p><b>OS:</b> <span id="os"></span></p>
      <p><b>Boot Time:</b> <span id="boot"></span></p></div>
    <div class="card">
      <h2>CPU Usage</h2>
      <div class="gauge"><div id="cpuNeedle" class="needle"></div></div>
      <div class="bar"><div id="cpuFill" class="fill">0%</div></div>
      <p id="cpu"></p>
    </div>
    <div class="card">
      <h2>RAM Usage</h2>
      <div class="gauge"><div id="ramNeedle" class="needle"></div></div>
      <div class="bar"><div id="ramFill" class="fill">0%</div></div>
      <p id="ram"></p>
    </div>
    <div class="card">
      <h2>Network</h2>
      <p><b>IP:</b> <span id="ip"></span></p>
      <p><b>MAC:</b> <span id="mac"></span></p>
    </div>
    <div class="card" style="margin:20px;">
      <h2>Raw data console</h2>
      <pre id="console" style="background:#000;color:#0f0;padding:10px;border-radius:6px;height:140px;overflow:auto;">{}</pre>
    </div>
  </body>
  </html>
  )rawliteral";
  server.send(200, "text/html", html);
}

// Serve JSON data for AJAX
void handleData() {
  String json = "{";
  json += "\"host\":\"" + host + "\",";
  json += "\"os\":\"" + os + "\",";
  json += "\"boot_time\":\"" + boot + "\",";
  json += "\"cpu_percent\":" + String(cpu) + ",";
  json += "\"ram\":{\"used\":\"" + ramUsed + "\",\"total\":\"" + ramTotal + "\",\"percent\":" + String(ramPct) + "},";
  json += "\"network\":{\"ip\":\"" + ip + "\",\"mac\":\"" + mac + "\"}";
  json += "}";
  server.send(200, "application/json", json);
}

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);

  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected!");
  Serial.print("ESP32 IP: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.begin();
}

void loop() {
  server.handleClient();

  if (Serial.available()) {
    String msg = Serial.readStringUntil('\n');
    JSONVar data = JSON.parse(msg);
    if (JSON.typeof(data) != "undefined") {
      host     = (const char*)data["host"];
      os       = (const char*)data["os"];
      boot     = (const char*)data["boot_time"];
      cpu      = double(data["cpu_percent"]);
      ramUsed  = (const char*)data["ram"]["used"];
      ramTotal = (const char*)data["ram"]["total"];
      ramPct   = double(data["ram"]["percent"]);
      ip       = (const char*)data["network"]["ip"];
      mac      = (const char*)data["network"]["mac"];

      // Blink LED when new data arrives
      digitalWrite(LED_PIN, HIGH);
      delay(100);
      digitalWrite(LED_PIN, LOW);
    }
  }
}

Notes

  • - Replace YOUR_SSID and YOUR_PASSWORD.
  • - Keep Serial Monitor closed while Python is running.
STEP 5
Python telemetry sender (PC side)

What it does:

  • - Collects real system stats using psutil.
  • - Formats a JSON object and sends it over USB serial to the ESP32 every 5 seconds.
  • - Newline terminator \n is required so the ESP32 can use readStringUntil('\n').
CODE
pip install pyserial psutil

Python script (run on your PC, update PORT)

CODE
import psutil
import platform
import socket
import time
import serial
import json
from datetime import datetime

PORT = "COM8"   # Change to your ESP32 port
BAUD = 9600

def get_size(bytes, suffix="B"):
    factor = 1024.0
    for unit in ["", "K", "M", "G", "T"]:
        if bytes < factor:
            return f"{bytes:.2f}{unit}{suffix}"
        bytes /= factor

def get_ip_mac():
    ip, mac = "N/A", "N/A"
    for iface, addrs in psutil.net_if_addrs().items():
        for addr in addrs:
            if addr.family == socket.AF_INET and not addr.address.startswith("127."):
                ip = addr.address
            if hasattr(addr.family, "name") and addr.family.name in ("AF_LINK", "AF_PACKET"):
                mac = addr.address
    return ip, mac

try:
    ser = serial.Serial(PORT, BAUD, timeout=1)
    time.sleep(2)

    while True:
        uname = platform.uname()
        boot_time = datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S")
        cpu = psutil.cpu_percent(interval=None)
        ram = psutil.virtual_memory()
        disk = psutil.disk_usage('/')
        ip, mac = get_ip_mac()

        data = {
            "host": uname.node,
            "os": f"{uname.system} {uname.release}",
            "boot_time": boot_time,
            "cpu_percent": cpu,
            "ram": {
                "used": get_size(ram.used),
                "total": get_size(ram.total),
                "percent": ram.percent
            },
            "disk": {
                "used": get_size(disk.used),
                "total": get_size(disk.total),
                "percent": disk.percent
            },
            "network": {
                "ip": ip,
                "mac": mac
            }
        }

        ser.write((json.dumps(data) + "\n").encode("utf-8"))
        print("Sent:", json.dumps(data))
        time.sleep(5)

except serial.SerialException as e:
    print("Serial error:", e)
except KeyboardInterrupt:
    print("Stopped by user.")
finally:
    try:
        ser.close()
    except:
        pass
STEP 6
Running and verification (step by step)
Prepare hardware
  • - Connect FireBeetle to PC via USB.
  • - Ensure board drivers are installed and the COM port is visible.
Upload Arduino sketch
  • - Open Arduino IDE, paste the sketch, set board to FireBeetle ESP32‑P4, update Wi‑Fi credentials, and upload.
  • - After upload, open Serial Monitor briefly to see Wi‑Fi connection logs, then close it. Note the printed ESP32 IP.
Start Python sender
  • - Update PORT to the ESP32 COM port (Windows: COMx, Linux: /dev/ttyUSB0), then run the Python script.
  • - Confirm the script prints ā€œSent:ā€ messages every 5 seconds.
Open dashboard
  • - In a browser on the same network, open http:/// (use the IP printed in Serial Monitor).
  • - The page will auto‑fetch /data every 5 seconds and update the UI.
  • - The raw JSON console shows the latest packet.

Verify LED:

  • Each time the Python script sends a new JSON packet, the onboard LED on pin 3 should blink briefly.
STEP 7
Next steps and enhancements
  • - Add authentication to the web UI (basic token or password).
  • - Send telemetry over Wi‑Fi (HTTP POST from Python to ESP32) to remove USB dependency.
  • - Add more metrics: network throughput, GPU stats (via GPUtil), disk per‑partition details.
  • - Persist logs on an SD card or send to a remote server.
  • - Add manual controls: buttons on the FireBeetle to change refresh rate or toggle panels.
  • - Improve visuals: use charting libraries (Chart.js) hosted locally for sparklines and history graphs.
STEP 8
Conclusion

You now have a complete local telemetry solution: a Python script on your PC collects CPU, RAM, disk, and network stats and streams them as newline‑terminated JSON over USB to your DFRobot FireBeetle ESP32‑P4, which parses the data, blinks its onboard LED on new packets, and serves a dark, single‑page AJAX dashboard that updates every five seconds and includes live gauges and a raw JSON console—giving you real‑time visibility, local control and privacy, and a flexible foundation for adding authentication, Wi‑Fi telemetry, logging, or alerts.

License
All Rights
Reserved
licensBg
0