12.7. Funciones de retorno de canal

El objeto backend que se pasa a protocol.register() es una clase de Python. La biblioteca del protocolo no le pregunta a la clase qué métodos implementa; inspecciona la instancia y conecta los que encuentra. Esa introspección es lo que hace flexible la interfaz del backend: el backend útil más pequeño tiene dos métodos, el más elaborado tiene doce, y la aplicación opta por cada capacidad de un método a la vez.

12.7.1. Las reglas de introspección

Cuando se ejecuta protocol.register(), la biblioteca recorre una lista fija de nombres de invocables y vincula cada uno que encuentra en la instancia del backend:

  • Añadir read a la clase activa CHANNEL_FLAG_READ. Una llamada del host a channel_read() solo llega al backend si este indicador está activado.

  • Añadir write activa CHANNEL_FLAG_WRITE, habilitando channel_write().

  • Añadir lock y unlock activa CHANNEL_FLAG_LOCK, permitiendo al host bloquear el canal para una lectura atómica de varios paquetes.

  • Añadir poll permite al host preguntar «¿hay algo listo?» de forma económica, sin forzar una lectura completa.

Los métodos faltantes no son errores: la biblioteca del protocolo simplemente deja deshabilitada la capacidad correspondiente. Un backend con solo size y read es perfectamente válido; es un canal de datos de solo lectura.

12.7.2. Un canal de sensor de solo lectura

Un canal de sensor que publica una lectura nueva cada vez que el host lo solicita, rechazando las escrituras del host, utiliza cuatro de las funciones de retorno:

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

Recorriendo lo que hace cada método:

  • poll devuelve el indicador de frescura. El host lo llama antes de leer y omite la lectura por completo cuando devuelve False. Eso ahorra el costo de ida y vuelta para «todavía no hay datos nuevos».

  • size regenera el búfer bajo demanda e informa su longitud. Realizar el muestreo aquí significa que el backend no necesita una tarea en segundo plano: una llamada del host impulsa cada medición.

  • read devuelve un segmento del búfer. La biblioteca del protocolo puede llamarlo más de una vez cuando el búfer es más grande que la carga útil máxima negociada; el argumento offset recorre los fragmentos.

  • La ausencia de write significa que las escrituras del host se rechazan en la capa de tramado, antes de que el backend intervenga.

12.7.3. El conjunto completo de funciones de retorno

Como referencia, cada método que la biblioteca busca en un backend:

Método

Devuelve

Propósito

init(self)

object

Inicialización opcional de una sola vez cuando el canal se vincula por primera vez a un host. Devuelve cualquier valor distinto de None en caso de éxito.

poll(self)

bool

Devuelve True cuando hay datos disponibles.

lock(self)

bool

Adquiere el canal para una transferencia atómica de varios paquetes.

unlock(self)

bool

Libera un lock previo.

size(self)

int

Número de bytes actualmente legibles del canal.

shape(self)

tuple

Hasta cuatro enteros que describen la estructura de datos (p. ej., altura de imagen, ancho, recuento de bytes). Usado por el host para desempaquetar búferes tipados.

read(self, offset, size)

bytes

Devuelve hasta size bytes empezando en offset. Se llama una vez por fragmento cuando la carga útil supera el máximo negociado.

readp(self, offset, size)

bytes

Variante sin copia de read: la memoria del búfer debe permanecer válida durante toda la transferencia.

write(self, offset, data)

int

El host escribió data en offset. data es una vista de bytearray sobre el búfer de recepción de la capa del protocolo: copia lo que quieras conservar antes de regresar.

ioctl(self, cmd, length, arg)

int

Código de operación definido por la aplicación fuera del modelo de lectura/escritura. Un valor de retorno negativo es un error.

flush(self)

object

Descarta cualquier dato almacenado en búfer. Se llama cuando el host quiere restablecer el canal.

is_active(self)

bool

Solo tiene sentido en backends que representan un transporte físico (los canales USB integrados). Los canales de aplicación no lo necesitan.

Esa es toda la interfaz del backend. Doce nombres de método, todos opcionales, y la biblioteca del protocolo decide lo que puede hacer cada canal según cuáles estén presentes.