12.7. Callbacky kanálu

Backend objekt předaný funkci protocol.register() je třída v Pythonu. Knihovna protokolu se třídy neptá, které metody implementuje; prozkoumá instanci a propojí ty, které najde. Právě tato introspekce činí rozhraní backendu flexibilním: nejmenší užitečný backend má dvě metody, ten nejpropracovanější dvanáct, a aplikace si jednotlivé schopnosti zapíná metodu po metodě.

12.7.1. Pravidla introspekce

Když se spustí protocol.register(), knihovna projde pevný seznam názvů volatelných metod a každou z nich, kterou na instanci backendu najde, naváže:

  • Přidání metody read do třídy zapne příznak CHANNEL_FLAG_READ. Volání channel_read() ze strany hostitele dosáhne backendu pouze tehdy, je-li tento příznak nastaven.

  • Přidání metody write zapne příznak CHANNEL_FLAG_WRITE a umožní tak channel_write().

  • Přidání metod lock a unlock zapne příznak CHANNEL_FLAG_LOCK a umožní hostiteli uzamknout kanál pro atomické čtení napříč více pakety.

  • Přidání metody poll umožní hostiteli levně se zeptat „je něco připraveno?“ bez nutnosti vynucovat plné čtení.

Chybějící metody nejsou chyby – knihovna protokolu jednoduše ponechá odpovídající schopnost vypnutou. Backend pouze s metodami size a read je naprosto platný; jde o datový kanál určený jen ke čtení.

12.7.2. Senzorový kanál určený jen ke čtení

Senzorový kanál, který při každém dotazu hostitele publikuje čerstvé měření a odmítá zápisy ze strany hostitele, využívá čtyři z callbacků:

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

Projděme si, co každá metoda dělá:

  • poll vrací příznak čerstvosti dat. Hostitel ji volá před čtením a čtení zcela přeskočí, když vrátí False. To šetří náklady na obousměrný přenos při „zatím žádná nová data“.

  • size na vyžádání znovu vygeneruje buffer a oznámí jeho délku. Provádění odběru vzorků právě zde znamená, že backend nepotřebuje úlohu běžící na pozadí – každé měření řídí volání hostitele.

  • read vrací výřez bufferu. Knihovna protokolu ji může zavolat vícekrát, pokud je buffer větší než vyjednaná maximální užitečná zátěž; argument offset prochází jednotlivými fragmenty.

  • Absence metody write znamená, že zápisy ze strany hostitele jsou odmítnuty již na úrovni rámcování, dříve než se zapojí backend.

12.7.3. Kompletní sada callbacků

Pro referenci uvádíme každou metodu, kterou knihovna na backendu hledá:

Metoda

Vrací

Účel

init(self)

object

Volitelná jednorázová inicializace, když se kanál poprvé naváže na hostitele. Při úspěchu vraťte libovolnou hodnotu různou od None.

poll(self)

bool

Vrací True, když jsou data k dispozici.

lock(self)

bool

Získá kanál pro atomický přenos napříč více pakety.

unlock(self)

bool

Uvolní předchozí lock.

size(self)

int

Počet bajtů aktuálně čitelných z kanálu.

shape(self)

tuple

Až čtyři celá čísla popisující datovou strukturu (např. výšku obrazu, šířku, počet bajtů). Hostitel je používá k rozbalení typovaných bufferů.

read(self, offset, size)

bytes

Vrací až size bajtů počínaje pozicí offset. Voláno jednou na fragment, když užitečná zátěž překračuje vyjednané maximum.

readp(self, offset, size)

bytes

Varianta metody read bez kopírování (zero-copy): paměť bufferu musí zůstat platná po celou dobu přenosu.

write(self, offset, data)

int

Hostitel zapsal data na pozici offset. data je pohled typu bytearray do přijímacího bufferu vrstvy protokolu – před návratem zkopírujte ven to, co si chcete ponechat.

ioctl(self, cmd, length, arg)

int

Aplikací definovaný operační kód mimo model čtení/zápisu. Záporná návratová hodnota značí chybu.

flush(self)

object

Zahodí veškerá data uložená v bufferu. Voláno, když chce hostitel kanál resetovat.

is_active(self)

bool

Smysluplné pouze na backendech, které představují fyzický přenosový kanál (vestavěné USB kanály). Aplikační kanály toto nepotřebují.

To je celé rozhraní backendu. Dvanáct názvů metod, všechny volitelné, a knihovna protokolu na základě toho, které z nich jsou přítomny, rozhodne, co může každý kanál dělat.