مراقبة وحدة المعالجة المركزية، وذاكرة الوصول العشوائي، والقرص، والشبكة، والبطارية، ووقت التشغيل على شاشة LCD I2C 16×2 مع ESP32 - مثالية لأي مكتب أو إعداد تقني منزلي الصنع.
الأشياء المستخدمة في هذا المشروع
وحدة عرض شاشة LCD من نوع DFRobot I2C 16x2 Arduino
وحدة التحكم الدقيقة DFRobot FireBeetle ESP32 IOT (تدعم Wi-Fi و Bluetooth)
بيئة تطوير أردوينو المتكاملة Arduino IDE
قصة
لطالما انزعجتُ من اضطراري المتكرر إلى التبديل بين النوافذ (Alt+Tab) لفتح مدير المهام أو أداة مراقبة النظام للتحقق من مقدار موارد حاسوبي المستخدمة. خاصةً أثناء عمليات الرندر المكثفة أو جلسات الألعاب، كان من شأن توفر هذه المعلومات بنظرة سريعة أن يوفر عليّ الكثير من التخمينات. لذا، بدت شاشة صغيرة مخصصة لعرض حمل المعالج، واستخدام ذاكرة الوصول العشوائي، وسعة القرص، وحركة مرور الشبكة، وحالة البطارية، الحل الأمثل.
بدلاً من شراء جهاز مكتبي باهظ الثمن، قررتُ بناء جهازي الخاص. يجمع هذا المشروع بين لوحة تطوير ESP32 منخفضة التكلفة وشاشة LCD قياسية 16×2 بتقنية I2C. يقوم برنامج بايثون يعمل على الحاسوب بجمع إحصائيات النظام باستخدام مكتبة psutil، ثم يرسلها عبر اتصال تسلسلي USB بسيط إلى ESP32. يقوم المتحكم الدقيق بتحليل البيانات بصيغة JSON، ويتنقل بين عدة صفحات معلوماتية، ويُحدّث الشاشة كل بضع ثوانٍ.
النظام بأكمله محلي بالكامل؛ لا يتطلب اتصالاً بشبكة Wi-Fi، ولا خدمة سحابية، ولا إعدادات معقدة. كل شيء يعمل عبر كابل USB واحد.
البرامج والمكتبات
بيئة تطوير Arduino المتكاملة (IDE) - لتحميل برنامج ESP32 الثابت
مكتبة LiquidCrystal I2C من تطوير فرانك دي برابندر - لتشغيل شاشة LCD بتقنية I2C
مكتبة ArduinoJson (الإصدار 6) من تطوير بينوا بلانشون - لتحليل سلسلة JSON الواردة
Python 3 - لتشغيل برنامج مراقبة الكمبيوتر
pyserial - لمعالجة الاتصال التسلسلي عبر USB من Python
psutil - لجمع معلومات النظام في الوقت الفعلي (وحدة المعالجة المركزية، الذاكرة، القرص، الشبكة، البطارية، وقت التشغيل)
يمكنك تثبيت حزمتي Python باستخدام:
pip install pyserial psutilفي بيئة تطوير Arduino المتكاملة (IDE)، استخدم مدير المكتبات لتثبيت LiquidCrystal I2C وArduinoJson (الإصدار 6).
🔌 مخطط التوصيل
من أفضل مزايا استخدام شاشة LCD بتقنية I2C هو قلة التوصيلات المطلوبة: أربعة أسلاك فقط. يعمل ناقل I2C على خطي إشارة (SDA وSCL) بالإضافة إلى خطي الطاقة والأرضي.
هام: منافذ I2C الافتراضية في معظم لوحات ESP32 هي GPIO21 (SDA) وGPIO22 (SCL). إذا كانت لوحتك تستخدم منافذ مختلفة، فغيّر سطر Wire.begin(SDA, SCL) في البرنامج.
⚙️ إعداد برنامج ESP32 الثابت
يستمع ESP32 باستمرار إلى البيانات التسلسلية، ويبني رسالة JSON حرفًا حرفًا، ويحللها فقط بعد استلام كائن كامل. هذه الطريقة الموثوقة تتجنب مشاكل البيانات المجزأة أو الجزئية.
1. تثبيت حزمة اللوحة
إذا لم يسبق لك استخدام ESP32 مع بيئة تطوير Arduino المتكاملة (IDE):
انتقل إلى ملف > تفضيلات، وأضف عنوان URL الخاص بفهرس لوحة Espressif ESP32 إلى حقل عناوين URL الإضافية لمدير اللوحات.
افتح الأدوات > اللوحة > مدير اللوحات، وابحث عن esp32 وقم بتثبيت الحزمة من Espressif Systems.
حدد وحدة تطوير ESP32 (أو لوحتك المحددة) من قائمة اللوحات.
٢. تحميل الرسم التخطيطي
أنشئ رسمًا تخطيطيًا جديدًا، وانسخ الكود الكامل من الأسفل، وقم بتغيير عنوان شاشة LCD إذا لزم الأمر، ثم قم بتحميله إلى وحدة ESP32 الخاصة بك.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
// LCD configuration
#define LCD_ADDRESS 0x27
#define LCD_COLUMNS 16
#define LCD_ROWS 2
LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS);
// Serial buffer – large enough for full JSON
#define SERIAL_BUFFER_SIZE 1024
char serialBuffer[SERIAL_BUFFER_SIZE];
int bufferIndex = 0;
// JSON accumulator: wait for balanced braces
int braceBalance = 0;
bool insideJson = false;
// Global variables (same as before)
float cpu_total = 0, cpu_cores[8] = {0};
int cpu_freq = 0;
float ram_perc = 0, ram_used = 0, ram_total = 0;
float disk_perc = 0, disk_used = 0, disk_total = 0;
float net_up = 0, net_down = 0;
int bat_perc = -1;
bool bat_plugged = false;
int uptime_h = 0, uptime_m = 0;
// Page cycling
unsigned long lastPageChange = 0;
const unsigned long PAGE_INTERVAL = 3000;
int currentPage = 0;
const int TOTAL_PAGES = 5;
// ========== JSON PARSING ==========
void parseStats(const char* jsonString) {
Serial.print("RAW JSON (full): ");
Serial.println(jsonString);
StaticJsonDocument<1024> doc;
DeserializationError error = deserializeJson(doc, jsonString);
if (error) {
Serial.print("❌ JSON parse failed: ");
Serial.println(error.c_str());
return;
}
Serial.println("✅ JSON parsed successfully");
cpu_total = doc["cpu_total"] | 0.0f;
cpu_freq = doc["cpu_freq"] | 0;
JsonArray cores = doc["cores"];
for (int i = 0; i < cores.size() && i < 8; i++) cpu_cores[i] = cores[i].as<float>();
ram_perc = doc["ram_perc"] | 0.0f;
ram_used = doc["ram_used"] | 0.0f;
ram_total = doc["ram_total"] | 0.0f;
disk_perc = doc["disk_perc"] | 0.0f;
disk_used = doc["disk_used"] | 0.0f;
disk_total = doc["disk_total"] | 0.0f;
net_up = doc["net_up"] | 0.0f;
net_down = doc["net_down"] | 0.0f;
bat_perc = doc["bat_perc"] | -1;
bat_plugged = doc["bat_plugged"] | false;
uptime_h = doc["uptime_h"] | 0;
uptime_m = doc["uptime_m"] | 0;
Serial.print("CPU: "); Serial.println(cpu_total);
Serial.print("RAM: "); Serial.println(ram_perc);
Serial.print("DISK: "); Serial.println(disk_perc);
}
// ========== DISPLAY PAGES (unchanged) ==========
void showPage0() {
lcd.setCursor(0,0); lcd.print("CPU "); lcd.print(cpu_total,0); lcd.print("% "); lcd.print(cpu_freq); lcd.print("MHz");
lcd.setCursor(0,1); lcd.print("Cores:");
for(int i=0;i<2 && cpu_cores[i]>0;i++) { lcd.print(" "); lcd.print((int)cpu_cores[i]); lcd.print("%"); }
}
void showPage1() {
lcd.setCursor(0,0); lcd.print("RAM "); lcd.print(ram_perc,0); lcd.print("% Used:");
lcd.setCursor(0,1); lcd.print(ram_used,1); lcd.print("/"); lcd.print(ram_total,1); lcd.print("GB");
}
void showPage2() {
lcd.setCursor(0,0); lcd.print("DISK "); lcd.print(disk_perc,0); lcd.print("% Used:");
lcd.setCursor(0,1); lcd.print(disk_used,1); lcd.print("/"); lcd.print(disk_total,1); lcd.print("GB");
}
void showPage3() {
lcd.setCursor(0,0); lcd.print("NET ↑"); lcd.print(net_up,0); lcd.print("MB");
lcd.setCursor(0,1); lcd.print(" ↓"); lcd.print(net_down,0); lcd.print("MB");
}
void showPage4() {
lcd.setCursor(0,0);
if(bat_perc>=0){ lcd.print("BAT "); lcd.print(bat_perc); lcd.print("%"); if(bat_plugged) lcd.print(" CHG"); else lcd.print(" ");}
else lcd.print("No battery ");
lcd.setCursor(0,1); lcd.print("UP "); lcd.print(uptime_h); lcd.print("h "); lcd.print(uptime_m); lcd.print("m ");
}
void updateDisplay() {
lcd.clear();
switch(currentPage){
case 0: showPage0(); break; case 1: showPage1(); break; case 2: showPage2(); break;
case 3: showPage3(); break; case 4: showPage4(); break; default: showPage0();
}
}
// ========== SETUP ==========
void setup() {
Serial.begin(115200);
Wire.begin(21,22);
lcd.begin(); lcd.backlight();
lcd.print(" PC Monitor "); lcd.setCursor(0,1); lcd.print(" v2.1 ");
delay(2000); lcd.clear(); updateDisplay();
Serial.println("ESP32 ready – waiting for JSON data...");
}
// ========== MAIN LOOP ==========
void loop() {
// Read all available serial characters
while (Serial.available()) {
char ch = Serial.read();
// Track braces to find complete JSON object
if (ch == '{') {
insideJson = true;
braceBalance = 1;
bufferIndex = 0;
serialBuffer[bufferIndex++] = ch;
}
else if (insideJson) {
serialBuffer[bufferIndex++] = ch;
if (ch == '{') braceBalance++;
else if (ch == '}') braceBalance--;
// When braces balance back to zero, we have a full JSON object
if (braceBalance == 0) {
serialBuffer[bufferIndex] = '\0';
parseStats(serialBuffer);
insideJson = false;
bufferIndex = 0;
// Immediately update display with new data
updateDisplay();
}
// Prevent buffer overflow
if (bufferIndex >= SERIAL_BUFFER_SIZE - 1) {
bufferIndex = 0;
insideJson = false;
Serial.println("Buffer overflow, resetting");
}
}
// Ignore any characters outside JSON (like stray newlines)
}
// Cycle pages
if (millis() - lastPageChange >= PAGE_INTERVAL) {
lastPageChange = millis();
currentPage = (currentPage + 1) % TOTAL_PAGES;
updateDisplay();
}
}بعد التحميل، افتح شاشة المراقبة التسلسلية (115200 باود) لرؤية رسالة "ESP32 جاهز".
ثم أغلق الشاشة - حان وقت إعداد جانب الكمبيوتر.
3. 💻 سكربت بايثون - جمع إحصائيات الكمبيوتر
يستخدم سكربت بايثون مكتبة psutil للحصول على معلومات النظام مباشرةً. تُرسل جميع البيانات المُجمّعة ككائن JSON واحد (متبوعًا بسطر جديد) عبر المنفذ التسلسلي إلى ESP32. يعمل السكربت باستمرار، ويُحدّث البيانات كل ثانيتين افتراضيًا.
يكفي تغيير سطر واحد فقط: يجب ضبط SERIAL_PORT على منفذ COM الصحيح (في نظام ويندوز) أو مسار الجهاز (في نظامي لينكس/ماك أو إس) الخاص بـ ESP32.
4. الحصول على منفذ COM
في نظام ويندوز: افتح إدارة الأجهزة، وابحث ضمن المنافذ (COM وLPT) عن منفذ مثل USB Serial Port (COMx).
لينكس: شغّل الأمر ls /dev/ttyUSB* أو ls /dev/ttyACM*؛ عادةً ما يظهر ESP32 كـ /dev/ttyUSB0.
ماك أو إس: سيظهر كـ /dev/cu.usbserial-xxxx.
5. كتابة الكود البرمجي
أنشئ ملفًا باسم pc_monitor.py والصق المحتوى التالي. عدّل قيمة SERIAL_PORT وفقًا لذلك.
import serial
import psutil
import time
import json
# === CONFIGURATION ===
SERIAL_PORT = 'COM4' # Change to your ESP32 port (e.g., '/dev/ttyUSB0')
BAUD_RATE = 115200
UPDATE_INTERVAL = 2 # seconds
def get_system_stats():
"""Return a dictionary with all stats."""
# CPU
cpu_total = psutil.cpu_percent(interval=0.5)
cpu_freq = psutil.cpu_freq().current if psutil.cpu_freq() else 0
cpu_cores = psutil.cpu_percent(percpu=True)
# Memory
mem = psutil.virtual_memory()
ram_perc = round(mem.percent, 1)
ram_used = round(mem.used / (1024**3), 1)
ram_total = round(mem.total / (1024**3), 1)
# Disk (C:\ on Windows, / on Linux)
disk = psutil.disk_usage('/')
disk_perc = round(disk.percent, 1)
disk_used = round(disk.used / (1024**3), 1)
disk_total = round(disk.total / (1024**3), 1)
# Network (cumulative MB)
net = psutil.net_io_counters()
net_up = round(net.bytes_sent / (1024**2), 1)
net_down = round(net.bytes_recv / (1024**2), 1)
# Battery
battery = psutil.sensors_battery()
bat_perc = battery.percent if battery else -1
bat_plugged = battery.power_plugged if battery else False
# Uptime
uptime_sec = time.time() - psutil.boot_time()
uptime_h = int(uptime_sec // 3600)
uptime_m = int((uptime_sec % 3600) // 60)
stats = {
"cpu_total": cpu_total,
"cpu_freq": cpu_freq,
"cores": cpu_cores,
"ram_perc": ram_perc,
"ram_used": ram_used,
"ram_total": ram_total,
"disk_perc": disk_perc,
"disk_used": disk_used,
"disk_total": disk_total,
"net_up": net_up,
"net_down": net_down,
"bat_perc": bat_perc,
"bat_plugged": bat_plugged,
"uptime_h": uptime_h,
"uptime_m": uptime_m
}
return stats
def main():
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(2) # Allow ESP32 to reset after serial open
print(f"Connected to {SERIAL_PORT}. Sending stats every {UPDATE_INTERVAL}s.\n")
while True:
stats = get_system_stats()
json_str = json.dumps(stats) + "\n"
ser.write(json_str.encode())
print("Sent:", json_str.strip())
time.sleep(UPDATE_INTERVAL)
except serial.SerialException as e:
print(f"Serial error: {e}")
except KeyboardInterrupt:
print("\nExiting...")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
if __name__ == "__main__":
main()٦. تشغيل البرنامج النصي
تأكد من توصيل وحدة ESP32 بمنفذ USB وإغلاق برنامج مراقبة المنفذ التسلسلي الخاص بـ Arduino (لأنه يحجب المنفذ).
افتح نافذة طرفية (موجه الأوامر، أو PowerShell، أو طرفية Linux/macOS) داخل المجلد الذي تم حفظ ملف pc_monitor.py فيه.
نفّذ الأمر التالي:
python pc_monitor.py
يجب أن ترى بيانات JSON تُطبع كل ثانيتين
في الوقت نفسه، ستبدأ شاشة LCD في وحدة ESP32 بعرض معلومات النظام مباشرةً.
7. 📺 ما تراه على شاشة LCD
تتنقل الشاشة بين خمس صفحات مختلفة، حيث تبقى كل صفحة لمدة 3 ثوانٍ قبل الانتقال إلى الصفحة التالية.
يتم تحديث البيانات فور وصول حزمة JSON جديدة من الحاسوب (كل ثانيتين)، لذا تبقى جميع الأرقام محدثة.
8. 🧪 الاختبار والتشغيل الأولي
عند توصيل الطاقة لأول مرة، ستعرض شاشة LCD شاشة بدء تشغيل قصيرة (PC Monitor v2.0) ثم صفحة الإحصائيات الأولى. إذا لم يتم استلام أي بيانات تسلسلية، فستبقى القيم عند الصفر.
بمجرد بدء تشغيل برنامج بايثون، ستلاحظ تغير الأرقام مباشرةً. كما يعرض الحاسوب كل حزمة JSON مُرسلة، لتتمكن من التحقق من إرسال البيانات فعليًا.
🖨️ غلاف مطبوع بتقنية الطباعة ثلاثية الأبعاد - من صنع JUSTWAY
بعد التأكد من عمل الدوائر الإلكترونية، تتمثل الخطوة التالية في إضفاء مظهر نهائي واحترافي على المشروع. يتطلب تصميم وطباعة غلاف يتناسب بدقة مع لوحة ESP32 وشاشة LCD وجميع الأسلاك وقتًا وطابعة ثلاثية الأبعاد جيدة.
مناسب تمامًا للوحات تطوير ESP32 القياسية وشاشات LCD I2C 16×2
فتحات لمنفذ USB والتهوية
متوفر بألوان متعددة (أسود، أبيض، شفاف)
سرعة في التنفيذ وشحن عالمي
👈 تفضل بزيارة JUSTWAY للحصول على عرض سعر لتصميم علبة مخصصة لك.
📚 أفكار أخيرة
يحوّل هذا المشروع وحدة ESP32 رخيصة الثمن وشاشة LCD بسيطة بتقنية I2C إلى شاشة عرض أداء مخصصة لجهاز الكمبيوتر، توضع على مكتبك. لا يتطلب المشروع اتصالاً بالإنترنت أو خدمات سحابية خارجية، ويكفي كابل USB واحد فقط للطاقة والبيانات. يتميز المشروع بمرونة استخدام لغة بايثون ومنصة أردوينو، كما يسهّل بروتوكول JSON إضافة أو تغيير البيانات المعروضة.
إذا واجهت أي مشكلة، تذكر التحقق من النقاط الأساسية أولاً: عنوان I2C، والتوصيلات الصحيحة، ومنفذ COM الصحيح، ومعدل نقل البيانات (115200). يحتوي كود ESP32 المرفق على خاصية تحليل JSON مدمجة مع تقارير الأخطاء، لذا سيُظهر لك مُراقب المنفذ التسلسلي دائمًا المشكلة بالتحديد.
الآن، ابدأ ببناء شاشتك الخاصة، ولن تحتاج إلى التبديل بين النوافذ للعودة إلى مدير المهام. إذا قمت ببناء واحدة، شاركنا صورك وأي تعديلات أجريتها - سأكون سعيدًا برؤية إبداعك.
مترجم
ليست هناك تعليقات:
إرسال تعليق