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-канали). Канали застосунку у цьому не потребують.

Це повний інтерфейс бекенду. Дванадцять імен методів, усі необов’язкові, і бібліотека протоколу вирішує, що може робити кожен канал, на основі присутніх методів.