Amazonのアソシエイトとして、ラズパイダ(raspida.com)は適格販売により収入を得ています。詳しくは当サイトの プライバシーポリシーをご覧ください。

Raspberry Piに市販されているSony製PaSoRiなどを繋げば、NFCタグの読み書き用に使えます。NFCリードライターを扱うのに、ちょうど良いパッケージのnfcpyもあります。

NFCカードリーダーとNFCタグさえあれば、インストールは1つで、あとはPythonプログラムだけですぐに試せます。

Raspberry Piで何かする中でも、NFCタグは容易に始められる部類に入ります。気になったら一度試してみてください。今回作成したPythonプログラムも使ってみてください。

Pythonコードを間違ってコピーしていたので修正しました。(2025-05-01)

今回の環境

費用として、RC-S380/Pだけ4~5000円かかりますが、NFCタグは安く1000円以下で10枚くらい買えます。

  • Raspberry Pi 5 bookworm 64bit
  • Sony PaSoRi RC-S380/P
  • nfcpy
  • NFCタグ カード(NXPNTAG215)492 byteまで可
  • NFCタグ カード(NXPNTAG213)137 byteまで可

一番大変なのはプログラムですね。

非エンジニアとしては、ChatGPTに頼るしかありません。でも、上手く行きましたよ。

nfcpyをインストール

ChatGPTに頼んでPythonプログラムを作ってもらうのですが、nfcpyを使う前提ですからインストールします。

pipでインストールしたいので、Pythonを仮想環境にしてからインストールしました。この辺のお話は以前に書いています。

python3 -m venv env     #Python仮想環境

source env/bin/activate #仮想環境をアクティブに

pip3 install nfcpy #pip3でインストール

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

ちなみに(env)から元のプロンプトへ戻りたい場合はdeactivateとだけ入力して実行すれば元に戻ります。今回は(env)のまま進めます。

sudoなしで実行させる

python3 -m nfcを実行してください。

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

これはNFCリーダーがroot権限で動作するのに対し、sudoなしのユーザー権限で実行しているからです。

sudoで実行しても良いのですけど、ターミナルに表示されたメッセージ通り、udevルールを追加すれば管理者権限は要りません。

エラーの出力されたターミナル画面に、どうすればいいのかコマンドが表示されていますから、それをコピーして実行すれば完了です。

こんなヤツです。

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

この中のidだけがNFCリーダーによって違うので、上記は参考までにしてコピペしないでください。

実行したら、次のコマンドで有効化します。

sudo udevadm control -R

そしてここで再起動すると、再起動後はsudoは必要ありません。

もう一度python3 -m nfcを実行してエラーが出ないか確認してください。エラーが無いと次のような出力になるハズです。

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

サンプルで中身を読み込む

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

サンプルがいくつかある中で、tagtool.pyを使います。tagtool.pyは、中身を見ることもformatすることもできます。書き込むこともできるのですが、ややコマンドが複雑になってしまうため、書き込むのはChatGPTに作ってもらうことにしました。

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

シールタイプのNFCタグを読み込んでみます。NXP NTAG213という商品で、読み込ませてみると、容量が137バイト、中身は書き込まれていないので0バイトになっているのが分かります。

早速、コマンドで読み込みをしてみます。

コマンドはpython3 tagtool.py showです。空のNFCタグは次のように表示されました。

[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=04BCB9C24C5880
NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 137 byte
  message   = 0 byte

シールタイプだと物に貼って使うか、テーブルに固定で使うといったことができますね。

ちなみにカードタイプも試しました。こちらNXP NTAG215で、492バイトを書き込めるようです。

[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

読み込まれていれば、事前の準備ができている証拠で、もしエラーが出たなら、先程の”sudoなしで実行する”まで見直しましょう。

サンプルtagtool.pyのコマンド

読み込む

python3 tagtool.py show

フォーマット(初期化)

書き込んだ中身のデータを消すのに使います。

python3 tagtool.py format

NFCに一度書き込んだデータは、およそ10万回は書き換えられるとされています。

■Pi 5は8GBモデルがオススメ

書き込めるバイト数

書き込む前に書き込めるバイト数について調べておきました。

今回書き込むのはWebのURLなどのURI、または日本語を含むテキスト、そしてvCardと呼ばれる(ビジネスカード≒連絡先)のデータです。

通常1バイトは英語なら1文字で、日本語だと2バイトになるのですが、書き込むのは文字コードutf-8のバイナリデータなので、日本語だと3バイトになります。

次のデータがバイナリデータの例です。

data = b'\x02ja\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'

最初のバイト列を取り出すと、\xe3\x83\xa9とあり、これはカタカナの「ラ」です。

Unicode: U+30e9 (ラ) UTF-8: \xe3\x83\xa9 (ラ)

実際には日本語で入力したものを、書き込む前にバイナリデータ(UTF-8)に変換(エンコード)して書き込みます。読み込んだ時に今度は読めるテキストへ復元(デコード)しているので、データ入力時に意識はしません。プログラム上のお話です。

レコードには制御文字(STX)がルールとして付け足されます。(このカードリーダーとNFCタグの例)同じように言語コード(jaやen)も足されます。

最初にあるb'\x02jaの部分です。何も指定していないと、恐らくen(英語)になっているでしょう。書き込むプログラムで指定しないとならない場合も出てきます。テキスト文字列を書き込むのに指定しています。

このように、英語はそのまま1バイトですが、日本語だと3バイト必要になるため、先程のカードやシールの容量から割り算すれば、おおよその文字数が分かりますね。

132バイト → 日本語44文字以下 492バイト → 日本語164文字以下

但し、これはテキストを指定したデータ形式で、且つレコード数が1つであればの話です。レコード毎に制御文字も必要ですし、他にも何バイトか必要になることもあり、実際はもう少し短い文字数しか入りません。

これを踏まえて、NFCタグに書き込めるPythonプログラムをChatGPTに作ってもらいました。

ChatGPTに作ってもらったコード

ChatGPTに頼んだPythonプログラムは、いきなり一発で書き込めましたが、vCard(名刺)がややこしくて何度かやり直しました。

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

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

シールタイプは容量が少ないので、vCard(名刺)には名前と電話番号だけにして、リビジョンは入れない仕様です。

リビジョン入りも最後にご紹介しています。

いずれにしても、非エンジニアがChatGPTに書いてもらったコードなので、動作はしてもエラー処理や例外処理は足りていません。おかしなところがあってもご容赦ください。

NFCタグへ書き込むPythonプログラム

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で読み込んで確かめてみましょう。

実行するとこんなUIです。

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

書き込んだ結果はこんな感じです。書き込んだデータは\x03raspida.com/です。\x03https:のことです。これも自動で変換されます。

type = 'urn:nfc:wkt:U'というのはURIだよってことです。ndef.UriRecord関数を使えば自動的に付与されます。

[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/'

テキストの場合。40バイトは間違っている??文字数になっている。

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'

書かれているのは、ラズパイダは非エンジニアでも楽しめるRaspberry Pi を紹介しています。です。

vCard形式の場合。

NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 137 byte
  message   = 64 byte
NDEF Message:
record 1
  type = 'text/vcard'
  name = ''
  data = bytearray(b'BEGIN:VCARD\nVERSION:3.0\nN:山田;太郎\nTEL:0312345678\nEND:VCARD')

iPhoneだとアプリなしでの読み込みはURLしか対応していない

URIに書き込んだURLは、スマホをそのままかざせば読み込めます。関連付けられたWebブラウザが立ち上がります。

しかし、他の形式は現在、スマホで読み込ませようとしても何もおきません。何かNFCタグを読み込むアプリを経由しないとなりません。

昔は反応した気がするのですけど、何年か前からはそういう仕様になっています。Androidは持っていないので分かりません。

セキュリティ的にヤバイですから仕方ありませんね。

アプリを経由すれば、連絡先に登録するといったことが行えます。

vCardデータにリビジョンを入れた修正版

最終的にvCardの場合、vCard 3.0仕様に沿ったリビジョンまで入れて修正したコードも載せておきます。

リビジョンの文字数が多いため、容量492バイトでギリギリでした。シールタイプではエラーになります。

機能追加の以前に、こちらのコードの方が読みやすくまとまりました。

write_nfc_rev.py(クリックして開く)

import nfc
import ndef
import datetime

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":
        now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
        full_name = name.replace(";", " ")
        # vCardフォーマットにnameを空文字列、last_nameとfirst_nameを含める
        vcard_text = (
            "BEGIN:VCARD\n"
            "VERSION:3.0\n"
            f"FN;CHARSET=UTF-8:{full_name}\n"
            f"N;CHARSET=UTF-8:{name};;;\n"
            f"TEL;TYPE=CELL:{content}\n"
            f"REV:{now}\n"
            "END: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, last_name, first_name)})
    except Exception as e:
        print(f"❌ NFCリーダーに接続できませんでした: {e}")

if __name__ == "__main__":
    main()

結果:

** waiting for a tag **
Type2Tag 'NXP NTAG215' ID=04DE79B2C11190
NDEF Capabilities:
  readable  = yes
  writeable = yes
  capacity  = 492 byte
  message   = 138 byte
NDEF Message:
record 1
  type = 'text/vcard'
  name = ''
  data = bytearray(b'BEGIN:VCARD\nVERSION:3.0\nFN;CHARSET=UTF-8:\nN;CHARSET=UTF-8:;;;\nTEL;TYPE=CELL:0312345678\nREV:2025-05-01T03:16:10.000Z\nEND:VCARD')

Pi 5でもNFCカードに書き込める

今やスマホにNFCは備わっていますから、何もRaspberry Piを使わなくても書き込んだり読み込んだりは可能です。

Raspberry Piなら、読み書きだけではなく、自身のプログラムでいかようにも扱えます。

NFCタグのIDや書き込んだデータを利用して、何かのトリガーとしたり、書き込まれたデータ自体をデータベースで管理したり、状況に応じて自動で書き換えたりもできますね。

今やスマホもiPhoneの標準アプリのような「ショートカット」アプリを用いれば、同じようなことができます。Raspberry Piならスマホのアプリを作るような意味合いで機能を拡大できると思います。

単純にデータを書き込んだり読み込んだりはプログラムの勉強にもなりますし、楽しいので一度トライしてみてはいかがでしょうか。