行业资讯

基于ESP32与WebSocket的PC性能监视器:实时系统仪表盘DIY指南

发布时间:2026/8/20 2:29:35
基于ESP32与WebSocket的PC性能监视器:实时系统仪表盘DIY指南 1. 项目概述当ESP32成为你的PC性能仪表盘你有没有想过在桌面上放一个酷炫的小屏幕实时显示着电脑的CPU占用率、内存使用量、网络速度和温度就像一台高性能跑车的仪表盘这听起来像是需要复杂的软件和昂贵的硬件但今天我要分享的恰恰是一个用几十块钱的ESP32开发板就能实现的“PC性能监视器”。这个项目不是简单的数据罗列而是一个实时的、低延迟的系统状态监控方案它能让你在不切换窗口、不打断工作流的情况下对电脑的运行状况了如指掌。对于开发者、游戏玩家、视频剪辑师或者任何需要密切关注系统资源的朋友来说这绝对是个既实用又有趣的玩具。核心思路很简单在电脑上运行一个轻量级的“服务端”程序收集系统性能数据然后通过Wi-Fi或串口将这些数据实时发送给ESP32ESP32再驱动一块小屏幕将这些信息以直观的图形或数字形式展示出来。整个过程延迟可以做到极低实现真正的“实时”监控。接下来我会从设计思路、硬件选型、软件实现到避坑指南完整拆解这个项目让你也能亲手打造一个专属的桌面性能监视器。2. 整体设计与核心思路拆解2.1 为什么选择ESP32在开始动手前我们得先搞清楚为什么ESP32是这个项目的绝佳选择而不是树莓派Pico、Arduino Uno或者其他微控制器。首先核心优势在于无线连接。ESP32集成了Wi-Fi和蓝牙这意味着它可以通过局域网Wi-Fi与你的PC通信无需任何物理线缆连接除了供电线极大地提升了布置的灵活性。你可以把它和屏幕一起放在显示器旁边、键盘上方甚至装在3D打印的外壳里挂在墙上完全不受线缆束缚。其次性能与性价比的完美平衡。ESP32通常是双核处理器主频高达240MHz拥有足够的内存通常520KB SRAM来处理数据解析、图形渲染和网络通信。对比单纯的Arduino它的性能强得多对比树莓派它的价格通常20-50元和功耗又低得多非常适合这种持续运行的小型显示设备。最后丰富的开发生态。无论是使用Arduino框架还是乐鑫官方的ESP-IDF都有海量的库和社区支持。驱动常见的OLED、TFT屏幕解析JSON或自定义协议的数据包这些都有成熟的轮子可用能让我们专注于应用逻辑本身。2.2 系统架构与通信协议选型整个系统的架构可以清晰地分为两端PC端数据生产者和ESP32端数据消费者与显示端。它们之间需要一个高效、可靠的通信桥梁。1. PC端角色与任务PC端需要以一个后台服务或常驻程序的形式运行。它的核心职责是周期性地例如每秒1-10次采集我们关心的系统指标。在Windows上这通常通过调用系统API如pdh.dll用于性能计数器或WMI查询来实现在Linux/macOS上则可以通过读取/proc文件系统或使用sysstat等工具。采集到的数据如CPU使用率、内存占用、GPU温度、网络吞吐量需要被封装成一定格式发送出去。2. 通信协议决策这是设计的关键。我们有几种主流选择HTTP REST APIESP32周期性向PC端的某个HTTP端点发起GET请求获取数据。实现简单但缺点是请求-响应模式有延迟且PC端需要运行一个Web服务器。在高频更新场景下如每秒多次这不是最佳选择。WebSocket在HTTP握手后建立全双工通信通道。一旦连接PC端可以随时主动推送数据给ESP32延迟极低非常适合实时数据流。这是目前最推荐的方式之一。TCP/UDP Socket原始套接字更底层的通信方式。我们可以自定义一个简单的二进制或文本协议。优点是极其轻量开销最小缺点是需要自己处理连接管理、数据分包和粘包等问题复杂度稍高。串口Serial如果ESP32通过USB线直接连接PC这是最简单稳定的方式无需网络配置延迟几乎为零。但牺牲了无线部署的便利性。我的选择与理由对于桌面场景我强烈推荐WebSocket。它平衡了易用性、实时性和可靠性。PC端可以用任何语言Python、C#、Go等轻松实现一个WebSocket服务器ESP32端也有稳定的库如WebSocketsClient支持。它允许PC在数据变化时立即推送避免了轮询的延迟和资源浪费。3. ESP32端角色与任务ESP32负责建立网络连接、接收数据、解析数据最后驱动屏幕进行渲染。它需要稳定地处理网络中断重连高效地解析数据包通常是JSON格式并根据预定的布局更新屏幕上的各种元素进度条、数字、图表等。3. 核心细节解析与实操要点3.1 PC端数据采集精准获取系统状态PC端程序是整个系统的数据源头其稳定性和准确性至关重要。这里以Windows平台为例使用Python进行说明因为Python跨平台且库丰富。核心库与方法psutil这是一个跨平台的进程和系统工具库是快速原型开发的瑞士军刀。它可以非常方便地获取CPU百分比、内存信息、磁盘IO、网络IO等。import psutil cpu_percent psutil.cpu_percent(interval0.5) # 获取CPU使用率 virtual_mem psutil.virtual_memory() # 获取内存信息 # virtual_mem.percent 即为内存使用百分比GPUtil专门用于获取NVIDIA GPU信息的库可以获取GPU负载、显存使用、温度等。pynvmlNVIDIA官方ML库的Python绑定功能比GPUtil更底层和全面。Windows Performance Counters对于更专业或更特定的指标如每个逻辑CPU核心的占用率、特定进程的详细资源消耗可以通过pywin32库调用Windows PDHPerformance Data HelperAPI。这是psutil等库在Windows下的底层实现之一数据最为权威。数据封装采集到数据后我们需要将其组织成一个结构化的格式方便通过网络传输。JSON是最通用、最易读的选择。import json system_stats { “cpu”: { “total”: cpu_percent, “cores”: psutil.cpu_percent(percpuTrue) # 每个核心的列表 }, “memory”: { “total”: virtual_mem.total, “available”: virtual_mem.available, “percent”: virtual_mem.percent }, “gpu”: { # 如果安装了GPU库 “load”: gpu_load, “temperature”: gpu_temp, “memory_used”: gpu_mem_used }, “network”: { “sent”: net_io.bytes_sent, “recv”: net_io.bytes_recv }, “timestamp”: time.time() } json_data json.dumps(system_stats)这个JSON对象就是我们要通过WebSocket推送给ESP32的数据包。3.2 ESP32端开发环境与库选择ESP32的开发主要有两大阵营Arduino框架和ESP-IDF乐鑫官方IoT开发框架。Arduino框架对于大多数爱好者和小型项目来说这是上手最快的方式。它抽象了底层细节有海量的第三方库编写代码的风格与标准Arduino相似。如果你追求快速实现功能Arduino是首选。ESP-IDF框架这是乐鑫官方的开发框架基于FreeRTOS提供对ESP32硬件最全面、最底层的控制性能优化潜力更大更适合复杂的、产品级的应用。但学习曲线相对陡峭。我的建议初次尝试本项目强烈建议使用Arduino框架。配合PlatformIOVSCode插件或Arduino IDE进行开发可以极大地简化库管理和项目构建过程。必须的库Wi-Fi连接WiFi.h(内置)WebSocket客户端WebSocketsClientby Markus Sattler。这是一个非常稳定且功能完善的库。屏幕驱动这取决于你使用的屏幕。OLED (SSD1306, SH1106)Adafruit_SSD1306和Adafruit_GFX图形库。TFT (ILI9341, ST7789)TFT_eSPI。这是一个性能极其强悍、功能丰富的库支持多种屏幕并且通过配置文件灵活适配是驱动SPI接口TFT屏幕的不二之选。开发环境搭建以PlatformIO为例在VSCode中安装PlatformIO插件。新建项目选择开发板为Espressif ESP32 Dev Module或你的具体型号。打开项目根目录下的platformio.ini文件添加依赖库[env:esp32dev] platform espressif32 board esp32dev framework arduino monitor_speed 115200 lib_deps links2004/WebSockets ^2.3.6 bodmer/TFT_eSPI ^2.5.0对于TFT_eSPI你还需要在项目目录中找到并编辑lib/TFT_eSPI/User_Setup.h文件根据你的屏幕型号和引脚连接注释/取消注释对应的驱动芯片和引脚定义。3.3 屏幕选型与UI布局设计屏幕是项目的脸面选择合适的屏幕和设计清晰的UI至关重要。屏幕类型推荐0.96寸/1.3寸 OLED (I2C接口)优点是功耗极低、纯黑背景下对比度无敌显示文字和简单图标非常清晰。缺点是尺寸小、分辨率低通常128x64且不支持彩色和流畅的图形动画。适合显示最核心的几项数字指标。1.54寸/2.4寸 TFT LCD (SPI接口)这是本项目的“甜点”选择。色彩丰富通常是RGB565分辨率更高如240x240, 320x240可以绘制进度条、折线图、图标等更丰富的UI元素。SPI接口速度足够快刷新率有保障。价格也非常亲民。更大尺寸的IPS屏幕如果你想要更震撼的显示效果可以考虑3.5寸甚至更大的电容触摸屏。但需要注意ESP32的RAM和SPI速度可能成为瓶颈驱动大分辨率屏幕可能会比较吃力需要优化图形操作。UI布局设计心得在一块小屏幕上显示多项信息布局必须简洁高效。一个经过验证的有效布局是分块显示顶部区域显示静态标题如“System Monitor”和实时时间。左侧主区域用横向进度条显示CPU和内存的总体使用率。进度条直观且占用空间小。中间区域用较大的数字字体实时显示CPU和内存的百分比数值。也可以加入GPU温度。右侧区域用迷你折线图显示最近一段时间如30秒内CPU或内存使用率的历史趋势。这能让你一眼看出负载是平稳、上升还是飙升。底部区域显示网络上传/下载的实时速度如“↑ 1.2 MB/s ↓ 350 KB/s”以及ESP32本身的Wi-Fi信号强度。使用TFT_eSPI这样的图形库绘制进度条和折线图都非常简单它提供了丰富的绘图函数。4. 实操过程与核心环节实现4.1 PC端WebSocket服务器搭建Python示例我们将使用Python的websockets库和asyncio异步框架来构建一个高效的推送服务器。import asyncio import websockets import json import psutil import time async def send_system_stats(websocket, path): print(“Client connected from ESP32”) try: while True: # 1. 采集数据 stats { “cpu”: psutil.cpu_percent(intervalNone), # intervalNone表示非阻塞式获取 “memory”: psutil.virtual_memory().percent, “gpu”: { # 此处需根据实际情况填充示例为假数据 “load”: 45, “temp”: 76 }, “network”: { “sent”: psutil.net_io_counters().bytes_sent, “recv”: psutil.net_io_counters().bytes_recv } } # 2. 计算网络瞬时速度需要记录上一次的值 # ... (此处省略速度计算逻辑需在循环外保存上一次的字节数) # 3. 序列化并发送 message json.dumps(stats) await websocket.send(message) # 4. 控制推送频率例如每秒2次 await asyncio.sleep(0.5) except websockets.exceptions.ConnectionClosed: print(“Client disconnected”) start_server websockets.serve(send_system_stats, “0.0.0.0”, 8765) # “0.0.0.0”表示监听所有网络接口8765是端口号 asyncio.get_event_loop().run_until_complete(start_server) print(“WebSocket server started on ws://YOUR_PC_IP:8765”) asyncio.get_event_loop().run_forever()将脚本中的YOUR_PC_IP替换为你电脑在局域网中的IP地址。运行此脚本PC端的服务就准备好了。4.2 ESP32端核心代码解析ESP32端的代码主要负责连接Wi-Fi、连接WebSocket服务器、接收数据、解析并刷新屏幕。关键代码结构#include WiFi.h #include WebSocketsClient.h #include TFT_eSPI.h #include ArduinoJson.h TFT_eSPI tft TFT_eSPI(); WebSocketsClient webSocket; // 定义你的Wi-Fi和服务器信息 const char* ssid “Your_WiFi_SSID”; const char* password “Your_WiFi_Password”; const char* ws_server “192.168.1.100”; // PC的IP const int ws_port 8765; const char* ws_path “/”; // 定义用于存储历史数据的数组用于绘制折线图 int cpuHistory[60]; // 存储60个历史CPU值 int historyIndex 0; void setup() { Serial.begin(115200); tft.init(); tft.setRotation(1); // 根据屏幕方向调整 tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_WHITE, TFT_BLACK); // 连接Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(“.”); } Serial.println(“\nWiFi Connected!”); // 初始化WebSocket webSocket.begin(ws_server, ws_port, ws_path); webSocket.onEvent(webSocketEvent); // 设置事件回调函数 } void loop() { webSocket.loop(); // 必须持续调用以处理WebSocket事件 // 这里可以添加一些非阻塞的定期任务如更新时钟 } void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { switch(type) { case WStype_DISCONNECTED: Serial.println(“WebSocket Disconnected!”); break; case WStype_CONNECTED: Serial.println(“WebSocket Connected!”); break; case WStype_TEXT: // 收到文本数据我们的JSON handleIncomingData((char*)payload); break; } } void handleIncomingData(char* jsonString) { StaticJsonDocument512 doc; // 根据JSON大小调整缓冲区 DeserializationError error deserializeJson(doc, jsonString); if (error) { Serial.print(“JSON解析失败: “); Serial.println(error.c_str()); return; } int cpuPercent doc[“cpu”]; int memPercent doc[“memory”]; // 提取其他数据... // 1. 更新历史数据数组 cpuHistory[historyIndex] cpuPercent; historyIndex (historyIndex 1) % 60; // 2. 清屏或局部刷新为了减少闪烁建议只刷新变化的区域 tft.fillRect(0, 20, 240, 20, TFT_BLACK); // 假设清除CPU显示区域 // 3. 绘制CPU进度条 int barWidth map(cpuPercent, 0, 100, 0, 200); tft.fillRoundRect(20, 25, 200, 15, 5, TFT_DARKGREY); // 背景 tft.fillRoundRect(20, 25, barWidth, 15, 5, TFT_GREEN); // 前景 // 4. 绘制CPU数值 tft.setCursor(110, 45); tft.printf(“%d%%”, cpuPercent); // 5. 绘制CPU历史折线图 drawSparkline(10, 120, 220, 60, cpuHistory, 60, TFT_CYAN); // 自定义函数 } // 一个简单的迷你折线图绘制函数 void drawSparkline(int x, int y, int w, int h, int data[], int len, uint16_t color) { int maxVal 0; for (int i0; ilen; i) if(data[i] maxVal) maxVal data[i]; if(maxVal 0) return; int prevX x; int prevY y h - map(data[0], 0, maxVal, 0, h); for (int i1; ilen; i) { int currX x map(i, 0, len-1, 0, w); int currY y h - map(data[i], 0, maxVal, 0, h); tft.drawLine(prevX, prevY, currX, currY, color); prevX currX; prevY currY; } }这段代码勾勒出了ESP32端的核心逻辑建立连接、事件驱动接收数据、解析JSON、更新UI。TFT_eSPI库的绘图函数是性能关键它直接操作帧缓冲区效率很高。4.3 供电与外壳设计供电方案ESP32和一块小屏幕的功耗并不高通常总电流在200mA-500mA之间。最简方案使用一根旧的手机USB数据线连接到一个5V/1A的手机充电头或电脑的USB口。这是最稳定可靠的方式。移动方案如果你想完全摆脱线缆可以使用一个小的充电宝供电。注意选择输出电流稳定的充电宝。集成方案如果你有3D打印机可以设计一个外壳将一个小型的5V降压模块如MP1584和18650电池集成进去做成一个独立的设备。外壳设计建议一个精致的外壳能让项目质感提升好几个档次。你可以使用免费软件如Fusion 360或Tinkercad进行3D建模。散热确保外壳有通风孔尤其是如果屏幕或ESP32在运行时发热明显。屏幕开孔开孔要比屏幕可视区域稍大并考虑屏幕保护镜片的厚度。固定方式可以在外壳背面设计一个卡槽方便将其卡在显示器边框上或者设计一个底座。线缆管理如果使用有线供电可以在外壳上设计一个凹槽或卡扣来固定USB线。5. 常见问题与排查技巧实录在实际制作过程中你几乎一定会遇到下面这些问题。这里是我踩过坑后总结的排查清单。5.1 连接类问题问题1ESP32无法连接到Wi-Fi。检查SSID和密码确保代码中的SSID和密码完全正确注意大小写。检查路由器设置有些路由器会设置“访客网络隔离”或“AP隔离”这会导致设备间无法通信。确保你的PC和ESP32在同一个子网内且可以互相访问。信号强度ESP32的Wi-Fi天线性能一般。如果距离路由器太远或有太多墙体阻隔尝试靠近路由器测试。串口输出信息打开串口监视器波特率115200查看ESP32启动时的日志通常会打印连接状态和分配的IP地址。问题2WebSocket连接失败连接被拒绝/超时。检查PC端服务器是否运行在PC的命令行执行netstat -an | findstr 8765(Windows) 或netstat -tuln | grep 8765(Linux/macOS)查看8765端口是否处于监听状态。检查防火墙这是最常见的原因Windows Defender或第三方防火墙可能会阻止入站连接。你需要在Windows防火墙中为你的Python解释器或脚本创建一个入站规则允许TCP端口8765。检查IP地址确保ESP32代码中填写的服务器IP是你PC在局域网中的正确IP而不是127.0.0.1。在PC上通过ipconfig(Windows) 或ifconfig(Linux/macOS) 查看。检查服务器代码绑定地址确保Python服务器代码中websockets.serve绑定的是“0.0.0.0”而不是“127.0.0.1”。“0.0.0.0”表示监听所有网络接口。5.2 数据与显示类问题问题3屏幕花屏、闪烁或不显示。检查引脚连接这是硬件项目永恒的“第一步”。确保屏幕的SCK、MOSI、DC、RESET、CS等引脚与ESP32的连接和代码中的定义完全一致。特别是TFT_eSPI库的User_Setup.h文件必须根据你的实际接线进行配置。检查电源屏幕供电不足会导致各种奇怪现象。确保使用5V电源并且电流足够建议1A以上。可以尝试单独给屏幕供电或者用万用表测量一下ESP32的5V输出引脚在带载时的电压是否跌落到4.5V以下。降低SPI频率在User_Setup.h中找到#define SPI_FREQUENCY尝试将其值从4000000040MHz降低到27000000或20000000。过高的SPI频率可能导致信号质量差尤其是连接线较长时。问题4数据更新延迟高或卡顿。优化PC端采集间隔不要采集得太频繁。对于系统监控每秒更新2-5次即间隔200-500毫秒已经完全足够且对PC压力很小。优化ESP32端JSON解析使用ArduinoJson库时务必根据实际数据大小分配合适的StaticJsonDocument缓冲区。太大浪费内存太小会导致解析失败。使用其 在线助手 可以帮你计算所需大小。优化屏幕刷新避免使用tft.fillScreen(TFT_BLACK)进行全屏清屏这非常耗时且会导致严重闪烁。应该只刷新需要改变的区域如数字、进度条部分即“局部刷新”。检查Wi-Fi信号强度信号弱会导致数据包重传增加延迟。可以在ESP32代码中加入读取并显示RSSI信号强度的功能。问题5ESP32运行一段时间后重启或死机。内存泄漏这是嵌入式开发常见问题。确保在loop()函数或事件回调函数中没有动态分配内存如频繁创建String对象、大数组。尽量使用全局或静态变量。使用ArduinoJson时确保JsonDocument对象在函数栈上分配局部变量函数退出时会自动释放。看门狗超时ESP32有硬件看门狗。如果你的代码在某个地方阻塞太久如使用delay()进行长延时会导致看门狗复位。将长任务拆解或者使用非阻塞的定时方式如millis()。电源不稳定使用质量差的USB线或充电头在大电流负载时电压可能跌落导致ESP32复位。换用更粗更短的USB线和高品质的5V电源适配器。5.3 进阶优化技巧当你解决了基本问题后这些技巧能让你的监视器更稳定、更专业掉线自动重连在webSocketEvent的WStype_DISCONNECTED事件中不要只是打印日志应该加入一个延迟后重新调用webSocket.begin()的逻辑实现自动重连。数据校验与容错在解析JSON前可以检查length参数避免处理空数据。解析后检查关键字段是否存在如doc.containsKey(“cpu”)并设置默认值防止异常数据导致显示错乱。低功耗考虑如果需要如果你用电池供电可以在屏幕驱动代码中当一段时间没有数据更新时调低屏幕亮度或让ESP32进入轻睡眠模式。美化UI使用TFT_eSPI库的字体功能加载更美观的矢量字体如Font 4。使用抗锯齿功能绘制更平滑的图形。为不同的数值范围如CPU50%绿色50-80%黄色80%红色设置不同的进度条颜色让信息一目了然。这个项目从构思到实现最享受的莫过于看到一个个模块从无到有最终协同工作的那一刻。它不仅仅是一个显示设备更是你对硬件、网络、软件编程一次完整的实践。当你成功运行起来后不妨尝试扩展它比如增加显示硬盘剩余空间、天气预报或者通过MQTT协议接入智能家居数据。