12.7. チャネルコールバック

protocol.register() に渡されるバックエンドオブジェクトは Python のクラスです。プロトコルライブラリは、そのクラスがどのメソッドを実装しているかをクラス自身に問い合わせることはしません。インスタンスを検査して、見つかったメソッドを結び付けます。このイントロスペクションこそが、バックエンドインターフェースを柔軟にしているものです。最小限で役立つバックエンドは 2 つのメソッドからなり、最も精巧なものは 12 個になり、アプリケーションは 1 度に 1 つのメソッドずつ、各機能をオプトインで利用します。

12.7.1. イントロスペクションのルール

protocol.register() が実行されると、ライブラリは決められた呼び出し可能名のリストをたどり、バックエンドインスタンス上で見つかったものをそれぞれバインドします。

  • クラスに read を追加すると CHANNEL_FLAG_READ がオンになります。ホストからの channel_read() の呼び出しは、このフラグが設定されている場合にのみバックエンドに到達します。

  • write を追加すると CHANNEL_FLAG_WRITE がオンになり、channel_write() が有効になります。

  • lockunlock を追加すると CHANNEL_FLAG_LOCK がオンになり、ホストが複数パケットのアトミックな読み取りのためにチャネルをロックできるようになります。

  • poll を追加すると、ホストは完全な読み取りを強制することなく、低コストで「何か準備ができているか?」を問い合わせられます。

メソッドが存在しなくてもエラーにはなりません。プロトコルライブラリは対応する機能を無効のままにするだけです。sizeread だけを持つバックエンドはまったく問題なく有効で、読み取り専用のデータチャネルになります。

12.7.2. 読み取り専用のセンサーチャネル

ホストが要求するたびに新しい読み取り値を発行し、ホストからの書き込みを拒否するセンサーチャネルは、コールバックのうち 4 つを利用します:

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

データ構造を記述する最大 4 つの整数(例: 画像の高さ、幅、バイト数)。型付きバッファをアンパックするためにホストが使用します。

read(self, offset, size)

bytes

offset から始まる最大 size バイトを返します。ペイロードがネゴシエートされた最大値を超える場合、フラグメントごとに 1 回ずつ呼び出されます。

readp(self, offset, size)

bytes

read のゼロコピー版: バッファのメモリは転送の間有効なままでなければなりません。

write(self, offset, data)

int

ホストが offsetdata を書き込みました。data はプロトコル層の受信バッファへの bytearray ビューです。保持したいものは返す前にコピーしてください。

ioctl(self, cmd, length, arg)

int

読み取り/書き込みモデルの外にあるアプリケーション定義のオペコード。負の戻り値はエラーです。

flush(self)

object

バッファされたデータをすべて破棄します。ホストがチャネルをリセットしたいときに呼び出されます。

is_active(self)

bool

物理的なトランスポート(組み込みの USB チャネル)を表すバックエンドでのみ意味を持ちます。アプリケーションチャネルにはこれは不要です。

これがバックエンドインターフェースの全体です。12 個のメソッド名はすべて省略可能で、プロトコルライブラリはどれが存在するかに基づいて各チャネルが何をできるかを決定します。