13.3.1.4. ערוצים מותאמים אישית

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

זוהי התכונה השימושית ביותר של החבילה והתכונה שהתיעוד הסטנדרטי מכסה בצורה הפחות טובה, ולכן עמוד זה עובר אותה מקצה לקצה.

13.3.1.4.1. שני החצאים

ערוץ מותאם אישית זקוק לקוד משתף פעולה משני הצדדים. הסקריפט בצד המצלמה מייבא את protocol, מגדיר מחלקה עם שלוש מתודות (size(), read(), poll()) בתוספת write() אופציונלית, וקורא ל-protocol.register(name=..., backend=...) כדי לפרסם את הערוץ תחת שם נבחר:

import protocol
import time

class TicksChannel:
    def size(self):
        return 10

    def read(self, offset, size):
        return f'{time.ticks_ms():010d}'

    def poll(self):
        return True

protocol.register(name='ticks', backend=TicksChannel())

המתודה size() מחזירה כמה בייטים זמינים כעת בערוץ. read() היא היצרן: בהינתן offset ו-size שהמארח מבקש, היא מחזירה את הבייטים (או מחרוזת ששכבת הפרוטוקול מקודדת). poll() מחזירה True כאשר יש משהו לקרוא – שכבת הפרוטוקול משתמשת בזה כדי לסמן את הערוץ כמוכן ב-read_status().

התוכנית בצד המארח משתמשת בארבע מתודות של openmv.Camera: has_channel() כדי לבדוק שהערוץ קיים, channel_size() כדי לשאול כמה נתונים ממתינים, channel_read() כדי למשוך בייטים החוצה, ו-channel_write() כדי לדחוף בייטים פנימה. read_status() מתשאלת כל ערוץ בבת אחת:

from openmv import Camera

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

    while True:
        status = cam.read_status()

        if status.get('ticks'):
            data = cam.channel_read('ticks')
            print(f"ticks: {data.decode()}")

לולאת המארח מתשאלת את read_status(); כאשר ערוץ ה-ticks מוכן היא קוראת ל-channel_read() ללא size כדי למשוך את כל מה שזמין. ה-TicksChannel.poll() של המצלמה מחזירה True בכל בדיקה, כך שהערוץ תמיד ”מוכן“ והמארח מקבל ערך tick טרי בכל תשאול.

13.3.1.4.2. ערוץ דו-כיווני

עבור מארח שצריך לדחוף נתונים בחזרה, המחלקה בצד המצלמה מוסיפה מתודת write() המקבלת את הבייטים הנכנסים:

import protocol

class CommandChannel:
    def __init__(self):
        self.last_command = b''
        self.replied = False

    def size(self):
        return len(self.last_command)

    def read(self, offset, size):
        self.replied = True
        return self.last_command

    def write(self, offset, data):
        self.last_command = b'echo: ' + bytes(data)
        self.replied = False

    def poll(self):
        return not self.replied and len(self.last_command) > 0

protocol.register(name='echo', backend=CommandChannel())

המארח כותב לערוץ עם channel_write() וקורא את התשובה בחזרה דרך תבנית read_status() / channel_read() הרגילה:

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

    cam.channel_write('echo', b'hello')

    while True:
        if cam.read_status().get('echo'):
            print(cam.channel_read('echo').decode())
            break

13.3.1.4.3. מה זה נותן ליישום

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

דגל ה---channel NAME של כלי שורת הפקודה הוא דרך מהירה לאמת ערוץ מותאם אישית מהטרמינל מבלי לכתוב תוכנית בצד המארח: כלי שורת הפקודה מתשאל את הערוץ הנקוב ומדפיס את עשרת הבייטים הראשונים של כל עדכון.

מגבלת הגודל על קריאה בודדת ל-channel_read() או channel_write() היא ה-max_payload שנקבע במשא ומתן של הפרוטוקול – 4096 בייטים כברירת מחדל. המתודות בצד המארח מפצלות אוטומטית כתיבות גדולות יותר למספר הנכון של מנות, כך שהיישום יכול להעביר חוצצים גדולים כרצונו; הפיצול בלתי נראה.