12.7. Callbacks de canal

O objeto de backend passado para protocol.register() é uma classe Python. A biblioteca de protocolo não pergunta à classe quais métodos ela implementa; ela inspeciona a instância e conecta os que encontrar. Essa introspecção é o que torna a interface de backend flexível: o menor backend útil tem dois métodos, o mais elaborado tem doze, e a aplicação adere a cada capacidade um método por vez.

12.7.1. As regras de introspecção

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

  • Adicionar read à classe ativa CHANNEL_FLAG_READ. Uma chamada do host a channel_read() só chega ao backend se esse flag estiver definido.

  • Adicionar write ativa CHANNEL_FLAG_WRITE, habilitando channel_write().

  • Adicionar lock e unlock ativa CHANNEL_FLAG_LOCK, permitindo que o host bloqueie o canal para uma leitura atômica de múltiplos pacotes.

  • Adicionar poll permite que o host pergunte “há algo pronto?” de forma barata, sem forçar uma leitura completa.

Métodos ausentes não são erros – a biblioteca de protocolo simplesmente deixa a capacidade correspondente desabilitada. Um backend com apenas size e read é perfeitamente válido; trata-se de um canal de dados somente leitura.

12.7.2. Um canal de sensor somente leitura

Um canal de sensor que publica uma nova leitura toda vez que o host solicita, 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 retorna o flag de atualização. O host o chama antes de ler e pula a leitura completamente quando ele retorna False. Isso economiza o custo da ida e volta para “ainda não há dados novos”.

  • size regenera o buffer sob demanda e relata seu tamanho. Fazer a amostragem aqui significa que o backend não precisa de uma tarefa em segundo plano – uma chamada do host conduz cada medição.

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

  • A ausência de write significa que escritas do host são recusadas na camada de framing, 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 em um backend:

Método

Retorna

Finalidade

init(self)

object

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

poll(self)

bool

Retorna 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

Libera um lock anterior.

size(self)

int

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

shape(self)

tuple

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

read(self, offset, size)

bytes

Retorna até size bytes começando em offset. Chamado uma vez por fragmento quando o payload excede o máximo negociado.

readp(self, offset, size)

bytes

Variante sem cópia 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 view bytearray no buffer de recepção da camada de protocolo – copie o que quiser manter antes de retornar.

ioctl(self, cmd, length, arg)

int

Opcode definido pela aplicação, fora do modelo de leitura/escrita. Retorno negativo é um erro.

flush(self)

object

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

is_active(self)

bool

Significativo apenas em backends que representam um transporte físico (os canais USB integrados). Canais de aplicação não precisam disso.

Essa é toda a interface de backend. Doze nomes de métodos, todos opcionais, e a biblioteca de protocolo decide o que cada canal pode fazer com base em quais deles estão presentes.