12.7. פונקציות callback של ערוצים

אובייקט ה-backend המועבר אל protocol.register() הוא מחלקת Python. ספריית הפרוטוקול אינה שואלת את המחלקה אילו מתודות היא ממשת; היא בוחנת את המופע ומחברת את אלו שהיא מוצאת. אינטרוספקציה זו היא מה שמקנה לממשק ה-backend את גמישותו: ה-backend השימושי הקטן ביותר מורכב משתי מתודות, המורכב ביותר משתים-עשרה, והאפליקציה מצטרפת לכל יכולת בנפרד, מתודה אחת בכל פעם.

12.7.1. כללי האינטרוספקציה

כאשר protocol.register() רץ, הספרייה עוברת על רשימה קבועה של שמות הניתנים לקריאה ומקשרת כל אחד שהיא מוצאת על מופע ה-backend:

  • הוספת read למחלקה מדליקה את CHANNEL_FLAG_READ. קריאה מהמארח אל channel_read() מגיעה אל ה-backend רק אם דגל זה מוגדר.

  • הוספת write מדליקה את CHANNEL_FLAG_WRITE, ומאפשרת את channel_write().

  • הוספת lock ו-unlock מדליקה את CHANNEL_FLAG_LOCK, ומאפשרת למארח לנעול את הערוץ לצורך קריאה אטומית מרובת מנות (packets).

  • הוספת poll מאפשרת למארח לשאול ”האם משהו מוכן?“ בזול, בלי לכפות קריאה מלאה.

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

12.7.2. ערוץ חיישן לקריאה בלבד

ערוץ חיישן המפרסם קריאה טרייה בכל פעם שהמארח מבקש, ומסרב לכתיבות מהמארח, עושה שימוש בארבע מפונקציות ה-callback:

import protocol
import struct

class TempChannel:
    def __init__(self, read_sensor):
        self._read_sensor = read_sensor
        self._buf = b''
        self._fresh = False

    def poll(self):
        # Tell the host whether a reading is waiting.
        return self._fresh

    def size(self):
        # Sample fresh data on every host-side size query.
        value = self._read_sensor()
        self._buf = struct.pack('<f', value)
        self._fresh = True
        return len(self._buf)

    def read(self, offset, size):
        end = offset + size
        if end >= len(self._buf):
            self._fresh = False
        return self._buf[offset:end]

protocol.register(name='temp', backend=TempChannel(read_temperature))

מעבר על מה שכל מתודה עושה:

  • poll מחזירה את דגל הטריות. המארח קורא לה לפני קריאה ומדלג על הקריאה לחלוטין כאשר היא מחזירה False. זה חוסך את עלות ההלוך-ושוב במקרה של ”אין נתונים חדשים עדיין“.

  • size מייצרת מחדש את החוצץ (buffer) לפי דרישה ומדווחת על אורכו. ביצוע הדגימה כאן משמעו שה-backend אינו זקוק למשימת רקע – קריאה מהמארח מניעה כל מדידה.

  • read מחזירה פלח של החוצץ (buffer). ספריית הפרוטוקול עשויה לקרוא לה יותר מפעם אחת כאשר החוצץ גדול מהמטען המרבי שסוכם; הארגומנט offset עובר דרך הקטעים.

  • היעדר write משמעו שכתיבות מהמארח נדחות בשכבת המסגור (framing), לפני שה-backend מעורב.

12.7.3. ערכת ה-callback המלאה

לעיון, כל מתודה שהספרייה מחפשת על backend:

מתודה

מחזירה

מטרה

init(self)

object

אתחול חד-פעמי אופציונלי כאשר הערוץ נקשר לראשונה למארח. החזר כל ערך שאינו None בעת הצלחה.

poll(self)

bool

החזר True כאשר נתונים זמינים.

lock(self)

bool

תפוס את הערוץ לצורך העברה אטומית מרובת מנות (packets).

unlock(self)

bool

שחרר lock קודם.

size(self)

int

מספר הבייטים הניתנים כעת לקריאה מהערוץ.

shape(self)

tuple

עד ארבעה מספרים שלמים המתארים את מבנה הנתונים (למשל גובה התמונה, רוחב, מספר בייטים). משמש את המארח כדי לפרוס חוצצים מטופסים.

read(self, offset, size)

bytes

החזר עד size בייטים החל מ-offset. נקרא פעם אחת לכל קטע כאשר המטען חורג מהמרבי שסוכם.

readp(self, offset, size)

bytes

וריאנט ללא העתקה (zero-copy) של read: זיכרון החוצץ חייב להישאר תקף למשך כל ההעברה.

write(self, offset, data)

int

המארח כתב data ב-offset. data הוא תצוגת bytearray לתוך חוצץ הקבלה של שכבת הפרוטוקול – העתק החוצה את מה שברצונך לשמור לפני החזרה.

ioctl(self, cmd, length, arg)

int

אופקוד מוגדר-אפליקציה מחוץ למודל הקריאה/כתיבה. ערך החזרה שלילי הוא שגיאה.

flush(self)

object

השלך כל נתון מאוחסן בחוצץ. נקרא כאשר המארח רוצה לאפס את הערוץ.

is_active(self)

bool

משמעותי רק על backends המייצגים תעבורה (transport) פיזית (ערוצי ה-USB המובנים). ערוצי אפליקציה אינם זקוקים לכך.

זהו ממשק ה-backend בשלמותו. שנים-עשר שמות מתודות, כולם אופציונליים, וספריית הפרוטוקול מחליטה מה כל ערוץ יכול לעשות על סמך אילו מהם נוכחים.