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 เริ่มต้นที่ทำงานอยู่แล้ว ดังนั้นการเรียกใช้นี้จึงจำเป็นเฉพาะเมื่อต้องการเปลี่ยนการขนส่งหรือแทนที่พารามิเตอร์การจัดเฟรมเริ่มต้นเท่านั้น

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 คือจำนวนครั้งในการส่งซ้ำ ค่าเริ่มต้นคือ 3

rtx_timeout_ms คือช่วงเวลาหมดเวลาการส่งซ้ำในมิลลิวินาที (เพิ่มเป็นสองเท่าหลังจากแต่ละการหมดเวลา) ค่าเริ่มต้นคือ 500

lock_interval_ms คือช่วงเวลาล็อกขั้นต่ำในมิลลิวินาที ค่าเริ่มต้นคือ 10

poll_ms คือช่วงเวลาโพลในมิลลิวินาที 0 (ค่าเริ่มต้น) ปิดการใช้งานการโพลแบบตัวจับเวลา

protocol.is_active() bool

ส่งคืน True หากโฮสต์เชื่อมต่ออยู่ในขณะนี้และสแต็กโปรโตคอลทำงานอยู่ มิฉะนั้นจะส่งคืน False

protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel

ลงทะเบียนออบเจกต์ Python backend เป็นช่องสัญญาณเชิงตรรกะใหม่และส่งคืน handle ProtocolChannel เมธอดที่มีอยู่ของออบเจกต์ 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 โดยตรง

send_event(event: int, wait_ack: bool = False) None

ส่งการแจ้งเตือนเหตุการณ์ช่องสัญญาณไปยังโฮสต์

event คือตัวระบุเหตุการณ์ (จำนวนเต็ม)

wait_ack หาก True จะบล็อกจนกว่าโฮสต์จะตอบรับเหตุการณ์

ยกข้อผิดพลาด RuntimeError หากการส่งเหตุการณ์ล้มเหลว

อินเทอร์เฟซแบ็กเอนด์

ออบเจกต์แบ็กเอนด์ที่ส่งไปยัง protocol.register อาจใช้เมธอดย่อยใดก็ได้จากต่อไปนี้ มีเฉพาะเมธอดที่มีอยู่ในออบเจกต์เท่านั้นที่จะถูกเชื่อมต่อกับชั้น C protocol เมธอดที่หายไปจะปิดใช้งานความสามารถที่เกี่ยวข้อง

class protocol.backend

ออบเจกต์แบ็กเอนด์ช่องสัญญาณที่ส่งไปยัง protocol.register เมธอดด้านล่างอธิบายอินเทอร์เฟซเสริมที่แบ็กเอนด์ Python อาจใช้งาน

init() object

เรียกใช้ครั้งเดียวเมื่อช่องสัญญาณถูกเริ่มต้น ส่งคืนค่าที่ไม่ใช่ None ใดก็ได้เพื่อบ่งชี้ความสำเร็จ ข้อยกเว้นหรือการขาดค่าส่งคืนจะถูกถือเป็นข้อผิดพลาด

poll() bool

ส่งคืน True หากช่องสัญญาณมีข้อมูลพร้อมให้โฮสต์อ่าน

lock() bool

ล็อกช่องสัญญาณสำหรับการถ่ายโอน ส่งคืน True เมื่อสำเร็จ

unlock() bool

ปลดล็อกช่องสัญญาณหลังจากการถ่ายโอน ส่งคืน True เมื่อสำเร็จ

size() int

ส่งคืนจำนวนไบต์ที่อ่านได้จากช่องสัญญาณในขณะนี้

shape() tuple

ส่งคืน tuple ของจำนวนเต็มสูงสุดสี่ตัวที่อธิบายรูปร่างของข้อมูล (เช่น มิติของภาพ) โดยโปรโตคอลเลเยอร์จะใช้สูงสุดสี่องค์ประกอบ

flush() object

ล้างข้อมูลที่รอดำเนินการ ส่งคืนค่าที่ไม่ใช่ None ใดก็ได้เพื่อบ่งชี้ความสำเร็จ

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 เมื่อสำเร็จตามค่าเริ่มต้น

ioctl(cmd: int, length: int, arg: bytearray | None) int

จัดการ ioctl โดย arg คือ None หาก length เป็นศูนย์ มิฉะนั้นจะเป็น bytearray ที่อ้างอิงบัฟเฟอร์ C ส่งคืน 0 หรือ None เมื่อสำเร็จ หรือจำนวนเต็มลบเมื่อเกิดข้อผิดพลาด

is_active() bool

สำหรับช่องสัญญาณการขนส่ง ส่งคืน True หากการขนส่งพื้นฐานเชื่อมต่ออยู่ในขณะนี้

class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)

แบ็กเอนด์ Python ระดับสูงกว่า (จัดเตรียมโดยแพ็กเกจ protocol ที่ถูกฝัง) ซึ่งจัดลำดับฟิลด์ที่มีชื่อเป็น CBOR โดยใช้คีย์จำนวนเต็มที่เข้ากันได้กับ SenML รองรับวิดเจ็ตแสดงผล (label, depth) และตัวควบคุมแบบโต้ตอบ (toggle, slider, select) พร้อมคอลแบ็ก 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) None

เพิ่มฟิลด์ที่มีชื่อในช่องสัญญาณ

name คือชื่อแสดงผล ต้องไม่ซ้ำกันภายในช่องสัญญาณนี้

type คือประเภทวิดเจ็ต: "label", "toggle", "slider", "select" หรือ "depth"

value คือค่าเริ่มต้น ค่าเริ่มต้นขึ้นอยู่กับ type

unit คือสตริงหน่วยสำหรับ label/slider (เช่น "Cel", "%RH")

min คือค่าต่ำสุด (ช่วง slider หรือช่วง depth)

max คือค่าสูงสุด (ช่วง slider หรือช่วง depth)

step คือขนาดขั้นตอน (slider)

options คือรายการสตริงตัวเลือก (select)

width คือความกว้างเป็นพิกเซล (depth)

height คือความสูงเป็นพิกเซล (depth)

__getitem__(name: str) object

ส่งคืนค่าปัจจุบันของฟิลด์ที่มีชื่อ สำหรับฟิลด์ depth จะส่งคืนบัฟเฟอร์ข้อมูลไบนารี มิฉะนั้นจะส่งคืนค่าสเกลาร์

__setitem__(name: str, value: Any) None

กำหนดค่าของฟิลด์ที่มีชื่อ สำหรับฟิลด์ slider tuple (min, max, value) จะอัปเดตช่วงและค่าปัจจุบันพร้อมกัน สำหรับฟิลด์ depth value คือบัฟเฟอร์ข้อมูลไบนารี

poll() bool

เมธอดอินเทอร์เฟซแบ็กเอนด์ ส่งคืน True เมื่อมีข้อมูลที่จัดลำดับแล้วพร้อมให้โฮสต์

size() int

เมธอดอินเทอร์เฟซแบ็กเอนด์ เรียกใช้ on_read (หากตั้งค่าไว้) และส่งคืนขนาดของบัฟเฟอร์ที่จัดลำดับแล้ว

read(offset: int, size: int) bytes

เมธอดอินเทอร์เฟซแบ็กเอนด์ ส่งคืนส่วนหนึ่งของบัฟเฟอร์ที่จัดลำดับแล้ว

write(offset: int, data: bytearray) int

เมธอดอินเทอร์เฟซแบ็กเอนด์ ถอดรหัสรายการอัปเดต CBOR และนำค่าไปใช้กับฟิลด์ที่มีชื่อที่ตรงกัน โดยเรียกใช้ on_write สำหรับแต่ละรายการ

ค่าคงที่

บิตแฟล็กช่องสัญญาณ (รวมกันแบบ bitwise ส่งไปยัง protocol.register ผ่าน flags หรือตั้งค่าโดยอัตโนมัติตามเมธอดของแบ็กเอนด์)

protocol.CHANNEL_FLAG_READ: int

ช่องสัญญาณรองรับการอ่าน

protocol.CHANNEL_FLAG_WRITE: int

ช่องสัญญาณรองรับการเขียน

protocol.CHANNEL_FLAG_LOCK: int

ช่องสัญญาณใช้งาน lock/unlock

protocol.CHANNEL_FLAG_PHYSICAL: int

ช่องสัญญาณแทนการขนส่งทางกายภาพ (ตรงกันข้ามกับช่องสัญญาณข้อมูลเชิงตรรกะ)

ตัวระบุช่องสัญญาณในตัว

protocol.CHANNEL_ID_TRANSPORT: int

ID ช่องสัญญาณสงวนไว้สำหรับการขนส่งที่ใช้งานอยู่

protocol.CHANNEL_ID_STDIN: int

ID ช่องสัญญาณของช่อง stdin ในตัว

protocol.CHANNEL_ID_STDOUT: int

ID ช่องสัญญาณของช่อง stdout ในตัว

protocol.CHANNEL_ID_STREAM: int

ID ช่องสัญญาณของช่อง stream ในตัว

protocol.CHANNEL_ID_PROFILE: int

ID ช่องสัญญาณของช่อง profiler ในตัว (มีอยู่เฉพาะเมื่อเฟิร์มแวร์ถูกสร้างพร้อมตัวสร้างโปรไฟล์ที่เปิดใช้งาน)