تختار بين Arduino Uno وESP32 وRaspberry Pi، تقرأ مستشعرات حقيقية بتوصيل آمن، ترسل القراءات عبر Wi-Fi بطلبات HTTP ورسائل MQTT، تعرضها في لوحة بيانات، وتحمي جهازك وبطاريته — وتختم بمراقب صوبة متصل.
كيف تدرس هذا المسار؟
افتح Wokwi.com إن لم تملك لوحة Arduino بعد.
انسخ كود كل درس وشغّله في المحاكي.
نفّذ المهمة — توصيلات آمنة أولاً.
أكمل الاختبار — التقدّم يُحفظ على جهازك.
مناسب من سن 13 فما فوق، وفي آخر كل درس روابط لمراجعه الرسمية.
1. ما هو إنترنت الأشياء؟ — من القراءة إلى القرار
الهدف: تفهم طبقات نظام إنترنت الأشياء: الجهاز والاتصال والوسيط ولوحة العرض، وتتتبع رحلة قراءة واحدة عبرها.
إنترنت الأشياء (Internet of Things) هو ربط أجهزة مادية صغيرة بالشبكة: جهاز يقيس شيئاً من العالم، كالحرارة أو الحركة أو مستوى الماء، ثم يرسل القياس إلى مكان يُخزَّن فيه ويُعرض، أو يُتخذ بناءً عليه قرار. الفارق بينه وبين مشروع إلكترونيات عادي هو الاتصال. مصباح يضيء وحده عند الظلام مشروع أتمتة محلية صحيح، لكنه يصبح جزءاً من إنترنت الأشياء حين يرسل سجل تشغيله إلى هاتفك أو يستقبل منك أمراً عن بُعد. هذا المسار يركّز على الاستشعار المتصل: أن تقيس بدقة، وترسل بأمان، وتعرض ما قسته بوضوح. أما تحريك المحركات وبناء الروبوتات فله مسار مستقل في Mesue Learn.
أي نظام من هذا النوع يتكوّن من أربع طبقات، وفهمها يختصر عليك نصف وقت التشخيص لاحقاً:
1) الجهاز الطرفي (Edge Device): متحكم دقيق مثل ESP32 موصول به مستشعر أو أكثر، يقرأ ويحوّل القراءة إلى رقم.
2) الاتصال: في المشاريع المنزلية غالباً Wi-Fi، وهناك بدائل مثل Bluetooth Low Energy للمسافات القصيرة وLoRa للمسافات البعيدة بسرعات منخفضة.
3) الوسيط أو الخادم: برنامج يستقبل الرسائل، مثل وسيط MQTT (Broker) أو خادم ويب يستقبل طلبات HTTP.
4) العرض والقرار: لوحة بيانات (Dashboard) ترسم المنحنيات، أو تنبيه على الهاتف، أو قاعدة تشغّل جهازاً آخر.
لنأخذ حالة متخيَّلة سنعود إليها في المشروع النهائي. سلمى طالبة هندسة في المنصورة تساعد عمها في صوبة صغيرة لزراعة الطماطم. كل صباح يذهب العم ليقرأ ترمومتراً معلقاً على العمود، لكنه لا يعرف ما حدث ليلاً: هل ارتفعت الرطوبة إلى حد يشجّع الفطريات؟ هل انخفضت الحرارة فجأة؟ صممت سلمى على الورق نظاماً بسيطاً: لوحة ESP32 مع مستشعر DHT22 تقيس الحرارة والرطوبة كل عشر دقائق، وترسل القراءة عبر شبكة Wi-Fi إلى وسيط MQTT يعمل على حاسوب صغير من نوع Raspberry Pi في البيت، ثم لوحة بيانات تعرض منحنى الليلة كاملة، وتنبيه يصل إلى الهاتف إذا تجاوزت الحرارة حداً معيناً.
تتبّع قراءة واحدة عبر الطبقات الأربع: في الساعة الثانية وعشر دقائق ليلاً قرأ المستشعر 31.4 درجة مئوية ورطوبة 78%.
أولاً، يحوّل المتحكم القراءة إلى نص قصير بصيغة JSON مثل {"t":31.4,"h":78}.
ثانياً، ينشر النص على موضوع (Topic) اسمه greenhouse/zone1/climate.
ثالثاً، يسلّم الوسيط الرسالة لكل برنامج مشترك في هذا الموضوع: لوحة العرض وبرنامج التنبيه معاً.
رابعاً، يقارن برنامج التنبيه 31.4 بالحد المضبوط على 30 فيرسل إشعاراً.
المخرج المتوقع: نقطة جديدة على المنحنى ورسالة على هاتف العم خلال ثوانٍ من القياس.
كل طبقة نقطة فشل مستقلة، لذلك يبدأ التشخيص الصحيح من الأسفل إلى الأعلى. إذا توقفت اللوحة عن عرض قراءات جديدة، فلا تبدأ بتغيير ألوان المنحنى أو إعادة تثبيت برنامج العرض. اسأل بالترتيب: هل يقرأ المستشعر قيماً منطقية على شاشة Serial؟ هل الجهاز متصل بالشبكة؟ هل وصلت الرسالة إلى الوسيط؟ هل لوحة العرض مشتركة في الموضوع نفسه بالتهجئة نفسها؟ أول سؤال إجابته «لا» هو مكان العطل، وغالباً لا تحتاج إلى فحص ما فوقه.
ومن القرارات المبكرة المهمة: ما الذي يجب أن يحدث على الجهاز نفسه، وما الذي يذهب إلى الخادم؟ القرار الذي يحتاج سرعة وموثوقية حتى لو انقطع الإنترنت، كإطفاء سخان عند ارتفاع الحرارة أو تشغيل مروحة تهوية، مكانه المتحكم داخل الصوبة. أما حفظ التاريخ ورسم المنحنيات وإرسال التنبيهات ومقارنة الأيام فمكانها الخادم. قاعدة عملية تصلح لمعظم المشاريع: الحماية محلية، والتحليل مركزي.
وتذكّر من البداية أن كل جهاز متصل باب إضافي إلى شبكة البيت أو العمل. كاميرات وأجهزة كثيرة اختُرقت لأن أصحابها تركوا كلمات المرور الافتراضية، وسنخصص الدرس الثامن لهذا الموضوع. ولا تحتاج لوحة حقيقية لتبدأ: محاكي Wokwi في المتصفح يحاكي ESP32 مع شبكة Wi-Fi افتراضية، وستستخدمه في الدرس التاسع لبناء نسخة كاملة من نظام الصوبة قبل شراء أي قطعة.
نظام إنترنت الأشياء أربع طبقات: جهاز طرفي، واتصال، ووسيط أو خادم، وعرض وقرار.
شخّص الأعطال من الطبقة السفلى إلى العليا: المستشعر، ثم الشبكة، ثم الوسيط، ثم العرض.
قرارات الحماية تعيش على الجهاز نفسه حتى تعمل إذا انقطع الإنترنت.
كل جهاز متصل يحتاج كلمات مرور غير افتراضية منذ اليوم الأول.
2. Uno أم ESP32 أم Raspberry Pi؟ — اختيار اللوحة وتجهيز البرمجة
الهدف: تختار اللوحة المناسبة لمشروعك المتصل، وتجهّز Arduino IDE لبرمجة Uno وESP32، وتشغّل أول برنامج.
الأسماء الثلاثة تتكرر في كل منتدى، لكنها تنتمي إلى عائلتين مختلفتين في الطبيعة. Arduino Uno R3 لوحة عليها متحكم دقيق (Microcontroller) اسمه ATmega328P، يعمل بجهد 5 فولت، وذاكرته صغيرة: 32 كيلوبايت للبرنامج و2 كيلوبايت فقط للمتغيرات، ولا يحتوي أي اتصال لاسلكي. ESP32 متحكم دقيق من شركة Espressif يعمل بجهد 3.3 فولت، وفيه Wi-Fi وBluetooth مدمجان وذاكرة أكبر بكثير، ويُبرمج بالبيئة نفسها ولغة Arduino نفسها. أما Raspberry Pi فحاسوب كامل يشغّل نظام Linux من بطاقة ذاكرة، منافذه العامة (GPIO) تعمل بجهد 3.3 فولت، ولا يحتوي محولاً تناظرياً رقمياً مدمجاً، فلا يقرأ مستشعراً تناظرياً دون شريحة إضافية.
كيف تختار؟ إذا كان مشروعك يقرأ مستشعرات ويرسل أرقاماً قليلة عبر Wi-Fi، وقد يعمل ببطارية، فاختر ESP32؛ فهو يجمع المتحكم والاتصال في قطعة واحدة رخيصة، ويستطيع النوم العميق بين القراءات. وإذا كنت تتعلم الدوائر لأول مرة دون شبكة، أو لديك مستشعرات ووحدات قديمة تعمل بجهد 5 فولت، فلوحة Uno أرحم بالأخطاء وأسهل في التوصيل. وإذا احتجت كاميرا ومعالجة صور، أو أردت تشغيل وسيط MQTT وقاعدة بيانات ولوحة عرض في جهاز واحد يعمل طوال اليوم، فهذا عمل Raspberry Pi. في نظام الصوبة من الدرس الأول تتكامل اللوحات بدل أن تتنافس: ESP32 داخل الصوبة يقيس ويرسل، وRaspberry Pi في البيت يستقبل ويخزّن ويعرض.
أشهر خطأين في الاختيار متعاكسان. الأول استخدام Raspberry Pi عقدةً طرفية تعمل ببطارية: يستهلك طاقة أكبر بكثير من ESP32، ويحتاج إطفاءً منظماً لأن قطع الكهرباء فجأة قد يفسد نظام الملفات على بطاقة الذاكرة. والثاني اختيار Uno لمشروع يحتاج Wi-Fi، ثم إضافة وحدة اتصال خارجية وأسلاك ومكتبات، بينما يؤدي ESP32 وحده المهمة كلها. ولاحظ أن الأرقام في هذا المسار تخص Uno R3 تحديداً؛ الإصدار الأحدث Uno R4 مبني على متحكم مختلف، فراجع صفحته الرسمية قبل نقل أي رقم منه.
تجهيز Arduino IDE خطوة بخطوة:
1) ثبّت Arduino IDE 2 من موقع Arduino الرسمي. لوحة Uno مدعومة مباشرة دون أي إضافة.
2) لدعم ESP32 افتح Boards Manager وابحث عن esp32 by Espressif Systems وثبّت الحزمة.
3) إن لم تظهر الحزمة، أضف رابط فهرس Espressif في خانة Additional boards manager URLs من نافذة Preferences. انسخ الرابط من صفحة Installing في توثيق Espressif نفسها لأنه قد يتغير.
4) من قائمة Tools اختر اللوحة، مثل Arduino Uno أو ESP32 Dev Module، ثم اختر المنفذ الذي ظهر بعد توصيل الكابل.
5) افتح مثال Blink وارفعه للتأكد من أن السلسلة كلها تعمل قبل كتابة أي كود خاص بك.
const int LED_PIN = LED_BUILTIN; // على كثير من لوحات ESP32 DevKit هو 2
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
دالة setup() تُنفَّذ مرة واحدة عند التشغيل، وفيها تخبر المتحكم أن المنفذ مخرج. ودالة loop() تتكرر بلا توقف: تشغيل، ثم انتظار نصف ثانية، ثم إطفاء، ثم انتظار. على Uno يقع LED المدمج على المنفذ 13. أما على ESP32 فالأمر يختلف بين اللوحات: كثير من لوحات DevKit تضعه على GPIO2، وبعضها لا يحتوي LED قابلاً للبرمجة أصلاً. إذا ظهر خطأ الترجمة LED_BUILTIN was not declared فاكتب رقم المنفذ الفعلي بدلاً منه، مثل const int LED_PIN = 2;، بعد مراجعة مخطط لوحتك.
وعند رفع البرنامج إلى ESP32 قد تظهر رسالة فشل الاتصال بعد سطر Connecting والنقاط. جرّب ثلاثة أمور بالترتيب: كابل USB آخر، لأن بعض الكابلات للشحن فقط ولا تنقل بيانات؛ ثم تعريف شريحة USB على اللوحة إن لم يظهر منفذ أصلاً، وأشهرها CP210x وCH340؛ ثم الضغط مطولاً على زر BOOT في اللوحة لحظة ظهور النقاط، لأن بعض اللوحات لا تدخل وضع البرمجة تلقائياً.
Uno R3 يعمل بجهد 5 فولت ومناسب لتعلّم الدوائر دون شبكة.
Raspberry Pi حاسوب Linux يناسب دور الخادم ولوحة العرض، لا العقدة الطرفية.
رقم LED المدمج على ESP32 يختلف بين اللوحات، فراجع مخطط لوحتك.
مخطط
عقدة طرفية ببطارية + Wi-Fi←ESP32
تعلّم دوائر 5V بلا شبكة←Uno R3
خادم + قاعدة بيانات + عرض←Raspberry Pi
مختبر: أول رفع ناجح
ثبّت حزمة ESP32 في Arduino IDE (أو افتح مشروع ESP32 في Wokwi)، وارفع Blink، ثم غيّر زمن الانتظار إلى 100 مللي ثانية ولاحظ الفرق.
مهمة عملية
ارفع Blink إلى لوحة ESP32 أو Uno (حقيقية أو في Wokwi)، واكتب في سطرين: اسم اللوحة المختارة من قائمة Tools، ورقم المنفذ الذي يعمل عليه LED المدمج في لوحتك.
اختبر فهمك (1/3)
مشروع يقيس حرارة صوبة ويرسلها عبر Wi-Fi ويعمل ببطارية. ما اللوحة الأنسب للعقدة الطرفية؟
اختبر فهمك (2/3)
ظهر خطأ LED_BUILTIN was not declared عند ترجمة Blink للوحة ESP32. ما الإصلاح؟
اختبر فهمك (3/3)
لماذا لا يُنصح بقطع الكهرباء فجأة عن Raspberry Pi؟
الهدف: تقرأ المنافذ الرقمية والتناظرية على Uno وESP32، وتحوّل القراءة إلى فولت، وتحمي لوحات 3.3V من إشارات 5V.
المنفذ الرقمي (Digital) يعرف حالتين فقط: HIGH أو LOW. لكن معنى HIGH يتغير حسب اللوحة: على Uno R3 يقارب 5 فولت، وعلى ESP32 وRaspberry Pi يقارب 3.3 فولت. هذا الفرق الصغير في الرقم هو مصدر أخطر خطأ في عالم الصانعين: توصيل مخرج قطعة تعمل بجهد 5 فولت مباشرة بمنفذ لوحة تعمل بجهد 3.3 فولت. منافذ ESP32 وRaspberry Pi لا تتحمل 5 فولت، وقد يتلف المنفذ أو الشريحة كلها، أحياناً فوراً وأحياناً تدريجياً بعد أيام من العمل المتقطع.
المنفذ التناظري (Analog) يمر عبر محوّل تناظري رقمي (ADC) يحوّل الجهد إلى رقم صحيح. في Uno R3 المحوّل بدقة 10 بت، أي 1024 مستوى، فتعيد analogRead قيمة من 0 إلى 1023 لمدى من صفر إلى 5 فولت. وفي ESP32 تضبط مكتبة Arduino-ESP32 الدقة افتراضياً على 12 بت، فتعيد القيمة من 0 إلى 4095 لمدى يقارب صفراً إلى 3.3 فولت. معنى ذلك أن الجهد الفيزيائي نفسه يعطي رقمين مختلفين تماماً على اللوحتين، وأي عتبة نسختها من مشروع Uno ستكون خاطئة على ESP32.
مثال محلول: جهد 1.65 فولت على المنفذ التناظري.
على Uno R3: القراءة ≈ 1.65 ÷ 5 × 1023 ≈ 338.
على ESP32 نظرياً: القراءة ≈ 1.65 ÷ 3.3 × 4095 ≈ 2048.
الكود التالي يكتب التحويل مرة واحدة ويعمل على اللوحتين، لأن حزمة ESP32 تعرّف الرمز ARDUINO_ARCH_ESP32:
#if defined(ARDUINO_ARCH_ESP32)
const float VREF = 3.3;
const int ADC_MAX = 4095;
const int SENSOR_PIN = 34;
#else
const float VREF = 5.0;
const int ADC_MAX = 1023;
const int SENSOR_PIN = A0;
#endif
void setup() {
Serial.begin(115200);
}
void loop() {
int raw = analogRead(SENSOR_PIN);
float volts = raw * VREF / ADC_MAX;
Serial.print(raw);
Serial.print(" -> ");
Serial.println(volts, 2);
delay(500);
}
وقبل الانتقال إلى التناظري، لاحظ أن قراءة زر على أي من اللوحتين تحتاج حالة ثابتة حين لا يكون مضغوطاً، وإلا «عام» المنفذ وقرأ قيماً عشوائية. الحل pinMode(13, INPUT_PULLUP) ووصل الزر بين المنفذ والأرضي، فتصبح القراءة LOW عند الضغط وHIGH عند الترك. هذا الانعكاس يربك المبتدئين أول مرة، فاكتب تعليقاً بجانب الشرط في الكود.
للمحوّل في ESP32 خصائص يجب أن تعرفها قبل الاعتماد على قراءته. أولاً، العلاقة بين الجهد والرقم ليست خطية تماماً، خصوصاً قرب طرفي المدى، لذلك توفّر المكتبة الدالة analogReadMilliVolts() التي تعيد الجهد بالمللي فولت بعد المعايرة، وهي أدق من حسابك اليدوي. ثانياً، في شريحة ESP32 الكلاسيكية مجموعتان من القنوات التناظرية: ADC1 على المنافذ GPIO32 إلى GPIO39، وADC2 على منافذ أخرى مثل GPIO4 وGPIO25 وGPIO26. يستخدم مشغّل Wi-Fi المجموعة الثانية، فتفشل قراءاتها أثناء تشغيل الاتصال. القاعدة البسيطة لأي مشروع متصل: المستشعرات التناظرية على منافذ ADC1 فقط. ثالثاً، المنافذ من GPIO34 إلى GPIO39 للإدخال فقط، ولا تحتوي مقاومات سحب داخلية.
والآن الحماية. إذا كان لديك مخرج إشارة بجهد 5 فولت يتجه إلى ESP32، فأبسط حل مقسّم جهد (Voltage Divider) من مقاومتين: الأولى بين الإشارة والمنفذ، والثانية بين المنفذ والأرضي. الجهد الواصل يساوي جهد الإشارة مضروباً في نسبة المقاومة الثانية إلى مجموع المقاومتين. بمقاومة 1 كيلو أوم ثم 2 كيلو أوم يصبح الجهد 5 × 2 ÷ 3 ≈ 3.33 فولت، وهو مناسب. هذا الحل يصلح لإشارة تسير في اتجاه واحد، أما الخطوط ثنائية الاتجاه مثل I2C فتحتاج وحدة تحويل مستوى (Level Shifter). وفي الاتجاه المعاكس، يقرأ Uno إشارة 3.3 فولت غالباً على أنها HIGH، لكن الهامش ضيق، فلا تبنِ عليه مشروعاً حساساً.
سجل تشخيص متخيَّل لطالب نقل دائرة قياس ضوء من Uno إلى ESP32:
• القراءة ثابتة على 4095 مهما تغيّر الضوء: طرف المقسّم موصول بمخرج 5V بدل 3.3V. افصل فوراً وأعد التوصيل إلى 3V3.
• القراءات سليمة حتى يبدأ الاتصال بالشبكة ثم تفشل: المستشعر على GPIO4 من مجموعة ADC2. الحل نقله إلى GPIO34.
• القراءة لا تصل إلى 4095 رغم أن الجهد 3.3 فولت: هذه لاخطية طرف المدى. استخدم analogReadMilliVolts() واعتمد على الجهد لا على الرقم الخام.
Uno R3 يقرأ من 0 إلى 1023 لمدى 5 فولت، وESP32 من 0 إلى 4095 لمدى 3.3 فولت تقريباً.
منافذ ESP32 وRaspberry Pi لا تتحمل 5 فولت؛ استخدم مقسّم جهد أو محوّل مستوى.
في مشروع متصل ضع المستشعرات التناظرية على منافذ ADC1 (من GPIO32 إلى GPIO39).
analogReadMilliVolts تعطي جهداً معايراً أدق من الحساب اليدوي على ESP32.
مخطط التوصيل
5V signal ──[1kΩ]──┬──> ESP32 GPIO (≈3.3V)
|
[2kΩ]
|
GND
مختبر: رقم خام أم مللي فولت؟
في Wokwi صل مقاومة متغيرة (Potentiometer) بين 3V3 وGND ومنتصفها إلى GPIO34، واطبع analogRead وanalogReadMilliVolts معاً عند خمسة مواضع للمقبض.
مهمة عملية
احسب قراءة Uno وقراءة ESP32 النظرية لجهود 0.5 و2.0 و3.0 فولت، ثم صمّم مقسّم جهد لإشارة 5 فولت يعطي جهداً بين 3.0 و3.3 فولت واذكر قيمتي المقاومتين.
اختبر فهمك (1/3)
جهد 1.65 فولت على منفذ تناظري. ما القراءة التقريبية على Uno R3 ثم على ESP32 بالإعداد الافتراضي؟
اختبر فهمك (2/3)
قراءات مستشعر على GPIO4 في ESP32 الكلاسيكي صارت تفشل بعد تشغيل Wi-Fi. ما الحل؟
اختبر فهمك (3/3)
مخرج إشارة بجهد 5V تريد إدخاله إلى ESP32. أي مقسّم جهد يعطي نحو 3.3V؟
4. المستشعرات — DHT22 وحساس الحركة PIR والموجات فوق الصوتية
الهدف: توصّل ثلاثة مستشعرات شائعة توصيلاً صحيحاً، وتكتب كوداً يقرأها ويتعامل مع القراءات الفاشلة.
قبل توصيل أي مستشعر اسأل سؤالاً واحداً: ما نوع الإشارة التي يخرجها؟ الإجابة تحدد التوصيل والكود معاً. المستشعرات الثلاثة في هذا الدرس تمثل ثلاثة أنواع مختلفة: DHT22 يتكلم ببروتوكول رقمي خاص على سلك واحد، وحساس الحركة PIR يعطي إشارة رقمية بسيطة HIGH أو LOW، وحساس الموجات فوق الصوتية HC-SR04 يعطي نبضة يتناسب طولها الزمني مع المسافة. وكل الأمثلة هنا مكتوبة لـ ESP32، مع الإشارة إلى الفرق على Uno حيث يلزم.
DHT22 للحرارة والرطوبة: يعمل بجهد من 3.3 إلى 5 فولت تقريباً، فصِل طرف التغذية بمخرج 3V3 على ESP32 حتى يخرج إشارة بالجهد نفسه، والأرضي بـ GND، وطرف البيانات بمنفذ رقمي مثل GPIO4. يحتاج خط البيانات مقاومة سحب نحو 10 كيلو أوم إلى التغذية، ومعظم الوحدات الجاهزة المثبتة على لوحة صغيرة تحتويها مسبقاً. استخدام GPIO4 هنا لا يتعارض مع ما قلناه عن ADC2، لأن القراءة رقمية لا تمر بالمحوّل التناظري. القيد الأهم أن DHT22 بطيء: لا تقرأه أكثر من مرة كل ثانيتين تقريباً، وإلا حصلت على قراءات فاشلة أو مكررة. ثبّت من Library Manager مكتبة DHT sensor library من Adafruit، وستطلب معها مكتبة Adafruit Unified Sensor.
#include <DHT.h>
const int DHT_PIN = 4;
DHT dht(DHT_PIN, DHT22);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
delay(2000);
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("DHT read failed");
return;
}
Serial.print("T=");
Serial.print(t, 1);
Serial.print(" C H=");
Serial.print(h, 0);
Serial.println(" %");
}
لاحظ فحص isnan: حين تفشل القراءة تعيد المكتبة قيمة خاصة اسمها NaN، أي «ليست رقماً». إرسال هذه القيمة إلى الخادم يفسد المنحنى والمتوسطات، لذلك تتخطاها وتسجّل الفشل. فشل قراءة واحدة كل فترة أمر طبيعي، أما الفشل المستمر فيعني غالباً سلكاً مفكوكاً أو غياب مقاومة السحب.
حساس الحركة PIR: وحدة HC-SR501 الشائعة تكتشف تغيّر الأشعة تحت الحمراء الصادرة عن جسم دافئ يتحرك، ولا تكتشف شخصاً ساكناً تماماً. تُغذّى من 5 فولت لأن عليها منظم جهد خاصاً بها، ومخرجها نحو 3.3 فولت، فيمكن وصله بمنفذ ESP32 مباشرة. بعد التشغيل تحتاج فترة استقرار قد تقارب دقيقة، وفيها قد تطلق إنذارات كاذبة، فتجاهل قراءاتها في البداية. وعلى الوحدة مقاومتان متغيرتان: واحدة للحساسية وأخرى لمدة بقاء المخرج على HIGH. في الكود نهتم بلحظة بدء الحركة، أي الانتقال من LOW إلى HIGH، حتى لا نسجّل الحدث نفسه مئات المرات:
const int PIR_PIN = 27;
int lastState = LOW;
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
}
void loop() {
int s = digitalRead(PIR_PIN);
if (s == HIGH && lastState == LOW) {
Serial.println("Motion started");
}
lastState = s;
delay(50);
}
حساس المسافة HC-SR04: يُغذّى من 5 فولت. ترسل من ESP32 نبضة مدتها 10 ميكروثوانٍ على طرف Trig، فيطلق الحساس موجة صوتية ويرفع طرف Echo طوال زمن ذهابها وعودتها. طرف Echo يخرج 5 فولت، فيمر إلى ESP32 عبر مقسّم الجهد من الدرس السابق: 1 كيلو أوم ثم 2 كيلو أوم. وعلى Uno يوصل مباشرة. سرعة الصوت في هواء بحرارة الغرفة نحو 343 متراً في الثانية، أي 0.0343 سنتيمتر في الميكروثانية، ونقسم على 2 لأن الموجة ذهبت وعادت.
const int TRIG_PIN = 26;
const int ECHO_PIN = 25;
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
void loop() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000);
if (us == 0) {
Serial.println("No echo");
} else {
float cm = us * 0.0343 / 2.0;
Serial.println(cm, 1);
}
delay(200);
}
مثال محلول: قاس الحساس نبضة طولها 1166 ميكروثانية. المسافة = 1166 × 0.0343 ÷ 2 ≈ 20.0 سنتيمتراً. المعامل الثالث في pulseIn مهلة بالميكروثانية؛ إذا لم يعد صدى خلالها تعيد الدالة صفراً بدل أن يتجمد البرنامج. ويحدث ذلك حين يكون الهدف أبعد من مدى الحساس، أو مائلاً بزاوية تعكس الصوت بعيداً، أو مصنوعاً من قماش يمتص الصوت. في خزان مياه مثلاً ثبّت الحساس عمودياً فوق سطح الماء تماماً، واحسب المستوى بطرح المسافة المقيسة من ارتفاع الخزان.
وأخيراً، مكان المستشعر يغيّر القراءة أكثر مما تغيّرها دقته. DHT22 معلّق في الشمس المباشرة يقيس حرارة غلافه البلاستيكي لا حرارة الهواء، وإذا وُضع ملاصقاً لمنظم الجهد على اللوحة قرأ دفأه أيضاً. ضعه في علبة مظللة فيها فتحات تهوية، على ارتفاع النباتات نفسها، وبعيداً عن رشاشات الماء. وحساس PIR يتأثر بتيارات الهواء الساخن وأشعة الشمس المتحركة على الجدار، فوجّهه بعيداً عن النوافذ وفتحات التكييف.
DHT22 رقمي على سلك واحد، ولا يُقرأ أكثر من مرة كل ثانيتين تقريباً.
افحص isnan قبل إرسال أي قراءة من DHT22 إلى الخادم.
حساس PIR يكتشف الحركة لا الوجود، ويحتاج فترة استقرار بعد التشغيل.
طرف Echo في HC-SR04 يخرج 5 فولت ويحتاج مقسّم جهد قبل ESP32.
5. LEDs والمخرجات — حدود التيار وقيادة الأحمال بأمان
الهدف: تحسب مقاومة LED على 3.3V و5V، وتعرف حدود تيار المنافذ، وتشغّل حملاً أكبر عبر ترانزستور دون الاقتراب من كهرباء المنزل.
في مشروع استشعار متصل تكون المخرجات غالباً بسيطة: LED يخبرك بحالة الجهاز (يحاول الاتصال، متصل، خطأ)، أو صفارة تنبيه، أو مروحة تهوية صغيرة. والقاعدة التي تحكم كل هذا أن منفذ المتحكم مصدر إشارة لا مصدر طاقة. يستطيع أن يقول «شغّل» لكنه لا يستطيع أن يحمل تيار محرك أو ملف ريليه. تجاهل هذه القاعدة يتلف المنافذ بصمت، وقد تعمل الدائرة أياماً قبل أن يظهر العطل.
حساب مقاومة LED بقانون أوم على ESP32: المنفذ يعطي 3.3 فولت، وLED الأحمر يأخذ نحو 2 فولت (جهد الانحياز الأمامي، تجده في ورقة بيانات القطعة).
الجهد الباقي على المقاومة = 3.3 − 2 = 1.3 فولت.
لمؤشر حالة يكفي تيار نحو 5 مللي أمبير، فالمقاومة = 1.3 ÷ 0.005 = 260 أوم، فتختار أقرب قيمة شائعة أعلى منها: 330 أوم.
التيار الفعلي = 1.3 ÷ 330 ≈ 3.9 مللي أمبير، وهو كافٍ ليُرى المؤشر بوضوح.
على Uno بجهد 5 فولت والمقاومة نفسها: (5 − 2) ÷ 330 ≈ 9 مللي أمبير.
أما LED الأزرق أو الأبيض فجهده نحو 3 فولت، فلا يبقى على المقاومة من 3.3 فولت إلا 0.3 فولت تقريباً، ويضيء خافتاً وبشكل يتفاوت من قطعة لأخرى. في لوحات 3.3 فولت اختر الأحمر أو الأصفر أو الأخضر للمؤشرات، أو استخدم مقاومة أصغر بعد حساب دقيق. ولكل LED مقاومته الخاصة؛ مشاركة مقاومة واحدة بين عدة LEDs متوازية تجعل أحدها يسحب تياراً أكبر من الباقي.
وكم يتحمل المنفذ؟ ورقة بيانات ATmega328P في Uno تحدد 40 مللي أمبير حداً مطلقاً للمنفذ، وهو رقم لا يُقترب منه، فالعرف العملي 20 مللي أمبير أو أقل، مع حد إجمالي للشريحة كلها. وقدرة منافذ ESP32 أقل من ذلك، فاجعل حمل كل منفذ بضعة مللي أمبيرات تكفي لمؤشر، وارجع إلى ورقة بيانات Espressif إن احتجت رقماً دقيقاً. أي شيء يحتاج أكثر، كمروحة أو صفارة كبيرة أو شريط إضاءة، يُشغَّل عبر ترانزستور.
قبل وبعد — مروحة 5 فولت صغيرة لتهوية علبة المشروع:
قبل: المروحة موصولة بين منفذ GPIO والأرضي. النتيجة دوران ضعيف أو لا دوران، ومنفذ مُجهَد قد يتلف.
بعد: المنفذ يقود ترانزستور NPN عبر مقاومة 1 كيلو أوم على القاعدة. باعث الترانزستور إلى GND، والمروحة بين مصدر 5 فولت وجامع الترانزستور. ثنائي حماية (Flyback Diode) بالتوازي مع المروحة، طرفه السالب (الكاثود) نحو موجب 5 فولت، يمتص النبضة العكسية حين تتوقف المروحة. وأرضي المصدر موصول بأرضي اللوحة.
المنفذ الآن يحمل أقل من بضعة مللي أمبيرات، والمروحة تأخذ تيارها من المصدر مباشرة.
ومؤشر الحالة نفسه يعلّمك عادة برمجية تحتاجها في الدروس القادمة. الدالة delay() توقف البرنامج كله، فإذا كتبت وميضاً بها توقفت معه مهام الشبكة أثناء الانتظار. البديل مقارنة الوقت الحالي من millis() بآخر لحظة تغيّر فيها LED. والطرح now - lastToggle يبقى صحيحاً حتى حين يعود عداد millis() إلى الصفر بعد نحو 49 يوماً من التشغيل المتواصل، ما دام المتغيران من النوع unsigned long:
const int STATUS_LED = 2;
unsigned long lastToggle = 0;
bool ledOn = false;
unsigned long blinkMs = 200; // 200 يحاول الاتصال، 1000 متصل
void setup() {
pinMode(STATUS_LED, OUTPUT);
}
void loop() {
unsigned long now = millis();
if (now - lastToggle >= blinkMs) {
lastToggle = now;
ledOn = !ledOn;
digitalWrite(STATUS_LED, ledOn ? HIGH : LOW);
}
}
قاعدة لا استثناء فيها في هذا المسار: لا كهرباء المنزل. الشبكة في مصر ومعظم الدول العربية 220 فولت متردد، والخطأ فيها صعق أو حريق. وحدات الريليه التي تُباع للتحكم في أجهزة البيت تبدو آمنة لأنها صغيرة، لكن أطرافها المكشوفة تحمل جهد الشبكة كاملاً. التزم بأحمال 5 فولت أو 12 فولت من محولات معتمدة، واترك أي شيء يتصل بالمقبس لفني مختص.
منفذ المتحكم مصدر إشارة لا مصدر طاقة؛ الأحمال الكبيرة عبر ترانزستور.
على 3.3 فولت مع LED أحمر، مقاومة 330 أوم تعطي نحو 4 مللي أمبير.
الأحمال الحثّية كالمراوح والريليهات تحتاج ثنائي حماية وأرضياً مشتركاً.
millis بدل delay حتى لا يتوقف البرنامج كله أثناء الانتظار.
لا كهرباء المنزل 220 فولت في مشاريع هذا المسار.
مخطط التوصيل
GPIO1kΩ←B (NPN)
5VFan←C
Diode
E←GND (مشترك مع اللوحة)
مختبر: مؤشر حالة بثلاثة أنماط
اكتب برنامج millis يغيّر نمط الوميض كل عشر ثوانٍ: سريع (يحاول الاتصال)، ثم بطيء (متصل)، ثم ومضتان وتوقف (خطأ).
مهمة عملية
احسب مقاومة LED أخضر جهده 2.1 فولت على ESP32 لتيار 4 مللي أمبير، واختر أقرب قيمة شائعة، ثم احسب التيار الفعلي بها.
اختبر فهمك (1/3)
LED أحمر جهده نحو 2V على منفذ ESP32 بجهد 3.3V مع مقاومة 330 أوم. كم التيار تقريباً؟
اختبر فهمك (2/3)
تريد تشغيل مروحة 5V صغيرة من ESP32. ما التوصيل الصحيح؟
اختبر فهمك (3/3)
لماذا يُفضّل millis على delay لوميض LED الحالة في جهاز متصل؟
الهدف: تستخدم الرسائل التسلسلية لتشخيص برنامجك مرحلة بمرحلة، وتوصل ESP32 بشبكة Wi-Fi بمهلة زمنية وإعادة اتصال.
اللوحة بلا شاشة، والاتصال التسلسلي (Serial) عبر كابل USB هو نافذتك الوحيدة إلى ما يحدث داخلها. في مشروع متصل تزداد أهمية هذه النافذة لأن مراحل الفشل أكثر: الإقلاع، ثم المستشعر، ثم الشبكة، ثم الخادم. القاعدة أن تطبع سطراً عند بداية كل مرحلة ونهايتها، حتى تعرف من آخر سطر ظهر أين توقف البرنامج بالضبط. تبدأ بـ Serial.begin(115200) في setup()، وتختار السرعة نفسها أسفل نافذة Serial Monitor. السرعة 115200 شائعة مع ESP32، وإذا اختلف الرقمان ظهرت رموز غير مفهومة بدل النص. وقد ترى عند الإقلاع أسطراً غريبة قبل رسائلك؛ هذه رسائل محمّل الإقلاع المدمج في الشريحة وليست عطلاً.
اجعل رسائلك قابلة للبحث بوضع وسم في أول كل سطر، مثل [BOOT] و[SENSOR] و[WIFI] و[MQTT]. حين يطول السجل تبحث عن الوسم بدل قراءة مئات الأسطر، وتستطيع لاحقاً نسخ السجل إلى ملف ومقارنته بسجل يوم سليم. وأضف القيم المهمة إلى الرسالة نفسها، كقوة الإشارة أو رمز الخطأ، فسطر يقول «فشل» فقط لا يساعد أحداً.
السطر WiFi.mode(WIFI_STA) يجعل اللوحة عميلاً (Station) يتصل براوتر موجود، بدل أن تنشئ شبكتها الخاصة. أهم ما في هذا الكود المهلة الزمنية. أمثلة كثيرة على الإنترنت تكتب حلقة انتظار بلا نهاية، فإذا تعطل الراوتر تجمّد الجهاز إلى الأبد ولم يعد يقيس شيئاً. مع المهلة تعيد الدالة false بعد خمس عشرة ثانية، فيقرر البرنامج ما يفعل: يحاول لاحقاً، أو يحفظ القراءة، أو ينام لتوفير البطارية كما سنفعل في المشروع النهائي. وفي loop() يراقب البرنامج الاتصال ويطلب إعادة الاتصال إذا انقطع.
سجل تشخيص متخيَّل من تركيب جهاز في مخزن:
• النقاط تظهر بلا نهاية ثم timeout، والشبكة تعمل على الهواتف: الراوتر يبث على نطاق 5 جيجاهرتز فقط، بينما ESP32 الكلاسيكي وأغلب أفراد عائلته يدعمون 2.4 جيجاهرتز. فُعّل نطاق 2.4 في الراوتر فاتصل الجهاز.
• اتصل الجهاز لكن RSSI يساوي −86: الإشارة ضعيفة جداً، والانقطاعات متوقعة. نُقل الجهاز إلى رف أقرب إلى الباب فصارت القيمة نحو −65. القيمة أقرب إلى الصفر تعني إشارة أقوى.
• إعادة تشغيل متكررة مع رسالة Brownout detector was triggered لحظة بدء Wi-Fi: الإرسال اللاسلكي يسحب ذروات تيار لا يتحملها كابل رديء أو منفذ USB ضعيف. استُبدل الكابل بآخر قصير وجيد فاختفت المشكلة.
• لا شيء يظهر في النافذة إطلاقاً: المنفذ المختار في Tools غير منفذ اللوحة، أو Serial.begin غير موجود في setup().
وإلى جانب المراقبة اليدوية في loop()، توفّر المكتبة الدالة WiFi.setAutoReconnect(true) التي تطلب من الشريحة إعادة الاتصال تلقائياً عند الانقطاع. المراقبة اليدوية تبقى مفيدة لأنها تطبع سطراً في السجل وقت الانقطاع، فتعرف كم مرة انقطعت الشبكة خلال يوم كامل. ولا تطبع كلمة مرور الشبكة في Serial مهما كان الإغراء أثناء التشخيص؛ السجلات تُنسخ وتُرسل في المنتديات وتبقى في ملفات قديمة، واسم الشبكة مع رمز الحالة يكفيان لمعرفة سبب الفشل.
استخدم Serial Plotter من قائمة Tools لمراقبة قيمة تتغير مع الزمن، كقوة الإشارة أثناء تحريك اللوحة في المكان بحثاً عن أفضل موقع. اطبع WiFi.RSSI() وحدها في سطر كل نصف ثانية، وستحصل على منحنى يدلك على الزاوية التي تفقد فيها الإشارة.
وتنبيه أخير يمهد لدرس الأمان: كلمة مرور الشبكة مكتوبة هنا داخل الكود للتوضيح فقط. إذا رفعت هذا الملف إلى GitHub أو أرسلته لزميل، فقد نشرت كلمة مرور شبكتك. في الدرس الثامن ستنقل الأسرار إلى ملف منفصل لا يُشارك.
اطبع سطراً بوسم واضح عند بداية كل مرحلة ونهايتها: الإقلاع والمستشعر والشبكة والخادم.
سرعة Serial Monitor يجب أن تطابق الرقم في Serial.begin.
ضع مهلة زمنية لانتظار Wi-Fi حتى لا يتجمد الجهاز إذا تعطل الراوتر.
أغلب لوحات ESP32 تتصل بنطاق 2.4 جيجاهرتز، ورسالة Brownout تشير غالباً إلى ضعف التغذية.
شغّل كود الاتصال واطبع RSSI كل نصف ثانية في Serial Plotter، ثم حرّك اللوحة بين ثلاث غرف وسجّل أفضل وأسوأ قيمة.
مهمة عملية
شغّل كود الاتصال مرتين: مرة بكلمة المرور الصحيحة ومرة بكلمة خاطئة، والتقط من Serial Monitor السطور التي تثبت أن المهلة الزمنية أنهت الانتظار في الحالة الثانية.
اختبر فهمك (1/3)
تظهر النقاط بلا نهاية رغم أن الشبكة تعمل على هواتف البيت. ما سبب شائع يستحق الفحص أولاً؟
اختبر فهمك (2/3)
لماذا تضع مهلة زمنية في حلقة انتظار اتصال Wi-Fi؟
اختبر فهمك (3/3)
تعيد اللوحة التشغيل برسالة Brownout detector was triggered لحظة بدء Wi-Fi. ما الأرجح؟
الهدف: ترسل قراءة من ESP32 بطلب HTTP POST وبرسالة MQTT عبر PubSubClient، وتعرف متى تختار كلاً منهما، مع إبقاء قرار الحماية على الجهاز.
بعد أن اتصل الجهاز بالشبكة، بقي أن يوصل قراءاته إلى مكان ما. هناك نمطان شائعان. الأول طلب HTTP: يرسل الجهاز طلب POST إلى عنوان على خادم ويب، فيرد الخادم برمز حالة مثل 200 أو 201 إذا نجح الاستلام. هذا النمط مألوف ويعمل مع أي خادم ويب تقريباً، لكنه يسير في اتجاه واحد: الجهاز يسأل والخادم يجيب، ولا يستطيع الخادم أن يبادر بإرسال أمر إلى الجهاز بسهولة. والثاني MQTT: بروتوكول خفيف قائم على النشر والاشتراك (Publish/Subscribe). يتصل الجهاز بوسيط ويبقى الاتصال مفتوحاً، فينشر رسائله على مواضيع، ويشترك في مواضيع أخرى ليستقبل الأوامر.
الدالة http.POST تعيد رمز الحالة الذي أرسله الخادم، أو رقماً سالباً إذا فشل الاتصال نفسه قبل الوصول إلى الخادم، كأن يكون العنوان خاطئاً أو الخادم مطفأ. اطبع الرمز دائماً؛ الفرق بين 404 و500 ورقم سالب يحدد إن كان الخطأ في المسار أو في الخادم أو في الشبكة. العنوان هنا محلي داخل شبكة البيت، وhttp بلا تشفير، وهذا مقبول للتعلم داخل الشبكة المنزلية فقط.
استدعاء mqtt.loop() في كل دورة يحافظ على الاتصال حياً ويعالج الرسائل الواردة، ولهذا تجنبنا delay() الطويلة في الدرس الخامس. والمعامل الأخير true في publish يجعل الرسالة «محفوظة» (Retained): يحتفظ الوسيط بآخر قيمة على الموضوع ويسلّمها فوراً لأي مشترك جديد، فلا تنتظر لوحة العرض دقيقة كاملة لترى أول رقم. واعرف حدود مكتبة PubSubClient كما يذكرها توثيقها: تنشر بجودة الخدمة صفر فقط (QoS 0)، والحجم الأقصى للرسالة مع ترويستها 256 بايت افتراضياً، ويمكن رفعه بالدالة setBufferSize. رسالة JSON طويلة تفشل بصمت إذا تجاوزت هذا الحد.
صمّم أسماء المواضيع مثل مسار ملفات: الموقع ثم المنطقة ثم نوع القياس، مثل greenhouse/zone1/climate وgreenhouse/zone1/fan. لا تضع مسافات، ولا تبدأ بشرطة مائلة. يستطيع المشترك استخدام الرمز + بدل مستوى واحد، والرمز # بدل كل ما بعده، فبرنامج يشترك في greenhouse/# يرى كل رسائل الصوبة. للتجربة من حاسوبك، الأمر mosquitto_sub -h 192.168.1.50 -t "greenhouse/#" -v يطبع كل رسالة مع اسم موضوعها.
وحين يفشل الاتصال بالوسيط، فالرقم الذي تعيده mqtt.state() يختصر التشخيص. القيمة −2 تعني أن الاتصال الشبكي نفسه فشل: عنوان الوسيط خاطئ، أو الوسيط مطفأ، أو جدار حماية على الحاسوب يمنع المنفذ 1883. والقيمتان 4 و5 تعنيان أن الوسيط وصل إليه الطلب لكنه رفض الدخول بسبب اسم المستخدم أو كلمة المرور أو الصلاحيات. وهناك عطل محيّر يستحق أن تعرفه: إذا اتصل جهازان بمعرّف العميل نفسه، يقطع الوسيط الاتصال القديم كلما دخل الجديد، فيظهر الجهازان متصلين ومنقطعين بالتناوب. اجعل معرّف كل جهاز فريداً، كأن تضيف إليه رقم المنطقة.
القرار يبقى محلياً: المروحة تعمل حسب قراءة الجهاز نفسه، وMQTT ينقل الحالة للعرض فقط. لاحظ الهامش بين العتبتين (Hysteresis) حتى لا ترتعش المروحة حول 30 درجة:
if (t > 30.0) fanOn = true;
else if (t < 28.0) fanOn = false;
digitalWrite(FAN_PIN, fanOn ? HIGH : LOW);
mqtt.publish("greenhouse/zone1/fan", fanOn ? "on" : "off", true);
متى تختار أيهما؟ اختر HTTP إذا كان لديك خادم ويب قائم بواجهة برمجية، والجهاز يرسل قراءة كل فترة طويلة ولا يحتاج أن يستقبل أوامر. واختر MQTT إذا تعددت الأجهزة، أو تعدد المستفيدون من القراءة نفسها، كلوحة العرض وبرنامج التنبيه وسجل البيانات معاً، أو إذا احتجت إرسال أوامر إلى الجهاز. وفي الحالتين، المنفذ 1883 وعناوين http تنقل البيانات نصاً مكشوفاً؛ عبر الإنترنت تحتاج تشفيراً ومصادقة، وهما موضوع الدرس التالي.
HTTP طلب ورد في اتجاه واحد، وMQTT نشر واشتراك عبر وسيط باتصال دائم.
استدعِ mqtt.loop في كل دورة، وتجنب delay الطويلة في الجهاز المتصل.
PubSubClient تنشر بجودة خدمة صفر، وحدّها الافتراضي للرسالة 256 بايت.
الرسالة المحفوظة (Retained) تعطي المشترك الجديد آخر قيمة فوراً.
قرار الحماية على الجهاز، والشبكة تنقل الحالة للعرض.
8. الأمان والسلامة — كلمات المرور والتحديثات وعدم كشف الجهاز
الهدف: تطبّق أهم ضوابط أمان إنترنت الأشياء على مشروعك: لا كلمات مرور افتراضية، أسرار خارج الكود، اتصال مشفّر، تحديثات محمية، ولا منافذ مفتوحة على الإنترنت.
جهاز إنترنت الأشياء حاسوب صغير يعمل طوال اليوم بلا شاشة، ولا يلاحظ أحد إن تغيّر سلوكه. هذا ما جعل الأجهزة المتصلة هدفاً مفضلاً: في عام 2016 جمعت شبكة Mirai عدداً هائلاً من الكاميرات والراوترات المنزلية بتجربة أسماء مستخدمين وكلمات مرور افتراضية معروفة، ثم استخدمتها في هجمات حجب خدمة واسعة. ولهذا يأتي في رأس قائمة OWASP لمخاطر إنترنت الأشياء (إصدار 2018) بند كلمات المرور الضعيفة أو السهلة التخمين أو المكتوبة داخل البرنامج.
حادثة متخيَّلة للتوضيح: محل صغير لصيانة الهواتف في طنطا ركّب ESP32 يعرض حرارة المخزن على صفحة ويب، ثم فتح منفذاً في الراوتر (Port Forwarding) ليراها صاحبه من خارج المحل. نسخ المبرمج صفحة التحديث عبر الشبكة من مثال جاهز، وبقيت كلمة الدخول كما في المثال. بعد أسابيع صار الجهاز يعيد التشغيل باستمرار ويتصل بعناوين غريبة. لم يكن في الجهاز شيء ثمين، لكنه أصبح نقطة دخول إلى شبكة المحل التي تحمل حاسوب الفواتير. ومثال التحديث عبر الويب في توثيق Arduino-ESP32 نفسه يستخدم فعلاً اسم admin وكلمة admin للتوضيح؛ المثال للتعلم، وتغيير الكلمة مسؤوليتك.
قائمة تحقق قبل تركيب أي جهاز، مرتبطة ببنود OWASP:
1) كلمات المرور: لا افتراضيات، وكلمة مختلفة لكل جهاز ولكل خدمة. في وسيط Mosquitto عطّل الاتصال المجهول بالإعداد allow_anonymous false وأنشئ المستخدمين بالأداة mosquitto_passwd.
2) الأسرار خارج الكود: كلمات المرور في ملف منفصل مضاف إلى .gitignore حتى لا يُرفع إلى GitHub.
3) خدمات الشبكة: لا تفتح منافذ في الراوتر إلى الجهاز. اجعل الجهاز هو من يبادر بالاتصال الصادر إلى الوسيط، وللوصول من الخارج استخدم شبكة افتراضية خاصة (VPN) إلى شبكة البيت، أو وسيطاً مشفّراً بمصادقة.
4) نقل البيانات: عبر الإنترنت استخدم TLS، أي MQTT على المنفذ 8883 أو HTTPS.
5) التحديث: حدّث حزمة ESP32 والمكتبات دورياً، واحمِ التحديث عبر الشبكة بكلمة مرور، مثل ArduinoOTA.setPassword(...).
6) الخصوصية: اجمع ما تحتاجه فقط. سجل حساس الحركة يكشف متى يكون البيت فارغاً.
7) العزل: ضع أجهزة إنترنت الأشياء على شبكة ضيوف أو شبكة منفصلة عن حواسيب العمل.
هكذا يبدو ملف الأسرار، ثم استخدامه في الاتصال بالوسيط بمصادقة:
// secrets.h — مضاف إلى .gitignore ولا يُرفع إلى Git
#pragma once
const char* WIFI_SSID = "GreenhouseNet";
const char* WIFI_PASS = "long-unique-wifi-pass";
const char* MQTT_HOST = "192.168.1.50";
const char* MQTT_USER = "gh-zone1";
const char* MQTT_PASS = "another-unique-pass";
التشفير وحده لا يكفي إذا لم تتحقق من هوية الطرف الآخر. على ESP32 تستبدل WiFiClient بالصنف WiFiClientSecure للاتصال المشفّر، ثم تعطيه شهادة الجهة المصدِرة لشهادة الخادم بالدالة setCACert. الدالة setInsecure تختصر هذه الخطوة، فيبقى الاتصال مشفّراً لكن الجهاز يقبل أي خادم يدّعي أنه الوسيط، ومن يستطيع اعتراض الشبكة يستطيع انتحال الخادم. استخدمها في تجربة سريعة، ولا تتركها في جهاز يُركَّب فعلاً.
والأمان المادي جزء من الصورة. جهاز في صوبة أو مخزن يستطيع أي زائر أن يفكه ويوصله بحاسوب ويقرأ ذاكرته، بما فيها كلمة مرور Wi-Fi. للمنتجات التجارية توفّر Espressif آليات مثل الإقلاع الآمن (Secure Boot) وتشفير ذاكرة الفلاش، وهي موضوع متقدم يُدرس قبل أي إنتاج. أما في مشروعك فالحد الأدنى علبة مغلقة، وشبكة منفصلة، وكلمة مرور خاصة بهذا الجهاز يسهل تغييرها إن فُقد.
وتبقى السلامة الكهربائية من الدرس الخامس شرطاً قبل الأمان الرقمي: لا كهرباء المنزل، وافصل التغذية قبل تعديل أي سلك، وتابع حرارة القطع عند أول تشغيل. جهاز آمن رقمياً لكنه يسخن حتى يذيب علبته ليس جهازاً آمناً.
كلمات المرور الافتراضية والمكتوبة في الكود هي أول خطر في قائمة OWASP لإنترنت الأشياء.
لا تفتح منافذ في الراوتر إلى الجهاز؛ الجهاز يبادر بالاتصال الصادر فقط.
TLS مع التحقق من الشهادة، وsetInsecure للتجارب السريعة لا للتركيب.
احمِ التحديث عبر الشبكة بكلمة مرور، وحدّث الحزمة والمكتبات دورياً.
مخطط
ESP32TLS 8883 + user/pass←Broker
✗ لا Port Forwarding إلى الجهاز
✓ VPN إلى شبكة البيت للوصول من الخارج
مختبر: تدقيق مشروع منشور
افتح مشروع ESP32 منشوراً من دروسك السابقة أو من Wokwi، وابحث فيه عن كلمات مرور مكتوبة، وعناوين http غير مشفّرة، واتصال بوسيط بلا مصادقة. سجّل كل ملاحظة وإصلاحها.
مهمة عملية
انقل كل الأسرار في مشروعك إلى secrets.h، وأضفه إلى .gitignore، ثم شغّل git status وتأكد أن الملف لا يظهر في قائمة الملفات المرشحة للرفع.
اختبر فهمك (1/3)
صاحب المحل يريد رؤية حرارة المخزن من خارج المحل. ما الخيار الأكثر أماناً؟
اختبر فهمك (2/3)
أي ممارسة تعالج البند الأول في قائمة OWASP لمخاطر أجهزة IoT؟
اختبر فهمك (3/3)
استخدمت WiFiClientSecure مع setInsecure للاتصال بالوسيط على المنفذ 8883. ما المشكلة؟
9. Wokwi ولوحة البيانات — محاكاة جهاز متصل وعرض قراءاته
الهدف: تبني جهاز ESP32 متصلاً في محاكي Wokwi، وترسل قراءاته إلى وسيط MQTT، وتختار لوحة البيانات المناسبة لعرضها.
محاكي Wokwi يعمل في المتصفح ويحاكي ESP32 بما فيه الاتصال اللاسلكي. يوفّر المحاكي نقطة وصول افتراضية اسمها Wokwi-GUEST، مفتوحة بلا كلمة مرور، ومنها يصل الجهاز المحاكى إلى الإنترنت، فيستطيع الاتصال بوسيط MQTT عام. ويذكر توثيق Wokwi أن تمرير رقم القناة 6 إلى WiFi.begin يتخطى مرحلة البحث عن الشبكات ويختصر بضع ثوانٍ من كل تشغيل. أما الاتصال في الاتجاه المعاكس، من متصفحك إلى خادم يعمل داخل المحاكاة، فيحتاج أداة إضافية اسمها Wokwi IoT Gateway. هذا يكفي لبناء نسخة كاملة من نظام الصوبة قبل شراء أي قطعة.
خطوات بناء الجهاز المحاكى:
1) أنشئ مشروعاً جديداً واختر ESP32.
2) أضف مستشعر DHT22، وصل VCC بـ 3V3، وGND بـ GND، وطرف البيانات بـ GPIO4.
3) من تبويب Library Manager في المحرر أضف مكتبتي DHT sensor library وPubSubClient.
4) الصق الكود التالي، وغيّر المقطع demo-7f3k إلى نص عشوائي خاص بك.
5) شغّل المحاكاة، ثم انقر على المستشعر وغيّر الحرارة والرطوبة من نافذته، وراقب الرسائل في Serial.
الوسيط test.mosquitto.org خدمة اختبار عامة يديرها مشروع Eclipse Mosquitto: أي شخص يعرف اسم الموضوع يستطيع قراءة رسائلك أو النشر عليه. لذلك تستخدم معرّفاً عشوائياً في الموضوع ومعرّف العميل، وترسل بيانات تجريبية فقط، ولا تعتمد عليه في جهاز حقيقي. لرؤية الرسائل من حاسوبك اكتب mosquitto_sub -h test.mosquitto.org -t "mesue-learn/demo-7f3k/#" -v، أو استخدم برنامجاً رسومياً لاستكشاف MQTT يعرض شجرة المواضيع وآخر قيمة في كل منها، وهو أنسب أداة لتشخيص الرسائل قبل بناء أي لوحة.
كيف تختار لوحة البيانات؟ هذه مقارنة عملية لأشهر الخيارات. Node-RED أداة برمجة مرئية تعمل على حاسوبك أو على Raspberry Pi: تسحب عقدة mqtt in، وتربطها بعقدة رسم بياني من إضافة لوحات العرض، فتحصل على صفحة تعرض المنحنى في دقائق، والبيانات تبقى داخل بيتك. وإذا كان في البيت نظام Home Assistant، فتكامل MQTT فيه يضيف الحساس إلى واجهته مباشرة. وحين تحتاج تاريخاً طويلاً بالأشهر مع رسوم قابلة للتكبير والمقارنة، فالخيار المعتاد قاعدة بيانات زمنية مع Grafana، وهو أثقل في الإعداد. وهناك خدمات مستضافة مثل Arduino Cloud تعطيك لوحة جاهزة مقابل ربط الجهاز بحساب خارجي. وأخيراً يستطيع ESP32 نفسه تقديم صفحة ويب صغيرة، وهو حل بلا خادم إضافي، لكنه بلا تاريخ، ولا يُرى إلا داخل الشبكة، ويزيد ما يجب حمايته على الجهاز.
القاعدة المختصرة للمبتدئ: برنامج استكشاف MQTT للتشخيص، ثم Node-RED لأول لوحة، ثم قاعدة بيانات زمنية مع Grafana حين يصبح السؤال «ماذا حدث في الشهر الماضي؟». وفي كل الحالات، لوحة العرض مشترك في الموضوع لا أكثر؛ إذا لم تظهر البيانات فارجع إلى ترتيب التشخيص من الدرس الأول، وابدأ من Serial.
إذا لم تصل الرسائل، فتحقق بالترتيب: هل طبع Serial سطر الاتصال بالشبكة؟ هل يطبع رسائل JSON كل عشر ثوانٍ؟ إذا كانت الرسائل تظهر في Serial ولا تصل إلى الطرفية، فقارن اسم الموضوع في الكود بالاسم في أمر الاشتراك حرفاً حرفاً، فالمواضيع حساسة لحالة الأحرف. وإذا لم تظهر رسائل JSON أصلاً، فغالباً لم ينجح الاتصال بالوسيط؛ اطبع mqtt.state() بعد كل محاولة لترى السبب.
وما لا تريك إياه المحاكاة: انقطاع Wi-Fi الحقيقي في آخر الصوبة، وهبوط الجهد عند بدء الإرسال، وضجيج القراءات، وتكثف الرطوبة على اللوحة صباحاً. المحاكاة تثبت أن المنطق والرسائل سليمة، ثم يأتي اختبار ميداني ليوم كامل على العتاد. واحفظ مع مشروعك ملف diagram.json الذي يخزن فيه Wokwi التوصيلات، وجدول المنافذ في ملف README، حتى يطابق التوثيق ما بنيته فعلاً.
Wokwi يوفّر نقطة الوصول Wokwi-GUEST بلا كلمة مرور لمحاكاة ESP32 متصل.
test.mosquitto.org وسيط عام: معرّف عشوائي وبيانات تجريبية فقط.
Node-RED لأول لوحة، وقاعدة بيانات زمنية مع Grafana للتاريخ الطويل.
10. المشروع النهائي — مراقب صوبة متصل يعمل بالبطارية
الهدف: تبني مراقب صوبة يقيس الحرارة والرطوبة، وينشرها عبر MQTT بمصادقة، وينام نوماً عميقاً لتوفير البطارية، ويجتاز خطة اختبار وقائمة أمان.
نعود إلى صوبة الطماطم المتخيَّلة من الدرس الأول لنبني نظامها كاملاً. المواصفات: لوحة ESP32 مع DHT22 داخل علبة مظللة جيدة التهوية. كل عشر دقائق يستيقظ الجهاز، فيقرأ المستشعر، ويتصل بشبكة Wi-Fi، وينشر القراءة على وسيط Mosquitto يعمل على Raspberry Pi في البيت باسم مستخدم وكلمة مرور، ثم يعود إلى النوم. إذا تجاوزت الحرارة 32 درجة ينشر رسالة على موضوع التنبيه، وبرنامج التنبيه على الخادم يتولى إبلاغ الهاتف. والجهاز يعمل ببطارية ليثيوم قابلة للشحن، لأن الصوبة بعيدة عن أقرب مقبس.
لماذا النوم العميق (Deep Sleep)؟ لأن Wi-Fi أثناء الإرسال يسحب تياراً تصل ذروته إلى مئات المللي أمبيرات وفق ورقة بيانات Espressif، بينما تنزل الشريحة في النوم العميق إلى ميكرو أمبيرات. في هذا الوضع يُطفأ المعالج والذاكرة الرئيسية والاتصال اللاسلكي، ولا يبقى إلا مؤقت صغير يوقظ الشريحة. والنتيجة المهمة برمجياً: بعد الاستيقاظ يبدأ البرنامج من أول setup() كأنه تشغيل جديد، وتضيع كل المتغيرات، إلا ما عرّفته بالكلمة RTC_DATA_ATTR فيُحفظ في ذاكرة صغيرة تبقى مغذاة أثناء النوم. لذلك يكتب البرنامج كله في setup()، وتبقى loop() فارغة.
لاحظ أن كل طريق في البرنامج ينتهي بالنوم: قراءة فاشلة، أو شبكة غائبة، أو وسيط رافض. لا توجد حلقة انتظار بلا نهاية تستنزف البطارية في ليلة انقطع فيها الراوتر. والعداد bootCount المحفوظ في ذاكرة RTC يُرسل مع كل رسالة، فإذا رأيت على الخادم قفزة في العداد بلا رسائل بينها، عرفت أن الجهاز استيقظ وفشل في الإرسال تلك المرات.
ميزانية البطارية بأرقام مفترضة للتوضيح، قِس أرقام لوحتك بنفسك:
الجهاز مستيقظ 5 ثوانٍ في كل دورة بمتوسط 120 مللي أمبير، ونائم 595 ثانية.
إذا كان تيار النوم 0.15 مللي أمبير (وحدة ESP32 مجردة مع منظم جهد منخفض الاستهلاك): المتوسط = (5 × 120 + 595 × 0.15) ÷ 600 ≈ 1.15 مللي أمبير. بطارية سعتها 2000 مللي أمبير ساعة تكفي نظرياً نحو 1740 ساعة، أي قرابة 72 يوماً.
وإذا كان تيار النوم 10 مللي أمبير (لوحة تطوير فيها شريحة USB وLED طاقة يعملان دائماً): المتوسط ≈ 10.9 مللي أمبير، فتكفي البطارية نحو 183 ساعة، أي قرابة ثمانية أيام.
الفرق كله في تيار النوم لا في الإرسال. وخطط لنصف الرقم النظري، لأن منظم الجهد والبرودة والتفريغ الذاتي تأكل جزءاً من السعة.
وسلامة البطارية جزء من التصميم. خلية الليثيوم أيون تصل عند الشحن الكامل إلى 4.2 فولت، وهذا أعلى مما يتحمله طرف 3.3 فولت في ESP32، فلا توصلها به مباشرة؛ استخدم منظم جهد مناسباً. واشحنها بوحدة شحن مخصصة لخلايا الليثيوم فيها دائرة حماية، ومن محول USB معتمد، لا من أي توصيل بكهرباء المنزل. لا تتركها تشحن على سطح قابل للاشتعال، وتخلّص من أي خلية منتفخة في مكان جمع المخلفات الإلكترونية إن وُجد.
خطة الاختبار قبل التسليم، ولكل حالة نتيجة متوقعة تسجّل بجانبها نجح أو فشل:
• على الطاولة عبر USB: تظهر رسالة كل دورة في mosquitto_sub، والعداد boot يزيد واحداً كل مرة. اختصر زمن النوم إلى دقيقة أثناء الاختبار ثم أعده.
• إطفاء الراوتر: ينام الجهاز بعد 15 ثانية ولا يتجمد، ثم يرسل طبيعياً عند عودة الشبكة.
• كلمة مرور خاطئة للوسيط في secrets.h: يفشل الاتصال، وينام الجهاز، ويرفض الوسيط الدخول في سجله.
• تدفئة المستشعر بيدك فوق 32 درجة: تصل رسالة على موضوع التنبيه.
• قياس تيار النوم بجهاز متعدد القياس موصول على التوالي مع البطارية، ومقارنته بالرقم الذي بنيت عليه الميزانية.
التسليم: كود منظم مع ملف secrets.h غير مرفوع، وREADME فيه جدول المنافذ، وأسماء المواضيع، وميزانية البطارية بأرقامك المقيسة، ونتائج خطة الاختبار، وقائمة الأمان من الدرس الثامن مع علامة أمام كل بند. هذا الملف هو ما يجعل المشروع قابلاً للإصلاح بعد شهور، وقابلاً للعرض في ملف أعمالك.
بعد النوم العميق يبدأ البرنامج من setup، ولا يبقى إلا ما عُرّف بـ RTC_DATA_ATTR.
كل مسار في البرنامج ينتهي بالنوم، حتى عند فشل القراءة أو الشبكة.
عمر البطارية يحدده تيار النوم غالباً؛ لوحات التطوير تستهلك أكثر بكثير من الوحدات المجردة.
خلية الليثيوم تحتاج منظم جهد ووحدة شحن بحماية، ولا توصل مباشرة بطرف 3.3 فولت.
الهيكل
wake → read DHT22 → Wi-Fi (≤15s) → MQTT publish → sleep 10 min
↑ |
└─────────────────────────────────────────────────────┘
مختبر: يوم كامل من القراءات
شغّل الجهاز (حقيقياً أو في Wokwi بزمن نوم دقيقة) لساعات، واشترك في المواضيع، وتحقق من أن العداد boot متتابع بلا فجوات غير مفسرة.
مهمة عملية
سلّم المشروع مع README يحتوي جدول المنافذ وأسماء المواضيع وميزانية بطارية محسوبة بأرقامك، ونتائج حالات الاختبار الخمس مع نجح أو فشل لكل حالة.
اختبر فهمك (1/3)
بعد الاستيقاظ من النوم العميق، من أين يبدأ برنامج ESP32؟
اختبر فهمك (2/3)
بالأرقام المفترضة في الدرس، لماذا تعمل لوحة التطوير أياماً بدل أسابيع؟
اختبر فهمك (3/3)
أطفأت الراوتر أثناء الاختبار. ما السلوك الصحيح للجهاز؟