nfcpyにおけるタイムアウト処理

2021-07-25

nfcpyを利用してカード情報を読み取る際のタイムアウト処理について考えます。

nfcpy

nfcpyと非接触型のデバイスを使って、NFC対応のカードから情報を読み取ることができます。

今回はPaSoRiのRC-S380を利用しています。対応デバイスは公式を確認してください。

nfcpy : Supported Devices

カード情報を読み取る簡単なコードは以下のようになります。

import nfc


with nfc.ContactlessFrontend('usb') as clf:
    clf.connect(rdwr={})

これだとカードの読み取りが行われるまで(カードが置かれ、離されるまで)待ち続けてしまいます。

nfcpyにおいて、以下のように実装されていることで読み込み待機が発生しています。

# nfc/clf/__init__.py

class ContactlessFrontend(object):
    ...

    def connect(self, **options):
        ...

        terminate = options.get('terminate', lambda: False)  # (1)
        ...

        try:
            while not terminate():  # (2)

                if rdwr_options:
                ...

        ...

(1) デフォルトのterminatelambda: Falseと設定されています。

(2) これにより、terminate()がFalseを返し続け、永遠に読み取りを行おうとします。

timeout

つまり、connectの引数にterminate=を与えることで、タイムアウト処理を実装できます。

公式のサンプルを参考に、5秒後に待機をやめるようにするには以下のようになります。

nfcpy : nfc.clf.ContactlessFrontend.connect

from functools import partial
import time

import nfc


def afrer(n, started):
    return time.time() - started > n

with nfc.ContactlessFrontend('usb') as clf:
    started = time.time()
    wait_s = 5
    clf.connect(
        rdwr={}, 
        terminate=partial(afrer, wait_s, started)
        )

”読み取りを開始した時間(started)から、5秒後(wait_s)にTureが返るような関数”をterminateに渡すことでタイムアウト処理を実装することができました。


これだとカードを置いている状態でも、タイムアウトが実行されてしまいます。

そこで、カードが置かれている場合はタイムアウト処理を停止させます。

class Card(object):

    def __init__(self):
        self.on_card = False

    def on_connect(self, tag):
        self.on_card = True
        return True

    def on_release(self, tag):
        self.on_card = False
        return True

    def after(self, started, n):
        return time.time() - started > n and not self.on_card

    def __call__(self, started, n):
        with nfc.ContactlessFrontend('usb') as clf:
            rdwr_options = {
                'on-connect': self.on_connect,
                'on-release': self.on_release
            }
            clf.connect(
                rdwr=rdwr_options,
                terminate=partial(self.after, started, n)
            )

card=Card()
card(time.time(), 5)

'on-connect''on-release'のオプションを追加してあげることで、カードの読み込み開始と終了を確認できます。

これでon_cardがTrueの際(カードが置かれたままの状態)には、タイムアウト処理が停止します。

外部制御

このterminateを利用することで、別の場所から読み込み待機を解除することが可能になります。

import threading
import time

import nfc


class Card(object):
    def __init__(self):
        self.flag = False

    def __call__(self):
        with nfc.ContactlessFrontend('usb') as clf:
            clf.connect(rdwr={}, terminate=lambda: self.flag)

threading.Thread(target=(card:=Card())).start()
time.sleep(5)
card.flag=True

外からflagをTrueにすることで、読み込み待機を解除できました。

この実装により、カード読み込みとカード登録などを一台の非接触形のデバイスでカバーできるようになります。