12.9. การไหลสองทิศทาง

ช่องไม่ได้เป็นทิศทางเดียว แบ็กเอนด์ที่ใช้งาน write ให้โฮสต์ส่งไบต์ ไปยัง กล้องได้ และกล้องจะตอบสนอง นั่นคือรูปแบบเบื้องหลังเครื่องมือโต้ตอบจริงทุกตัว: ผู้ปฏิบัติงานหมุนปุ่มบน GUI ของโฮสต์ โฮสต์เขียนค่าใหม่ไปยังช่อง config กล้องอ่านค่าในครั้งถัดไปที่ถ่าย

12.9.1. ช่อง config

เพิ่มสคริปต์ฝั่งกล้องสตรีมมิ่ง โดยเปิดเผยช่องที่สองสำหรับคุณภาพ 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)

ลูปการถ่ายภาพอ่านจาก config.quality เมื่อใดก็ตามที่บีบอัดเฟรม:

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

โฮสต์ตอนนี้มีปุ่มควบคุม ตั้งค่าเป็น 50 และเฟรมถัดไปจะเล็กลง (และดูไม่สวย) ตั้งค่าเป็น 95 และเฟรมถัดไปจะใหญ่ขึ้น (และคมชัดขึ้น) กล้องยังคงถ่ายต่อไปโดยไม่ต้องรีสตาร์ท โฮสต์ไม่ต้องส่งสคริปต์ใหม่

12.9.2. การเรียก write จากโฮสต์

ฝั่งโฮสต์ channel_write() ส่งไบต์ไปยังช่องที่มีชื่อ:

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

SDK ของโฮสต์เข้ารหัสไบต์เป็นแพ็กเก็ต CHANNEL_WRITE เดี่ยว (หรือหลายส่วน) ชั้นโปรโตคอลส่งมอบไปยังกล้อง write(offset=0, data=...) ของกล้องทำงาน และฝั่งกล้องตอบรับ เมื่อการเรียกคืนค่าแล้ว กล้องได้รับและยอมรับค่าใหม่แล้ว

การเขียนเป็นแบบ atomic จากมุมมองของกล้อง -- ไลบรารีโปรโตคอลรับประกันว่า write ของแบ็กเอนด์ทำงานจนเสร็จสมบูรณ์ก่อนที่การดำเนินการอื่น ๆ บนช่องนั้นจะดำเนินต่อ โค้ดแอปพลิเคชันสามารถอ่าน config.quality จากภายในลูปการถ่ายภาพโดยไม่ต้องกังวลว่าโฮสต์จะมาแทรกกลางสแนปช็อต

12.9.3. ขนาด stub และการอ่านบนช่องที่เขียนอย่างเดียว

ช่องที่เขียนอย่างเดียวยังต้องการ size และ read ที่กำหนดไว้ แม้ว่าจะเป็นแค่ stub ที่คืน 0 และ b'' ไลบรารีใช้ การมีอยู่ ของเมธอดเพื่อหาค่าแฟล็กความสามารถของช่อง แบ็กเอนด์ที่ขาด read จะไม่ได้รับการตั้งค่า CHANNEL_FLAG_READ และโฮสต์จะปฏิเสธการพยายามอ่าน

ไบต์ที่คืนมาจาก read บนช่องที่เขียนอย่างเดียวมีประโยชน์สำหรับจุดประสงค์ที่แตกต่างออกไป: การสะท้อนค่าปัจจุบันกลับมาเพื่อให้โฮสต์ที่เพิ่งเชื่อมต่อสามารถถามกล้องว่า "การตั้งค่าปัจจุบันคืออะไร?" แทนที่จะเริ่มจากค่าเริ่มต้น เพื่อให้สิ่งนี้ทำงานได้ทั้งสองทิศทางต้องตกลงกันเรื่องการซีเรียลไลเซชัน การแยกวิเคราะห์ raw-bytes int(bytes(data)) ในตัวอย่างก่อนหน้าทำงานได้สำหรับฟิลด์จำนวนเต็มเดี่ยวแต่จะไม่ขยายเมื่อมีปุ่มที่สองต้องตั้งค่า การเปลี่ยน write ให้แยกวิเคราะห์ JSON และจับคู่กับ read ที่คืน JSON dump ที่ตรงกันเปลี่ยนช่องให้กลายเป็นพื้นที่เก็บค่า config แบบ round-trip จริง:

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)

ตอนนี้โฮสต์เขียน cam.channel_write('config', b'{"quality": 50}') เพื่อตั้งค่าและ cam.channel_read('config') เพื่ออ่านสถานะปัจจุบันกลับมา กล้องซีเรียลไลซ์ JSON dump ใหม่ในทุกการอ่านเพื่อให้โฮสต์เห็นค่าล่าสุดเสมอ และการเพิ่มปุ่มอีกอัน (threshold, exposure, orientation) คือหนึ่งบรรทัดใน dict JSON ในแต่ละฝั่ง

12.9.4. ลูปสมบูรณ์

ด้วยช่องเฟรมสำหรับข้อมูลกล้อง → โฮสต์ ช่อง config สำหรับควบคุมโฮสต์ → กล้อง และกาวเล็กน้อย แอปพลิเคชันกลายเป็นเครื่องมือโต้ตอบ:

  • โฮสต์เปิดกล้อง เริ่มดึงเฟรม และแสดงในหน้าต่าง

  • เมื่อผู้ปฏิบัติงานลาก slider โฮสต์เขียนค่าใหม่บน config

  • ลูปการถ่ายภาพของกล้องรับค่าในเฟรมถัดไป

  • เฟรมใหม่ไหลผ่านช่อง frame เดิม

นั่นคือโมเดลทั้งหมด สองช่อง สองคอลแบ็กแต่ละช่อง ลูปการถ่ายภาพบนกล้อง ลูปอ่านและเขียนบนโฮสต์ ไม่มีตรรกะการจัดกรอบให้เห็น ไม่มีการจัดการข้อผิดพลาดให้เห็น -- ไลบรารีโปรโตคอลทำให้การเคลื่อนย้ายไบต์ที่เชื่อถือได้หายไป

ทุกอย่างหลังจากจุดนี้คือโค้ดแอปพลิเคชัน การเพิ่มช่องที่สามสำหรับฮิสโตแกรม ช่องที่สี่สำหรับข้อมูลวัด หรือช่องที่ห้าสำหรับทริกเกอร์เซนเซอร์คือสูตร backend-class-and-protocol.register เดิม ที่ทำซ้ำ เมื่อโปรเจกต์กล้องถึงจุดนี้ โปรโตคอลก็หยุดเป็นปัญหาที่น่าสนใจ ตรรกะของแอปพลิเคชันเองต่างหากที่ทำ