12.7. คอลแบ็กของแชนเนล

ออบเจกต์ backend ที่ส่งให้กับ protocol.register() คือคลาส Python ไลบรารีโปรโตคอลไม่ถามคลาสว่ามีเมธอดใดบ้าง แต่จะตรวจสอบ instance และเชื่อมโยงเมธอดที่พบ การตรวจสอบแบบนี้ทำให้ interface ของ backend มีความยืดหยุ่น: backend ที่เล็กที่สุดที่ยังใช้งานได้ต้องการแค่สองเมธอด ส่วน backend ที่ซับซ้อนที่สุดมีถึงสิบสองเมธอด และแอปพลิเคชันสามารถเลือกใช้ความสามารถแต่ละอย่างทีละเมธอด

12.7.1. กฎการตรวจสอบ

เมื่อ protocol.register() ทำงาน ไลบรารีจะวนตรวจสอบรายชื่อ callable ที่กำหนดไว้ และเชื่อมโยงแต่ละชื่อที่พบบน instance ของ backend:

  • การเพิ่ม read ลงในคลาสจะเปิด CHANNEL_FLAG_READ การเรียก channel_read() จากฝั่ง host จะส่งถึง backend ก็ต่อเมื่อ flag นี้ถูกตั้งค่าเท่านั้น

  • การเพิ่ม write จะเปิด CHANNEL_FLAG_WRITE เพื่อเปิดใช้งาน channel_write()

  • การเพิ่ม lock และ unlock จะเปิด CHANNEL_FLAG_LOCK ทำให้ host สามารถล็อกแชนเนลสำหรับการอ่านแบบ atomic หลายแพ็กเก็ตได้

  • การเพิ่ม poll ทำให้ host สามารถถามว่า "มีข้อมูลพร้อมหรือยัง?" ได้อย่างรวดเร็ว โดยไม่ต้องอ่านข้อมูลเต็มรูปแบบ

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

12.7.2. แชนเนล sensor แบบอ่านอย่างเดียว

แชนเนล sensor ที่เผยแพร่ค่าใหม่ทุกครั้งที่ host ร้องขอ โดยปฏิเสธการเขียนจาก host ใช้คอลแบ็กสี่ตัว:

import protocol
import struct

class TempChannel:
    def __init__(self, read_sensor):
        self._read_sensor = read_sensor
        self._buf = b''
        self._fresh = False

    def poll(self):
        # Tell the host whether a reading is waiting.
        return self._fresh

    def size(self):
        # Sample fresh data on every host-side size query.
        value = self._read_sensor()
        self._buf = struct.pack('<f', value)
        self._fresh = True
        return len(self._buf)

    def read(self, offset, size):
        end = offset + size
        if end >= len(self._buf):
            self._fresh = False
        return self._buf[offset:end]

protocol.register(name='temp', backend=TempChannel(read_temperature))

อธิบายสิ่งที่แต่ละเมธอดทำ:

  • poll คืนค่า flag ความสดใหม่ของข้อมูล host จะเรียกก่อนอ่านและข้ามการอ่านทั้งหมดเมื่อคืนค่า False ซึ่งประหยัดต้นทุนการส่งข้อมูลกลับไปกลับมาสำหรับกรณี "ยังไม่มีข้อมูลใหม่"

  • size สร้างบัฟเฟอร์ใหม่ตามต้องการและรายงานความยาวของมัน การเก็บข้อมูลที่นี่หมายความว่า backend ไม่จำเป็นต้องมี task ที่ทำงานเบื้องหลัง -- การเรียกจาก host ขับเคลื่อนการวัดทุกครั้ง

  • read คืนค่า slice ของบัฟเฟอร์ ไลบรารีโปรโตคอลอาจเรียกมากกว่าหนึ่งครั้งเมื่อบัฟเฟอร์มีขนาดใหญ่กว่า payload สูงสุดที่ตกลงกันไว้ อาร์กิวเมนต์ offset จะเดินผ่านส่วนต่างๆ ของข้อมูล

  • การไม่มี write หมายความว่าการเขียนจาก host จะถูกปฏิเสธที่ชั้น framing ก่อนที่จะถึง backend

12.7.3. ชุดคอลแบ็กครบถ้วน

สำหรับการอ้างอิง ทุกเมธอดที่ไลบรารีค้นหาบน backend:

เมธอด

ค่าที่คืน

วัตถุประสงค์

init(self)

object

การเริ่มต้นครั้งเดียวแบบ optional เมื่อแชนเนลผูกกับ host เป็นครั้งแรก คืนค่าที่ไม่ใช่ None เมื่อสำเร็จ

poll(self)

bool

คืนค่า True เมื่อมีข้อมูลพร้อม

lock(self)

bool

ครอบครองแชนเนลสำหรับการถ่ายโอนข้อมูลแบบ atomic หลายแพ็กเก็ต

unlock(self)

bool

ปลดล็อก lock ก่อนหน้า

size(self)

int

จำนวนไบต์ที่อ่านได้จากแชนเนลในขณะนั้น

shape(self)

tuple

จำนวนเต็มสูงสุดสี่ค่าที่อธิบายโครงสร้างข้อมูล (เช่น ความสูง ความกว้าง จำนวนไบต์ของภาพ) ใช้โดย host เพื่อแกะบัฟเฟอร์ที่มีชนิดข้อมูลกำกับ

read(self, offset, size)

bytes

คืนค่าสูงสุด size ไบต์เริ่มต้นที่ offset เรียกหนึ่งครั้งต่อส่วนเมื่อ payload เกินขนาดสูงสุดที่ตกลงกันไว้

readp(self, offset, size)

bytes

ตัวแปรแบบ zero-copy ของ read: หน่วยความจำของบัฟเฟอร์ต้องคงอยู่ตลอดระยะเวลาการถ่ายโอน

write(self, offset, data)

int

host เขียน data ที่ offset -- data คือ view แบบ bytearray ไปยังบัฟเฟอร์รับของชั้นโปรโตคอล คัดลอกข้อมูลที่ต้องการเก็บออกไปก่อน return

ioctl(self, cmd, length, arg)

int

opcode ที่กำหนดโดยแอปพลิเคชันนอกเหนือจากโมเดลการอ่าน/เขียน ค่าที่คืนเป็นลบคือข้อผิดพลาด

flush(self)

object

ลบข้อมูลที่บัฟเฟอร์ไว้ เรียกเมื่อ host ต้องการรีเซ็ตแชนเนล

is_active(self)

bool

มีความหมายเฉพาะกับ backend ที่แทน transport จริง (แชนเนล USB ที่ built-in) แชนเนลของแอปพลิเคชันไม่จำเป็นต้องมีเมธอดนี้

นั่นคือ interface ของ backend ทั้งหมด ชื่อเมธอดสิบสองชื่อ ทั้งหมด optional และไลบรารีโปรโตคอลจะตัดสินว่าแชนเนลแต่ละอันทำอะไรได้บ้าง โดยอิงจากเมธอดที่มีอยู่