12.9. Luồng dữ liệu hai chiều

Các kênh không phải một chiều. Một backend triển khai write cho phép host đẩy byte tới cam, và cam phản ứng. Đó là mẫu đằng sau mọi công cụ tương tác thực sự: người vận hành xoay một núm trên GUI host, host ghi giá trị mới vào kênh cấu hình, cam đọc giá trị đó vào lần tiếp theo nó chụp ảnh.

12.9.1. Kênh cấu hình

Thêm vào tập lệnh phía cam phát trực tuyến, hiển thị một kênh thứ hai cho chất lượng 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)

Vòng lặp chụp ảnh đọc từ config.quality khi nó nén một khung hình:

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

Host giờ đây có một núm điều chỉnh. Đặt nó thành 50 và khung hình tiếp theo sẽ nhỏ hơn (và xấu hơn); đặt nó thành 95 và khung hình tiếp theo sẽ lớn hơn (và sắc nét hơn). Cam tiếp tục chụp mà không cần khởi động lại; host không cần đẩy một tập lệnh mới.

12.9.2. Lệnh ghi từ host

Ở phía host, channel_write() gửi byte đến một kênh được đặt tên:

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

SDK host mã hóa các byte thành một gói CHANNEL_WRITE duy nhất (hoặc được phân mảnh), lớp giao thức chuyển nó đến cam, write(offset=0, data=...) của cam chạy, và phía cam xác nhận. Đến khi lệnh gọi trả về, cam đã nhận và chấp nhận giá trị mới.

Lệnh ghi là nguyên tử từ góc nhìn của cam -- thư viện giao thức đảm bảo write của backend chạy đến hoàn thành trước khi bất kỳ thao tác nào khác trên kênh đó tiếp tục. Mã ứng dụng có thể đọc config.quality từ bên trong vòng lặp chụp ảnh mà không lo host can thiệp giữa chừng khi đang chụp.

12.9.3. Kích thước stub và đọc trên kênh chỉ ghi

Một kênh chỉ ghi thuần túy vẫn cần định nghĩa sizeread, ngay cả khi chúng là các stub trả về 0 và b''. Thư viện sử dụng sự hiện diện của các phương thức để suy ra các cờ khả năng của kênh; một backend thiếu read sẽ không có CHANNEL_FLAG_READ được đặt và host sẽ từ chối một yêu cầu đọc.

Tuy nhiên, các byte trả về từ read trên kênh chỉ ghi hữu ích cho một mục đích khác: phản chiếu lại giá trị hiện tại để một host vừa kết nối có thể hỏi cam "cài đặt hiện tại là gì?" thay vì bắt đầu từ giá trị mặc định. Để điều đó hoạt động cả hai chiều phải đồng ý về một phương thức tuần tự hóa. Phân tích int(bytes(data)) dữ liệu byte thô trong ví dụ trước hoạt động cho một trường số nguyên nhưng sẽ không mở rộng được khi có một núm điều chỉnh thứ hai cần đặt. Chuyển write sang phân tích JSON và ghép với một read trả về JSON dump tương ứng biến kênh thành một kho lưu trữ cấu hình round-trip thực sự:

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 giờ đây ghi cam.channel_write('config', b'{"quality": 50}') để đặt giá trị và cam.channel_read('config') để đọc lại trạng thái hiện tại. Cam tuần tự hóa một JSON dump mới trên mỗi lần đọc để host luôn thấy các giá trị mới nhất, và việc thêm một núm điều chỉnh khác (threshold, exposure, orientation) chỉ là một dòng trong JSON dict ở mỗi phía.

12.9.4. Vòng lặp hoàn chỉnh

Với một kênh khung hình cho dữ liệu cam → host, một kênh cấu hình cho điều khiển host → cam, và một ít mã kết nối, ứng dụng trở thành một công cụ tương tác:

  • Host mở cam, bắt đầu lấy khung hình và hiển thị chúng trong một cửa sổ.

  • Khi người vận hành kéo một thanh trượt, host ghi giá trị mới lên config.

  • Vòng lặp chụp của cam lấy giá trị đó vào khung hình tiếp theo.

  • Các khung hình mới chảy qua cùng kênh frame.

Đó là toàn bộ mô hình. Hai kênh, hai hàm gọi lại mỗi kênh, một vòng lặp chụp ảnh trên cam, một vòng lặp đọc-ghi trên host. Không có logic đóng khung nào hiển thị, không có xử lý lỗi nào hiển thị -- thư viện giao thức làm cho việc di chuyển byte đáng tin cậy trở nên vô hình.

Mọi thứ sau điểm này đều là mã ứng dụng. Thêm kênh thứ ba cho biểu đồ tần suất, kênh thứ tư cho dữ liệu đo từ xa, hoặc kênh thứ năm cho các kích hoạt cảm biến đều sử dụng cùng công thức backend-class-và-protocol.register, lặp đi lặp lại. Khi một dự án cam đạt đến điểm này, giao thức không còn là vấn đề thú vị nữa; logic riêng của ứng dụng mới là.