برمجة المتحكّمات بلغة مايكروبايثون · الفصل 21 — مشروع متكاملפרויקט מסכם · תכנות מיקרו-בקרים בשפת MicroPython⌂📚🏠
الفصل 21

مشروع متكامل

פרויקט מסכם – Final Project
3 ساعات تعليمية

كل فصل حتى الآن عالج أمرا واحدا. المشروع يجمعها، وتظهر فيه مشكلة جديدة: كيف نبني شيئا كبيرا دون أن نضيع فيه؟ الجواب في هذا الفصل بسيط: نكتب ما نريد قبل أن نبرمج، ونرتّب الملفات، ونضيف أمرا واحدا في كل مرة، ونسجّل ما فعلناه.

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

21.1 ماذا يطلب المنهج؟ – מחוון הפרויקט (Project Requirements)

النوعالمطلوب
مشروع فرديمتحكّم يتصل بنظام آخر عبر Bluetooth أو WiFi أو NFC أو RFID أو ZigBee.
مشروع زوجيمتحكّمان على الأقل، وأحدهما متصل بالإنترنت.

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

سؤال 21.1: طالبان يبنيان لوحتين تتبادلان الرسائل عبر UART بسلك فقط. هل هذا مشروع زوجي مقبول؟ وما أصغر تعديل يصلحه؟

21.2 التخطيط: الفكرة والمخطّط والمواصفات – רעיון, תרשים מלבנים, מפרט (Planning)

الفكرة، في جملة: مراقب دفيئة (חממה) صغيرة: لوحة في الدفيئة تقرأ الحرارة والرطوبة والضوء وتشغّل مروحة، ولوحة في المكتب تعرض الحالة وتقرّر متى تعمل المروحة.

DHT22LDRمروحةمرحّلعقدة الحسّاساتESP32 أو Pico Wالوسيطفي الإنترنتعقدة العرضESP32 أو Pico WLCDI2Cdatadata, statefan/setfan/set
المخطّط الصندوقي: كل لوحة عقدة (צומת)، ولا تعرف إحداهما عنوان الأخرى. فالوسيط بينهما كما في الفصل 16.

المواصفات (מפרט): كل سطر يجب أن يقبل الفحص بنعم أو لا. فجملة «المشروع سريع» ليست مواصفة، وجملة «تُرسل قراءة كل 10 ثوان» مواصفة:

المواصفةكيف نفحصها
1تُرسل عقدة الحسّاسات قراءة كل 10 ثوان.نقرأ الموضوع من الحاسوب ونقيس الزمن.
2تعرض الشاشة الحرارة والرطوبة والضوء وحالة المروحة.نقارن الشاشة بالقراءة.
3تعمل المروحة فوق 30 درجة، وتتوقف تحت 27.نسخّن الحسّاس بقرب اليد ونراقب.
4بعد صمت 30 ثانية تكتب الشاشة NO DATA.نفصل تغذية عقدة الحسّاسات.
5تعود الرسائل وحدها بعد انقطاع الشبكة، دون أن نضغط شيئا.نطفئ الموجّه دقيقة ثم نشغّله.
تحذير: العتبتان في المواصفة 3 هما التخلّف من القسم 6.8، والوسيط هنا عام ومفتوح: كل من يعرف اسم الموضوع يقرؤه ويكتب إليه. يكفي هذا لمروحة في تدريب، ولا يكفي لشيء قد يؤذي. ولمشروع حقيقي نستعمل وسيطا خاصا بنا، كما في الفصل 16.
سؤال 21.2: حوّلوا الجملة «الجهاز يستجيب بسرعة للضغط على الزر» إلى مواصفة تقبل الفحص.

21.3 شجرة الملفات – עץ הקבצים (Project Tree)

نقرّر مكان كل شيء قبل أن نكتب. القاعدة: ما تشترك فيه اللوحتان في shared، وما يخص لوحة في مجلدها، والملف main.py قصير ويستدعي غيره. وعلى اللوحة نفسها ننسخ ملفات shared وملفات مجلد العقدة معا إلى المجلد الرئيسي، لأن البرنامج يبحث عن مكتباته هناك.

greenhouse/README.mdما هو المشروع وكيف نشغّلهdocs/logbook.mdسجلّ العملai_log.mdسجلّ الذكاء الاصطناعيproject_book.mdكتاب المشروعshared/ينسخ إلى اللوحتينboard.pyمن الفصل 6wifi.pyمن الفصل 14secrets.pyلا يدخل أبدا في المشاركةgh_link.pyالموضوعات والاتصالnode_sensor/main.pygh_sensor_main.pygh_sense.pyالحسّاساتnode_display/main.pygh_display_main.pygh_view.pyالشاشةi2c_lcd.pyمن الفصل 9tools/gh_selftest.pyفحص العتاد
شجرة ملفات مشروع الدفيئة. في حزمة تنزيل الكتاب يحمل الملفان main.py الاسمين المكتوبين بجانبهما، فنعيد تسميتهما عند النسخ إلى اللوحة.

الملف المشترك gh_link.py: أسماء الموضوعات في مكان واحد، فلا يمكن أن تختلف بين اللوحتين. والاتصال وإعادة الاتصال من الفصل 16.

# gh_link.py - the greenhouse MQTT link, the same on both nodes.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import json
import time
from umqtt.simple import MQTTClient
import wifi

BROKER = "broker.hivemq.com"
DATA = b"school/gh1/data"
FAN_SET = b"school/gh1/fan/set"
FAN_STATE = b"school/gh1/fan/state"


class Link:
    def __init__(self, client_id, topics, handler):
        self.client = MQTTClient(client_id, BROKER)
        self.topics = topics
        self.handler = handler
        self.client.set_callback(self._deliver)

    def _deliver(self, topic, msg):
        try:
            obj = json.loads(msg)
        except ValueError:
            print("bad message on", topic)
            return
        self.handler(topic, obj)

    def connect(self, tries=4):
        wifi.connect()
        wait = 1
        for _ in range(tries):
            try:
                self.client.connect()
                for topic in self.topics:
                    self.client.subscribe(topic)
                return True
            except OSError:
                print("no broker, waiting", wait, "s")
                time.sleep(wait)
                wait *= 2
        return False

    def send(self, topic, obj):
        msg = json.dumps(obj).encode()
        try:
            self.client.publish(topic, msg)
        except OSError:
            if self.connect():
                self.client.publish(topic, msg)

    def poll(self):
        try:
            self.client.check_msg()
        except OSError:
            self.connect()
سؤال 21.3: لماذا كتبنا أسماء الموضوعات في gh_link.py وليس في كل main.py؟

21.4 البناء خطوة خطوة – בנייה הדרגתית (Building Step by Step)

لا نكتب كل شيء ثم نجرّب. نضيف آلية واحدة، ونجرّبها وحدها، ولا نتقدّم حتى تعمل. وعندما يظهر عطل نعرف أين نبحث: في آخر ما أضفناه.

الخطوةالآلية الجديدةالمواصفةنعرف أنها تعمل عندما
1الحسّاسات، دون شبكة—تظهر قراءات معقولة في نافذة الطباعة.
2النشر إلى الوسيط1تظهر القراءات على الحاسوب.
3الشاشة تعرض ما يصل2تتغيّر الشاشة مع الحرارة.
4القرار وأمر المروحة3تعمل المروحة وتعود حالتها إلى الشاشة.
5الصمت والانقطاع4، 5نقطع التغذية أو الشبكة، فيتصرّف الجهاز كما كتبنا.
ملاحظة: في هذا الكتاب تجري كل لوحة وحدها في المُحاكاة، وكل ما يصل من اللوحة الأخرى هو رسائل جاهزة في العالم المُحاكى. لذلك لا تثبت هذه المخرجات أن اللوحتين تعملان معا. هذا تفحصونه أنتم على اللوحتين الحقيقيتين.

الخطوة 1: الحسّاسات:

نجمع القراءات في قاموس واحد، وعند فشل الحسّاس نعيد None ولا يتوقف البرنامج.
# gh_sense.py - the sensors of the greenhouse, as one reading.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import dht
from machine import Pin
import board


class Sensors:
    def __init__(self):
        self.air = dht.DHT22(Pin(board.DHT))
        self.ldr = board.analog_in(board.LDR)

    def read(self):
        try:
            self.air.measure()
        except OSError:
            return None
        return {"t": self.air.temperature(),
                "h": self.air.humidity(),
                "light": self.ldr.read_u16() * 100 // 65535}
الخطوة 1، بلا شبكة.
import time
from gh_sense import Sensors

sensors = Sensors()
for _ in range(3):
    print(sensors.read())
    time.sleep(10)
ESP32 + Pico W · MicroPython 1.29 · simulated board
{'t': 24, 'light': 50, 'h': 60}
{'t': 24, 'light': 50, 'h': 60}
{'t': 24, 'light': 60, 'h': 60}

الخطوتان 2 و3: النشر والعرض:

الشاشة في ملف خاص بها: إذا بدّلناها لا نلمس غيره.
# gh_view.py - what the greenhouse display shows, on a 16x2 LCD.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import board
from i2c_lcd import LCD


class View:
    def __init__(self):
        self.lcd = LCD(board.i2c())

    def data(self, d, fan):
        self.lcd.line(0, "T %.1fC  H %.0f%%" % (d["t"], d["h"]))
        self.lcd.line(1, "L %d%%  Fan %s"
                      % (d["light"], "ON" if fan else "off"))

    def no_data(self):
        self.lcd.line(0, "NO DATA")
        self.lcd.line(1, "check sensor")
عقدة العرض في صورتها الأولى. الرسالتان من العالم المُحاكى.
import time
from gh_view import View
from gh_link import Link, DATA

view = View()


def on_message(topic, obj):
    print("got", obj)
    view.data(obj, False)


link = Link("gh1-display", (DATA,), on_message)
link.connect()
for _ in range(25):
    link.poll()
    time.sleep(1)
ESP32 + Pico W · MicroPython 1.29 · simulated board
got {'h': 60, 't': 24.5, 'light': 50}
got {'h': 58, 't': 25.0, 'light': 61}
[LCD]
|T 25.0C  H 58%  |
|L 61%  Fan off  |
...

الخطوات 4 و5: العقدتان في صورتهما النهائية:

main.py في عقدة الحسّاسات: تنشر كل 10 ثوان، وتنفّذ الأمر، وتعيد الحالة. أمر المروحة من العالم المُحاكى.
import time
import machine
from machine import Pin, WDT
import board
from gh_sense import Sensors
from gh_link import Link, DATA, FAN_SET, FAN_STATE

SEND_EVERY_MS = 10000

fan = Pin(board.LED, Pin.OUT)
sensors = Sensors()


def on_message(topic, cmd):
    fan.value(1 if cmd["on"] else 0)
    link.send(FAN_STATE, {"on": fan.value()})
    print("fan:", fan.value())


link = Link("gh1-sensor", (FAN_SET,), on_message)
if not link.connect():
    time.sleep(30)
    machine.reset()

guard = WDT(timeout=8000)
next_send = time.ticks_ms()
while True:
    guard.feed()
    link.poll()
    if time.ticks_diff(time.ticks_ms(), next_send) >= 0:
        next_send = time.ticks_add(next_send, SEND_EVERY_MS)
        data = sensors.read()
        if data:
            link.send(DATA, data)
        else:
            print("sensor failed")
    time.sleep_ms(100)
ESP32 + Pico W · MicroPython 1.29 · simulated board
fan: 1
[mqtt_pub]
school/gh1/data {"t": 31, "light": 60, "h": 60}
school/gh1/data {"t": 31, "light": 60, "h": 60}
school/gh1/fan/state {"on": 1}
school/gh1/data {"t": 31, "light": 60, "h": 60}
...
ملاحظة: مهلة WDT(timeout=8000) اخترناها لتناسب اللوحتين: أكبر مهلة في RP2040 حسب التوثيق هي 8388 ms. وننشئ مؤقّت الحراسة بعد الاتصال الأول، لأنه يأخذ أكثر من ذلك، ولا يمكن إيقافه بعد تشغيله. فإذا تعطّل البرنامج أو طال انتظار الوسيط أعاد المؤقّت تشغيل اللوحة، وهذا مقبول في جهاز يعمل وحده.

والآن main.py في عقدة العرض. انظروا فيه إلى want وhave: المتغيّر الأول هو ما نريده، والثاني هو ما تقوله اللوحة الأخرى. وما داما مختلفين نكرّر الأمر مع كل قراءة. فلو ضاعت رسالة تصحّحت بعد 10 ثوان. هذه حلقة مغلقة عبر الشبكة، بالفكرة نفسها التي في الفصل 20: نقيس ونقارن ونصحّح. وفي المخرجات أدناه تصمت الرسائل بعد t = 25، كأن الحسّاس انقطع.

import time
from gh_view import View
from gh_link import Link, DATA, FAN_SET, FAN_STATE

FAN_ON = 30.0
FAN_OFF = 27.0
STALE_MS = 30000

view = View()
want = False
have = False
seen = time.ticks_ms()
stale = False


def on_data(d):
    global want, seen, stale
    seen = time.ticks_ms()
    stale = False
    if d["t"] > FAN_ON:
        want = True
    elif d["t"] < FAN_OFF:
        want = False
    if want != have:
        print("T", d["t"], "ask fan", want)
        link.send(FAN_SET, {"on": want})
    view.data(d, have)


def on_message(topic, obj):
    global have
    if topic == DATA:
        on_data(obj)
    elif topic == FAN_STATE:
        have = bool(obj["on"])
        print("fan is", have)


link = Link("gh1-display", (DATA, FAN_STATE), on_message)
link.connect()
while True:
    link.poll()
    late = time.ticks_diff(time.ticks_ms(), seen)
    if late > STALE_MS and not stale:
        stale = True
        print("silent for", late // 1000, "s")
        view.no_data()
    time.sleep_ms(100)
ESP32 + Pico W · MicroPython 1.29 · simulated board
T 31.0 ask fan True
fan is True
T 26.0 ask fan False
fan is False
silent for 30 s
[mqtt_pub]
school/gh1/fan/set {"on": true}
school/gh1/fan/set {"on": false}
[LCD]
|NO DATA         |
|check sensor    |
...
سؤال 21.4: تقرأ لوحة العرض حرارة 28 درجة، والمروحة تعمل. هل ترسل أمرا؟ ولماذا؟

21.5 البحث عن الأعطال: عتاد أم برنامج؟ – איתור תקלות: חומרה מול תוכנה (Troubleshooting)

أول سؤال هو أين العطل، وليس كيف نصلحه. نقسم السلسلة إلى نصفين، ونفحص في الوسط، فنعرف أي النصفين سليم. وفي مشروعنا الوسيط هو الوسط الطبيعي: أي القراءات تصل إليه؟

على الحاسوب: نرى كل ما تنشره اللوحتان، دون أن نلمسهما. الرمز الشامل من الفصل 16.
mosquitto_sub -h broker.hivemq.com -t "school/gh1/#" -v

إن ظهرت القراءات فعقدة الحسّاسات سليمة، والعطل في جهة العرض. وإن لم تظهر فالعكس. ولفحص العتاد كله نكتب برنامجا صغيرا لا يعرف شيئا عن المشروع:

فحص ذاتي، في عالم مُحاكى ليس فيه شاشة ولا شبكة، والمقاومة الضوئية مفصولة.
import board
import dht
from machine import Pin
import wifi


def lcd():
    return 0x27 in board.i2c().scan()


def air():
    s = dht.DHT22(Pin(board.DHT))
    s.measure()
    return s.humidity() > 0


def light():
    return 1000 < board.analog_in(board.LDR).read_u16() < 64500


def net():
    return wifi.connect(5).isconnected()


for name, test in (("lcd", lcd), ("air", air),
                   ("light", light), ("net", net)):
    try:
        ok = test()
    except OSError:
        ok = False
    print("%-6s %s" % (name, "PASS" if ok else "FAIL"))
ESP32 + Pico W · MicroPython 1.29 · simulated board
lcd    FAIL
air    PASS
light  FAIL
net    FAIL

وعندما يكون كل شيء موصولا يعطي البرنامج: lcd PASS, air PASS, light PASS, net PASS. وأي سطر FAIL يعني عطلا في العتاد أو في التوصيل، فلا نفتّش في main.py قبل أن نصلحه.

وعندما نشكّ في العتاد نأخذ أداة القياس. المقياس المتعدّد (רב-מודד) يعطي قيمة واحدة ثابتة، ويناسب ما لا يتغيّر بسرعة. أما راسم الذبذبات (משקף תנודות) فيرسم الجهد مع الزمن، ويناسب ما يتغيّر:

العَرَضنفحص أولاالأداة
اللوحة لا تعمل أو تعيد التشغيلجهد 3V3 على الطرف، وجودة التغذية عند تشغيل الحملمقياس متعدّد، وراسم ذبذبات للهبوط اللحظي
حسّاس لا يتغيّرالجهد على الطرف نفسه وليس على الحسّاس، واستمرار الأسلاكمقياس متعدّد
scan() فارغالتغذية، وتبديل SDA وSCL، ومقاومات الرفع: الخطّان مرتفعان في السكونمقياس متعدّد، ثم راسم ذبذبات للنبضات
مروحة لا تدور والطرف يتغيّرالجهد على القاعدة، وتغذية المحرّك، والأرضي المشتركمقياس متعدّد
PWM لا يعمل كما نتوقّعالشكل والتردّد ودورة العملراسم ذبذبات. والمقياس يعرض المتوسّط فقط
الرسالة لا تصل والعتاد سليماسم الموضوع، ومعرّف العميل، وcheck_msg() في الحلقةالبرنامج والطباعة
مثال 21.1
نقيس بمقياس متعدّد طرف PWM بدورة عمل 70% من 3.3 V. ماذا يعرض؟ ولماذا لا نرى الجهد 3.3 V؟
الحل:
V = D · VCC
V = 0.7 · 3.3
V = 2.31 V

يعرض المتوسّط تقريبا، لأن المقياس أبطأ من النبضات. وهذا لا يعني أن الطرف عند هذا الجهد: راسم الذبذبات يبيّن أنه يقفز بين 0 و3.3 V.

V ≈ 2.31 V
خطأ شائع: نغيّر شيئا واحدا في كل تجربة، ونكتبه في السجلّ. إذا غيّرنا ثلاثة أشياء معا وعمل الجهاز، فلا نعرف ما الذي أصلحه، وقد يعود العطل.
سؤال 21.5: الشاشة تكتب NO DATA، والأمر mosquitto_sub يعرض قراءات جديدة. أين العطل؟

21.6 استعمال الذكاء الاصطناعي: واجب التوثيق – שימוש ב-AI: חובת תיעוד (Using AI)

يُسمح باستعمال أدوات الذكاء الاصطناعي في المشروع، لكن المنهج يُلزمنا (الفصل 8 منه) بأن نوثّق ما كُتب بمساعدتها. ولا يكفي أن نقول في النهاية «استعملناها»: نسجّل ذلك عند حدوثه، في docs/ai_log.md.

نموذج لسجلّ واحد. مثال للتوضيح، والأسماء والتواريخ غير حقيقية.
## 2026-11-14 - gh_link.py, function connect()
Tool and date: (name of the tool), 2026-11-14
Asked: "retry MQTT connect with growing waits in MicroPython"
Took: the loop with wait *= 2
Changed: removed a call that does not exist in umqtt.simple
Checked: ran with the broker off, read every line, can explain it
سؤال 21.6: طلبت طالبة من أداة كتابة المكتبة gh_view.py كما هي دون تعديل، وعملت من أول مرة. ماذا تسجّل؟

21.7 سجلّ العمل وكتاب المشروع – יומן עבודה וספר פרויקט (Logbook and Project Book)

سجلّ العمل (יומן עבודה) نكتبه في كل جلسة، ببضعة أسطر. فبعد شهر لا نتذكّر لماذا غيّرنا تلك المقاومة، والسجلّ يتذكّر. وهو أيضا المادة الخام لكتاب المشروع.

نموذج لجلسة واحدة. مثال للتوضيح، والأسماء غير حقيقية.
## 2026-11-12 (2 hours) - Sara, Omar
Goal: stage 3, the display shows a real reading.
Did: wired the LCD, wrote gh_view.py.
Problem: LCD lit, no text. scan() returned [].
Cause: SDA and SCL swapped. Found with scan(), fixed the wires.
Next: stage 4, the fan command.

كتاب المشروع (ספר פרויקט) نكتبه لقارئ لا يعرف شيئا عن مشروعنا. وهذا ترتيب مقترح لأجزائه:

الجزءما فيهمن أين
الفكرة والهدفالمشكلة وما يفعله الجهاز21.2
المخطّط والمواصفاتالمخطّط الصندوقي وجدول المواصفات21.2
العتادالرسوم وقائمة القطع والأطرافالدوائر التي بنيناها
البرنامجشجرة الملفات، ودور كل ملف، والموضوعات21.3
الفحصجدول المواصفات ونتيجة كل سطر21.4
الأعطال وما تعلّمناهأهمّ ما وقع وكيف حللناهسجلّ العمل
استعمال الذكاء الاصطناعيملخّص السجلّai_log.md
سؤال 21.7: كتب طالب في جدول الفحص: «المواصفة 4 — تعمل». ما الذي ينقصه؟

أهمّ أفكار الفصل:

أوامر الفصل:

الأمرعمله
Link(id, topics, handler)اتصال عقدة، من المكتبة gh_link.py.
link.send(topic, obj)نشر قاموس بصيغة JSON، مع إعادة الاتصال عند الحاجة.
link.poll()فحص الرسائل الواردة دون انتظار.
WDT(timeout=8000)مؤقّت الحراسة، بمهلة تناسب اللوحتين.
mosquitto_sub -t "a/#" -vقراءة كل ما يُنشر تحت موضوع من الحاسوب.

قاموس المصطلحات – מילון מונחים:

بالعربيةبالعبريةEnglish
المواصفاتמפרטSpecification
العقدةצומתNode
شجرة الملفاتעץ קבציםFile Tree
المقياس المتعدّدרב-מודדMultimeter
راسم الذبذباتמשקף תנודותOscilloscope
سجلّ العملיומן עבודהLogbook
كتاب المشروعספר פרויקטProject Book
الدفيئةחממהGreenhouse
الإجابات النهائية لأسئلة الفصل
21.1 لا، فلا توجد لوحة متصلة بالإنترنت. يكفي أن تنشر إحداهما قراءاتها إلى وسيط في الإنترنت عبر WiFi.
21.2 مثلا: «تتغيّر حالة الشاشة خلال ثانيتين من الضغط على الزر»، ونفحصها بساعة توقيت.
21.3 حتى يكون لكل اسم مكان واحد. لو اختلف حرف واحد بين اللوحتين لما وصلت الرسالة، ولما ظهر أي خطأ.
21.4 لا. القراءة بين 27 و30، فلا يتغيّر want، وهو يساوي have.
21.5 في عقدة العرض أو في اتصالها، فعقدة الحسّاسات والوسيط سليمان. ننظر في اشتراكها واتصالها بالشبكة.
21.6 الملف والأداة والتاريخ والطلب، وأنها أخذت الكود كله دون تغيير، وكيف فحصته. فعدم التعديل لا يلغي واجب التوثيق.
21.7 كيف فحصها وما النتيجة: فصلنا التغذية في ساعة كذا، وبعد كم ثانية ظهر NO DATA.

أسئلة للتمرين:

المستوى الأول — أساسي
1.
أي الجمل مواصفة:
(أ) الجهاز جميل.
(ب) يعمل المصباح خلال ثانية من حلول الظلام.
(ج) يستهلك طاقة قليلة.
الإجابات: (ب) فقط. يمكن قياسها والحكم عليها بنعم أو لا.
2.
في أي مجلد نضع ملفا يحتاج إليه الاثنان؟ وفي أي مجلد ملف الشاشة؟
الإجابات: الأول في shared، والثاني في node_display.
3.
الأداة أعطتنا دالة تعمل، وغيّرنا اسمها فقط. هل نسجّل ذلك؟
الإجابات: نعم، لأن الكود كُتب بمساعدتها، ونذكر التعديل.
المستوى الثاني — تطبيق
4.
أضيفوا إلى قراءة عقدة الحسّاسات حقل "up" فيه عدد الثواني منذ التشغيل. أي ملف نغيّر؟ ولماذا يبقى gh_view.py كما هو؟
الإجابات: نغيّر gh_sense.py فقط، بإضافة time.ticks_ms() // 1000. أما الشاشة فتقرأ الحقول التي تعرفها، ولا تتأثر بحقل جديد.
5.
نريد أن تتوقف المروحة تحت 25 درجة وليس 27. أي سطر نغيّر؟ وماذا يحدث لتموّج الحرارة وعدد مرات التبديل؟
الإجابات: FAN_OFF في gh_display_main.py. يكبر نطاق التخلّف، فيزيد التموّج ويقل التبديل.
6.
مؤقّت الحراسة بمهلة 8 s. كم يطول connect() إذا فشل كله؟ وماذا يحدث إذا انقطع الوسيط أثناء العمل؟
الإجابات: الانتظار 1 + 2 + 4 + 8 = 15 s وأكثر. وهذا أطول من المهلة، فتعيد اللوحة تشغيلها، ثم تحاول من جديد عند البداية. ولأن ذلك مقبول هنا تركناه، لكنه يجب أن يظهر في كتاب المشروع.
المستوى الثالث — تعمّق
7.
تتغيّر حرارة الدفيئة ببطء، والقراءة كل 10 ثوان.
(أ) هل نحتاج إلى قراءة أسرع حسب الفصل 20؟
(ب) ما الذي يمنعنا من قراءة كل ثانية في الحسّاس DHT22؟
الإجابات: (أ) لا. فثابت زمن كبير لا يحتاج إلى عيّنة سريعة. (ب) يحتاج الحسّاس إلى فترة بين القراءات، نحو ثانيتين حسب ورقة بياناته.
8.
(أ) كيف تعرف لوحة العرض أن عقدة الحسّاسات انقطعت قبل مرور 30 ثانية؟
(ب) أي آلية من الفصل 16 تناسب؟
الإجابات: (أ) لا تعرف إلا من الصمت، إذا لم يخبرها الوسيط. (ب) set_last_will: يرسل الوسيط رسالة نيابة عن اللوحة حين ينقطع اتصالها، وليس فورا، بل بعد انتهاء مهلة keepalive.
9.
صمّموا مشروعا فرديا من مكوّنات هذا الفصل: لوحة واحدة تكفي. ماذا نحذف؟ وهل يبقى MQTT ضروريا؟
الإجابات: نحذف العقدة الثانية، ونجمع الشاشة والحسّاسات والقرار في لوحة واحدة. ويبقى شرط الاتصال بنظام آخر، مثل WiFi إلى وسيط يقرأ منه هاتف. فإن حذفناه لم يبق المشروع مقبولا.