13.3.1.5. Események

Az eddigi oldalak a kamera felé hívnak: szkript feltöltése, képkocka olvasása, csatornára írás. Ezen műveletek mindegyikét a gazdagép kezdeményezi – a gazdagép kérdez, a kamera válaszol. A protokoll a másik irányban is működik. A kamera eseményeket tolhat a gazdagépnek anélkül, hogy megkérdeznék, és a gazda SDK mindegyiket eljuttatja egy visszahíváshoz, amelyet az alkalmazás felülírhat.

Ez a megfelelő eszköz, amikor az alkalmazás reagálni szeretne valamire, amit a kamera észrevett, mielőtt rákérdezne. Események nélkül az egyetlen mód a kiderítésére az, hogy folyamatosan, ciklusban hívjuk a read_status() metódust.

13.3.1.5.1. Az alapértelmezett visszahívás

A Camera belsőleg már feliratkozik az eseményekre. A _handle_event() az a visszahívás, amelyet a transzportréteg lefuttat, valahányszor egy eseménycsomag megérkezik. Az alapértelmezett három rendszereseményt kezel:

  • CHANNEL_REGISTERED – egy új csatorna jelent meg a kamerán, miután a gazdagép csatlakozott. A keretrendszer frissíti a csatorna-gyorsítótárát, így a következő has_channel() keresés megtalálja azt.

  • CHANNEL_UNREGISTERED – egy csatorna eltűnt.

  • SOFT_REBOOT – a kamera magától újraindult (watchdog, hardverhiba, szándékos machine.reset()).

Nyomon követi a stream csatorna képkocka-kész eseményét is a streamelési útvonalhoz, valamint a stdin csatorna szkriptindítását/-leállítását az stdout-pufferezéshez. A konstruktor events=True alapértelmezése mindezt bekapcsolva tartja; egy alkalmazás, amely semmit sem akar belőle, átadhatja az events=False értéket a Camera osztálynak, és az eseményalrendszer csendben marad.

13.3.1.5.2. Alosztályozás a reagáláshoz

A kamera által kiváltott alkalmazásspecifikus események kezeléséhez hozz létre alosztályt a Camera osztályból, és írd felül a _handle_event() metódust. Hívd először a szülőt az alapértelmezett viselkedés megtartásához, majd irányítsd el azokat az eseményeket, amelyek az alkalmazást érdeklik:

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

Az aláírás (channel_id, event). A channel_id rendszeresemények esetén 0, egyébként pedig annak a csatornának a numerikus azonosítója, amely kiváltotta; az event egy egész szám, amelyet a kameraoldali szkript választott. A EventType enum neveket ad a három rendszereseménynek; a csatornaesemények bármilyen értéket használnak, amelyet a kameraoldali backend definiál.

A csatornaesemények numerikus azonosító, nem pedig név szerint kulcsolva érkeznek vissza. A gyorsítótárazott channels_by_id szótár az, amelyet a fenti felülírás a név kikereséséhez használ; a channels_by_name ennek a tükre, a másik irányban kulcsolva.

13.3.1.5.3. A kameraoldali fél

A kameraoldali szkript egy esemény kiváltásához meghívja a send_event() metódust a protocol.register() által visszaadott azonosítón:

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)

Az eseményszám egy, az alkalmazás által választott egész szám. Bármilyen érték megengedett, amelyet a gazdagép felülírása kezelni képes; a protokollréteg átlátszatlan hasznos teherként kezeli. Alapértelmezés szerint a hívás kiküld és elfelejt; add meg a wait_ack=True értéket, hogy blokkoljon, amíg a gazdagép visszaigazol, amikor annak ismerete, hogy az esemény megérkezett, fontosabb, mint az oda-vissza út késleltetése.

Egy csatorna, amely csak eseményeket vált ki és nem hordoz olvasható adatot, érvényes minta – a size 0 értéket ad vissza, a read pedig üres bájtokat. A protokollkönyvtárnak ennek ellenére mindkét metódusra szüksége van ahhoz, hogy a csatornát olvashatóként jelölje; a kameraoldali szkript egyszerűen soha nem tesz bele adatot.

13.3.1.5.4. A fogadási útvonal hajtása tétlenség közben

Az események ugyanazon a kapcsolaton érkeznek, mint minden más, így bármely gazdahívás, amely bájtokat küld vagy fogad, lehetőséget ad a transzportrétegnek a függőben lévő események menet közbeni feldolgozására. Egy lekérdező ciklus, amely már ciklusonként egyszer meghívja a read_status() vagy a read_frame() metódust, nem igényel semmi extrát.

Olyan programok esetén, amelyek percekig elvannak más I/O nélkül, a poll_events() egyszer lefuttatja a fogadási útvonalat parancs küldése nélkül. Amint a bejövő puffer kiürül, visszatér, így egy körülötte futó szoros ciklus – vagy egy rövid időzítő egy GUI eseményciklusban – az, ami a kezelőket reagálóképesen tartja.

13.3.1.5.5. Egy teljes ciklus

Végponttól végpontig a minta a következő: a kameraoldali szkript regisztrál egy csatornát, és meghívja a send_event() metódust, amikor valami történik; a gazdaoldali alosztály felülírja a _handle_event() metódust és elirányítja. Egy gazdaciklus, amely nem tesz mást, csak az eseményeket szolgálja ki, így néz ki:

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

    while True:
        cam.poll_events()

A kamera rögzít, dönt és eseményeket vált ki. A gazdagép a poll_events() metóduson belül ül, amíg egy meg nem érkezik, majd lefut az on_motion. Egyetlen read_status() hívás sem fut, amikor semmi nem történt, és egyetlen képkockát sem húzunk át az USB-n, amikor a kamerának nincs semmi jelentenivalója.