12.9. Aliran dua arah

Saluran tidak satu arah. Backend yang mengimplementasikan write memungkinkan host mendorong byte ke cam, dan cam bereaksi. Itulah pola di balik setiap alat interaktif nyata: operator memutar kenop di GUI host, host menulis nilai baru ke saluran config, cam membacanya saat berikutnya melakukan capture.

12.9.1. Saluran config

Menambah skrip sisi cam streaming, ekspos saluran kedua untuk kualitas JPEG:

class ConfigChannel:
    def __init__(self):
        self.quality = 85

    def size(self):
        return 0

    def read(self, offset, size):
        # Not used for "host writes to cam" -- but the library
        # still needs the method present.
        return b''

    def write(self, offset, data):
        # data is a bytearray view into the protocol buffer.
        # Copy out the contents before doing anything with it.
        new_q = int(bytes(data))
        if 1 <= new_q <= 100:
            self.quality = new_q
        return len(data)

config = ConfigChannel()
protocol.register(name='config', backend=config)

Loop capture membaca dari config.quality setiap kali mengkompresi bingkai:

while True:
    img = csi0.snapshot()
    latest_jpeg = bytes(
        img.compress(quality=config.quality).bytearray()
    )
    ch.send_event(0x01)

Host sekarang memiliki kenop. Atur ke 50 dan bingkai berikutnya lebih kecil (dan lebih jelek); atur ke 95 dan bingkai berikutnya lebih besar (dan lebih tajam). Cam terus melakukan capture tanpa memulai ulang; host tidak harus mendorong skrip baru.

12.9.2. Panggilan write dari host

Di sisi host, channel_write() mengirimkan byte ke saluran bernama:

cam.channel_write('config', b'50')

SDK host mengenkode byte sebagai satu paket CHANNEL_WRITE (atau terfragmentasi), lapisan protokol mengantarkannya ke cam, write(offset=0, data=...) cam berjalan, dan sisi cam mengonfirmasi. Saat panggilan kembali, cam telah menerima dan menerima nilai baru.

Penulisan bersifat atomik dari sudut pandang cam -- pustaka protokol menjamin write backend berjalan hingga selesai sebelum operasi lain pada saluran tersebut berlanjut. Kode aplikasi dapat membaca config.quality dari dalam loop capture tanpa khawatir host menimpa di tengah snapshot.

12.9.3. Ukuran stub dan pembacaan pada saluran write-only

Saluran write murni masih memerlukan size dan read yang didefinisikan, meskipun hanya stub yang mengembalikan 0 dan b''. Pustaka menggunakan keberadaan metode untuk menurunkan flag kemampuan saluran; backend yang tidak memiliki read tidak akan mendapatkan CHANNEL_FLAG_READ disetel dan host akan menolak upaya pembacaan.

Namun byte yang dikembalikan dari read pada saluran write-only berguna untuk tujuan yang berbeda: menggemakan kembali nilai saat ini sehingga host yang baru terhubung dapat bertanya kepada cam "apa pengaturan saat ini?" daripada memulai dari default. Agar keduanya bekerja, kedua arah harus menyetujui serialisasi. Penguraian byte mentah int(bytes(data)) dalam contoh sebelumnya bekerja untuk satu bidang integer tetapi tidak akan berskala begitu ada kenop kedua yang harus disetel. Mengalihkan write untuk mengurai JSON dan memasangkannya dengan read yang mengembalikan JSON dump yang sesuai mengubah saluran menjadi penyimpanan konfigurasi round-trip yang sebenarnya:

import json

class ConfigChannel:
    def __init__(self):
        self.quality = 85
        self._buf = b''
    def size(self):
        self._buf = json.dumps({'quality': self.quality}).encode()
        return len(self._buf)
    def read(self, offset, size):
        return self._buf[offset:offset + size]
    def write(self, offset, data):
        new = json.loads(bytes(data))
        if 'quality' in new:
            self.quality = int(new['quality'])
        return len(data)

Host sekarang menulis cam.channel_write('config', b'{"quality": 50}') untuk menyetel nilai dan cam.channel_read('config') untuk membaca kembali status saat ini. Cam membuat serialisasi JSON dump baru pada setiap pembacaan sehingga host selalu melihat nilai terbaru, dan menambahkan kenop lain (threshold, exposure, orientation) adalah satu baris dalam dict JSON di setiap sisi.

12.9.4. Loop lengkap

Dengan saluran bingkai untuk data cam → host, saluran config untuk kontrol host → cam, dan sedikit lem, aplikasi menjadi alat interaktif:

  • Host membuka cam, mulai menarik bingkai, dan menampilkannya di jendela.

  • Ketika operator menyeret slider, host menulis nilai baru pada config.

  • Loop capture cam mengambil nilai pada bingkai berikutnya.

  • Bingkai baru mengalir melalui saluran frame yang sama.

Itulah seluruh modelnya. Dua saluran, dua callback masing-masing, loop capture di cam, loop baca-dan-tulis di host. Tidak ada logika framing yang terlihat, tidak ada penanganan kesalahan yang terlihat -- pustaka protokol membuat pergerakan byte yang andal menghilang.

Semua yang melewati titik ini adalah kode aplikasi. Menambahkan saluran ketiga untuk histogram, keempat untuk telemetri, atau kelima untuk pemicu sensor adalah resep backend-class-dan-protocol.register yang sama, diulang. Setelah proyek cam mencapai titik ini, protokol berhenti menjadi masalah yang menarik; logika aplikasi sendiri yang melakukannya.