12.7. Функции обратного вызова канала

Объект бэкенда, передаваемый в protocol.register(), представляет собой класс Python. Библиотека протокола не спрашивает у класса, какие методы он реализует; она исследует экземпляр и подключает те методы, которые находит. Именно это самоисследование делает интерфейс бэкенда гибким: минимальный полезный бэкенд состоит из двух методов, самый сложный – из двенадцати, и приложение подключает каждую возможность по одному методу за раз.

12.7.1. Правила самоисследования

При выполнении protocol.register() библиотека проходит по фиксированному списку имён вызываемых объектов и привязывает каждый из них, который находит в экземпляре бэкенда:

  • Добавление read в класс включает флаг CHANNEL_FLAG_READ. Вызов с хоста channel_read() достигает бэкенда только в том случае, если этот флаг установлен.

  • Добавление write включает флаг CHANNEL_FLAG_WRITE, активируя channel_write().

  • Добавление lock и unlock включает флаг CHANNEL_FLAG_LOCK, позволяя хосту заблокировать канал для атомарного многопакетного чтения.

  • Добавление poll позволяет хосту дёшево спросить «есть ли что-нибудь готовое?», не выполняя полное чтение.

Отсутствие методов не является ошибкой – библиотека протокола просто оставляет соответствующую возможность отключённой. Бэкенд только с size и read вполне допустим; это канал данных, доступный только для чтения.

12.7.2. Канал датчика, доступный только для чтения

Канал датчика, который публикует свежее показание каждый раз, когда хост запрашивает, и отклоняет запись с хоста, использует четыре из функций обратного вызова:

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

Разберём, что делает каждый метод:

  • poll возвращает флаг свежести данных. Хост вызывает его перед чтением и полностью пропускает чтение, когда он возвращает False. Это экономит затраты на обращение в случае «новых данных пока нет».

  • size пересоздаёт буфер по запросу и сообщает его длину. Выполнение выборки здесь означает, что бэкенду не нужна фоновая задача – каждое измерение запускается вызовом с хоста.

  • read возвращает фрагмент буфера. Библиотека протокола может вызвать его более одного раза, когда буфер больше согласованной максимальной полезной нагрузки; аргумент offset проходит по фрагментам.

  • Отсутствие write означает, что запись с хоста отклоняется на уровне формирования кадров, до того как задействуется бэкенд.

12.7.3. Полный набор функций обратного вызова

Для справки – каждый метод, который библиотека ищет в бэкенде:

Метод

Возвращает

Назначение

init(self)

object

Необязательная однократная инициализация при первой привязке канала к хосту. Возвращает любое значение, отличное от None, при успехе.

poll(self)

bool

Возвращает True, когда данные доступны.

lock(self)

bool

Захватывает канал для атомарной многопакетной передачи.

unlock(self)

bool

Освобождает ранее установленную блокировку lock.

size(self)

int

Количество байтов, доступных для чтения из канала в данный момент.

shape(self)

tuple

До четырёх целых чисел, описывающих структуру данных (например, высота изображения, ширина, количество байтов). Используется хостом для распаковки типизированных буферов.

read(self, offset, size)

bytes

Возвращает до size байтов, начиная с offset. Вызывается один раз для каждого фрагмента, когда полезная нагрузка превышает согласованный максимум.

readp(self, offset, size)

bytes

Вариант read без копирования: память буфера должна оставаться действительной на всё время передачи.

write(self, offset, data)

int

Хост записал data по смещению offset. data – это представление bytearray в приёмный буфер уровня протокола; скопируйте то, что хотите сохранить, перед возвратом.

ioctl(self, cmd, length, arg)

int

Определяемый приложением код операции вне модели чтения/записи. Отрицательное возвращаемое значение означает ошибку.

flush(self)

object

Сбрасывает любые буферизованные данные. Вызывается, когда хост хочет сбросить канал.

is_active(self)

bool

Имеет смысл только для бэкендов, представляющих физический транспорт (встроенные USB-каналы). Каналам приложения это не нужно.

Это весь интерфейс бэкенда. Двенадцать имён методов, все необязательные, и библиотека протокола решает, что может делать каждый канал, на основе того, какие из них присутствуют.