12.6. แชนเนลที่มีชื่อ¶
channel ID ในส่วนหัวของแต่ละแพ็กเก็ตช่วยให้ stream อิสระสูงสุด 32 เส้นแชร์ transport จริงเดียวกัน ชั้นแชนเนลแปลง ID ตัวเลขเหล่านั้นเป็น endpoint ที่มีชื่อ ซึ่งแอปพลิเคชันสามารถมองเห็นได้ และโค้ดฝั่ง host สามารถอ้างอิงได้ด้วย string
12.6.1. แชนเนล built-in สี่ตัว¶
cam ลงทะเบียนแชนเนลสี่ตัวเมื่อบูต ก่อนที่โค้ดแอปพลิเคชันใดจะทำงาน:
stdin-- ไบต์ของสคริปต์ที่ host ส่งมายัง cam เพื่อประมวลผล IDE ใช้แชนเนลนี้เพื่อส่งสคริปต์ที่กำลังแก้ไข;exec()บน host SDK คือการเรียกที่เทียบเท่าจากโปรแกรม Pythonstdout-- ไบต์จากการเรียกprint()บน cam และ traceback ของ exception ที่ไม่ถูกจัดการ คอนโซลซีเรียลของ IDE อ่านแชนเนลนี้stream-- แชนเนลแสดงตัวอย่างสด IDE ดึงเฟรม JPEG จากมัน; สคริปต์ใดๆ บน host ก็สามารถทำเช่นเดียวกันด้วยread_frame()profile-- เหตุการณ์ profiler มีเฉพาะเมื่อ cam ถูก build พร้อม profiling เปิดใช้งาน build สำหรับ release ส่วนใหญ่จะไม่มี
โค้ดแอปพลิเคชันแทบไม่จำเป็นต้องแตะ built-in ใดๆ งานที่น่าสนใจเกิดขึ้นบนแชนเนลที่แอปพลิเคชันลงทะเบียนเอง
12.6.2. การลงทะเบียนแชนเนล¶
สคริปต์ฝั่ง cam ลงทะเบียนแชนเนลใหม่โดยเรียก protocol.register() พร้อมชื่อและออบเจกต์ backend ของ Python:
import json
import protocol
import time
trigger_count = 0
class StatusChannel:
def size(self):
# Refresh the snapshot on every host query.
self._buf = json.dumps({
'uptime_s': time.ticks_ms() // 1000,
'triggers': trigger_count,
}).encode()
return len(self._buf)
def read(self, offset, size):
return self._buf[offset:offset + size]
protocol.register(name='status', backend=StatusChannel())
เมธอดของออบเจกต์ backend กำหนดว่าแชนเนลทำอะไรได้ backend ที่มีแค่ size และ read คือ แชนเนลข้อมูลแบบอ่านอย่างเดียว; เพิ่ม write แล้วจะกลายเป็นสองทิศทาง; เพิ่ม poll แล้ว host สามารถถามว่ามีข้อมูลใหม่พร้อมหรือยังก่อนจ่ายค่าการอ่าน การสุ่มตัวอย่างข้อมูลภายใน size คือรูปแบบที่ง่ายที่สุดเมื่อ payload เล็กพอที่จะใส่ในส่วนเดียว -- บัฟเฟอร์ถูกสร้างตามความต้องการ ไม่เคยถูก cache และไม่มี race condition payload ขนาดใหญ่ -- เฟรมภาพ, trace ของ sensor -- ต้องการรูปแบบการล็อกที่ถือบัฟเฟอร์ไว้จนกว่า host จะอ่านหลายส่วนเสร็จ ซึ่งอธิบายพร้อมกับแชนเนลเฟรม
การจัดการบัญชีเล็กน้อยเกิดขึ้นโดยอัตโนมัติ:
ไลบรารีจะกำหนด channel ID ถัดไปที่ว่าง (ระหว่าง 0 ถึง 31)
flag ความสามารถจะได้มาจากเมธอดที่มีอยู่:
CHANNEL_FLAG_READถ้าreadถูกกำหนด,CHANNEL_FLAG_WRITEถ้าwriteถูกกำหนด,CHANNEL_FLAG_LOCKถ้าlock/unlockถูกกำหนดแพ็กเก็ตเหตุการณ์
CHANNEL_REGISTEREDจะถูกส่งไปยัง host ที่เชื่อมต่ออยู่ เพื่ออัปเดตรายการแชนเนล
ค่าที่คืนมาคือ handle protocol.ProtocolChannel ที่แอปพลิเคชันสามารถเก็บไว้ เมธอด send_event() ของ handle คือ hook ฝั่ง cam สำหรับบอก host ว่า "มีบางอย่างเกิดขึ้นบนแชนเนลนี้โดยไม่เปลี่ยนข้อมูลที่อ่านได้" -- trigger ทำงาน ปุ่มถูกกด หรือถึงจุดสำคัญของจำนวนตัวอย่าง
12.6.3. การอ่านแชนเนลจาก host¶
Host SDK มาพร้อมกับแพ็กเกจ openmv บน PyPI (pip install openmv) ซึ่งสร้างบน pyserial สำหรับ transport คลาส openmv.camera.Camera ของมันเปิดเผยแชนเนลที่มีชื่อของ cam ผ่านเมธอดระดับสูง:
from openmv.camera import Camera
with Camera('/dev/ttyACM0', baudrate=921600) as cam:
cam.update_channels()
if cam.has_channel('status'):
size = cam.channel_size('status')
data = cam.channel_read('status', size)
Warning
แพ็กเกจ openmv ต้องการ CPython 3.12 หรือใหม่กว่า interpreter เวอร์ชันเก่ากว่าขาดลักษณะเด่นที่ SDK ต้องการ ติดตั้ง build 3.12+ ก่อน pip install openmv
สิ่งที่ควรสังเกตในการตั้งค่ามีดังนี้:
สตริง serial-port --
/dev/ttyACM0ที่นี่ -- จะเป็นรูปแบบCOM3บน Windows,/dev/cu.usbmodemXXXXบน macOS และ/dev/ttyACM*บน Linux ตัวเลขจริงขึ้นอยู่กับ port ที่ cam ลงทะเบียนไว้อัตราบอดคือค่าพิเศษ
921600ของโปรโตคอล ซึ่ง USB-CDC stack ของ cam จะรู้จักว่า "client นี้พูดโปรโตคอล ไม่ใช่ REPL" อัตราอื่นจะถอยกลับไปเป็นสายซีเรียลธรรมดาcontext manager
with Camera(...) as cam:จะเปิด transport ทำPROTO_SYNCแลกเปลี่ยน capability และเมื่อออกจะปิด port อย่างสะอาด การเรียกupdate_channels()อย่างชัดเจนหลังเข้าสู่ context จะรีเฟรชรายการแชนเนล local ด้วยแชนเนลที่แอปพลิเคชันลงทะเบียนหลังบูต
channel_size() และ channel_read() คือเมธอดหลัก; channel_write() ส่งบัฟเฟอร์ไปกลับกับ cam ถ้า backend มีเมธอด write; has_channel() คือวิธีปลอดภัยในการตรวจสอบว่าชื่อถูกลงทะเบียนก่อนใช้งาน ชื่อแชนเนลจะถูกค้นหาครั้งเดียวเพื่อแปลงเป็น channel ID ที่ cam กำหนดตอน register และใช้ในทุกแพ็กเก็ตนับจากนั้น
แต่ละคู่ channel_size() / channel_read() ใช้สอง round-trip: หนึ่งแพ็กเก็ตเพื่อถามขนาด หนึ่งแพ็กเก็ตเพื่อถามไบต์ ผ่าน USB-CDC ทั้งสองเสร็จในเวลาประมาณหนึ่งมิลลิวินาทีรวมกัน ผ่าน UART การแลกเปลี่ยนเดียวกันใช้เวลานานกว่าตามสัดส่วนของอัตราบอดของสายซีเรียล โค้ดแอปพลิเคชันที่อ่านใน loop แบบต่อเนื่องควรเรียก channel_size() เฉพาะเมื่อขนาดสามารถเปลี่ยนแปลงได้จริงๆ -- สำหรับข้อมูลขนาดคงที่ ขนาดจากการเรียกครั้งแรกสามารถ cache ไว้ได้
12.6.4. ความเป็นอิสระระหว่างแชนเนล¶
มีสามสิ่งที่ควรทราบเกี่ยวกับการที่แชนเนลโต้ตอบกัน:
การควบคุมการไหลแบบอิสระ. แต่ละแชนเนลมีสถานะการอ่านที่รอดำเนินการของตัวเอง ข้อมูลของตัวเอง และคอลแบ็ก
size/read/writeของตัวเอง การอ่านที่ใช้เวลานานบนแชนเนลstreamไม่ block การอ่านบนแชนเนลconfigของแอปพลิเคชันตามลำดับต่อแชนเนล. ภายในแชนเนลเดียว แพ็กเก็ตจะถูกส่งตามลำดับ ชั้น reliability รับประกันสิ่งนี้แม้ในกรณีที่มีการส่งใหม่
Transport ร่วม, งบประมาณการส่งใหม่ร่วม. ทุกแชนเนลแชร์ link จริงเดียว ดังนั้นปริมาณงานที่ผ่านได้สูงบนแชนเนลหนึ่งจะทำให้แชนเนลอื่นช้าลงเพราะครอบครองสาย กลไก
CHANNEL_LOCKให้แชนเนลหนึ่งจองสายสำหรับการอ่าน atomic หลายแพ็กเก็ต โดย backend เลือกใช้โดยการ implement คอลแบ็กlock/unlock
แชนเนลคือพื้นที่ผิวขั้นต่ำที่โปรแกรม host และโปรแกรม cam ตกลงที่จะร่วมมือกัน ชื่อ ทิศทาง (อ่านหรือเขียนหรือทั้งสอง) เมธอดคอลแบ็กฝั่ง cam และการเรียกเมธอดที่ตรงกันฝั่ง host คือสัญญาทั้งหมด