13.3.1.4. Kênh tùy chỉnh¶
Một kênh là một luồng byte hai chiều có tên giữa tập lệnh phía camera và máy chủ. Camera đăng ký một kênh và cung cấp các hàm gọi lại để tạo ra hoặc tiêu thụ dữ liệu; máy chủ đọc từ và ghi vào kênh đó theo tên. Cơ chế mà gói sử dụng nội bộ cho kênh stream mang khung hình, kênh stdout mang đầu ra tập lệnh, và kênh stdin mang tải lên tập lệnh được tiếp xúc với các tập lệnh người dùng, do đó bất kỳ dữ liệu dành riêng cho ứng dụng nào mà máy chủ cần đều có thể sử dụng cùng kết nối USB mà không cần phát minh một giao thức thứ hai.
Đây là tính năng hữu ích nhất của gói và là tính năng mà tài liệu tiêu chuẩn đề cập ít nhất, vì vậy trang này làm việc qua nó từ đầu đến cuối.
13.3.1.4.1. Hai nửa¶
Một kênh tùy chỉnh cần mã hợp tác trên cả hai phía. Tập lệnh phía camera nhập protocol, định nghĩa một lớp với ba phương thức (size(), read(), poll()) cùng một write() tùy chọn, và gọi protocol.register(name=..., backend=...) để xuất bản kênh dưới một tên được chọn:
import protocol
import time
class TicksChannel:
def size(self):
return 10
def read(self, offset, size):
return f'{time.ticks_ms():010d}'
def poll(self):
return True
protocol.register(name='ticks', backend=TicksChannel())
Phương thức size() trả về số byte mà kênh hiện có sẵn. read() là bộ sản xuất: cho một offset và size được yêu cầu bởi máy chủ, nó trả về các byte (hoặc một chuỗi mà lớp giao thức mã hóa). poll() trả về True khi có gì đó để đọc -- lớp giao thức sử dụng điều này để đánh dấu kênh là sẵn sàng trong read_status().
Chương trình phía máy chủ sử dụng bốn phương thức openmv.Camera: has_channel() để kiểm tra kênh tồn tại, channel_size() để hỏi có bao nhiêu dữ liệu đang chờ, channel_read() để kéo byte ra, và channel_write() để đẩy byte vào. read_status() thăm dò mọi kênh cùng một lúc:
from openmv import Camera
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('ticks_cam.py').read())
while True:
status = cam.read_status()
if status.get('ticks'):
data = cam.channel_read('ticks')
print(f"ticks: {data.decode()}")
Vòng lặp máy chủ thăm dò read_status(); khi kênh ticks sẵn sàng, nó gọi channel_read() không có size để kéo bất cứ thứ gì có sẵn. TicksChannel.poll() của camera trả về True trên mỗi lần kiểm tra, do đó kênh luôn "sẵn sàng" và máy chủ nhận được một giá trị tick mới mỗi lần thăm dò.
13.3.1.4.2. Kênh hai chiều¶
Đối với máy chủ cần đẩy dữ liệu trở lại, lớp phía camera thêm một phương thức write() chấp nhận các byte đến:
import protocol
class CommandChannel:
def __init__(self):
self.last_command = b''
self.replied = False
def size(self):
return len(self.last_command)
def read(self, offset, size):
self.replied = True
return self.last_command
def write(self, offset, data):
self.last_command = b'echo: ' + bytes(data)
self.replied = False
def poll(self):
return not self.replied and len(self.last_command) > 0
protocol.register(name='echo', backend=CommandChannel())
Máy chủ ghi vào kênh bằng channel_write() và đọc phản hồi trở lại thông qua mẫu read_status() / channel_read() thông thường:
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('echo_cam.py').read())
cam.channel_write('echo', b'hello')
while True:
if cam.read_status().get('echo'):
print(cam.channel_read('echo').decode())
break
13.3.1.4.3. Lợi ích cho ứng dụng¶
Kênh tùy chỉnh là công cụ phù hợp khi một ứng dụng muốn sử dụng kết nối USB hiện có cho dữ liệu không phải khung hình, không phải in: bộ đếm đo từ xa, núm cấu hình được truyền trực tiếp từ giao diện người dùng trên máy chủ, lệnh điều khiển được gửi theo chiều ngược lại, kết quả của một phép đo mà camera đã tính toán không phù hợp với khung "ảnh" mà kênh stream giả định. Lớp giao thức xử lý việc đóng khung, phân mảnh, xác nhận và thử lại; tập lệnh chỉ cần triển khai backend bốn phương thức, và máy chủ chỉ cần biết tên kênh và hình dạng dữ liệu.
Cờ --channel NAME của CLI là cách nhanh để xác minh một kênh tùy chỉnh từ terminal mà không cần viết chương trình phía máy chủ: CLI thăm dò kênh được đặt tên và in mười byte đầu tiên của mỗi lần cập nhật.
Giới hạn kích thước trên một lần gọi channel_read() hoặc channel_write() là max_payload đã được đàm phán của giao thức -- mặc định là 4096 byte. Các phương thức phía máy chủ tự động chia nhỏ các lần ghi lớn hơn thành số lượng gói tin phù hợp, vì vậy ứng dụng có thể truyền các bộ đệm tùy ý lớn; việc phân mảnh là vô hình.