12.7. Callbacks de canal

O objeto de backend passado a protocol.register() é uma classe Python. A biblioteca de protocolo não interroga a classe sobre os métodos que implementa; inspeciona a instância e liga os que encontra. É essa introspecção que torna a interface de backend flexível: o backend minimamente útil tem dois métodos, o mais elaborado tem doze, e a aplicação adere a cada capacidade um método de cada vez.

12.7.1. As regras de introspecção

Quando protocol.register() é executado, a biblioteca percorre uma lista fixa de nomes invocáveis e liga cada um que encontrar na instância de backend:

  • Adicionar read à classe ativa CHANNEL_FLAG_READ. Uma chamada do host a channel_read() só chega ao backend se este sinalizador estiver ativo.

  • Adicionar write ativa CHANNEL_FLAG_WRITE, habilitando channel_write().

  • Adicionar lock e unlock ativa CHANNEL_FLAG_LOCK, permitindo ao host bloquear o canal para uma leitura atómica de múltiplos pacotes.

  • Adicionar poll permite ao host perguntar «há algo pronto?» de forma económica, sem forçar uma leitura completa.

Os métodos em falta não são erros – a biblioteca de protocolo simplesmente deixa a capacidade correspondente desativada. Um backend com apenas size e read é perfeitamente válido; é um canal de dados só de leitura.

12.7.2. Um canal de sensor só de leitura

Um canal de sensor que publica uma leitura atualizada sempre que o host pergunta, recusando escritas do host, exercita quatro dos callbacks:

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

Percorrendo o que cada método faz:

  • poll devolve o sinalizador de atualização. O host chama-o antes de ler e salta a leitura por completo quando devolve False. Isto poupa o custo da ida e volta para «ainda não há dados novos.»

  • size regenera o buffer a pedido e reporta o seu comprimento. Fazer a amostragem aqui significa que o backend não precisa de uma tarefa em segundo plano – cada medição é desencadeada por uma chamada do host.

  • read devolve uma fatia do buffer. A biblioteca de protocolo pode chamá-lo mais do que uma vez quando o buffer é maior que o payload máximo negociado; o argumento offset percorre os fragmentos.

  • A ausência de write faz com que as escritas do host sejam recusadas na camada de enquadramento, antes de o backend ser envolvido.

12.7.3. O conjunto completo de callbacks

Para referência, todos os métodos que a biblioteca procura num backend:

Método

Devolve

Objetivo

init(self)

objeto

Inicialização opcional de execução única quando o canal se liga pela primeira vez a um host. Devolva qualquer valor não None em caso de sucesso.

poll(self)

bool

Devolve True quando há dados disponíveis.

lock(self)

bool

Adquire o canal para uma transferência atómica de múltiplos pacotes.

unlock(self)

bool

Liberta um lock anterior.

size(self)

int

Número de bytes atualmente legíveis do canal.

shape(self)

tuple

Até quatro inteiros que descrevem a estrutura dos dados (por exemplo, altura, largura e contagem de bytes de uma imagem). Usado pelo host para desempacotar buffers tipados.

read(self, offset, size)

bytes

Devolve até size bytes a partir de offset. Chamado uma vez por fragmento quando o payload excede o máximo negociado.

readp(self, offset, size)

bytes

Variante de cópia zero de read: a memória do buffer deve permanecer válida durante toda a transferência.

write(self, offset, data)

int

O host escreveu data em offset. data é uma vista bytearray no buffer de receção da camada de protocolo – copie o que pretender guardar antes de retornar.

ioctl(self, cmd, length, arg)

int

Opcode definido pela aplicação fora do modelo de leitura/escrita. Um valor de retorno negativo indica erro.

flush(self)

objeto

Descarta quaisquer dados em buffer. Chamado quando o host quer reiniciar o canal.

is_active(self)

bool

Só é relevante em backends que representam um transporte físico (os canais USB integrados). Os canais de aplicação não precisam disto.

É esta a interface de backend completa. Doze nomes de métodos, todos opcionais, e a biblioteca de protocolo decide o que cada canal pode fazer com base nos que estão presentes.