13.3.1.5. Події¶
Попередні сторінки викликають дії на камері: завантажити скрипт, зчитати кадр, записати в канал. Кожна з цих операцій ініціюється хостом — хост запитує, камера відповідає. Протокол також працює у зворотному напрямку. Камера може надсилати події хосту без запиту, і 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 зберігає все це увімкненим; застосунок, якому нічого з цього не потрібно, може передати events=False до Camera, і підсистема подій залишиться тихою.
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. Половина на стороні камери¶
Скрипт на стороні камери піднімає подію, викликаючи send_event() на дескрипторі, поверненому з protocol.register()
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, коли камері нема про що звітувати.