12.6. ערוצים בעלי שם¶
מזהה הערוץ בכותרת של כל מנה (packet) מאפשר עד 32 זרמים עצמאיים לחלוק את אותה תעבורה (transport) פיזית. שכבת הערוצים הופכת את המזהים המספריים הללו לנקודות קצה בעלות שם, גלויות לאפליקציה, שקוד המארח יכול להתייחס אליהן באמצעות מחרוזת.
12.6.1. ארבעת הערוצים המובנים¶
המצלמה רושמת ארבעה ערוצים בעת האתחול, לפני שכל קוד אפליקציה רץ:
stdin– בייטים של סקריפט שהמארח דוחף אל המצלמה לצורך הרצה. ה-IDE משתמש בערוץ זה כדי לשלוח את הסקריפט הנערך;exec()ב-SDK של המארח היא הקריאה המקבילה מתוך תוכנית Python.stdout– בייטים מקריאותprint()של המצלמה ומ-tracebacks של חריגות שלא נתפסו. קונסולת ה-serial של ה-IDE קוראת ערוץ זה.stream– ערוץ התצוגה המקדימה החיה. ה-IDE מושך ממנו פריימים בפורמט JPEG; כל סקריפט מארח יכול לעשות זאת בעזרתread_frame().profile– אירועי profiler, נוכח רק כאשר המצלמה נבנתה עם profiling מופעל. רוב גרסאות ה-release משמיטות אותו.
קוד אפליקציה לעיתים נדירות בלבד זקוק לגעת באחד מהמובנים; העבודה המעניינת מתרחשת על ערוצים שהאפליקציה רושמת בעצמה.
12.6.2. רישום ערוץ¶
סקריפט בצד המצלמה רושם ערוץ חדש על ידי קריאה ל-protocol.register() עם שם ואובייקט backend של Python:
import json
import protocol
import time
trigger_count = 0
class StatusChannel:
def size(self):
# Refresh the snapshot on every host query.
self._buf = json.dumps({
'uptime_s': time.ticks_ms() // 1000,
'triggers': trigger_count,
}).encode()
return len(self._buf)
def read(self, offset, size):
return self._buf[offset:offset + size]
protocol.register(name='status', backend=StatusChannel())
המתודות של אובייקט ה-backend מחליטות מה הערוץ יכול לעשות. backend עם size ו-read בלבד הוא ערוץ נתונים לקריאה בלבד; הוסף write והוא הופך לדו-כיווני; הוסף poll והמארח יכול לשאול אם נתונים חדשים מוכנים לפני ששילם עבור קריאה. דגימת הנתונים בתוך size היא הדפוס הפשוט ביותר כאשר המטען קטן מספיק כדי להיכנס בקטע אחד – החוצץ נוצר לפי דרישה, אף פעם לא נשמר במטמון, אף פעם לא נתון לתחרות (race). מטענים גדולים יותר – פריימים של תמונות, עקבות חיישן – זקוקים לדפוס נעילה (latching) המחזיק את החוצץ עד שהמארח מסיים את הקריאה מרובת-הקטעים שלו, נושא המכוסה בערוץ הפריימים.
מעט ניהול רישום מתרחש באופן אוטומטי:
הספרייה מקצה את מזהה הערוץ הפנוי הבא (בין 0 ל-31).
דגלי היכולות נגזרים מהמתודות הנוכחות:
CHANNEL_FLAG_READאםreadמוגדרת,CHANNEL_FLAG_WRITEאםwriteמוגדרת,CHANNEL_FLAG_LOCKאםlock/unlockמוגדרות.מנת אירוע
CHANNEL_REGISTEREDנשלחת לכל מארח מחובר כך שרשימת הערוצים שלו מתעדכנת.
ערך ההחזרה הוא ידית (handle) מסוג protocol.ProtocolChannel שהאפליקציה יכולה להחזיק. המתודה send_event() של הידית היא ה-hook בצד המצלמה שנועד לומר למארח ”משהו קרה בערוץ זה בלי לשנות את הנתונים הניתנים לקריאה“ – טריגר הופעל, כפתור נלחץ, אבן-דרך של מספר דגימות נחצתה.
12.6.3. קריאת ערוצים מהמארח¶
ה-SDK של המארח מסופק כחבילה openmv ב-PyPI (pip install openmv), בנוי על pyserial עבור התעבורה (transport). מחלקת openmv.camera.Camera שלו חושפת את הערוצים בעלי השם של המצלמה דרך מתודות ברמה גבוהה:
from openmv.camera import Camera
with Camera('/dev/ttyACM0', baudrate=921600) as cam:
cam.update_channels()
if cam.has_channel('status'):
size = cam.channel_size('status')
data = cam.channel_read('status', size)
אזהרה
החבילה openmv דורשת CPython 3.12 או חדש יותר. מפרשנים מוקדמים יותר חסרים תכונות שה-SDK תלוי בהן; התקן בנייה של 3.12+ לפני pip install openmv.
מספר דברים שכדאי לשים לב אליהם בהגדרה:
מחרוזת ה-serial-port –
/dev/ttyACM0כאן – היא בסגנוןCOM3ב-Windows,/dev/cu.usbmodemXXXXב-macOS, ו-/dev/ttyACM*ב-Linux. המספר בפועל תלוי באיזה פורט המצלמה נספרה.קצב הבָּאוּד (baud rate) הוא הערך הקסום של הפרוטוקול
921600, שמחסנית ה-USB-CDC של המצלמה מזהה כ“לקוח זה מדבר את הפרוטוקול, לא את ה-REPL“. כל קצב אחר נסוג לקו serial רגיל.מנהל ההקשר
with Camera(...) as cam:פותח את התעבורה (transport), מריץPROTO_SYNC, מחליף יכולות, וביציאה סוגר את הפורט בצורה נקייה. הקריאה המפורשתupdate_channels()לאחר הכניסה מרעננת את רשימת הערוצים המקומית עם כל ערוץ שהאפליקציה רשמה לאחר האתחול.
channel_size() ו-channel_read() הן מתודות סוס-העבודה; channel_write() מעבירה חוצץ הלוך-ושוב אל המצלמה אם ל-backend יש מתודת write; has_channel() היא הדרך הבטוחה לבדוק ששם רשום לפני השימוש בו. שם הערוץ נבדק פעם אחת אל מול מזהה הערוץ שהמצלמה הקצתה במהלך register, ומשמש בכל מנה מאותו רגע והלאה.
כל זוג channel_size() / channel_read() עולה שתי נסיעות הלוך-ושוב: מנה אחת כדי לבקש את הגודל, אחת כדי לבקש את הבייטים. מעל USB-CDC שתיהן מסתיימות בכמילישנייה אחת בסך הכל; מעל UART אותו חילוף אורך זמן רב יותר ביחס לקצב הבָּאוּד (baud rate) של קו ה-serial. קוד אפליקציה הקורא בלולאה צמודה צריך לקרוא ל-channel_size() רק כאשר הגודל באמת יכול להשתנות – עבור נתונים בגודל קבוע, ניתן לשמור במטמון את הגודל מהקריאה הראשונה.
12.6.4. עצמאות בין ערוצים¶
שלושה דברים שכדאי לדעת על האופן שבו ערוצים מקיימים אינטראקציה:
בקרת זרימה עצמאית. לכל ערוץ יש מצב קריאה ממתין משלו, נתונים משלו, ופונקציות ה-callback
size/read/writeמשלו. קריאה ממושכת על ערוץ ה-streamאינה חוסמת קריאות על ערוץ ה-configשל האפליקציה.סדרתי בכל ערוץ. בתוך ערוץ יחיד, מנות נמסרות לפי הסדר. שכבת המהימנות מבטיחה זאת אפילו כאשר מעורבות שידורים חוזרים.
תעבורה משותפת, תקציב שידור חוזר משותף. כל הערוצים חולקים את הקישור הפיזי האחד, ולכן מבול של תעבורה על ערוץ אחד מאט את האחרים על ידי השתלטות על החוט. מנגנון ה-
CHANNEL_LOCKמאפשר לערוץ אחד לשמור את החוט לצורך קריאה אטומית מרובת מנות; ה-backend מצטרף על ידי מימוש פונקציות ה-callbacklock/unlock.
ערוץ הוא שטח הפנים המינימלי שעליו תוכנית מארח ותוכנית מצלמה מסכימות לשתף פעולה. השם, הכיווניות (קריאה או כתיבה או שתיהן), מתודות ה-callback בצד המצלמה, וקריאות המתודה התואמות בצד המארח הן החוזה כולו.