Real Time System Telemetry Dashboard with FireBeetle ESP32P4

Auteur avatarCETECH | Dernière modification 20/12/2025 par CETECH

Build a real‑time system telemetry dashboard that shows your PC’s CPU, RAM, and network stats in a dark, single‑page web UI.

Introduction

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.

Matériaux

Outils

Étape 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




Étape 2 - Get PCBs for Your Projects Manufactured

You must check out PCBWAY for ordering PCBs online for cheap!

You get 10 good-quality PCBs manufactured and shipped to your doorstep for cheap. You will also get a discount on shipping on your first order. Upload your Gerber files onto PCBWAY to get them manufactured with good quality and quick turnaround time. PCBWay now could provide a complete product solution, from design to enclosure production. Check out their online Gerber viewer function. With reward points, you can get free stuff from their gift shop. Also, check out this useful blog on PCBWay Plugin for KiCad from here. Using this plugin, you can directly order PCBs in just one click after completing your design in KiCad.




Étape 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.

LED

  • 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.



Étape 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://<ESP32-IP>/.
  • 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):


#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.




Étape 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').
pip install pyserial psutil
Python script (run on your PC, update PORT)
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




Étape 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://<ESP32-IP>/ (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.

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.



Étape 7 - 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.




Commentaires

Published