13.3.1.5. 事件¶
到目前為止的各頁面都是呼叫進入相機:上傳指令碼、讀取影格、寫入頻道。其中每一項操作都是由主機發起的——主機詢問,相機回應。通訊協定也能朝另一個方向運作。相機可以在未被詢問的情況下將 事件(events)推送給主機,而主機 SDK 會將每個事件交付給應用程式可覆寫的回呼函式。
每當應用程式想對相機在被詢問之前所注意到的某件事做出反應時,這就是合適的工具。若沒有事件機制,唯一的查知方式就是在迴圈中不斷呼叫 read_status()。
13.3.1.5.1. 預設回呼函式¶
Camera 內部已訂閱事件。_handle_event() 是傳輸層在每次事件封包抵達時執行的回呼函式。預設會處理三個系統事件:
CHANNEL_REGISTERED——主機連線後,相機上出現了一個新頻道。框架會更新其頻道快取,讓下一次has_channel()查詢能找到它。CHANNEL_UNREGISTERED——某個頻道消失了。SOFT_REBOOT——相機自行重新開機了(看門狗、硬體故障,或刻意呼叫machine.reset())。
它也會追蹤 stream 頻道的影格就緒事件以供串流路徑使用,以及 stdin 頻道的指令碼啟動/停止以供 stdout 緩衝使用。建構子的 events=True 預設值會讓這一切保持啟用;不需要這些功能的應用程式可向 Camera 傳入 events=False,事件子系統便會保持靜默。
13.3.1.5.2. 子類別化以做出反應¶
若要處理相機引發的應用程式專屬事件,請子類別化 Camera 並覆寫 _handle_event()。先呼叫父類別以保留預設行為,然後派發應用程式關心的事件:
from openmv import Camera
class MyCamera(Camera):
def _handle_event(self, channel_id, event):
super()._handle_event(channel_id, event)
name = self.channels_by_id.get(
channel_id, {}).get('name')
if name == 'motion' and event == 1:
self.on_motion()
def on_motion(self):
print("motion detected")
其簽章為 (channel_id, event)。對系統事件而言 channel_id 為 0,否則為引發事件之頻道的數值 ID;event 是相機端指令碼所選擇的整數。EventType 列舉為這三個系統事件命名;頻道事件則使用相機端後端所定義的任何值。
頻道事件回傳時是以數值 ID 而非名稱作為鍵值。上述覆寫所使用的快取 channels_by_id 字典就是用來查詢名稱的;channels_by_name 則是它的鏡像,以另一個方向作為鍵值。
13.3.1.5.3. 相機端部分¶
相機端指令碼透過對 protocol.register() 回傳的控制代碼呼叫 send_event() 來引發事件:
import protocol
class MotionChannel:
def size(self):
return 0
def read(self, offset, size):
return b''
def poll(self):
return False
ch = protocol.register(
name='motion', backend=MotionChannel())
while True:
if detect_motion():
ch.send_event(1)
事件編號是應用程式所選擇的整數。任何主機端覆寫準備好處理的值都可使用;通訊協定層將其視為不透明的酬載。預設情況下,該呼叫會發出後即不再理會;傳入 wait_ack=True 可阻塞直到主機確認,適用於知道事件確實送達比往返延遲更重要的情況。
一個只引發事件而不承載任何可讀資料的頻道是有效的模式——size 回傳 0,read 回傳空位元組。通訊協定函式庫仍需這兩個方法都存在才能將頻道標記為可讀;相機端指令碼只是從不在其中放入資料。
13.3.1.5.4. 在閒置時驅動接收路徑¶
事件與其他所有資料都在同一條連線上抵達,因此任何傳送或接收位元組的主機呼叫,都讓傳輸層有機會就地處理待處理的事件。一個本來就每個週期呼叫一次 read_status() 或 read_frame() 的輪詢迴圈,不需要任何額外處理。
對於數分鐘內都沒有其他 I/O 的程式,poll_events() 會在不傳送命令的情況下執行一次接收路徑。它在輸入緩衝區清空後立即返回,因此環繞它的緊密迴圈——或 GUI 事件迴圈中的一個短計時器——就是讓處理常式保持反應靈敏的方式。
13.3.1.5.5. 一個完整的迴圈¶
從端到端來看,其模式為:相機端指令碼註冊一個頻道,並在某事發生時呼叫 send_event();主機端子類別覆寫 _handle_event() 並進行派發。一個只服務事件而不做其他事的主機迴圈看起來像這樣:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
相機進行擷取、判斷並引發事件。主機停留在 poll_events() 中,直到有事件抵達,接著 on_motion 便執行。在沒有任何事發生時,不會執行 read_status() 呼叫;在相機沒有任何回報內容時,也不會透過 USB 取出影格。