13.3.1.4. 自訂頻道

頻道(channel)是相機端指令碼與主機之間具名的雙向位元組串流。相機註冊一個頻道並提供產生或消耗資料的回呼函式;主機則依名稱讀取與寫入該頻道。套件內部用於承載影格的 stream 頻道、承載指令碼輸出的 stdout 頻道,以及承載指令碼上傳的 stdin 頻道所使用的同一套機制,也開放給使用者指令碼使用,因此主機所需的任何應用程式專屬資料都能搭乘同一條 USB 連線,無需另外發明第二套通訊協定。

這是本套件最實用的功能,卻也是標準文件介紹得最不完善的部分,因此本頁將從頭到尾完整說明它。

13.3.1.4.1. 兩個部分

自訂頻道需要兩端互相配合的程式碼。相機端指令碼 匯入 protocol,定義一個具有三個方法(size()read()poll())外加一個選用的 write() 的類別,並呼叫 protocol.register(name=..., backend=...) 以所選的名稱發布該頻道:

import protocol
import time

class TicksChannel:
    def size(self):
        return 10

    def read(self, offset, size):
        return f'{time.ticks_ms():010d}'

    def poll(self):
        return True

protocol.register(name='ticks', backend=TicksChannel())

size() 方法回傳該頻道目前可用的位元組數。read() 是產生端:給定主機要求的 offsetsize,它回傳這些位元組(或由通訊協定層編碼的字串)。poll() 在有資料可讀時回傳 True——通訊協定層用它在 read_status() 中將頻道標記為就緒。

主機端程式 使用四個 openmv.Camera 方法:has_channel() 檢查頻道是否存在、channel_size() 查詢有多少資料正在等待、channel_read() 取出位元組,以及 channel_write() 推入位元組。read_status() 一次輪詢每一個頻道:

from openmv import Camera

with Camera('/dev/ttyACM0') as cam:
    cam.stop()
    cam.exec(open('ticks_cam.py').read())

    while True:
        status = cam.read_status()

        if status.get('ticks'):
            data = cam.channel_read('ticks')
            print(f"ticks: {data.decode()}")

主機迴圈輪詢 read_status();當 ticks 頻道就緒時,它呼叫 channel_read() 且不帶 size,以取出任何可用的資料。相機的 TicksChannel.poll() 在每次檢查時都回傳 True,因此該頻道永遠處於「就緒」狀態,主機在每次輪詢時都會取得一個最新的 tick 值。

13.3.1.4.2. 雙向頻道

對於需要回推資料的主機,相機端類別會新增一個接受傳入位元組的 write() 方法:

import protocol

class CommandChannel:
    def __init__(self):
        self.last_command = b''
        self.replied = False

    def size(self):
        return len(self.last_command)

    def read(self, offset, size):
        self.replied = True
        return self.last_command

    def write(self, offset, data):
        self.last_command = b'echo: ' + bytes(data)
        self.replied = False

    def poll(self):
        return not self.replied and len(self.last_command) > 0

protocol.register(name='echo', backend=CommandChannel())

主機用 channel_write() 寫入該頻道,並透過慣用的 read_status() / channel_read() 模式讀回回應:

with Camera('/dev/ttyACM0') as cam:
    cam.stop()
    cam.exec(open('echo_cam.py').read())

    cam.channel_write('echo', b'hello')

    while True:
        if cam.read_status().get('echo'):
            print(cam.channel_read('echo').decode())
            break

13.3.1.4.3. 這為應用程式帶來什麼

每當應用程式想搭乘現有的 USB 連線來傳輸非影格、非輸出的資料時,自訂頻道就是合適的工具:遙測計數器、從主機 UI 即時串流的設定選項、反向傳送的控制命令,或是相機計算出但不符合 stream 頻道所假設「影像」框架的量測結果。通訊協定層處理框架化、分段、確認與重試;指令碼只需實作四個方法的後端,而主機只需知道頻道名稱與資料形狀。

CLI 的 --channel NAME 旗標是一種無需撰寫主機端程式、即可從終端機驗證自訂頻道的快速方法:CLI 輪詢指定的頻道,並列印每次更新的前十個位元組。

單次 channel_read()channel_write() 呼叫的大小上限為通訊協定協商出的 max_payload——預設為 4096 位元組。主機端方法會自動將較大的寫入分割為適當數量的封包,因此應用程式可傳入任意大的緩衝區;分段過程是看不見的。