protocol --- ช่องสัญญาณโปรโตคอล OpenMV#
โมดูล protocol เปิดเผยโปรโตคอลโฮสต์ OpenMV สู่ Python ช่วยให้สามารถเริ่มต้นและกำหนดค่าสแต็กโปรโตคอลฝั่งเฟิร์มแวร์ได้ และให้โค้ดผู้ใช้ลงทะเบียนช่องสัญญาณเชิงตรรกะแบบกำหนดเองที่รองรับด้วยออบเจกต์ Python ที่ใช้อินเทอร์เฟซช่องสัญญาณ (read, write, size, poll เป็นต้น) นี่คือสิ่งที่เครื่องมือคู่หูบนเดสก์ท็อปพูดถึงเมื่อสตรีมข้อมูลภาพหรือแสดงวิดเจ็ตแบบโต้ตอบกับกล้องที่เชื่อมต่อ
ตัวอย่าง#
สตรีมภาพ RGB565 ไปยังเครื่องมือโฮสต์โดยใช้แบ็กเอนด์แบบกำหนดเองที่ใช้อินเทอร์เฟซช่องสัญญาณดิบ (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 เริ่มต้นที่ทำงานอยู่แล้ว ดังนั้นจึงจำเป็นต้องเรียกฟังก์ชันนี้เฉพาะเมื่อต้องการเปลี่ยนทรานสปอร์ตหรือแทนที่พารามิเตอร์การจัดเฟรมเริ่มต้นเท่านั้น การเรียกอีกครั้งจะเริ่มต้นสแตกใหม่: ตัวตั้งเวลาการ poll เดิมจะถูกลบออกและช่องสัญญาณต่างๆ จะถูกลงทะเบียนใหม่crcเปิดใช้งานการตรวจสอบ CRC บนเฟรมโปรโตคอลseqเปิดใช้งานการติดตามหมายเลขลำดับackเปิดใช้งานการตอบรับต่อเฟรมeventsเปิดใช้งานการแจ้งเตือนเหตุการณ์ช่องสัญญาณmax_payloadคือขนาดเพย์โหลดสูงสุดในหน่วยไบต์ หากละไว้จะใช้ค่าเริ่มต้นต่อกล้องด้านล่าง ซึ่งได้มาจากขนาดบัฟเฟอร์โปรโตคอลของแต่ละบอร์ดเป็นbuffer - 10 (header) - 4 (CRC)Camera
ขนาดบัฟเฟอร์
เพย์โหลดสูงสุด
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คือจำนวนครั้งในการส่งซ้ำ ค่าเริ่มต้นคือ3rtx_timeout_msคือช่วงเวลาหมดเวลาการส่งซ้ำในมิลลิวินาที (เพิ่มเป็นสองเท่าหลังจากแต่ละการหมดเวลา) ค่าเริ่มต้นคือ500lock_interval_msคือช่วงเวลาล็อกขั้นต่ำในมิลลิวินาที ค่าเริ่มต้นคือ10poll_msคือช่วงเวลาโพลในมิลลิวินาที0(ค่าเริ่มต้น) ปิดการใช้งานการโพลแบบตัวจับเวลา
- protocol.is_active() bool#
ส่งคืน
Trueหากโฮสต์เชื่อมต่ออยู่ในขณะนี้และสแต็กโปรโตคอลทำงานอยู่ มิฉะนั้นจะส่งคืนFalse
- protocol.poll() int#
รันการวนซ้ำหนึ่งรอบของงานโปรโตคอล: ระบายบัฟเฟอร์รับของทรานสปอร์ต แยกวิเคราะห์เฟรมที่สมบูรณ์ทั้งหมด ส่งต่อคำสั่ง และให้บริการเหตุการณ์ของช่องสัญญาณ โดยปกติเฟิร์มแวร์จะทำสิ่งนี้ให้จากอินเทอร์รัปต์ USB และจากตัวตั้งเวลา
poll_msดังนั้นจึงจำเป็นเฉพาะกับทรานสปอร์ตแบบกำหนดเองที่ไม่ได้ถูกขับเคลื่อนด้วยทั้งสองอย่างนี้ -- เช่น เมื่อpoll_ms=0และทรานสปอร์ตถูก poll จากลูปหลักของสคริปต์คืนค่า
0เมื่อสำเร็จ และ-1หากไม่มีทรานสปอร์ตที่ใช้งานอยู่ถูกลงทะเบียนไว้ หรือถูกเรียกจากบริบทของอินเทอร์รัปต์
- protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel#
ลงทะเบียนออบเจกต์ Python
backendเป็นช่องสัญญาณเชิงตรรกะใหม่และส่งคืน handleProtocolChannelเมธอดที่มีอยู่ของออบเจกต์backend(ดู อินเทอร์เฟซแบ็กเอนด์ ด้านล่าง) จะกำหนดความสามารถของช่องสัญญาณ โดยprotocol.CHANNEL_FLAG_READ,protocol.CHANNEL_FLAG_WRITEและprotocol.CHANNEL_FLAG_LOCKจะถูกเพิ่มในflagsโดยอัตโนมัติเมื่อมีการใช้งานเมธอดที่เกี่ยวข้องnameคือชื่อช่องสัญญาณเป็นสตริง จะถูกตัดให้สั้นลงตามขนาดบัฟเฟอร์ชื่อช่องสัญญาณของเฟิร์มแวร์ จำเป็นbackendคือออบเจกต์ Python ที่ใช้อินเทอร์เฟซแบ็กเอนด์ จำเป็น โดยทั่วไปส่งผ่านโดยคีย์เวิร์ด (backend=...)flagsคือบิตแฟล็กช่องสัญญาณเพิ่มเติม (ดูค่าคงที่CHANNEL_FLAG_*) ไม่บังคับ ค่าเริ่มต้นคือ0ยกข้อผิดพลาด
RuntimeErrorหากไม่สามารถลงทะเบียนช่องสัญญาณได้ (เช่น ไม่มีช่องสัญญาณว่าง)
คลาส#
- class protocol.ProtocolChannel#
Handle ที่ส่งคืนโดย
protocol.registerไม่สร้าง Instance โดยตรง
อินเทอร์เฟซแบ็กเอนด์#
ออบเจกต์แบ็กเอนด์ที่ส่งไปยัง protocol.register อาจใช้เมธอดย่อยใดก็ได้จากต่อไปนี้ มีเฉพาะเมธอดที่มีอยู่ในออบเจกต์เท่านั้นที่จะถูกเชื่อมต่อกับชั้น C protocol เมธอดที่หายไปจะปิดใช้งานความสามารถที่เกี่ยวข้อง
- class protocol.backend#
ออบเจกต์แบ็กเอนด์ช่องสัญญาณที่ส่งไปยัง
protocol.registerเมธอดด้านล่างอธิบายอินเทอร์เฟซเสริมที่แบ็กเอนด์ Python อาจใช้งาน- init() object#
เรียกใช้ครั้งเดียวเมื่อช่องสัญญาณถูกเริ่มต้น ส่งคืนค่าที่ไม่ใช่
Noneใดก็ได้เพื่อบ่งชี้ความสำเร็จ ข้อยกเว้นหรือการขาดค่าส่งคืนจะถูกถือเป็นข้อผิดพลาด
- shape() tuple#
ส่งคืน tuple ของจำนวนเต็มสูงสุดสี่ตัวที่อธิบายรูปร่างของข้อมูล (เช่น มิติของภาพ) โดยโปรโตคอลเลเยอร์จะใช้สูงสุดสี่องค์ประกอบ
- read(offset: int, size: int) bytes#
ส่งคืนสูงสุด
sizeไบต์เริ่มต้นที่offsetเป็นออบเจกต์คล้ายbytesที่รองรับโปรโตคอลบัฟเฟอร์
- readp(offset: int, size: int) bytes#
ตัวแปร zero-copy ของ
readส่งคืนบัฟเฟอร์ที่หน่วยความจำพื้นฐานถูกอ่านโดยตรงโดยชั้นโปรโตคอล บัฟเฟอร์ต้องยังคงใช้งานได้ตลอดระยะเวลาการถ่ายโอน
- 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)#
แบ็กเอนด์ Python ระดับสูงขึ้น (จัดเตรียมโดยแพ็กเกจ
protocolแบบ frozen) ที่ซีเรียลไลซ์ฟิลด์ที่มีชื่อเป็น CBOR โดยใช้คีย์จำนวนเต็มที่เข้ากันได้กับ SenML รองรับวิดเจ็ตแสดงผล (label,text,depth,waveform) และตัวควบคุมแบบโต้ตอบ (toggle,pushbutton,slider,spinbox,select,radio,lineedit) พร้อมคอลแบ็กon_read/on_writeon_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"-- การกระทำชั่วขณะ (trigger, calibrate, ...) โฮสต์จะเขียนTrueเมื่อคลิกและไม่แสดงสถานะใดๆ"slider"-- ตัวควบคุมค่าตัวเลขพร้อมmin/max/step"spinbox"-- ช่องป้อนค่าตัวเลขแบบขั้นที่แม่นยำ ใช้ฟิลด์ร่วมกับ slider และรูปแบบการอัปเดตด้วย tuple(min, max, value)"select"-- รายการแบบดรอปดาวน์ของสตริงoptions"radio"--optionsที่แสดงเป็นกลุ่มของปุ่มเรดิโอ"lineedit"-- ช่องป้อนสตริงที่เขียนได้"depth"-- การแสดงแผนที่ความลึก 2 มิติขนาดwidthxheight"waveform"-- กราฟอนุกรมเวลา 1 มิติของตัวอย่างสัญญาณแบบสลับกัน (interleaved)
valueคือค่าตั้งต้น ค่าเริ่มต้นจะขึ้นอยู่กับtype:0สำหรับlabel,""สำหรับtext/lineedit/select/radio,Falseสำหรับtoggle/pushbutton,minสำหรับslider/spinboxunitคือสตริงหน่วยสำหรับ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สำหรับข้อมูล accelerometer แบบ XYZtypecodeคือ 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-like ของตัวอย่างสัญญาณที่สลับกันตามtypecodeของฟิลด์ จำนวนตัวอย่างต่ออนุกรมจะคำนวณจากความยาวของบล็อก และจะแนบไทม์สแตมป์แบบเพิ่มขึ้นเสมอ (ในหน่วยไมโครวินาที สะสมจากtime.ticks_us()) เพื่อให้โฮสต์สามารถวางชิ้นข้อมูลนี้บนแกนเวลาได้
- size() int#
เมธอดอินเทอร์เฟซแบ็กเอนด์ เรียกใช้
on_read(หากตั้งค่าไว้) และส่งคืนขนาดของบัฟเฟอร์ที่จัดลำดับแล้ว
ค่าคงที่#
บิตแฟล็กช่องสัญญาณ (รวมกันแบบ bitwise ส่งไปยัง protocol.register ผ่าน flags หรือตั้งค่าโดยอัตโนมัติตามเมธอดของแบ็กเอนด์)
- protocol.CHANNEL_FLAG_PHYSICAL: int#
ช่องสัญญาณแทนการขนส่งทางกายภาพ (ตรงกันข้ามกับช่องสัญญาณข้อมูลเชิงตรรกะ)
ตัวระบุช่องสัญญาณในตัว