13.3.1.5. Kejadian

Halaman-halaman sebelumnya memanggil kamera: unggah skrip, baca bingkai, tulis ke saluran. Setiap operasi tersebut dimulai oleh host -- host bertanya, kamera merespons. Protokol juga berjalan ke arah sebaliknya. Kamera dapat mendorong kejadian ke host tanpa diminta, dan SDK host mengirimkan setiap kejadian ke callback yang dapat diganti oleh aplikasi.

Ini adalah alat yang tepat setiap kali aplikasi ingin bereaksi terhadap sesuatu yang diperhatikan kamera sebelum ditanya. Tanpa kejadian, satu-satunya cara untuk mengetahuinya adalah dengan terus memanggil read_status() dalam sebuah loop.

13.3.1.5.1. Callback default

Camera sudah berlangganan kejadian secara internal. _handle_event() adalah callback yang dijalankan transport setiap kali paket kejadian tiba. Default menangani tiga kejadian sistem:

  • CHANNEL_REGISTERED -- saluran baru muncul di kamera setelah host terhubung. Framework menyegarkan cache salurannya sehingga pencarian has_channel() berikutnya menemukannya.

  • CHANNEL_UNREGISTERED -- sebuah saluran menghilang.

  • SOFT_REBOOT -- kamera melakukan reboot sendiri (watchdog, hard fault, machine.reset() yang disengaja).

Ia juga melacak kejadian frame-ready saluran stream untuk jalur streaming dan mulai/berhenti skrip saluran stdin untuk buffering stdout. Default konstruktor events=True menjaga semua ini aktif; aplikasi yang tidak menginginkannya dapat meneruskan events=False ke Camera dan subsistem kejadian tetap diam.

13.3.1.5.2. Subclassing untuk bereaksi

Untuk menangani kejadian spesifik aplikasi yang dimunculkan kamera, buat subclass Camera dan timpa _handle_event(). Panggil parent terlebih dahulu untuk mempertahankan perilaku default, lalu proses kejadian yang dipedulikan aplikasi:

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

Tanda tangannya adalah (channel_id, event). channel_id adalah 0 untuk kejadian sistem dan ID numerik saluran yang memunculkannya untuk yang lainnya; event adalah integer yang dipilih oleh skrip sisi kamera. Enum EventType memberikan nama pada tiga kejadian sistem; kejadian saluran menggunakan nilai apa pun yang didefinisikan backend sisi kamera.

Kejadian saluran dikembalikan dengan kunci ID numerik, bukan nama. Dict channels_by_id yang di-cache adalah yang digunakan override di atas untuk mencari nama; channels_by_name adalah cerminnya, dengan kunci sebaliknya.

13.3.1.5.3. Bagian sisi kamera

Skrip sisi kamera memunculkan kejadian dengan memanggil send_event() pada handle yang dikembalikan dari 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)

Nomor kejadian adalah integer yang dipilih oleh aplikasi. Nilai apa pun yang siap ditangani oleh override host adalah valid; lapisan protokol memperlakukannya sebagai payload buram. Secara default panggilan ini bersifat fire-and-forget; teruskan wait_ack=True untuk memblokir hingga host mengakui, ketika mengetahui bahwa kejadian telah diterima lebih penting daripada latensi round trip.

Saluran yang hanya memicu kejadian dan tidak membawa data yang dapat dibaca adalah pola yang valid -- size mengembalikan 0 dan read mengembalikan byte kosong. Library protokol masih membutuhkan kedua metode tersebut untuk menandai saluran sebagai dapat dibaca; skrip sisi kamera hanya tidak pernah memasukkan data ke dalamnya.

13.3.1.5.4. Menjalankan jalur penerimaan saat diam

Kejadian tiba pada koneksi yang sama seperti semua hal lainnya, sehingga setiap panggilan host yang mengirim atau menerima byte memberi transport kesempatan untuk memproses kejadian yang tertunda secara inline. Loop polling yang sudah memanggil read_status() atau read_frame() sekali per siklus tidak membutuhkan apa pun lagi.

Untuk program yang berjalan selama beberapa menit tanpa I/O lain, poll_events() menjalankan jalur penerimaan sekali tanpa mengirim perintah. Ia kembali segera setelah buffer masuk kosong, sehingga loop ketat di sekitarnya -- atau timer singkat dalam loop kejadian GUI -- adalah yang membuat handler tetap responsif.

13.3.1.5.5. Loop lengkap

Dari ujung ke ujung, polanya adalah: skrip sisi kamera mendaftarkan saluran dan memanggil send_event() ketika sesuatu terjadi; subclass sisi host menimpa _handle_event() dan memprosesnya. Loop host yang hanya melayani kejadian terlihat seperti:

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

    while True:
        cam.poll_events()

Kamera menangkap, memutuskan, dan memunculkan kejadian. Host berada di dalam poll_events() hingga satu kejadian tiba, kemudian on_motion berjalan. Tidak ada panggilan read_status() yang berjalan ketika tidak ada yang terjadi, dan tidak ada bingkai yang ditarik melalui USB ketika kamera tidak memiliki apa pun untuk dilaporkan.