الجمعة، 22 مايو 2026

مراقب أداء الكمبيوتر ESP32 – إحصائيات النظام في الوقت الفعلي

 مراقبة وحدة المعالجة المركزية، وذاكرة الوصول العشوائي، والقرص، والشبكة، والبطارية، ووقت التشغيل على شاشة 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 مدمجة مع تقارير الأخطاء، لذا سيُظهر لك مُراقب المنفذ التسلسلي دائمًا المشكلة بالتحديد.


الآن، ابدأ ببناء شاشتك الخاصة، ولن تحتاج إلى التبديل بين النوافذ للعودة إلى مدير المهام. إذا قمت ببناء واحدة، شاركنا صورك وأي تعديلات أجريتها - سأكون سعيدًا برؤية إبداعك.

مترجم















السبت، 16 مايو 2026

باث فايندر



باث فايندر هو جهاز محمول يُرتدى على الجسم، ويحتوي على خريطة لمدينتي، ويمكن عرضها والتنقل فيها باستخدام أزرار لوحة التحكم.

المواد المستخدمة في هذا المشروع

المكونات المادية

DFRobot UNIHIKER M10 - كمبيوتر لوحي أحادي مزود بشاشة لمس، يدعم برمجة بايثون لإنترنت الأشياء

PCBWay لوحات الدوائر المطبوعة المخصصة

تطبيقات البرامج والخدمات عبر الإنترنت

أوتوديسك فيوجن

الأدوات اليدوية وآلات التصنيع

طابعة ثلاثية الأبعاد (عامة)

قصة

تحية طيبة أيها المسافرون الأعزاء، وأهلاً بكم من جديد


هذا هو PathFinder، جهاز خريطة رقمية يمكنك صنعه بنفسك، يعمل بلوحة UNIHIKER M10، مصمم لمساعدتك في إيجاد طريقك.


أمارس رياضة ركوب الدراجات في المناطق الجبلية القريبة من مسقط رأسي، وهناك، غالبًا ما أواجه انقطاعًا في شبكة الإنترنت في بعض المناطق. الاعتماد كليًا على خرائط جوجل غير عملي؛ ففي بعض الأحيان، وبسبب مشاكل في البيانات، لا يتم تحميل الخرائط بالكامل، وقد يُسبب ذلك مشكلة كبيرة إذا علقت في مكان ما.


يمكن حل كل هذا لو كان لدينا خريطة ورقية وبعض مهارات الملاحة. لا أملك خريطة ورقية، لكنني صنعت خريطة رقمية يتم تحميلها على لوحة UNIHIKER M10. يمكننا التنقل في الخريطة باستخدام أزرار الاتجاهات. وبهذا، يمكن لأي شخص أن يساعد نفسه إذا علق في مكان ما.


لاختبار هذه الخريطة، ذهبت إلى منطقة نائية في الجبال بالقرب من مسقط رأسي واستخدمتها للتنقل في طريق لم أزره من قبل. اتبعت الخريطة وتمكنت من إيجاد طريقي دون أي مشاكل.

تتناول هذه المقالة عملية بناء هذا المشروع بالكامل، بدءًا من تصنيع لوحات الدوائر المطبوعة (PCBs) وصولًا إلى البرمجة وتجميع الجهاز، فلنبدأ إذًا.

المواد المطلوبة

هذه هي المواد المستخدمة في هذا المشروع:

لوحات دوائر مطبوعة مخصصة (مقدمة من PCBWAY)

لوحة تطوير Unihiker M0

أجزاء مطبوعة بتقنية الطباعة ثلاثية الأبعاد

دائرة متكاملة لإدارة الطاقة IP5306

مكثفات 10 ميكروفاراد 1206

منفذ من النوع C

زر ضغط رأسي

مصباح LED أخضر 0603

ملف حث 1 ميكروهنري SMD

أزرار ضغط بحجم 4x4 مم.

معدات - يونيهايكر إم 1



يُعدّ جهاز UNIHIKER M10 نجم مشروعنا، وهو حاسوب لوحي فريد من نوعه. يأتي مزودًا بشاشة لمس مدمجة بحجم 2.8 بوصة، بالإضافة إلى تقنيتي Wi-Fi وBluetooth.

ما يُميّز هذا الجهاز حقًا هو المعالج والمعالج المساعد. فهو يعمل بمعالج RK3308 ARM 64 بت، رباعي النواة، بتردد 1.2 جيجاهرتز. ويحتوي على ذاكرة وصول عشوائي DDR3 بسعة 512 ميجابايت، بالإضافة إلى شريحة eMMC مدمجة بسعة 16 جيجابايت.

للتحكم في منافذ الإدخال/الإخراج العامة (GPIO)، يحتوي الجهاز على معالج مساعد، هو GD32VF103C8T6. قد يبدو الاسم طويلًا، ولكنه معالج RISC-V بتردد 108 ميجاهرتز، مزود بذاكرة فلاش بسعة 64 كيلوبايت وذاكرة SRAM بسعة 32 كيلوبايت.

لا يستطيع المعالج المركزي التحكم المباشر في منافذ الإدخال/الإخراج العامة، لذا أُضيف معالج مساعد يُمكننا التحكم به باستخدام لغة بايثون. يتحكم النظام بالمعالج المساعد باستخدام مكتبة PinPong.

صُنعت اللوحة بواسطة شركة DFRobot، ويمكنكم الاطلاع على المزيد من التفاصيل عنها عبر صفحة ويكي الخاصة بهم.

هنــــــــــــــــــــــــــــا



يعمل جهاز UNIHIKER M10 بنظام Debian Linux كامل. يتم التحكم بكل مكوناته، بما في ذلك الشاشة ومنافذ GPIO والمستشعرات، عبر لغة Python، لذا لا يدعم بيئة تطوير Arduino، وللأسف، لا يدعم لغة C.

استلهمتُ تصميم الخريطة من واجهة Pip-Boy 3000 في لعبة Fallout: New Vegas، والتي تتميز بلونها الكهرماني. فالخرائط العادية مملة، وبصفتي من مُحبي سلسلة Fallout، كان من واجبي تصميم خريطة مستوحاة من Pip-Boy.

يُستخدم مكتبتان. الأولى هي برنامج تشغيل UNIHIKER، الذي يتحكم بشاشة العرض المدمجة بدقة 240×320. أما الثانية فهي مكتبة PinPong، التي تُستخدم لقراءة وكتابة منافذ GPIO.

عرض الإطارات


نقوم أولاً بإنشاء عنصر واجهة المستخدم للص

gui = GUI()

# Create once
display_img = gui.draw_image(x=0, y=0, image="/tmp/frame.png")

# Every frame: save new image, then update the same widget
frame.save("/tmp/frame.png")
display_img.config(image="/tmp/frame.png")

تُنشأ جميع الرسومات في الذاكرة باستخدام Pillow - بدون إطار عرض أو Pygame. تمر كل إطار بالمراحل التالية:


قص جزء من الخريطة بحجم 2000×2000 لعرض 320×240 بكسل

تطبيق لون كهرماني

رسم شبكة CRT مع أيقونات نقاط الاهتمام

تركيب خطوط المسح الضوئي

تحريك مؤشر التصويب

رسم أشرطة واجهة المستخدم الرسومية

تدوير الخريطة إلى الوضع الأفقي

حفظها ورفعها

```python
from PIL import Image, ImageDraw

viewport = self._map.crop((x0, y0, x0 + VIEWPORT_W, y0 + VIEWPORT_H))
frame = viewport.convert("RGBA")
draw = ImageDraw.Draw(frame)
```

لون كهرماني

يتم تحميل الخريطة بنظام ألوان RGB عادي. وللحصول على مظهر فسفور Pip-Boy، تُستخدم القناة الحمراء كمصدر سطوع للقنوات الثلاث جميعها.

r, g, b = img.split()
img = Image.merge("RGB", (
r,
r.point(lambda v: int(v * 0.71)), # amber green
r.point(lambda v: int(v * 0.26)), # amber blue
))

Ratios `(1.0, 0.71, 0.26)` match the target amber `(255, 182, 66)`.

الأزرار المادية

تُوصَّل الأزرار بدبابيس GPIO باستخدام مقاومات سحب داخلية. عند الضغط عليها، ينخفض ​​مستوى إشارة الدبوس، لذا فإن قراءة القيمة `0` تعني "مضغوط".

from pinpong.board import Board, Pin

Board("unihiker").begin()
btn_up = Pin(Pin.P3, Pin.IN, Pin.PULLUP)

if btn_up.read_digital() == 0:
move_y = -PAN_SPEED

if not hasattr(Pin, "PULLUP") and hasattr(Pin, "PULL_UP"):
Pin.PULLUP = Pin.PULL_UP

عرض أفقي على شاشة عمودية

شاشة العرض الفعلية بحجم ٢٤٠×٣٢٠ بكسل (عمودي). أرسم على لوحة رسم بحجم ٣٢٠×٢٤٠ بكسل (أفقي) وأقوم بتدوير كل إطار قبل الحفظ.

frame = frame.rotate(-90, expand=True)
# result: 240×320 — fills the screen when held sideways

حلقة العرض

حلقة قياسية ذات خطوة زمنية ثابتة — توقف مؤقتًا للمدة المتبقية في ميزانية الإطار

target_fps = 15
frame_time = 1.0 / target_fps

while True:
t0 = time.time()
# ... read buttons, render, push frame ...
sleep_t = frame_time - (time.time() - t0)
if sleep_t > 0:
time.sleep(sleep_t)

المكتبات

| Library | Install | Purpose |
|---|---|---|
| `pillow` | `pip install pillow` | All image rendering |
| `unihiker` | pre-installed | Push frames to the screen |
| `pinpong` | pre-installed | GPIO buttons and buzzer |
| `math`, `time`, `os` | standard library | Animation, timing, file checks |


بعد ضبط إعدادات الخريطة، انتقلنا إلى مشكلة مصدر الطاقة في لوحة UNIHIKER. تستخدم لوحة UNIHIKER M10 منفذ USB من النوع C كمصدر للطاقة، مما يوفر جهدًا ثابتًا قدره 5 فولت لتشغيلها. تكمن المشكلة في هذه اللوحة في عدم وجود موصل بطارية مدمج.

بالقرب من منفذ النوع C، نجد طرفين. الطرف الأول موصول بجهد VCC الخاص بمنفذ USB من النوع C، والطرف الآخر موصول بالأرضي (GND).

بتوفير جهد 5 فولت لهذين الطرفين، يمكننا تشغيل اللوحة، ولكننا نحتاج إلى مصدر طاقة ثابت.

تصميم ثلاثي الأبعاد


في هذا المشروع، كانت فكرتي تصميم جهاز بشاشة وأربعة أزرار بجانبها، مستوحى من أدب الخيال العلمي السايبربانك، حيث يبرز التركيز على الشاشات، ويتميز التصميم بشكله الصندوقي المتناظر.

ولتحسين التصميم، أضفتُ غطاءً للشاشة، له وظيفة عملية أيضًا. فعند استخدام الجهاز في الهواء الطلق، يوفر الغطاء ظلًا للشاشة، مما يُسهّل رؤيتها.

كما أضفتُ مقبضًا كبيرًا، وهو ليس جزءًا عمليًا أو وظيفيًا، وإنما وُضع لأغراض جمالية بحتة. بالإضافة إلى ذلك، أضفتُ قطعة أخرى في الجهة الأمامية لأغراض جمالية.

بإضافة هذه القطع المختلفة، كان هدفي استخدام ثلاثة خيوط طباعة ثلاثية الأبعاد ملونة، الأحمر والأبيض والأسود، لطباعة المكونات، مما يُنتج مزيجًا لونيًا رائعًا.

يحتو النموذج على تخطيط دوائر كهربائية مُنظم، بما في ذلك لوحة الأزرار. ولكل دائرة نقاط تثبيت خاصة بها، حيث نضع اللوحات ونثبتها باستخدام براغي M2.

أضفنا أيضًا فتحات على أحد جانبي الجهاز للوصول إلى منفذ USB الخاص بـ UNIHIKER M10 في حال رغبنا في إعادة برمجة الجهاز. كما أضفنا فتحات للوصول إلى منفذ USB الخاص بوحدة الطاقة وزر التشغيل/الإيقاف.

ولتركيب حزام بطاقة الهوية، أضفنا قطعة على شكل خطاف في أحد طرفي الحزام.

أزاء مطبوعة بتقنية الطباعة ثلاثية الأبعاد


بعد الانتهاء من تصميم النموذج، قمنا بطباعة جميع الأجزاء بتقنية الطباعة ثلاثية الأبعاد باستخدام خيوط Hyper PLA بنفس الإعدادات، والتي تضمنت ارتفاع طبقة 0.2 مم، ونسبة تعبئة 25%، ودعامات شجرية بمسافة 0.3 مم على المحور Z.

طُبع الهيكل الأمامي بخيوط Hyper PLA بيضاء. أما الهيكل الخلفي، وغطاء الشاشة، والجزء الزخرفي، فطُبعت جميعها بخيوط Hyper PLA سوداء.

استُخدمت خيوط Hyper PLA حمراء لطباعة المفاتيح الأربعة ومقبض الجهاز.

تصميم لوحة الدوائر المطبوعة - لوحة الطاقة


هنا، نعيد استخدام الدائرة المستخدمة في مشروع الشاشة المحمولة.

في هذه الدائرة، تسحب دائرة إدارة الطاقة IP5306 تيارًا ثابتًا قدره 5 فولت/2 أمبير من بطارية ليثيوم أيون 3.7 فولت. تمنع خصائص قطع التيار العالي والمنخفض فيها الشحن الزائد والتفريغ الزائد للبطارية.

منفذ الشحن المستخدم هو منفذ من النوع C ذو فتحات توصيل، ويتصل بمنفذ شحن الدائرة المتكاملة. بالإضافة إلى منفذ الشحن والأرضي (GND)، أضفنا مكثف ترشيح سعته 10 ميكروفاراد، ومجموعة مكثفات سعتها 10 ميكروفاراد مع مقاومة 2 أوم.

كما أضفنا أربعة مصابيح LED، تعمل كمؤشرات امتلاء البطارية، إلى منفذ LED الخاص بالدائرة المتكاملة.

أضفنا زر ضغط لتشغيل وإيقاف هذا الجهاز.

كما أضفنا مكثفَي ترشيح إضافيين إلى مخرجي الدائرة المتكاملة والأرضي (GND).

خدمة PCBWAY





في عملية تجميع لوحة الدوائر المطبوعة هذه، نستخدم محقنة لتوزيع معجون اللحام. نضع معجون اللحام على كل نقطة توصيل للمكونات لبدء عملية تجميع الدائرة الرئيسية. نستخدم هنا معجون لحام Sn/Pb بنسبة 63/37، والذي تبلغ درجة انصهاره 190 درجة مئوية.

بعد ذلك، نختار كل مكون من مكونات SMD ونضعه في مكانه الصحيح.

ثم تُثبّت جميع المكونات بشكل دائم على نقاط التوصيل الخاصة بها عند وضع الدائرة بأكملها على صفيحة التسخين بالتدفق، والتي تُسخّن لوحة الدوائر المطبوعة إلى درجة انصهار معجون اللحام.

بعد ذلك، نضع كل مكون من مكونات THT، بما في ذلك منفذ Type C وزر الضغط العمودي، في مكانه الصحيح. نضع منفذ Type C في الأعلى، بينما نعكس وضع زر الضغط ونضعه في الأسفل. باستخدام مكواة اللحام، نلحم كلا المكونين في مكانهما.

مصدر الطاقة


نستخدم في هذا المشروع خلية ليثيوم أيون كمصدر للطاقة، وتحديدًا خلية ليثيوم أيون من نوع 14500 بجهد 3.7 فولت وسعة 600 مللي أمبير، مزودة بدائرة PCM مثبتة مسبقًا. تحمي هذه الدائرة الخلية من الشحن الزائد والتفريغ الزائد، كما توفر حماية من قصر الدائرة.

يتم لحام طرفي الخلية (B+ وB-) بأطراف البطارية على لوحة إدارة الطاقة باستخدام مكواة لحام.

لاختبار الدائرة، استخدمتُ جهاز قياس متعدد، وضغطتُ على الزر الرأسي لتشغيلها، فرأينا مؤشر LED يضيء.

باستدام جهاز القياس المتعدد، تحققنا من جهد خرج الدائرة، والذي بلغ 5 فولت ثابتة، مما يعني أن الدائرة تعمل بشكل صحيح.

تصميم لوحة الدوائر المطبوعة - لوحة الأزرار


هذه هي لوحة الدوائر المطبوعة الثانية التي استخدمناها في مشروعنا، وهي لوحة الأزرار.

أضفنا هنا أربعة أزرار ضغط بحجم 4×4 مم على لوحة بحجم 20×20 مم. تم تحديد موضع كل زر وفتحات التثبيت وفقًا للنموذج ثلاثي الأبعاد.

كما أضفنا موصل CON5. تم توصيل الطرف الأول بالأرضي (GND)، بينما تم توصيل الأطراف الأربعة المتبقية بدبابيس كل زر. سنستخدم هذا الموصل لاحقًا لربط جهاز UNIHIKER M10 بلوحة الأزرار.

للعلم، نعيد استخدام هذه اللوحة من مشروع سابق، يمكنك الاطلاع عليه عبر الرابط أدناه.

هنــــــــــــــــــــــــــــــــــــــــــــــــــا


كان تجميع لوحة الأزرار بسيطًا للغاية.

وضعنا الأزرار الأربعة في أماكنها، ثم قلبنا اللوحة، واستخدمنا مكواة اللحام لتوصيل جميع أطراف الأزرار.

إعدادات الإلكترونيات


يتكون النظام الإلكتروني لهذا المشروع من وحدة UNIHIKER M10 موصولة بلوحة الأزرار بالترتيب التالي:

الزر A موصول بالمنفذ P3 في وحدة UNIHIKER. الزر B موصول بالمنفذ P0 في وحدة UNIHIKER. الزر C موصول بالمنفذ P1. الزر D موصول بالمنفذ P2.

استخدمنا أيضًا الجرس المدمج، الموصول بالمنفذ P26.

بعد ذلك، قمنا بتوصيل طرفي 5 فولت وGND في لوحة الطاقة بطرفي 5 فولت وGND في وحدة UNIHIKER باستخدام سلكين.

بالضغط على الزر العمودي، يتم تشغيل النظام، وتضيء شاشة UNIHIKER، مما يشير إلى أن النظام يعمل.

عملية تجميع الهيكل


نبدأ الآن عملية التجميع، والتي تبدأ بوضع لوحة الأزرار فوق نقاط تثبيت البراغي الأربعة. ثم نثبتها باستخدام أربعة براغي M2.

بعد ذلك، توضع خلية الليثيوم في الجزء السفلي من الجهاز، حيث قمنا بتصميم حامل البطارية. كما توضع الدائرة الكهربائية في مكانها فوق نقطتي تثبيت البراغي وتُشدّ باستخدام برغيين M2. نستخدم القليل من الغراء الساخن لتثبيت الخلية في مكانها.

نستخدم غراءً فائقًا لتثبيت الجزء الزخرفي الذي صممناه ووضعه فوق الجزء الأمامي من الجهاز. نضع الغراء الفائق على الجهة الأمامية، ثم نضع الجزء الزخرفي ونثبته في مكانه.

يتم وضع جهاز UNIHIKER M10 من داخل الجزء الأمامي من الجهاز. نستخدم الغراء الساخن لتثبيته بإحكام.

تُضاف الأزرار المطبوعة بتقنية الطباعة ثلاثية الأبعاد في مكانها من داخل الجزء الأمامي من الجهاز. ثم يتم تجميع الجزأين الأمامي والخلفي معًا وتثبيتهما باستخدام أربعة براغي M2.

بعد ذلك، يُضاف مقبض الجهاز في مكانه على الجهة الأمامية من الجهاز. هذا المقبض مخصص للزينة فقط وليس له وظيفة عملية.

من الجهة الأمامية، نضع غطاء المصباح في مكانه ونثبته باستخدام برغيين من نوع M2.

تمّ التجميع.

إليكم النتيجة النهائية لهذا المشروع: PathFinder، جهاز الخرائط الرقمية الذي صنعته بنفسي، والذي يساعد المسافرين على إيجاد طريقهم. إنه ببساطة خريطة داخل شاشة، يتم تحميلها دون الحاجة إلى شبكة Wi-Fi أو أي اتصال بالإنترنت. باستخدام لوحة التحكم، يمكننا التنقل بسهولة على الخريطة.

صُممت الخريطة لتشبه خريطة لعبة Fallout: New Vegas، بلونها الكهرماني، لكنها تُظهر مدينتي، وهذا رائع حقًا.

لاختبار الجهاز عمليًا، أخذت دراجتي النارية وذهبت إلى منطقة لم أزرها من قبل. جربت التنقل باستخدام PathFinder، وقد عمل بشكل ممتاز.

اتبعت الخريطة، وسلكت طريقًا في منطقة نائية، في قلب غابة، وتنقلت عبرها، ثم عدت في النهاية إلى طريق سريع مألوف.

الخلاصة

هذه الخريطة تعمل، بل وتعمل بشكل جيد، لكنني واجهت بعض المشاكل أثناء الاختبار الميداني.

كانت أكثرها إزعاجًا هي الشاشة. إضاءة شاشة UNIHIKER الخلفية خافتة جدًا، مما يجعل قراءتها صعبة تحت أشعة الشمس المباشرة. حتى أنني صممت غطاءً لها، لكن ذلك لم يُجدِ نفعًا. إضافةً إلى ذلك، تحتوي الشاشة على طبقة لامعة عاكسة للمس، مما يزيد من سوء الرؤية في الهواء الطلق.

من القيود الأخرى حجم الخريطة. حاليًا، تغطي الخريطة مساحة محدودة تبلغ حوالي 2000×2000 بكسل. يمكن زيادة هذه المساحة بتعديل حجم ملف map.py، لكن ذلك يزيد من حجم الكود. لذا، أحتاج إلى إيجاد طريقة لتوسيع الخريطة بكفاءة دون زيادة حجم الكود بشكل ملحوظ.

أدركت أيضًا أن هذا الجهاز قد يكون أكثر عمليةً إذا تم تثبيته على دراجتي النارية بدلًا من حمله كشارة.

في هذه الحالة، يمكنني توسيع وظائفه بإضافة مستشعرات حرارة، أو تسجيل فيديو، أو حتى بعض ميزات الذكاء الاصطناعي مثل تتبع المركبات في الأمام. قد يتضمن الجهاز مكبر صوت بلوتوث إذا تم تركيبه على الدراجة؛ فالخيارات لا حصر لها.


في الإصدار الثاني من هذا المشروع، سأعمل أولاً على إصلاح المشاكل الأساسية، وربما أجربه في رحلة.

شكرًا جزيلًا لكم على وصولكم إلى هنا، وسأعود قريبًا بمشروع جديد!

مع السلامة.