PythonでNFC・RFIDタグを読み書き!
産業用ラズベリーパイ活用ハンズオン

どの業界でもRFIDを用いた業務管理が広まった印象がします。今よりも遥かにイニシャルコストが高かった2010年頃からでしょうか、大手企業や特定の業界での導入が目立ちました。

日本の社会が抱えている課題に対し、業務の自動化や効率化が求められています。
人手不足や人材確保の面からも、企業が効率化を図るなら、今後はAI技術も導入していく流れになっています。

効率化を考える際、データの収集・蓄積、分析の時代は過ぎました。今後はデータから予測し、自動化を含めた更なる最適化のニーズが高まっています。

中でもRFIDタグを利用する仕組みは、導入のハードルも下がり、様々な事案に対応できる範囲も広いため最初の取り組みにオススメです。

RFIDはどんなもの?

すでに一般的になった電子決済もRFIDの一種です。
RFIDに関係する用語がたくさんあり、非常に分かりにくいと感じている人は多いでしょう。

RFIDは「Radio Frequency Identification」の略で、直訳すると「無線周波数識別」となり、電波を用いてデータを非接触で受信する仕組みです。

電波というだけあって、帯域があります。
例えば、電子決済で用いるNFCはHF帯(短波:13.56MHz)となり、短い距離でやり取りする仕組みです。
NFCは「Near Field Communication」の略ですから、「近距離無線通信」という意味になり、短い距離が用語になっています。
NFCはRFIDのHF帯と言い換えることもできます。

UHF帯

他の帯域にUHF帯があります。
「Ultra High Frequency」の略です。極超短波帯で、日本だと920MHz帯、海外だと860MHz帯などが一般的です。

この帯域は主に産業用途で利用されています。産業用途でのメリットが大きいからです。

産業用RFIDタグ製品例

  • 金属面に取り付けられる
  • コンクリートに埋め込める
  • 完全防水
  • 高速移動体でも読み取れる

前述のHF帯のNFCは、約10cmの距離でしか読み込むことができない仕様です。
電子決済を想像してください。概ね、あの距離感です。

一方、UHF帯は数m〜5mと離れていても読み込むことができます。※どちらも条件により距離は異なる。
加えて、NFC(≒HF帯)では1対1の通信なのに対して、UHF帯は複数を一度に読み込むことも可能です。

業務で利用するRFID

RFIDを用いた仕組みで想像しやすいのは商品管理です。
配送センターでの商品のピックアップや、倉庫での在庫・入出庫管理などで有効な手段になります。

工場などでは工程管理や品質管理に役立つでしょう。
半製品の在庫管理に利用して、完成品の過剰在庫や欠品を防止する仕組みも考えられます。

昔は紙ベースの作業からバーコード管理へ、そしてQRコード管理も一般的になりました。今ではRFID、特にUHF帯で複数を同時にスキャンする仕組みが一般的になっています。

普及したQRコードなどは、汚れや障害物に弱いため、想定する現場では使えないことも多くあります。
その点、RFIDタグは汚れも障害物にも影響されにくい性質ですから、どの現場でも適応させられます。

以前に比べてイニシャルコストもランニングコストも低下しました。
産業用ラズベリーパイに代表されるように、大きなPC類と直結させずに設置できる省スペース化も可能な時代です。
連携するIoT端末も、ここ数年で処理速度が向上してきました。

このような環境からも、RFIDタグを活用したシステム化も、できることが増えています。
 

UHFタグの読み込みシステム(PL-R4の例)

一口にRFIDやNFCといっても、非常に細かく複雑な内容を含んでいますから、最初から産業用に特化したデバイスを選びたいところです。

産業用ラズベリーパイでは、NFCやUHF帯を使ったRFIDリーダーの仕組み化も無理がありません。場所も取らず非常にコンパクトに収まります。
小さなモニターを取り付ければリアルタイムで可視化ができます。

ラズベリーパイ本体も産業用途向けですから、厳しい環境に設置することが可能です。

NFCを産業用ラズベリーパイでも試してみる

ここからは実際にRFIDタグの動作を試してみます。
読み込み速度など、実際に動かしてみると理解が深まりますのでオススメです。

UHF帯ではなく、NFC(≒HF帯)であれば、手元ですぐに試すことができます。

動作テストするプログラムもサンプルプログラムを試したり、自力でコーディングするにしても、今ではChatGPTを利用すれば僅かな労力で用意できます。

NFCタグを読み取るリードライターも、容易に手に入る市販製品を使い、産業用ラズベリーパイで読み書きのテストをしてみました。

後半に動作するPythonプログラムを載せてありますので、コピーしてお使いください。

今回の環境

産業用ラズベリーパイであるPL-R4(CM4利用)には、Raspberry Pi OS(bullseye)がインストールしています。32bit版です。

PL-R4 BASIC RJ Plus IP20 [サンプル機]
NFCカードリーダ
  • PL-R4(Raspberry Pi CM4搭載)
  • Raspberry Pi OS (bullseye) 32bit
  • Sony PaSoRi S380/P
  • 読み書き用NFCカード2種類

Sony S380/Pは、USBケーブルで接続してlsusbで確認するば、必要なIDが確認できます。

lsusb

Bus 001 Device 008: ID 054c:06c3 Sony Corp. RC-S380

nfcpyをインストール

Raspberry PiでNFCリーダーを扱うのに便利なパッケージにnfcpyがあります。
先ずはnfcpyをインストールして、サンプルプログラムから試してみましょう。

pipでインストールしたいので、Pythonを仮想環境にしてからインストールしました。

source env/bin/activateを実行すると、ターミナルには(env)と表示されます。これで仮想環境にいることが分かります。

python3 -m venv env     #Python仮想環境
source env/bin/activate #仮想環境をアクティブに
pip3 install nfcpy #pip3でインストール

最後に(env)から元のプロンプトへ戻りたい場合はdeactivateとだけ入力して実行すれば元に戻ります。今回は全て終わるまで(env)のまま進めます。

nfcpyをインストールすると、その他のモジュールを含め、以下のようなバージョンでした。(記事執筆時点)

libusb1-3.3.1 ndeflib-0.3.3 nfcpy-1.0.4 pydes-2.0.1 pyserial-3.5

あとは動作確認してから、読み書きするプログラムを実行するだけです。

動作確認

はじめに、python3 -m nfcを実行してください。最終的には次のように表示されればOKです。

python3 -m nfc

This is the 1.0.4 version of nfcpy run in Python 3.9.2
on Linux-6.1.21-v8+-aarch64-with-glibc2.31
I'm now searching your system for contactless devices
** found SONY RC-S380/P NFC Port-100 v1.11 at usb:001:008
I'm not trying serial devices because you haven't told me
-- add the option '--search-tty' to have me looking
-- but beware that this may break other serial devs

最初は恐らくエラーになります。

NFCリーダーがroot権限で動作するデバイスなのに、ユーザー権限で実行しているからです。
sudo付きで実行しても良いのですけど、ターミナルのメッセージ通り、udevルールを追加します。
どうすれば良いのかコマンドが表示されていますから、そのままコピーして実行すれば完了です。

以下の例は、私の環境です。
VendorやProductの値は機器によってそれぞれ異なっています。このままコピーしないでください。自分の環境でターミナルに表示されているコマンドで実行しましょう。

SonyのS380/Pなら054c06c3がIDです。

sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'

sudo udevadm control -R #即時有効化

これで準備が整いました。
一旦ここで再起動します。

再起動後、sudo権限での実行は不要になります
もう一度python3 -m nfcを実行して、最初に記載したようにエラーが出ないか確認してください。

以後、NFCリーダーを扱うPythonプログラムもsudoなしで実行できます。

再起動後、source env/bin/activateを実行してからサンプルプログラムを実行します。
先程ホームディレクトリでpython3 -m venv envを実行したなら、source ~/env/bin/activateです。
venv環境にしないと、ModuleNotFoundError: No module named 'ndef'のエラーになります。
 

サンプルを試す

nfcpyのサンプルファイルをgit cloneで取得します。ファイル群は/nfcpy/examplesにあります。

git clone https://github.com/nfcpy/nfcpy.git
cd ./nfcpy/examples

いくつかある中で、tagtool.pyを使い情報を取得してみます。

tagtool.pyは、読み書きやフォーマットも可能なプログラムです。
中身を閲覧するならshowコマンドを追加し、フォーマット(初期化)するならformatを追加します。

読み込む

python3 tagtool.py show

フォーマット(初期化)

python3 tagtool.py format

NFCに一度書き込んだデータは、ロックを掛けることもできます。ロックを掛けていなければ、中身を消して新たに書き込めます。およそ10万回は書き換えられるとされています。十分ですね。

書き込むコマンドだけはtagtool.pyでは少々複雑なので、あとでChatGPTに書き込むプログラムを書いてもらうことにします。

最初に試したカードは、無地ホワイトのNXP NTAG215です。

tagtool.pyによる中身の表示はこれです。

python3 tagtool.py show

[nfc.clf] searching for reader on path usb
[nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:001:008
** waiting for a tag **
Type2Tag 'NXP NTAG215' ID=04C154B2C11190
NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 492 byte
  message   = 0 byte

タグの種類がType2Tag、容量(capacity)は492バイトで読み書きが可能とあり、NDEF Messageはまだ0バイトなので空というわけです。

タグの種類はいくつかあり、例えばType3Tagは電子マネーで有名なFeliCaのことです。今回はType2Tagです。

次は、データを書き込んであった別のカードを試してみます。

[nfc.clf] searching for reader on path usb
[nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:001:008
** waiting for a tag **
Type2Tag 'NXP NTAG215' ID=04A32EB2C11190
NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 492 byte
  message   = 117 byte
NDEF Message:
record 1
  type = 'urn:nfc:wkt:U'
  name = ''
  data = b'\x04raspida.com'
record 2
  type = 'urn:nfc:wkt:T'
  name = ''
  data = b'\x02en\xe3\x83\xa9\xe3\x82\xba\xe3\x83\x91\xe3\x82\xa4\xe3\x83\x80\xe3\x81\xaf\xe9\x9d\x9e\xe3\x82\xa8\xe3\x83\xb3\xe3\x82\xb8\xe3\x83\x8b\xe3\x82\xa2\xe3\x81\xa7\xe3\x82\x82\xe6\xa5\xbd\xe3\x81\x97\xe3\x82\x81\xe3\x82\x8bRaspberry Pi \xe3\x82\x92\xe7\xb4\xb9\xe4\xbb\x8b\xe3\x81\x97\xe3\x81\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82'

NDEF Message以下にレコードが2つ、dataとして何やら入っています。
record 1はサイトのURL、レコード2にはUTF-8で符号化されたUnicode(業界標準規格)の文字列がズラッと並んでいます。

record 2にある最初の”\x02″は制御文字(STX)です。enはjaなど指定できます。
それ以降に続くのは、UTF-8だと、英語(ASCII)は1バイトで1文字、日本語(Unicode)は3バイトで1文字になっています。

| 日本語 | Unicode | UTF-8符号化 |
| :-: | :—–: | :———-: |
| ラ | U+30E9 | \xe3\x83\xa9 |

実際に書き込んだのは、開始記号=”\x02en”を除いた以降の文字列です。

バイナリデータの変換

特に必要はありませんが興味があったので、UTF-8にエンコードしたUnicode文字をテキストに戻す(デコード)コードを、ChatGPTに頼みPythonで作成しました。

“\x02en”は、リードライターを使うと勝手に付いてきてしまうので、読みやすいように取り除いています。

decode_binary.py

# バイナリ文字列(UnicodeのUTF-8)を標準入力から受け取るスクリプト

def main():
    # 入力を受け取る(例: \x02en\xe3\x83... のような文字列)
    raw_input = input("バイナリ文字列を入力してください(例: \\x02en...):\n")

    try:
        # 文字列をバイト列に変換(エスケープシーケンスとして解釈)
        byte_data = raw_input.encode('utf-8').decode('unicode_escape').encode('latin1')

        # 先頭3バイト (\x02 + 'e' + 'n') を取り除く
        if byte_data.startswith(b'\x02en'):
            byte_data = byte_data[3:]

        # UTF-8としてデコード
        decoded_text = byte_data.decode('utf-8')

        print("\n変換結果:")
        print(decoded_text)

    except Exception as e:
        print("\nエラーが発生しました:")
        print(e)

if __name__ == "__main__":
    main()

変換結果:
https://raspida.com
ラズパイダは非エンジニアでも楽しめるRaspberry Pi を紹介しています。

これで変換すると読めるようになりました。

書き込む

次は書き込みです。
再びChatGPTに頼んだPythonプログラムです。

条件として、書き込むデータタイプを3つに絞り、選択して書き込めるようにしました。
書き込めるレコードは1つのみで、複数レコードに対応させていません。

  • URI
  • テキスト
  • vCard(名刺)

URIとはURLを書き込むデータ形式です。NFCタグにURLを書き込むと、スマホでそのまま読み込めます。関連付けられたWebブラウザが立ち上がります。

他の形式は現在、スマホで読み込ませようとしても何もおきません。別途でNFCタグを読み込むアプリが必要です。

なお、vCardは、vCard 3.0仕様に沿ったデータ形式が望ましいです。今回は137バイトしかないNFCタグだったので容量が足りず、敢えてREVといったタイムスタンプは設定していません。

このようなシールタイプのNFCタグです。NXP NTAG213です。

write_nfc.py

import nfc
import ndef

def write_tag(tag, data_type, content, language, name=None, last_name=None, first_name=None):
    if tag.ndef is None:
        print("❌ このタグはNDEFに対応していません。")
        return False

    if data_type == "uri":
        records = [ndef.UriRecord(content)]
    elif data_type == "text":
        records = [ndef.TextRecord(content, language=language)]
    elif data_type == "vcard":
        # vCardフォーマットにnameを空文字列、last_nameとfirst_nameを含める
        vcard_text = f"BEGIN:VCARD\nVERSION:3.0\nN:{last_name};{first_name}\nTEL:{content}\nEND:VCARD"
        records = [ndef.Record(type="text/vcard", name="", data=vcard_text.encode('utf-8'))]
    else:
        print(f"❌ データタイプ '{data_type}' は未対応です。")
        return False

    tag.ndef.records = records
    print("✅ 書き込み完了!")
    return True

def main():
    print("データタイプを選んでください:")
    print("  1: URI")
    print("  2: Text")
    print("  3: vCard")
    choice = input("番号を入力してください (1, 2, 3): ")

    language = "ja"  # 常に日本語に設定

    if choice == "1":
        data_type = "uri"
        content = input("URIを入力してください: ")
        name = None  # URIの場合、nameは不要
        last_name = first_name = None
    elif choice == "2":
        data_type = "text"
        content = input("テキストを入力してください: ")
        name = None  # Textの場合、nameは不要
        last_name = first_name = None
    elif choice == "3":
        data_type = "vcard"
        name = ""  # vCardのnameは空文字
        last_name = input("姓 (例: 山田): ")
        first_name = input("名 (例: 太郎): ")
        content = input("電話番号 (例: 0312345678): ")
    else:
        print("無効な選択肢です。終了します。")
        return

    print("カードをタッチしてください...")
    try:
        clf = nfc.ContactlessFrontend('usb')
        clf.connect(rdwr={'on-connect': lambda tag: write_tag(tag, data_type, content, language, name,>
    except Exception as e:
        print(f"❌ NFCリーダーに接続できませんでした: {e}")

if __name__ == "__main__":
    main()

ターミナルで1つずつ入力待ちにしています。
テキストを書き込んでから、tagtool.pyで書き込まれたか、それぞれ確かめてみます。

入力例:1

データタイプを選んでください:
  1: URI
  2: Text
  3: vCard
番号を入力してください (1, 2, 3): 1
URIを入力してください: http://raspida.com/
カードをタッチしてください...
✅ 書き込み完了!

tagtool.py showの結果:

[nfc.clf] searching for reader on path usb
[nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:001:008
** waiting for a tag **
Type2Tag 'NXP NTAG213' ID=0420B2C24C5880
NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 137 byte
  message   = 17 byte
NDEF Message:
record 1
  type = 'urn:nfc:wkt:U'
  name = ''
  data = b'\x03raspida.com/'

入力例:2

データタイプを選んでください:
  1: URI
  2: Text
  3: vCard
番号を入力してください (1, 2, 3): 2
テキストを入力してください: 書き込みのテストです。
カードをタッチしてください...
✅ 書き込み完了!

tagtool.py showの結果:

[nfc.clf] searching for reader on path usb
[nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:001:008
** waiting for a tag **
Type2Tag 'NXP NTAG213' ID=0420B2C24C5880
NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 137 byte
  message   = 40 byte
NDEF Message:
record 1
  type = 'urn:nfc:wkt:T'
  name = ''
  data = b'\x02ja\xe6\x9b\xb8\xe3\x81\x8d\xe8\xbe\xbc\xe3\x81\xbf\xe3\x81\xae\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82'

入力例:3

データタイプを選んでください:
  1: URI
  2: Text
  3: vCard
番号を入力してください (1, 2, 3): 3
姓 (例: 山田): 山田
名 (例: 太郎): 太郎
電話番号 (例: 0312345678): 0312345678
カードをタッチしてください...
✅ 書き込み完了!

tagtool.py showの結果:

python3 tagtool.py show

[nfc.clf] searching for reader on path usb
[nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:001:008
** waiting for a tag **
Type2Tag 'NXP NTAG215' ID=04DE79B2C11190
NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 492 byte
  message   = 77 byte
NDEF Message:
record 1
  type = 'text/vcard'
  name = ''
  data = bytearray(b'BEGIN:VCARD\nVERSION:3.0\nN:\xe5\xb1\xb1\xe7\x94\xb0;\xe5\xa4\xaa\xe9\x83\x8e\nTEL:0312345678\nEND:VCARD')

データは正しく書き込まれていました。

このようにNFCタグを読み込んだり、書き込んだりするのは、Pythonプログラムで実現できました。
非エンジニアの私でも、ChatGPTを使えばエラーがなく動作するプログラムを用意できます。
このコードを元に、他のデータ形式や複数レコードなどに改変してみてください。


参考
https://nfcpy.readthedocs.io/en/latest/topics/get-started.html
https://vcardmaker.com


記事寄稿:ラズパイダ

非エンジニアでも楽しく扱えるRaspberry Pi 情報サイト raspida.com を運営。ラズベリーパイに長年触れた経験をもとに、ラズベリーパイを知る人にも、これから始めたいと興味を持つ人にも参考になる情報・トピックを数多く発信。PiLinkのサイトへは産業用ラズベリーパイについて技術ブログ記事を寄稿。