13.3.1.5. אירועים

העמודים עד כה קוראים אל תוך המצלמה: מעלים סקריפט, קוראים פריים, כותבים לערוץ. כל אחת מהפעולות האלה יזומה על ידי המארח – המארח שואל, המצלמה מגיבה. הפרוטוקול גם רץ בכיוון ההפוך. המצלמה יכולה לדחוף אירועים למארח מבלי שתישאל, וה-SDK של המארח מספק כל אחד מהם לפונקציית callback שהיישום יכול לעקוף.

זהו הכלי הנכון בכל פעם שהיישום רוצה להגיב למשהו שהמצלמה הבחינה בו לפני שהוא שואל. ללא אירועים, הדרך היחידה לגלות היא להמשיך לקרוא ל-read_status() בלולאה.

13.3.1.5.1. פונקציית ה-callback של ברירת המחדל

Camera כבר נרשמת לאירועים פנימית. _handle_event() היא פונקציית ה-callback שהשכבה התעבורתית מריצה בכל פעם שמנת אירוע מגיעה. ברירת המחדל מטפלת בשלושה אירועי מערכת:

  • CHANNEL_REGISTERED – ערוץ חדש הופיע במצלמה לאחר שהמארח התחבר. המסגרת מרעננת את מטמון הערוצים שלה כך שחיפוש ה-has_channel() הבא ימצא אותו.

  • CHANNEL_UNREGISTERED – ערוץ נעלם.

  • SOFT_REBOOT – המצלמה אתחלה מחדש מעצמה (watchdog, תקלה קשה, machine.reset() מכוונת).

היא גם עוקבת אחר אירוע מוכנות-הפריים של ערוץ ה-stream עבור נתיב ההזרמה ואחר התחלת / עצירת הסקריפט של ערוץ ה-stdin עבור באפור stdout. ברירת המחדל events=True של הבנאי משאירה את כל זה פעיל; יישום שאינו רוצה דבר מכך יכול להעביר events=False ל-Camera ותת-מערכת האירועים נשארת שקטה.

13.3.1.5.2. יצירת תת-מחלקה כדי להגיב

כדי לטפל באירועים ייחודיים ליישום שהמצלמה מעלה, צור תת-מחלקה של Camera ועקוף את _handle_event(). קרא תחילה להורה כדי לשמר את התנהגות ברירת המחדל, ואז נתב את האירועים שהיישום מעוניין בהם:

from openmv import Camera

class MyCamera(Camera):
    def _handle_event(self, channel_id, event):
        super()._handle_event(channel_id, event)

        name = self.channels_by_id.get(
            channel_id, {}).get('name')
        if name == 'motion' and event == 1:
            self.on_motion()

    def on_motion(self):
        print("motion detected")

החתימה היא (channel_id, event). channel_id הוא 0 עבור אירועי מערכת ואחרת המזהה המספרי של הערוץ שהעלה אותו; event הוא מספר שלם שהסקריפט בצד המצלמה בחר. ה-enum של EventType נותן שמות לשלושת אירועי המערכת; אירועי ערוץ משתמשים בכל ערך שה-backend בצד המצלמה מגדיר.

אירועי ערוץ חוזרים מפותחים לפי מזהה מספרי, לא לפי שם. מילון ה-channels_by_id במטמון הוא מה שהעקיפה לעיל משתמשת בו כדי לחפש את השם; channels_by_name הוא הראי שלו, מפותח בכיוון ההפוך.

13.3.1.5.3. החצי בצד המצלמה

הסקריפט בצד המצלמה מעלה אירוע על ידי קריאה ל-send_event() על המזהה המוחזר מ-protocol.register()

import protocol

class MotionChannel:
    def size(self):
        return 0

    def read(self, offset, size):
        return b''

    def poll(self):
        return False

ch = protocol.register(
    name='motion', backend=MotionChannel())

while True:
    if detect_motion():
        ch.send_event(1)

מספר האירוע הוא מספר שלם שהיישום בוחר. כל ערך שהעקיפה של המארח מוכנה לטפל בו תקף; שכבת הפרוטוקול מתייחסת אליו כמטען אטום. כברירת מחדל הקריאה יורה ושוכחת; העבר wait_ack=True כדי לחסום עד שהמארח מאשר, כאשר הידיעה שהאירוע הגיע חשובה יותר מההשהיה של הנסיעה הלוך ושוב.

ערוץ שרק יורה אירועים ואינו נושא נתונים קריאים הוא תבנית תקפה – size מחזירה 0 ו-read מחזירה בייטים ריקים. ספריית הפרוטוקול עדיין צריכה ששתי המתודות יהיו נוכחות כדי לסמן את הערוץ כקריא; הסקריפט בצד המצלמה פשוט אף פעם לא שם בו נתונים.

13.3.1.5.4. הפעלת נתיב הקבלה בזמן סרק

אירועים מגיעים על אותו חיבור כמו כל השאר, כך שכל קריאת מארח ששולחת או מקבלת בייטים נותנת לשכבה התעבורתית הזדמנות לעבד אירועים ממתינים תוך כדי. לולאת תשאול שכבר קוראת ל-read_status() או ל-read_frame() פעם אחת בכל מחזור אינה זקוקה לדבר נוסף.

עבור תוכניות שעוברות דקות ללא קלט/פלט אחר, poll_events() מריצה את נתיב הקבלה פעם אחת מבלי לשלוח פקודה. היא חוזרת ברגע שהחוצץ הנכנס ריק, כך שלולאה הדוקה סביבה – או טיימר קצר בלולאת אירועים של ממשק גרפי – היא מה ששומר על פונקציות הטיפול תגובתיות.

13.3.1.5.5. לולאה שלמה

מקצה לקצה, התבנית היא: הסקריפט בצד המצלמה רושם ערוץ וקורא ל-send_event() כשמשהו קורה; תת-המחלקה בצד המארח עוקפת את _handle_event() ומנתבת. לולאת מארח שאינה עושה דבר מלבד לטפל באירועים נראית כך:

with MyCamera('/dev/ttyACM0') as cam:
    cam.stop()
    cam.exec(open('motion_cam.py').read())

    while True:
        cam.poll_events()

המצלמה לוכדת, מחליטה, ומעלה אירועים. המארח יושב בתוך poll_events() עד שאחד מגיע, ואז on_motion רץ. שום קריאת read_status() אינה רצה כשדבר לא קרה, ושום פריים אינו נמשך דרך USB כשלמצלמה אין על מה לדווח.