USB接続のNFCリーダー「RC-S380」を購入したので、Pythonで手元のNFCカードを読み取ってみました。
調べてみると、個人向け・業務用のモデルに加え、ソニーが提供するSDKやオープンソースのライブラリなど、複数の選択肢が見つかります。初めて触る場合、手元の機器で何を用意すればよいのか、少し分かりにくく感じました。
この記事では、ソニーの有料SDKを使わずに読み取れた方法と、取得したデータの見方をまとめます。Pythonのコードを保存し、ターミナルから実行できる方を想定しています。NFCの知識は前提にしません。
NTAG215・自分の学生証・PASMOの読み取り結果を取り上げ、カードごとの違いと、取得したデータから分かる範囲を説明します。自分が所有するカード、または読み取りの許可を得たカードを使用してください。記事に掲載する識別情報や文字列の例は、伏せ字または架空の値に置き換えています。
詳細調査用のプログラムはGitHubリポジトリ「demo-NFC」でも公開しています。1 本文ではまず短いコードで読み取りを試し、さらに調べたい場合にGitHub版を使う構成です。GitHub版では、カード固有の情報を既定で伏せ字にします。
TL;DR
- MacとRC-S380/Sで、Pythonの
nfcpyを使ってNFCカードを読み取れた- ソニー製の有料SDKは使用せず、USBアクセスには
libusbを利用した
- ソニー製の有料SDKは使用せず、USBアクセスには
- NTAG215からはUIDとNDEFに記録されたURL、学生証からはIDmと認証不要の一部領域のデータを取得できた
- PASMOはIDmなどの識別情報まで確認し、利用履歴の読み取り・解析は行っていない
- 詳細調査用コードはGitHubで公開している。1
- 記事の最小コードは学習用、GitHub版は調査用として使い分ける
I. 検証環境
| 項目 | 検証環境 |
|---|---|
| PC | M2 MacBook Air(macOS 26.6.2) |
| NFCリーダー | SONY RC-S380/S(PaSoRi) USB認識名: RC-S380/Snfcpyのデバイス指定: usb:054c:06c1
|
| Pythonバージョン | 3.14.0 |
| パッケージ管理 | uv 0.9.7 |
| NFCライブラリ | nfcpy 1.0.4 |
| Python側のUSBライブラリ | libusb1 3.4.0 |
| 読み取り対象 | NTAG215、自分の学生証、PASMO |
Macへの対応について
RC-S380の付属セットアップガイドはWindows向けです。本記事は上記のMac環境でnfcpyを使って確認した手順です。
今回のMacでは動作しましたが、nfcpyのドキュメントではmacOSが定期テスト環境に含まれていません。メーカー提供ソフトの対応OSと、この記事のnfcpyによる動作実績は区別してください。
参考:2
RC-S380について
今回購入したNFCリーダーはこちらです。購入時の価格は5,031円でした。3
Amazonの商品ページでは「PaSoRi RC-S380/S」と表記されていましたが、届いた箱には「RC-S380」と記載されていました。実際にUSB機器の情報を確認すると「RC-S380/S」と認識されていたため、この記事ではRC-S380/Sとして扱います。
II. RC-S380がMacから認識されているかを確認
-
RC-S380をMacのUSBポートに接続する
-
ターミナルでUSB機器の認識情報を確認する
- 次のコマンドは、RC-S380に該当する行とその後の24行を表示する
ターミナルioreg -p IOUSB -l -w 0 | grep -A 24 'RC-S380' -
実行結果を確認する
実行結果"USB Product Name" = "RC-S380/S" "USB Vendor Name" = "SONY" "idVendor" = 1356 "idProduct" = 1729-
idVendor:メーカーを識別する番号(Vendor ID)- 10進数の
1356は16進数で054cとなり、ソニーを表す
- 10進数の
-
idProduct:製品を識別する番号(Product ID)- 同じメーカーの製品を区別するための番号
- 10進数の
1729は16進数で06c1となる
ここでは、箱の表記「RC-S380」ではなく、USBの認識名として「RC-S380/S」が表示されています。
-
-
nfcpyで使うデバイス指定を確認する
-
ioregのidVendorとidProductをそれぞれ16進数にすると、054c:06c1となる - nfcpyでのデバイス指定は、これに
usb:を付けたusb:054c:06c1
16進数の確認printf '%04x:%04x\n' 1356 1729実行結果054c:06c1 -
III. Pythonの実行環境を用意する
-
uvとlibusbを用意する-
command -v uvやbrew list --versions libusbで導入状況を確認する - インストールされていない場合は、Homebrewで導入する
brew install uv libusb
-
-
uvプロジェクトを作成する4
ターミナルuv init --python 3.14 nfc-card-demo cd nfc-card-demo-
uv run python main.pyを実行し、作成されたサンプルプログラムのメッセージが表示されることを確認する
-
-
nfcpyを追加する5ターミナルuv add nfcpy==1.0.4-
uv run python -m nfcを実行し、以下のように表示されればnfcpyの導入を確認できるThis is the 1.0.4 version of nfcpy run in Python 3.14.0
-
GitHubのサンプルコードを使う場合
本文の手順は、自分でPythonファイルを作成する方法です。一方、詳細調査用コードをそのまま使いたい場合は、リポジトリを取得して依存関係を準備できます。1
ターミナルで、上記のnfc-card-demoとは別の場所に移動して実行します。Homebrewでuvとlibusbを導入済みであることを前提とします。
git clone https://github.com/ari-show/demo-NFC.git
cd demo-NFC
uv sync --locked
uv run python main.py
uv sync --lockedは、リポジトリにあるuv.lockを確認し、ロックファイルを変更せずに環境を同期します。main.pyはFeliCaのシステム・サービスも調査するプログラムです。コードの内容やオプションは第VI章の付録で紹介します。
IV. PythonでNFCカードを読み取る
-
main.pyを次の内容に書き換える6ここで扱うのは読み取りの基本を理解するための最小コードです。カードのUID・IDmやNDEFの内容をそのまま表示するため、ターミナル出力をそのまま公開しないでください。GitHub版とはプログラムの内容と既定の表示方法が異なります。
main.pyimport time import nfc DEVICE = "usb:054c:06c1" # 前章で確認したnfcpyのデバイス指定 def on_connect(tag): print("カード種類:", tag.type) print("詳細:", tag) label = "IDm" if tag.type == "Type3Tag" else "UID" print(f"{label}:", tag.identifier.hex().upper()) ndef = tag.ndef if ndef is None: print("NDEFは取得できませんでした") else: print("NDEFの長さ:", ndef.length, "バイト") for record in ndef.records: print("保存データ:", record) return False def main(): try: with nfc.ContactlessFrontend(DEVICE) as reader: print("リーダー:", reader) print("カードをかざしてください。最大60秒待ちます", flush=True) deadline = time.monotonic() + 60 tag = reader.connect( rdwr={"on-connect": on_connect}, terminate=lambda: time.monotonic() >= deadline, ) if not tag: print("カードを取得できずに終了しました") except KeyboardInterrupt: print("読み取りを中断しました") except (OSError, nfc.tag.TagCommandError, ValueError) as exc: print("読み取りエラー", exc) if __name__ == "__main__": main() -
main.pyを実行するuv run python main.py- 待機メッセージが出たら、カードをリーダー中央に置く
- カードへ書き込みは行わず、1枚取得したら終了する
- URLを取得してもブラウザを自動で開くこともない
- 待機メッセージが出たら、カードをリーダー中央に置く
-
実行結果を確認する
- 今回はURLをNDEFとして保存したNTAG215を読み取りました。識別情報は架空の値に置き換えています。
カード種類: Type2Tag 詳細: Type2Tag 'NXP NTAG215' ID=XXX UID: XXX NDEFの長さ: 16 バイト 保存データ: NDEF Uri Record ID '' Resource 'https://example.com'
ここでは、UIDとURLが別々の情報として取得されていることに注目してください。
- UID:タグのICを識別する番号
- URL:タグのユーザーデータ領域にNDEF形式で記録された情報
NTAG215では、通常のデータ書き換えで製造時のUIDが変更されるわけではありません。一方、書き込みが許可されたユーザーデータ領域にあるNDEFメッセージは、対応する書き込み操作によって更新できます。今回のプログラムは読み取り専用で、書き込みは行いません。
以降では、同じリーダーでNTAG215以外のカードを読み取ると、何が異なるのかを見ていきます。
カードが読み取れない・想定した結果が出ない場合
次の点を確認してください。
- RC-S380/SとRC-S380/Pでデバイス指定を取り違えていないか
-
libusb本体がインストールされているか - 他のアプリケーションがリーダーを使用していないか
- 実行環境からUSB機器にアクセスできるか
- USBケーブルやUSBポートを変えても同じ結果になるか
- 対象カードがnfcpyとリーダーの対応する方式か
V. 読み取ったデータの意味を理解する
今回確認できた情報と読み取りの範囲
| 確認したいもの | 今回の結果・記事の範囲 |
|---|---|
| カードの識別番号(UID / IDm) | NTAG215・学生証・PASMOで取得できた |
| カードに保存されたURL | NTAG215からNDEFとして取得できた |
| 学生証に入っている文字列 | 認証不要の一部領域から取得できた。意味までは断定しない |
| 認証必須の領域 | 今回のコードでは読まない |
| PASMOなどの駅名・残高 | 今回は実機でカードの識別情報まで確認。交通系データの読み取り・解釈は未実施 |
NFCは、機器とカードを近づけて通信するための技術です。通信できた後に、どの情報を取り出せるかはカードごとに異なります。
NTAG215:UIDとNDEFは別の情報
NTAG215のユーザーデータ領域は504バイトで、メモリーは4バイト単位の「ページ」で扱います。7
URLをNDEFとして保存した場合、概念的には次のようになります。
NDEFは、URLやテキストなどのデータを共通の形式で表現する仕組みです。どの種類のデータなのかを示す情報と、その内容をレコードにまとめます。読み取り側はその種類を見て、URLなどとして解釈できます。8
504バイトのすべてをURLの文字に使えるわけではありません。管理情報などにも容量を使います。また、NTAG215のデータが必ずNDEFであるわけでもありません。
FeliCa:学生証やPASMOは同じ構造?
自分の学生証を読み取ったときは、nfcpyでは Type3Tag、製品名は FeliCa Standard と表示されました。先ほどのNTAG215とは別の方式です。
| 比較 | 今回のNTAG215 | FeliCa方式のカード |
|---|---|---|
| 通信方式 | NFC-A | NFC-F |
| 識別番号 | UID | IDm |
| データを扱う基本単位 | ページ:4バイト | ブロック:16バイト |
| 今回のデータの扱い方 | NDEFからURLを取り出す | サービスを指定してブロックを読む |
なお、「NDEFとFeliCaのどちらか一方しか使えない」という意味ではありません。FeliCaにもNDEFを扱う仕組みはありますが、今回の学生証からNDEFは取得できませんでした。
FeliCaには、システム、エリア、サービスという仕組みがあります。ファイルシステムに例えると、エリアはフォルダー、サービスはファイルのような役割を持ちます。ただし、PCのファイルシステムと同じものではありません。9
読み取り側は、システムを選択し、サービスとブロック番号を指定してデータを要求します。サービスには認証不要のものと、鍵による認証が必要なものがあります。また、異なるサービスが同じデータ領域を参照する場合もあります。
PASMOなどの交通系カードでは、交通サービス用のデータ形式を解釈する必要があります。NDEFのURLを読むコードだけでは、乗車日・駅名・残高などは表示できません。リーダーが通信できることに加えて、ソフト側がデータの読み方を知っている必要があります。
学生証のデータ構造を調べる
ここまで説明した「システム・エリア・サービス・ブロック」は、FeliCaの論理的なデータ管理の仕組みです。ただし、どのサービスに何の情報が保存されているかは、学生証ごとに異なり得ます。
自分の学生証では、nfcpyからType3Tag(FeliCa Standard)として認識され、調査用プログラムで複数のシステムとサービスを確認できました。一部の認証不要サービスでは16バイト単位のブロックを読み取れましたが、得られた文字列の意味までは特定していません。
調査で分かったことを、3つの段階に分けて整理します。
| 段階 | 分かること | 今回の確認範囲 |
|---|---|---|
| FeliCaの論理構造 | システム・エリア・サービス・ブロックの関係 | 公開仕様をもとに説明できる |
| 今回の学生証での配置 | どのシステムやサービスが存在し、どのブロックが読めるか | 実機で一部を確認できた |
| アプリケーションデータの意味 | 取得した値が学籍番号・日付などの何を表すか | 発行元の仕様が不明なため断定できない |
調査用プログラムは、サービスの属性を調べたうえで、認証不要のサービスに限ってRead Without Encryptionを実行します。認証が必要な領域にアクセスするものではありません。10
以下は処理の概念図です。
今回の学生証でも、このような構造をたどってデータにアクセスできました。個々のシステムコード・サービスコードや実データは、発行元や用途を推測する手掛かりになり得るため掲載しません。次の図も説明のための抽象図であり、実際の学生証のシステム数や配置を再現したものではありません。
ここで重要なのは、「読めたバイト列」と「その意味が分かったこと」を分けることです。以降では、公開に適した架空のデータを使って出力の見方を説明します。調査コードの実行方法は第VI章の付録に掲載します。
16進数と文字列の読み方
調査中に理解が進んだのが、16進数と文字の対応でした。以下は、実際の個人情報を使わずに作った説明用データです。
[0001] 39 30 30 30 30 30 30 30 31 00 32 30 39 39 30 31 | 900000001.209901
[0002] 30 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 02..............
1行の読み方は次のとおりです。
| 部分 | 意味 |
|---|---|
[0001] |
ブロック番号1(このプログラムでは10進数表示) |
39 30 30 … |
読み取った各バイトを16進数で表示 |
900000001.209901 |
同じバイト列を印字可能なASCII文字として表示 |
例えば、16進数の 39 はASCIIの文字 9、30 は文字 0 です。「数値の9をそのまま保存したバイト 09」とは異なります。
上の例を文字としてつなげると、次のようになります。
ブロック1:900000001 [00] 209901
ブロック2:02 [00] …
連結後:900000001 [00] 20990102 [00] …
上の例からは、900000001と20990102という2つの文字列候補を取り出せます。後者は、16バイトのブロック境界をまたいでいます。これは架空の出力例であり、実際の学生証の格納値や桁数を表すものではありません。
この例では、値が00のバイトが.と表示されています。実際のデータにピリオドが保存されているわけではありません。このプログラムでは、印字できないASCII範囲外のバイトを.に置き換えるため、正確な値は左側の16進数で確認します。
また、20990102は日付のように見えても、データ仕様を知らなければ日付とは断定できません。学生証の情報がすべてASCII文字列で保存されているとも限りません。数値・日付・フラグなどがバイナリ形式で格納されている場合もあり、値の意味を特定するには発行元の仕様が必要です。
FeliCaの出力:IDm・PMm・システムコード
PASMOを読み取ったとき、次のような情報が表示されました。
カード種類: Type3Tag
詳細: Type3Tag ID=[伏せ字] PMM=[伏せ字] SYS=0003
IDm: [伏せ字]
NDEFは取得できませんでした
それぞれの項目は、次のように読めます。
| 表示 | 意味 | 公開時の扱い |
|---|---|---|
Type3Tag |
nfcpyが認識したFeliCa系カードの種類 | 個人を示す情報ではない |
ID / IDm
|
FeliCaのカード識別情報。詳細 の ID と IDm は同じ値 |
カードを区別・関連付ける手掛かりになるため伏せる |
PMM / PMm
|
FeliCaの製造パラメーター情報 | 氏名や利用履歴ではない。記事では省略または伏せてもよい |
SYS=0003 |
nfcpyが表示したFeliCaのシステムコード | この値だけから個人や利用履歴は分からない |
| NDEFの取得結果 | この読み取りではNDEFメッセージを取得できなかった | カード内に他のデータがないという意味ではない |
IDmは氏名や学籍番号そのものではありませんが、カードを識別する値として扱い、記事・画像・ログを公開するときは伏せるのが安全です。PMmやシステムコードから、この出力だけで氏名・乗車駅・残高・利用日時が分かるわけではありません。11
PASMOでは何が違う?
PASMOもFeliCaを使う身近なカードです。今回の環境ではカードを検出し、IDm・PMm・システムコードを取得できました。一方、短い読み取りコードではNDEFを取得できず、交通系サービスのデータや利用履歴は読み取っていません。
PASMOの公式案内では、利用日・利用内容・SF残額などを残額履歴として確認できます。また、ソニーのSFCard Viewer 2には、PASMOの利用履歴や残高を表示する機能が案内されています。1213
このような表示には、カードから読み取ったバイト列を交通系のデータ形式として解釈する処理が必要です。学生証の例のようにASCII文字を探すだけでは、日付や残高、駅名を表示できるとは限りません。
| カード | データの解釈の例 | この記事での扱い |
|---|---|---|
| NTAG215 | NDEFを解釈し、URLを取り出す | 実機で確認 |
| 今回の学生証 | 認証不要のブロックを読み、文字列候補を探す | 実機で確認。文字列の用途は未確定 |
| PASMO | 交通系の形式に合わせて、取得データを日付・残高などとして解釈する | 実機でカード検出と識別情報を確認。履歴の読み取り・解析は未実施 |
同じFeliCaでも、今回の学生証のサービスコードや文字列の配置を、そのままPASMOに当てはめることはできません。カードと通信する処理に加えて、そのカードのデータ形式に合った解釈が必要という点がポイントです。
「データを読む」と「履歴を表示する」の違い
これは処理の概念図で、PASMOのメモリー配置図ではありません。付録のプログラムはカードの識別情報と認証不要のブロックを表示する調査用のもので、交通系データの解釈や駅名への変換は行いません。
なお、SFCard Viewer 2は表示機能の参考として挙げています。同ソフトの現行版はWindows向けで、2026年5月の更新でRC-S380がサポート外になっています。この記事のMac・nfcpyの構成で使用するソフトではありません。13
VI. 補足:リーダーのモデル・SDK・ライブラリ
RC-S380とRC-S380/Sの違い
ソニーはRC-S380を個人向け、RC-S380/Sを業務用途専用モデルとして案内しています。また、個人向けRC-S380のセットアップガイドには、ソフト上でRC-S380/Pを選ぶ説明があります。1415
nfcpyの対応機器一覧には、次の2つの指定が掲載されています。
| nfcpyの機種表記 | デバイス指定 | この記事での確認範囲 |
|---|---|---|
| RC-S380/P | usb:054c:06c3 |
対応一覧に掲載。今回の実機検証は未実施 |
| RC-S380/S | usb:054c:06c1 |
今回確認したUSBの認識名・IDと一致し、カード読み取りを確認 |
出典:16
製品の用途区分だけでは、nfcpyで使えるかどうかは判断できません。手元の機器のVendor ID・Product IDを確認し、対応一覧のデバイス指定と照らし合わせます。/P は同一覧には掲載されていますが、定期テスト対象ではありません。
有料SDKは必要?
この記事で行った読み取りには、ソニーの有料SDKを使っていません。Pythonライブラリのnfcpyからlibusbを通じてリーダーを操作しました。
SDKは「ソフトウェア開発キット」のことで、アプリケーションを作るためのライブラリや資料などをまとめたものです。ソニーの製品ページには別売SDKのほか、評価用の無償キットも案内されています。「SDK」という言葉が出てきても、今回のような読み取りに必ず購入が必要とは限りません。17
ただし、nfcpyがソニーのSDKの全機能を代替するわけではありません。今回確認したのは、自分のカードの識別情報、NDEF、FeliCaの認証不要領域です。
nfcpy・libusb1・libusbの役割
今回の構成は次のとおりです。
ここで紛らわしかったのが、libusb1 と libusb の違いです。
| 名前 | 役割 | 今回の導入方法 |
|---|---|---|
nfcpy |
NFC機器を操作し、対応するカード形式を扱う | uv add |
libusb1 |
Pythonからlibusbを呼び出す橋渡し | nfcpyの依存関係として入る |
libusb |
OSを介してUSB機器と通信する本体 | Homebrewで別途導入 |
実際に今回のPython環境を確認すると、libusb1 にはlibusb本体が同梱されておらず、Homebrewの libusb-1.0.dylib が読み込まれていました。
つまり、このMacでは nfcpyをインストールすることと、libusb本体を用意することは別の作業でした。nfcpyの導入資料でも、USBアクセスにはlibusbが必要と説明されています。18
付録:GitHubの詳細調査用プログラムを使う
第V章で説明したFeliCaの調査コードは、GitHubリポジトリのmain.pyに公開しています。10 本文の第IV章にある短いmain.pyとは別のプログラムです。詳細調査を試す場合は、以下のようにGitHub版を取得して実行してください。
git clone https://github.com/ari-show/demo-NFC.git
cd demo-NFC
brew install uv libusb
uv sync --locked
uv run python main.py --device usb:054c:06c1
既定のusb:054c:06c1は今回のRC-S380/S用です。nfcpyの対応一覧にはRC-S380/P(usb:054c:06c3)もありますが、本記事では実機検証していません。16
GitHub版の主な機能は次のとおりです。
| 対象 | GitHub版での扱い |
|---|---|
| UID・IDm・PMm | 取得するが、既定では伏せ字で表示 |
| NDEF | 取得できる場合はレコード種別を表示。内容は既定で非表示 |
| FeliCaのシステム・エリア・サービス | 列挙し、既定では件数のみ表示 |
| 認証不要のブロック | 読み取り、既定では件数・文字列候補数を表示 |
| 認証必須のサービス | 内容を読み取らない |
| PASMOの利用履歴や残高 | データ形式の解析を行わない |
識別情報やブロック内容を実際に確認する場合は、自分のカードだけを対象に--revealを指定します。
uv run python main.py --device usb:054c:06c1 --reveal
--revealではIDm、システムコード、サービスコード、文字列候補なども表示されます。実際の出力を記事・Issue・チャットやログに貼り付けないように注意してください。GitHub版のREADMEにもプライバシーに関する注意事項があります。1
その他のオプションは次のとおりです。
| オプション | 既定値 | 内容 |
|---|---|---|
--device |
usb:054c:06c1 |
nfcpyのデバイス指定 |
--timeout |
180 |
カード待機時間(秒) |
--max-blocks |
32 |
1サービスあたりの最大読み取りブロック数 |
--reveal |
無効 | 詳細な値を伏せずに表示 |
処理には、FeliCaのシステム・サービス調査を約30秒で打ち切る制限や、1システムあたり最大256件の探索上限があります。1枚読み取ったら終了し、カードへの書き込みは行いません。
記事は処理の背景や取得データの意味を解説し、GitHubは詳細調査コードと更新履歴を提供するという役割分担です。コードの最新版を確認したい場合は、リポジトリを参照してください。
VII. まとめ
MacとRC-S380/Sを使い、ソニーの有料SDKを購入せずに、PythonのnfcpyからNTAG215のUIDとURL、学生証・PASMOの識別情報を読み取れました。学生証については、認証不要の一部ブロックからデータを取得できました。
今回の実験で特に重要だったのは、カードと通信してデータを取得できることと、そのデータの意味を説明できることは別だという点です。NTAG215ではNDEFのURLを解釈できましたが、学生証のバイト列については発行元の仕様がなく、用途までは特定していません。また、PASMOの利用履歴の解析も対象外です。
初めての方は第IV章の短いコードから試してみてください。より詳しく調べたい場合は、詳細調査用コードと実行方法をまとめたGitHubリポジトリも参照してください。1
