13.3.1.5. Ereignisse¶
Die bisherigen Seiten rufen die Kamera auf: ein Skript hochladen, ein Einzelbild lesen, in einen Kanal schreiben. Jeder dieser Vorgänge wird vom Host ausgelöst – der Host fragt, die Kamera antwortet. Das Protokoll funktioniert auch in die andere Richtung. Die Kamera kann Ereignisse an den Host senden, ohne gefragt zu werden, und das Host-SDK liefert jedes einzelne an einen Callback, den die Anwendung überschreiben kann.
Dies ist das richtige Werkzeug, wann immer die Anwendung auf etwas reagieren möchte, das die Kamera bemerkt hat, bevor sie danach fragt. Ohne Ereignisse ist die einzige Möglichkeit, es herauszufinden, read_status() wiederholt in einer Schleife aufzurufen.
13.3.1.5.1. Der Standard-Callback¶
Camera abonniert bereits intern Ereignisse. _handle_event() ist der Callback, den der Transport ausführt, sobald ein Ereignispaket eintrifft. Der Standard behandelt drei Systemereignisse:
CHANNEL_REGISTERED– ein neuer Kanal ist auf der Kamera erschienen, nachdem der Host die Verbindung hergestellt hat. Das Framework aktualisiert seinen Kanal-Cache, sodass die nächstehas_channel()-Abfrage ihn findet.CHANNEL_UNREGISTERED– ein Kanal ist verschwunden.SOFT_REBOOT– die Kamera hat von sich aus neu gestartet (Watchdog, Hard Fault, absichtlichesmachine.reset()).
Es verfolgt außerdem das Frame-Ready-Ereignis des stream-Kanals für den Streaming-Pfad sowie Start/Stopp des Skripts im stdin-Kanal für die stdout-Pufferung. Der Standardwert events=True des Konstruktors hält all dies aktiv; eine Anwendung, die nichts davon möchte, kann events=False an Camera übergeben, und das Ereignis-Subsystem bleibt still.
13.3.1.5.2. Ableiten zum Reagieren¶
Um anwendungsspezifische Ereignisse zu behandeln, die die Kamera auslöst, leiten Sie von Camera ab und überschreiben Sie _handle_event(). Rufen Sie zuerst die Elternklasse auf, um das Standardverhalten beizubehalten, und verarbeiten Sie dann die Ereignisse, die für die Anwendung relevant sind:
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")
Die Signatur ist (channel_id, event). channel_id ist 0 für Systemereignisse und andernfalls die numerische ID des Kanals, der es ausgelöst hat; event ist eine ganze Zahl, die das kameraseitige Skript gewählt hat. Das EventType-Enum gibt den drei Systemereignissen Namen; Kanalereignisse verwenden die Werte, die das kameraseitige Backend definiert.
Kanalereignisse kommen nach numerischer ID zurück, nicht nach Name. Das zwischengespeicherte channels_by_id-Dict ist das, was die obige Überschreibung verwendet, um den Namen nachzuschlagen; channels_by_name ist das entsprechende Gegenstück, das nach dem anderen Schlüssel sortiert ist.
13.3.1.5.3. Die kameraseitige Hälfte¶
Das kameraseitige Skript löst ein Ereignis aus, indem es send_event() auf dem von protocol.register() zurückgegebenen Handle aufruft:
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)
Die Ereignisnummer ist eine von der Anwendung gewählte ganze Zahl. Jeder Wert, den die Überschreibung des Hosts behandeln kann, ist zulässig; die Protokollschicht behandelt ihn als undurchsichtige Nutzdaten. Standardmäßig löst der Aufruf das Ereignis aus und vergisst es; übergeben Sie wait_ack=True, um zu blockieren, bis der Host bestätigt – wenn es wichtiger ist, zu wissen, dass das Ereignis angekommen ist, als die Latenz des Hin- und Rückwegs.
Ein Kanal, der nur Ereignisse auslöst und keine lesbaren Daten transportiert, ist ein gültiges Muster – size gibt 0 zurück und read gibt leere Bytes zurück. Die Protokollbibliothek benötigt dennoch beide Methoden, um den Kanal als lesbar zu kennzeichnen; das kameraseitige Skript legt einfach nie Daten hinein.
13.3.1.5.4. Den Empfangspfad im Leerlauf antreiben¶
Ereignisse treffen über dieselbe Verbindung wie alles andere ein, sodass jeder Host-Aufruf, der Bytes sendet oder empfängt, dem Transport die Möglichkeit gibt, anstehende Ereignisse inline zu verarbeiten. Eine Abfrageschleife, die ohnehin einmal pro Zyklus read_status() oder read_frame() aufruft, benötigt nichts Zusätzliches.
Für Programme, die minutenlang ohne anderen I/O auskommen, führt poll_events() den Empfangspfad einmal aus, ohne einen Befehl zu senden. Es kehrt zurück, sobald der eingehende Puffer leer ist, sodass eine enge Schleife darum herum – oder ein kurzer Timer in einer GUI-Ereignisschleife – die Handler reaktionsfähig hält.
13.3.1.5.5. Eine vollständige Schleife¶
Von Anfang bis Ende lautet das Muster: Das kameraseitige Skript registriert einen Kanal und ruft send_event() auf, wenn etwas passiert; die hostseitige Unterklasse überschreibt _handle_event() und verarbeitet das Ereignis. Eine Host-Schleife, die nichts anderes tut, als Ereignisse zu bedienen, sieht so aus:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
Die Kamera nimmt auf, entscheidet und löst Ereignisse aus. Der Host wartet innerhalb von poll_events(), bis eines eintrifft, dann läuft on_motion. Kein read_status()-Aufruf wird ausgeführt, wenn nichts passiert ist, und kein Einzelbild wird über USB übertragen, wenn die Kamera nichts zu melden hat.