12.7. 채널 콜백

protocol.register() 에 전달되는 백엔드 객체는 Python 클래스입니다. 프로토콜 라이브러리는 그 클래스에게 어떤 메서드를 구현했는지 묻지 않습니다. 대신 인스턴스를 살펴보고 발견한 메서드를 연결합니다. 이러한 인트로스펙션 덕분에 백엔드 인터페이스가 유연해집니다. 가장 작은 유용한 백엔드는 메서드 두 개이고, 가장 정교한 것은 열두 개이며, 애플리케이션은 메서드를 하나씩 추가하면서 각 기능을 선택적으로 활성화합니다.

12.7.1. 인트로스펙션 규칙

protocol.register() 가 실행되면, 라이브러리는 정해진 호출 가능 이름 목록을 훑으면서 백엔드 인스턴스에서 발견한 각각을 바인딩합니다:

  • 클래스에 read 를 추가하면 CHANNEL_FLAG_READ 이 켜집니다. 호스트가 channel_read() 를 호출하더라도 이 플래그가 설정되어 있어야만 백엔드에 도달합니다.

  • write 를 추가하면 CHANNEL_FLAG_WRITE 이 켜져 channel_write() 가 활성화됩니다.

  • lockunlock 을 추가하면 CHANNEL_FLAG_LOCK 이 켜져, 호스트가 여러 패킷에 걸친 원자적 읽기를 위해 채널을 잠글 수 있게 됩니다.

  • poll 을 추가하면 호스트가 전체 읽기를 강제하지 않고도 “준비된 것이 있는가?”를 저렴하게 물어볼 수 있습니다.

메서드가 없는 것은 오류가 아닙니다. 프로토콜 라이브러리는 해당 기능을 비활성화된 채로 둘 뿐입니다. sizeread 만 있는 백엔드도 완벽하게 유효하며, 이는 읽기 전용 데이터 채널입니다.

12.7.2. 읽기 전용 센서 채널

호스트가 요청할 때마다 새로운 측정값을 게시하고 호스트의 쓰기를 거부하는 센서 채널은 콜백 네 개를 활용합니다:

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 은 신선도 플래그를 반환합니다. 호스트는 읽기 전에 이것을 호출하고, False 가 반환되면 읽기를 완전히 건너뜁니다. 이는 “아직 새 데이터가 없음”에 대한 왕복 비용을 절약합니다.

  • size 는 요청 시 버퍼를 다시 생성하고 그 길이를 보고합니다. 여기서 샘플링을 수행하므로 백엔드는 백그라운드 작업이 필요 없습니다. 호스트 호출이 모든 측정을 구동합니다.

  • read 는 버퍼의 일부를 반환합니다. 버퍼가 협상된 최대 페이로드보다 클 때 프로토콜 라이브러리는 이것을 두 번 이상 호출할 수 있습니다. offset 인수가 조각들을 차례로 훑습니다.

  • write 가 없으면 호스트의 쓰기는 백엔드가 관여하기 전에 프레이밍 계층에서 거부됩니다.

12.7.3. 전체 콜백 집합

참고로, 라이브러리가 백엔드에서 찾는 모든 메서드는 다음과 같습니다:

메서드

반환값

용도

init(self)

object

채널이 호스트에 처음 바인딩될 때의 선택적 일회성 초기화. 성공 시 None 이 아닌 임의의 값을 반환합니다.

poll(self)

bool

데이터가 사용 가능할 때 True 를 반환합니다.

lock(self)

bool

원자적 다중 패킷 전송을 위해 채널을 확보합니다.

unlock(self)

bool

이전의 lock 을 해제합니다.

size(self)

int

현재 채널에서 읽을 수 있는 바이트 수.

shape(self)

tuple

데이터 구조를 설명하는 최대 네 개의 정수(예: 이미지 높이, 너비, 바이트 수). 호스트가 형식이 지정된 버퍼를 언패킹하는 데 사용됩니다.

read(self, offset, size)

bytes

offset 에서 시작하여 최대 size 바이트를 반환합니다. 페이로드가 협상된 최대치를 초과할 때 조각마다 한 번씩 호출됩니다.

readp(self, offset, size)

bytes

read 의 제로 카피 변형: 버퍼의 메모리는 전송이 지속되는 동안 유효하게 유지되어야 합니다.

write(self, offset, data)

int

호스트가 offsetdata 를 썼습니다. data 는 프로토콜 계층의 수신 버퍼에 대한 bytearray 뷰입니다. 보관하고 싶은 내용은 반환하기 전에 복사해 두십시오.

ioctl(self, cmd, length, arg)

int

읽기/쓰기 모델을 벗어난 애플리케이션 정의 오피코드. 음수 반환은 오류입니다.

flush(self)

object

버퍼링된 데이터를 모두 버립니다. 호스트가 채널을 재설정하려 할 때 호출됩니다.

is_active(self)

bool

물리적 전송 수단을 나타내는 백엔드(내장 USB 채널)에서만 의미가 있습니다. 애플리케이션 채널은 이것이 필요 없습니다.

이것이 백엔드 인터페이스의 전부입니다. 열두 개의 메서드 이름은 모두 선택 사항이며, 프로토콜 라이브러리는 어떤 것이 존재하는지에 따라 각 채널이 무엇을 할 수 있는지 결정합니다.