protocol — ערוצי פרוטוקול OpenMV#
המודול protocol חושף את פרוטוקול המארח של OpenMV ל-Python. הוא מאפשר לאתחל ולהגדיר את מחסנית הפרוטוקול בצד הקושחה, ומאפשר לקוד המשתמש לרשום ערוצים לוגיים מותאמים אישית הנתמכים על ידי אובייקט Python המממש את ממשק הערוץ (read, write, size, poll וכו«). זה מה שכלי הלוויין השולחניים מדברים אליו כאשר הם מזרימים נתוני תמונה או חושפים רכיבים אינטראקטיביים למצלמה מחוברת.
דוגמאות#
הזרמת תמונת RGB565 לכלי מארח באמצעות backend מותאם אישית המממש את ממשק הערוץ הגולמי (backend.size(), backend.shape(), backend.poll(), backend.read()):
import csi
import protocol
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.HD)
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
class FrameChannel:
def size(self):
return len(img_mv)
def shape(self):
return (img.height(), img.width(), len(img_mv))
def poll(self):
return frame_ready
def read(self, offset, size):
global frame_ready
end = offset + size
chunk = img_mv[offset:end]
if end >= len(img_mv):
frame_ready = False
return chunk
protocol.register(name="frame", backend=FrameChannel())
while True:
if not frame_ready:
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
הסקריפט התואם בצד המארח, המשתמש בחבילת ה-Python openmv (pip install openmv) כדי להתחבר, לדחוף את הסקריפט שעל המצלמה ולמשוך כל פריים:
import cv2
import numpy as np
from openmv.camera import Camera
# The on-cam script above, stored as a string (or read from a file).
SCRIPT = open("frame_streamer_on_cam.py").read()
with Camera("/dev/ttyACM0", baudrate=921600) as cam:
cam.stop()
cam.exec(SCRIPT)
while True:
status = cam.read_status()
if not cam.has_channel("frame") or not status.get("frame"):
continue
h, w, size = cam._channel_shape(cam.get_channel(name="frame"))
if cam.channel_size("frame") < size:
continue
data = cam.channel_read("frame", size)
rgb565 = np.frombuffer(data, dtype="<u2").reshape(h, w)
# Unpack RGB565 to an HxWx3 uint8 RGB image.
r = ((rgb565 >> 11) & 0x1F) << 3
g = ((rgb565 >> 5) & 0x3F) << 2
b = ( rgb565 & 0x1F) << 3
frame = np.dstack([r, g, b]).astype(np.uint8)
# Display with OpenCV (cv2 expects BGR, not RGB).
cv2.imshow("OpenMV", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
החלף את /dev/ttyACM0 ביציאה הטורית של המצלמה (למשל COM3 ב-Windows). הבנאי openmv.camera.Camera מקבל את אותם פרמטרי פרוטוקול כמו init (crc / seq / ack / events / max_payload / max_retry / timeout) כאשר המחסנית בצד המצלמה הוגדרה מחדש כדי להתאים.
פונקציות#
- protocol.init(crc: bool = True, seq: bool = True, ack: bool = True, events: bool = True, max_payload: int = ..., rtx_retries: int = 3, rtx_timeout_ms: int = 500, lock_interval_ms: int = 10, poll_ms: int = 0) None#
מאתחל (או מגדיר מחדש) את מחסנית הפרוטוקול ורושם את ערוצי הנתונים הלוגיים המוגדרים כברירת מחדל (
stdin,stdout,streamואם קומפל פנימה,profile). מעוררRuntimeErrorאם האתחול נכשל. הקושחה עולה כאשר מחסנית פרוטוקול USB ברירת מחדל כבר רצה, ולכן קריאה זו נדרשת רק כדי לשנות את התעבורה או לדרוס את פרמטרי המסגור המוגדרים כברירת מחדל. קריאה חוזרת מאתחלת את המחסנית מחדש: טיימר התשאול הקודם מוסר והערוצים נרשמים מחדש.crcמפעיל אימות CRC על מסגרות הפרוטוקול.seqמפעיל מעקב אחר מספרי רצף.ackמפעיל אישורי קבלה לכל מסגרת.eventsמפעיל התראות אירועי ערוץ.max_payloadהוא גודל ה-payload המרבי בבתים. אם מושמט, נעשה שימוש בברירת המחדל לכל מצלמה שלהלן; היא נגזרת מגודל חוצץ (buffer) הפרוטוקול של כל לוח כ-buffer - 10 (header) - 4 (CRC).מצלמה
גודל חוצץ (buffer)
Payload מרבי
OpenMV Cam M4 (
OPENMV2)512
498
OpenMV Cam M7 (
OPENMV3)512
498
OpenMV Cam H7 (
OPENMV4)512
498
OpenMV Cam H7 Plus (
OPENMV4P)4096
4082
OpenMV Pure Thermal (
OPENMVPT)4096
4082
OpenMV Cam RT1062 (
OPENMV_RT1060)4096
4082
OpenMV Cam N6 (
OPENMV_N6)8192
8178
OpenMV AE3 (
OPENMV_AE3)8192
8178
Arduino Portenta H7 (
ARDUINO_PORTENTA_H7)4096
4082
Arduino Giga (
ARDUINO_GIGA)4096
4082
Arduino Nicla Vision (
ARDUINO_NICLA_VISION)4096
4082
rtx_retriesהוא מספר ניסיונות השידור החוזר. ברירת מחדל3.rtx_timeout_msהוא פסק הזמן של השידור החוזר במילישניות (מוכפל לאחר כל פסק זמן). ברירת מחדל500.lock_interval_msהוא מרווח הנעילה המינימלי במילישניות. ברירת מחדל10.poll_msהוא מרווח התשאול במילישניות.0(ברירת המחדל) משבית תשאול מבוסס טיימר.
- protocol.poll() int#
מריץ איטרציה אחת של משימת הפרוטוקול: מרוקן את חוצץ הקליטה של התעבורה, מנתח את כל המסגרות השלמות, משגר פקודות ומטפל באירועי ערוצים. הקושחה עושה זאת בדרך כלל עבורכם מתוך פסיקת ה-USB ומתוך טיימר ה-
poll_ms, ולכן פונקציה זו נדרשת רק לתעבורות מותאמות אישית שאינן מונעות על ידי אף אחד מהם – למשל כאשרpoll_ms=0והתעבורה מתושאלת מהלולאה הראשית של הסקריפט.מחזיר
0בהצלחה ו--1אם לא רשומה תעבורה פעילה או אם הקריאה בוצעה מהקשר של פסיקה.
- protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel#
רישום אובייקט
backendשל Python כערוץ לוגי חדש והחזרת ידיתProtocolChannel. השיטות הזמינות של אובייקט ה-backend(ראו ממשק ה-Backend להלן) קובעות את יכולות הערוץ;protocol.CHANNEL_FLAG_READ,protocol.CHANNEL_FLAG_WRITEו-protocol.CHANNEL_FLAG_LOCKמתווספים ל-flagsבאופן אוטומטי כאשר השיטות המתאימות ממומשות.nameהוא שם הערוץ כמחרוזת. נחתך לגודל חוצץ שם-הערוץ של הקושחה. חובה.backendהוא אובייקט ה-Python המממש את ממשק ה-backend. חובה. בדרך כלל מועבר באמצעות מילת מפתח (backend=...).flagsהם ביטי דגל ערוץ נוספים (ראו את הקבועיםCHANNEL_FLAG_*). אופציונלי; ברירת המחדל היא0.מעלה
RuntimeErrorאם לא ניתן לרשום את הערוץ (למשל אין משבצות ערוץ פנויות).
מחלקות#
- class protocol.ProtocolChannel#
ידית המוחזרת על ידי
protocol.register. מופעים אינם נבנים ישירות.
ממשק ה-Backend#
אובייקט backend המועבר ל-protocol.register עשוי לממש כל תת-קבוצה של השיטות הבאות. רק השיטות הקיימות באובייקט מחוברות לשכבת פרוטוקול ה-C; שיטות חסרות משאירות את היכולת המתאימה מושבתת.
- class protocol.backend#
אובייקט backend של ערוץ המועבר ל-
protocol.register. השיטות שלהלן מתארות את הממשק האופציונלי ש-backend של Python עשוי לממש.- init() object#
נקראת פעם אחת כאשר הערוץ מאותחל. החזר כל ערך שאינו
Noneבהצלחה; חריגה או היעדר ערך מוחזר מטופלים כשגיאה.
- shape() tuple#
מחזיר tuple של עד ארבעה מספרים שלמים המתארים את צורת הנתונים (למשל ממדי תמונה). עד ארבעה איברים נצרכים על ידי שכבת הפרוטוקול.
- read(offset: int, size: int) bytes#
מחזיר עד
sizeבתים החל מ-offsetכאובייקט דמויbytesהתומך בפרוטוקול ה-buffer.
- readp(offset: int, size: int) bytes#
וריאנט zero-copy של
read. מחזיר חוצץ (buffer) שהזיכרון הבסיסי שלו נקרא ישירות על ידי שכבת הפרוטוקול; החוצץ חייב להישאר תקף למשך כל ההעברה.
- write(offset: int, data: bytearray) int#
כתיבת
dataב-offset.dataהואbytearrayהמפנה ישירות לחוצץ ה-C. מחזיר את מספר הבתים שנכתבו, או0בהצלחה ברירת מחדל.
- class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)#
צד-שרת (backend) ברמה גבוהה יותר ב-Python (המסופק על ידי החבילה הקפואה
protocol) המסדר (serialize) שדות בעלי שם ל-CBOR באמצעות מפתחות שלמים תואמי SenML. תומך ברכיבי תצוגה (label,text,depth,waveform) ובפקדים אינטראקטיביים (toggle,pushbutton,slider,spinbox,select,radio,lineedit) עם callbacks שלon_read/on_write.on_readהוא callable אופציונליon_read(channel)הנקרא לפני שהערוץ עובר סריאליזציה עבור המארח. השתמש בו כדי לרענן ערכי שדות.on_writeהוא callable אופציונליon_write(channel, name, value)הנקרא כאשר המארח כותב ערך חדש עבור שדה בעל שם.- add(name: str, type: str, value: Any = None, unit: str | None = None, min: int | float | None = None, max: int | float | None = None, step: int | float | None = None, options: list | None = None, width: int | None = None, height: int | None = None, sample_rate: float | None = None, series: int = 1, typecode: str | None = None) None#
הוספת שדה בעל שם לערוץ.
nameהוא שם התצוגה; חייב להיות ייחודי בתוך ערוץ זה.typeהוא סוג הרכיב:"label"– תצוגה מספרית לקריאה בלבד עםunitאופציונלי."text"– תצוגת מחרוזת לקריאה בלבד."toggle"– מתג בוליאני."pushbutton"– פעולה רגעית (הפעלה, כיול, …). המארח כותבTrueבלחיצה ואינו מציג מצב."slider"– פקד מספרי עםmin/max/step."spinbox"– הזנה מספרית מדויקת בצעדים; חולק את שדות ה-slider ואת צורת העדכון באמצעות tuple(min, max, value)."select"– רשימה נפתחת של מחרוזותoptions."radio"–optionsהמוצגות כקבוצה של לחצני רדיו."lineedit"– קלט מחרוזת הניתן לכתיבה."depth"– תצוגת מפת עומק דו-ממדית בגודלwidthxheight."waveform"– תרשים סדרה עתית חד-ממדי של דגימות משולבות (interleaved).
valueהוא הערך ההתחלתי. ברירת המחדל תלויה ב-type:0עבורlabel,""עבורtext/lineedit/select/radio,Falseעבורtoggle/pushbutton,minעבורslider/spinbox.unitהיא מחרוזת היחידה עבורlabel/slider/spinbox/waveform(למשל"Cel","%RH").minהוא הערך המינימלי (טווח slider/spinbox, או טווח התצוגה של depth/waveform).maxהוא הערך המקסימלי (טווח slider/spinbox, או טווח התצוגה של depth/waveform).stepהוא גודל הצעד (slider/spinbox).optionsהיא רשימת מחרוזות האפשרויות (select/radio), או רשימת השמות לכל סדרה (waveform).widthהוא הרוחב בפיקסלים (depth).heightהוא הגובה בפיקסלים (depth).sample_rateהוא קצב הדגימה בדגימות לשנייה (waveform). נשלח למארח כמחזור הדגימה.seriesהוא מספר הסדרות המשולבות בכל בלוק דגימות (waveform), למשל3עבור עקבת מד תאוצה XYZ.typecodeהוא קוד הטיפוס שלarrayעבור דגימות ה-waveform (למשל"H","h","f"). ברירת המחדל היא"H"(16 סיביות ללא סימן).
- __getitem__(name: str) object#
מחזיר את הערך הנוכחי של השדה בעל השם. עבור שדות
depthו-waveformמוחזר חוצץ הנתונים הבינארי, אחרת הערך הסקלרי.
- __setitem__(name: str, value: Any) None#
מגדיר את ערכו של השדה בעל השם. עבור שדות
sliderו-spinbox, tuple(min, max, value)מעדכן את הטווח ואת הערך הנוכחי בו-זמנית. עבור שדותdepth,valueהוא חוצץ הנתונים הבינארי. עבור שדותwaveform,valueהוא בלוק דמוי-bytes של דגימות משולבות ב-typecodeשל השדה; מספר הדגימות לכל סדרה נגזר מאורכו, ומצורפת חותמת זמן מונוטונית (במיקרו-שניות, המצטברת מ-time.ticks_us()) כדי שהמארח יוכל למקם את המקטע על ציר הזמן שלו.
קבועים#
ביטי דגל ערוץ (משולבים בפעולת bitwise; מועברים ל-protocol.register באמצעות flags או נקבעים אוטומטית על סמך השיטות של ה-backend).
מזהי ערוץ מובנים.