試してみたブログ

AI関連・iPhone/Pixelなどのガジェット・音声入力・サーマルプリンタなど興味をある事をどんどん試してみた際の記録

Santek EZ Sign NFC電子ペーパーディスプレイをMacで使うのにPaSoRiの何が問題だったか

tameshitemita.blog

(AIに記事書かせています)

PaSoRi RC-S300 で NFC e-ink ディスプレイのリフレッシュができない問題と原因

TL;DR

Sony PaSoRi RC-S300 を macOS で使って NFC 電子ペーパー(Santek EZ Sign NFC)に画像を書き込むことはできるが、画面リフレッシュコマンド(約24秒かかる)がタイムアウトで失敗する。原因は PaSoRi の CCID ドライバが NFC の WTX(Waiting Time Extension)フレームを host に伝播しないため。Android では問題なく動作する。


背景

EZ Sign NFC は NFC 経由で画像データを転送できる e-ink ディスプレイだ。公式アプリは Android/iOS のみ対応なので、Mac + PaSoRi RC-S300 で PC から直接書き込めるツールを Python(pyscard)で自作した。

画像データの転送(F0D3 コマンド)までは完璧に動く。問題はその後の 画面リフレッシュ だった。

何が起きるか

NFC プロトコルでの画面更新は2段階になっている:

F0D4058000  → 68C6(リフレッシュ準備OK)
F0D4850000  → 9000(約24秒後、リフレッシュ完了)

2つ目の F0D4850000 は e-ink パネルの物理的な画面書き換えをトリガーする。4色モデルでは完了まで約24秒かかり、その間 NFC タグは応答を返さない。

Android の NFC スタックでは、タグが NFC レイヤの WTX(Waiting Time Extension)フレーム を送信し、ホスト側がそれを受け入れて接続を維持する。結果として24秒後に 9000(成功)が返ってくる。

一方、Mac + PaSoRi では 約5秒でタイムアウトして Transaction failed になる

原因:CCID ドライバの WTX 非対応

NFC カード(ISO 14443-4 / ISO-DEP)が長時間の処理を行う場合、カードはホストに対して WTX フレームを送信して「まだ処理中だから待って」と伝える。

通常の流れ:

Host → Card: F0D4850000(リフレッシュ実行)
Card → Host: WTX request(まだ処理中)
Host → Card: WTX response(了解、待つ)
  ... 24秒間、WTX のやり取りが繰り返される ...
Card → Host: 9000(完了)

PaSoRi RC-S300 は USB CCID(Chip/Smart Card Interface Devices)プロトコルでホストと通信するが、WTX フレームをカードから受信しても、それを USB 側の host に伝えない(あるいはそもそも処理しない)

これは PaSoRi 固有の問題ではなく、CCID ドライバの非対応リーダーリストでも複数のリーダーで「Time extension requests を host に伝播しない」問題が報告されている。

試したこと(全滅)

1. PC/SC の transmit タイムアウト延長

pyscard の connection.transmit() はデフォルトで CCID ドライバのタイムアウトに従う。Python 側でいくら待とうとしても、CCID レイヤで切断される。

結果: 約5秒でタイムアウト。

2. USB 直接通信(pyusb) + bBWI 最大値

PC/SC を迂回し、pyusb で PaSoRi に直接 CCID メッセージを送る方式。CCID の PC_to_RDR_XfrBlock メッセージの bBWI(Block Waiting Time Integer)を 0xFF(最大値)に設定して送信した。

header = struct.pack('<BIBBBH',
    0x6F,   # PC_to_RDR_XfrBlock
    len(apdu),
    0,      # bSlot
    seq,    # bSeq
    0xFF,   # bBWI = 最大待ち時間
    0x0000, # wLevelParameter
)

USB の read タイムアウトも60秒に設定。

結果: ICC_MUTE(カード無応答)が返る。PaSoRi は bBWI パラメータを無視していると考えられる。

3. タイムアウト後に再接続 + ステータスポーリング

F0D4850000 がタイムアウトした後、25秒待ってからカードを再起動(ICC Power On)し、PIN 再認証してから F0DE(ステータスポーリング)コマンドを送る方式。

# 25秒待機
time.sleep(25)
# カード再接続
ccid_power_on(ep_out, ep_in, seq)
# PIN 再認証
ccid_send(ep_out, ep_in, bytes.fromhex("002000010420091210"), seq)
# ポーリング
ccid_send(ep_out, ep_in, bytes.fromhex("F0DE000001"), seq)

結果: ポーリングは通るが、019000(処理中)が永続し、009000(完了)に到達しない。タイムアウトでの切断がリフレッシュプロセス自体を中断してしまっている可能性が高い。

4. 5秒間隔で再接続リトライ

タイムアウト後に何度も再接続して F0D4850000 を再送する方式。

結果: 毎回タイムアウト。リフレッシュが途中で中断されるため、画面が中途半端な状態になる。

結論:PaSoRi では解決不可能

問題は PaSoRi のファームウェアレベルにある。ソフトウェア側でできることは何もない。

根本的な解決策: - WTX に対応した NFC リーダーに変える(ACR1252U 等、ただし要検証) - Windows で試す(公式アプリが Windows + PaSoRi で動作するため、Windows の CCID ドライバは WTX を処理できる可能性がある)

実用的な回避策

現在は「Mac で画像書き込み、Android でリフレッシュ」という二段構えで運用している。

Step 1: Mac で画像データを書き込む

python write_only.py image.png

F0D3(データ転送)は数秒で完了するので PaSoRi で問題ない。最後に F0D4058000 → 68C6 が返ればデータ書き込み成功。

Step 2: Android でリフレッシュを実行

Android スマホの NFC で EZ Sign にタッチし、以下のコマンドを送信:

002000010420091210  → 9000(PIN認証)
F0D4058000          → 68C6(リフレッシュ開始)
F0D4850000          → 9000(24秒後、リフレッシュ完了)

自作の Flutter アプリか、APDU Sender Contactless で送信できる。

学び

  • NFC リーダーは「NFC が読める」だけでは足りない。長時間応答を待つユースケースでは WTX 対応 が必須
  • CCID 仕様上 bBWI でタイムアウトを制御できるはずだが、実装がそれを尊重するかはリーダー次第
  • USB レベルで直接 CCID メッセージを組み立てても、ファームウェアが対応していなければ意味がない
  • 「PC/SC で動く」と「長いトランザクションが通る」は別の話

参考リンク