13.3.1.5. Händelser

Sidorna hittills anropar in i kameran: ladda upp ett skript, läs en bildruta, skriv till en kanal. Var och en av dessa operationer initieras av värden – värden frågar, kameran svarar. Protokollet körs också åt andra hållet. Kameran kan skicka händelser till värden utan att bli tillfrågad, och värd-SDK:t levererar var och en till ett återanrop som applikationen kan åsidosätta.

Detta är rätt verktyg närhelst applikationen vill reagera på något kameran märkt innan den frågar efter det. Utan händelser är det enda sättet att få reda på det att fortsätta anropa read_status() i en slinga.

13.3.1.5.1. Standardåteranropet

Camera prenumererar redan på händelser internt. _handle_event() är det återanrop som transporten kör närhelst ett händelsepaket anländer. Standardvarianten hanterar tre systemhändelser:

  • CHANNEL_REGISTERED – en ny kanal dök upp på kameran efter att värden anslutit. Ramverket uppdaterar sin kanalcache så att nästa has_channel()-uppslagning hittar den.

  • CHANNEL_UNREGISTERED – en kanal försvann.

  • SOFT_REBOOT – kameran startade om på egen hand (watchdog, hårt fel, avsiktlig machine.reset()).

Den spårar också stream-kanalens bildruta-redo-händelse för strömningsvägen och stdin-kanalens skriptstart/-stopp för stdout-buffring. Konstruktorns standardinställning events=True håller allt detta igång; en applikation som inte vill ha något av det kan skicka events=False till Camera, varpå händelsesubsystemet förblir tyst.

13.3.1.5.2. Subklassa för att reagera

För att hantera applikationsspecifika händelser som kameran utlöser, subklassa Camera och åsidosätt _handle_event(). Anropa föräldern först för att behålla standardbeteendet, och dela sedan ut de händelser applikationen bryr sig om:

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

Signaturen är (channel_id, event). channel_id är 0 för systemhändelser och annars det numeriska ID:t för den kanal som utlöste den; event är ett heltal som skriptet på kamerasidan valt. EventType-enumen ger namn åt de tre systemhändelserna; kanalhändelser använder de värden som backenden på kamerasidan definierar.

Kanalhändelser kommer tillbaka nycklade på numeriskt ID, inte på namn. Den cachade channels_by_id-ordboken är det som åsidosättningen ovan använder för att slå upp namnet; channels_by_name är dess spegel, nycklad åt andra hållet.

13.3.1.5.3. Halvan på kamerasidan

Skriptet på kamerasidan utlöser en händelse genom att anropa send_event() på det handtag som returneras från 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)

Händelsenumret är ett heltal valt av applikationen. Vilket värde som helst som värdens åsidosättning är beredd att hantera är tillåtet; protokolllagret behandlar det som ogenomskinlig nyttolast. Som standard utlöses anropet och glöms bort; skicka wait_ack=True för att blockera tills värden bekräftar, när det är viktigare att veta att händelsen kom fram än latensen för tur- och returresan.

En kanal som bara utlöser händelser och inte bär någon läsbar data är ett giltigt mönster – size returnerar 0 och read returnerar tomma byte. Protokollbiblioteket kräver fortfarande att båda metoderna finns för att markera kanalen som läsbar; skriptet på kamerasidan lägger bara aldrig någon data i den.

13.3.1.5.4. Driva mottagningsvägen under inaktivitet

Händelser anländer på samma anslutning som allt annat, så varje värdanrop som skickar eller tar emot byte ger transporten en chans att bearbeta väntande händelser i farten. En pollningsslinga som redan anropar read_status() eller read_frame() en gång per cykel behöver inget extra.

För program som går minuter utan annan I/O kör poll_events() mottagningsvägen en gång utan att skicka ett kommando. Den returnerar så snart den inkommande bufferten är tom, så en tät slinga runt den – eller en kort timer i en GUI-händelseslinga – är vad som håller hanterarna reaktiva.

13.3.1.5.5. En komplett slinga

Från början till slut är mönstret: skriptet på kamerasidan registrerar en kanal och anropar send_event() när något händer; subklassen på värdsidan åsidosätter _handle_event() och delar ut händelserna. En värdslinga som inte gör något annat än att betjäna händelser ser ut så här:

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

    while True:
        cam.poll_events()

Kameran fångar, beslutar och utlöser händelser. Värden sitter inuti poll_events() tills en anländer, varpå on_motion körs. Inget anrop till read_status() körs när inget har hänt, och ingen bildruta hämtas över USB när kameran inte har något att rapportera.