12.7. Wywołania zwrotne kanału

Obiekt backendu przekazywany do protocol.register() jest klasą Pythona. Biblioteka protokołu nie pyta klasy, które metody implementuje; sprawdza instancję i podłącza te, które znajdzie. Ta introspekcja sprawia, że interfejs backendu jest elastyczny: najmniejszy użyteczny backend to dwie metody, najbardziej rozbudowany to dwanaście, a aplikacja włącza każdą zdolność po jednej metodzie naraz.

12.7.1. Reguły introspekcji

Gdy uruchamia się protocol.register(), biblioteka przechodzi przez stałą listę nazw wywoływalnych i wiąże każdą z nich, którą znajdzie w instancji backendu:

  • Dodanie read do klasy włącza CHANNEL_FLAG_READ. Wywołanie hosta channel_read() dociera do backendu tylko wtedy, gdy ta flaga jest ustawiona.

  • Dodanie write włącza CHANNEL_FLAG_WRITE, umożliwiając channel_write().

  • Dodanie lock i unlock włącza CHANNEL_FLAG_LOCK, umożliwiając hostowi zablokowanie kanału w celu atomowego odczytu wielu pakietów.

  • Dodanie poll pozwala hostowi tanio zapytać „czy coś jest gotowe?”, bez wymuszania pełnego odczytu.

Brakujące metody nie są błędami – biblioteka protokołu po prostu pozostawia odpowiednią zdolność wyłączoną. Backend mający tylko size i read jest całkowicie poprawny; jest to kanał danych tylko do odczytu.

12.7.2. Kanał sensora tylko do odczytu

Kanał sensora, który publikuje świeży odczyt za każdym razem, gdy host o niego prosi, odrzucając zapisy hosta, wykorzystuje cztery z wywołań zwrotnych:

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))

Omówienie, co robi każda metoda:

  • poll zwraca flagę świeżości. Host wywołuje ją przed odczytem i całkowicie pomija odczyt, gdy zwraca False. Oszczędza to koszt rundy w obie strony dla „jeszcze nie ma nowych danych”.

  • size regeneruje bufor na żądanie i raportuje jego długość. Wykonywanie próbkowania w tym miejscu oznacza, że backend nie potrzebuje zadania w tle – każdy pomiar napędza wywołanie hosta.

  • read zwraca wycinek bufora. Biblioteka protokołu może wywołać ją więcej niż raz, gdy bufor jest większy niż wynegocjowany maksymalny ładunek; argument offset przechodzi przez fragmenty.

  • Brak write oznacza, że zapisy hosta są odrzucane na warstwie ramkowania, zanim backend zostanie zaangażowany.

12.7.3. Pełny zestaw wywołań zwrotnych

Dla odniesienia, każda metoda, której biblioteka szuka w backendzie:

Metoda

Zwraca

Cel

init(self)

object

Opcjonalna jednorazowa inicjalizacja przy pierwszym powiązaniu kanału z hostem. W przypadku powodzenia zwróć dowolną wartość różną od None.

poll(self)

bool

Zwraca True, gdy dane są dostępne.

lock(self)

bool

Przejmuje kanał dla atomowego transferu wielu pakietów.

unlock(self)

bool

Zwalnia wcześniejszy lock.

size(self)

int

Liczba bajtów aktualnie możliwych do odczytu z kanału.

shape(self)

tuple

Do czterech liczb całkowitych opisujących strukturę danych (np. wysokość obrazu, szerokość, liczba bajtów). Używane przez hosta do rozpakowania typowanych buforów.

read(self, offset, size)

bytes

Zwraca do size bajtów począwszy od offset. Wywoływana raz na fragment, gdy ładunek przekracza wynegocjowane maksimum.

readp(self, offset, size)

bytes

Wariant read bez kopiowania: pamięć bufora musi pozostać ważna przez cały czas trwania transferu.

write(self, offset, data)

int

Host zapisał data pod adresem offset. data to widok bytearray do bufora odbiorczego warstwy protokołu – skopiuj to, co chcesz zachować, przed powrotem.

ioctl(self, cmd, length, arg)

int

Kod operacji zdefiniowany przez aplikację, poza modelem odczyt/zapis. Ujemny zwrot oznacza błąd.

flush(self)

object

Odrzuca wszelkie zbuforowane dane. Wywoływana, gdy host chce zresetować kanał.

is_active(self)

bool

Znacząca tylko dla backendów reprezentujących fizyczny transport (wbudowane kanały USB). Kanały aplikacji tego nie potrzebują.

To cały interfejs backendu. Dwanaście nazw metod, wszystkie opcjonalne, a biblioteka protokołu decyduje, co każdy kanał może zrobić, na podstawie tego, które z nich są obecne.