12.6. Kênh có tên

ID kênh trong header mỗi gói cho phép tối đa 32 luồng độc lập chia sẻ cùng một transport vật lý. Tầng kênh chuyển đổi những ID số đó thành các endpoint có tên, hiển thị với ứng dụng, mà code phía host có thể tham chiếu bằng chuỗi.

One transport wire on the left fanning out into four labelled channels on the cam side -- stdin, stdout, stream, and a user-registered status channel -- each showing as an independent box.

12.6.1. Bốn kênh tích hợp

Cam đăng ký bốn kênh khi khởi động, trước khi bất kỳ code ứng dụng nào chạy:

  • stdin -- các byte script mà host đẩy lên cam để thực thi. IDE sử dụng kênh này để gửi script đang được chỉnh sửa; exec() trong host SDK là lệnh gọi tương đương từ chương trình Python.

  • stdout -- các byte từ lệnh gọi print() của cam và các traceback exception chưa được bắt. Bảng điều khiển serial của IDE đọc kênh này.

  • stream -- kênh xem trước trực tiếp. IDE lấy các khung hình JPEG từ đó; bất kỳ script host nào cũng có thể làm điều tương tự với read_frame().

  • profile -- các sự kiện profiler, chỉ có mặt khi cam được xây dựng với profiling được bật. Hầu hết các bản phát hành đều bỏ qua nó.

Code ứng dụng hiếm khi cần chạm vào bất kỳ kênh tích hợp nào; công việc thú vị xảy ra trên các kênh mà ứng dụng tự đăng ký.

12.6.2. Đăng ký kênh

Một script phía cam đăng ký kênh mới bằng cách gọi protocol.register() với tên và đối tượng Python backend

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())

Các phương thức của đối tượng backend quyết định kênh có thể làm gì. Một backend chỉ có sizereadkênh dữ liệu chỉ đọc; thêm write và nó trở thành hai chiều; thêm poll và host có thể hỏi xem dữ liệu mới có sẵn không trước khi thực hiện đọc. Lấy mẫu dữ liệu bên trong size là mẫu đơn giản nhất khi payload đủ nhỏ để vừa trong một đoạn -- bộ đệm được tạo theo yêu cầu, không bao giờ được cache, không bao giờ bị xung đột. Payload lớn hơn -- khung hình ảnh, dấu vết cảm biến -- cần mẫu chốt giữ bộ đệm cho đến khi host hoàn thành việc đọc nhiều đoạn, được đề cập với kênh frame.

Một lượng nhỏ bookkeeping xảy ra tự động:

  • Thư viện gán ID kênh rảnh tiếp theo (từ 0 đến 31).

  • Các cờ khả năng được suy ra từ các phương thức có mặt: CHANNEL_FLAG_READ nếu read được định nghĩa, CHANNEL_FLAG_WRITE nếu write được định nghĩa, CHANNEL_FLAG_LOCK nếu lock / unlock được định nghĩa.

  • Một gói sự kiện CHANNEL_REGISTERED được gửi đến bất kỳ host đang kết nối nào để cập nhật danh sách kênh của nó.

Giá trị trả về là một handle protocol.ProtocolChannel mà ứng dụng có thể giữ lại. Phương thức send_event() của handle là hook phía cam để thông báo cho host "có gì đó đã xảy ra trên kênh này mà không thay đổi dữ liệu có thể đọc" -- một trigger đã kích hoạt, một nút được nhấn, một mốc đếm mẫu đã qua.

12.6.3. Đọc kênh từ host

Host SDK được đóng gói là openmv trên PyPI (pip install openmv), xây dựng trên pyserial cho transport. Lớp openmv.camera.Camera của nó cung cấp các kênh có tên của cam thông qua các phương thức cấp cao:

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)

Cảnh báo

Gói openmv yêu cầu CPython 3.12 hoặc mới hơn. Các phiên bản trình thông dịch cũ hơn thiếu các tính năng mà SDK phụ thuộc vào; hãy cài đặt bản 3.12+ trước khi pip install openmv.

Một vài điều cần lưu ý về cách thiết lập:

  • Chuỗi cổng serial -- /dev/ttyACM0 ở đây -- có dạng COM3 trên Windows, /dev/cu.usbmodemXXXX trên macOS, và /dev/ttyACM* trên Linux. Số thực tế phụ thuộc vào cổng mà cam được liệt kê.

  • Tốc độ baud là giá trị đặc biệt 921600 của protocol, mà stack USB-CDC của cam nhận ra là "client này sử dụng protocol, không phải REPL". Bất kỳ tốc độ nào khác sẽ dự phòng về đường serial thuần túy.

  • Context manager with Camera(...) as cam: mở transport, chạy PROTO_SYNC, trao đổi khả năng, và khi thoát đóng cổng sạch sẽ. Lệnh gọi update_channels() rõ ràng sau khi vào sẽ làm mới danh sách kênh cục bộ với bất kỳ kênh nào mà ứng dụng đăng ký sau khi khởi động.

channel_size()channel_read() là các phương thức chính; channel_write() truyền bộ đệm đến cam nếu backend có phương thức write; has_channel() là cách an toàn để kiểm tra xem tên có được đăng ký hay không trước khi sử dụng. Tên kênh được tra cứu một lần thành ID kênh mà cam đã gán trong quá trình register và được sử dụng trong mọi gói sau đó.

Mỗi cặp channel_size() / channel_read() tốn hai round-trip: một gói để hỏi kích thước, một gói để hỏi các byte. Qua USB-CDC cả hai hoàn thành trong khoảng một millisecond kết hợp; qua UART cùng một trao đổi mất lâu hơn tỷ lệ với tốc độ baud của đường serial. Code ứng dụng đọc trong vòng lặp chặt chẽ nên chỉ gọi channel_size() khi kích thước thực sự có thể thay đổi -- với dữ liệu có kích thước cố định, kích thước từ lần gọi đầu tiên có thể được cache.

12.6.4. Sự độc lập giữa các kênh

Ba điều đáng biết về cách các kênh tương tác:

  • Kiểm soát luồng độc lập. Mỗi kênh có trạng thái đọc đang chờ riêng, dữ liệu riêng, và các hàm gọi lại size / read / write riêng. Một lần đọc kéo dài trên kênh stream không chặn các lần đọc trên kênh config của ứng dụng.

  • Tuần tự theo từng kênh. Trong một kênh đơn, các gói được giao theo thứ tự. Tầng reliability đảm bảo điều này ngay cả khi có retransmit.

  • Transport chung, ngân sách retransmit chung. Tất cả các kênh chia sẻ một liên kết vật lý, vì vậy lưu lượng lớn trên một kênh làm chậm các kênh khác do chiếm dụng đường truyền. Cơ chế CHANNEL_LOCK cho phép một kênh giữ đường truyền cho một lần đọc nguyên tử nhiều gói; backend chọn tham gia bằng cách triển khai các hàm gọi lại lock / unlock.

Một kênh là diện tích bề mặt tối thiểu mà một chương trình host và một chương trình cam đồng ý hợp tác. Tên, hướng (đọc hoặc ghi hoặc cả hai), các phương thức hàm gọi lại phía cam, và các lệnh gọi phương thức tương ứng phía host là toàn bộ hợp đồng.