NFCタグを活用して水を楽に買いたい
作ったもの
リポジトリ
経緯
「やばい、水がなくなりそうAmazonで頼まないと」とか思ってるのにいつの間にかベッドの上。
NFCタグにスマホをかざすだけで商品ページに飛びたい。
よし遊んでみよう。
NFCとは
NFCはNear Field Communicationの略で、近距離無線通信の規格です。
その名の通り、NFCを内蔵したスマホとタグなどをピッタリくっつけることで、データ通信を行うことができます。
キャッシュレス決済などを想像すると分かりやすいです。
今回はタグにURLを書き込んで、スマホをかざすことで商品ページが開く仕組みを作る。
買ったもの
こちらのNFCタグを購入しました。
NTAG215と商品名に書いてありますが、これはチップの型番らしくNTAG215は504バイト書き込むことができるようです。
今回は、URL(38バイト)を書き込みたいので十分そう。
実装
今回はAndroid端末で試したいのでKotlinで開発します。
権限
AndroidManifestには、NFCを使うための権限が必要です。
スマホのNFCハードウェアへアクセスするためのもの。
<uses-permission android:name="android.permission.NFC" />
NfcAdpter
調べていくうちに見つけたのがこれ。
端末の NFC アダプター(コントローラ)を操作するためのクラスです。
var nfcAdapter: NfcAdapter? = null
デフォルトのNFCアダプターを取得
そしてこれ。
getDefaultAdapter
関数を呼び出すことで、デフォルトの NFC アダプターを取得することができます。
nfcAdapter = NfcAdapter.getDefaultAdapter(activity)
NFC アダプターがない場合はnull
を返されるので念のため。
設定で「NFCの使用」をON/OFF試してみたけど関係なさそう。
この時点でnull
が返ってきたら使えないということで。
if (nfcAdapter == null) {
Toast.makeText(
activity,
"この端末はNFC非対応です。",
Toast.LENGTH_SHORT
).show()
}
リーダーモードに制限
読み取り/書き込みとしてのみ動作させたいので、enableReaderMode
関数を使います。
val readerCallback = NfcAdapter.ReaderCallback { tag: Tag? ->
if (tag == null) return@ReaderCallback
// ここで書き込み処理を書く予定
}
// NTAG215を使う予定なので、Type A のフラグを使う
val flag = NfcAdapter.FLAG_READER_NFC_A
// NFC アダプターをリーダーモードに制限する
nfcAdapter?.enableReaderMode(activity, readerCallback, flag, null)
こちらが引数です。
引数 | 説明 |
---|---|
activity |
Activityを渡せばいい。 |
callback |
読み取りできた時にやりたい処理を渡せばいい。 |
flags |
NFC の種類を渡せばいい。 |
extras |
リーダーモード設定用の追加オプション。今回は特に必要ないのでnull を渡す。 |
NTAG215は、Type A の仕様に完全に準拠するように設計されています。
なのでFLAG_READER_NFC_A
をflags
に渡している。
リーダーモードを元に戻す
リーダーモードを元に戻したいときは、disableReaderMode
関数を使います。
// リーダーモードを元に戻す
nfcAdapter?.disableReaderMode(activity)
NDEF
NDEF(NFC データ交換形式)は、型付きデータをカプセル化するために使用される軽量バイナリフォーマットです。
NFC フォーラムによって NFC による送信と保存のために仕様化されている。
データフォーマットは、後述する NdefRecord
と NdefMessage
で実装されています。
NdefRecord
NDEFレコードはデータであり、後述するNDEFメッセージを構成するために必要です。
NDEFレコードには、URIなどの型指定されたデータが含まれます。
createUri
今回はURLを書き込みたいので、 createUri
関数を使ってURIのレコードを作成する。
※ inputText
はString型であり、テキストフィールドから入力したもの
// URIレコードを作成
val uriRecord = NdefRecord.createUri(inputText.toUri())
NdefMessage
NDEFメッセージは、1つ以上のNDEFレコードを持っているコンテナです。
NDEFメッセージ作成
このようにコンストラクタを呼び出すことで、 NdefMessage
のインスタンスを生成できる。
// URIレコードを作成
val message = NdefMessage(arrayOf(uriRecord))
書き込み処理
NdefRecord
と NdefMessage
を作り終えたので、次は書き込みの処理。
NDEF のクラスがあるので、タグ上の NdefMessage
を上書きする。
get
get
関数があるので前述した NfcAdapter.ReaderCallback
で得たタグの NDEF
を取得する。
タグが NDEF
形式でないか、 NDEF
形式だけど Android デバイスが実装していない仕様の場合は、 null
が返される。
val readerCallback = NfcAdapter.ReaderCallback { tag: Tag? ->
if (tag == null) return@ReaderCallback
try {
val ndef = Ndef.get(tag)
} catch (e: Exception) {
}
}
connect
NDEF への 入力/出力 操作を有効にします。
ndef.connect()
writeNdefMessage
NDEF の NDEFメッセージを上書きします。
ndef.writeNdefMessage(message)
close
NDEF への 入力/出力 操作を無効にし、リソースを解放します。
ndef.close()
書き込み処理の全体像
こちらが書き込み処理の全体像です。
あとは、Android端末をかざしてNFCタグを読み取るだけ。
val readerCallback = NfcAdapter.ReaderCallback { tag: Tag? ->
if (tag == null || !isWritingUrlToTag) return@ReaderCallback
try {
// URIレコードを作成
val uriRecord = NdefRecord.createUri(inputText.toUri())
val message = NdefMessage(arrayOf(uriRecord))
// 読み込んだタグのNDEFを取得
val ndef = Ndef.get(tag)
if (ndef != null) {
ndef.connect()
// 書き込み可能かチェック
if (!ndef.isWritable) {
ndef.close()
// UIスレッドで表示する
activity.runOnUiThread {
Toast.makeText(
activity,
"タグが書き込み禁止です。",
Toast.LENGTH_SHORT
).show()
}
return@ReaderCallback
}
// 容量チェック
val needed = message.toByteArray().size
val max = ndef.maxSize
if (max < needed) {
ndef.close()
activity.runOnUiThread {
Toast.makeText(
activity,
"容量不足: 必要 $needed / 上限 $max バイト",
Toast.LENGTH_SHORT
).show()
}
return@ReaderCallback
}
// 書き込み
ndef.writeNdefMessage(message)
ndef.close()
activity.runOnUiThread {
Toast.makeText(
activity,
"URLを書き込みました。",
Toast.LENGTH_SHORT
).show()
}
} else {
activity.runOnUiThread {
Toast.makeText(
activity,
"書き込みできませんでした。",
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
activity.runOnUiThread {
Toast.makeText(
activity,
"書き込みエラー: ${e.message ?: ""}",
Toast.LENGTH_SHORT
).show()
}
}
}
※ inputText
はString型であり、テキストフィールドから入力したもの
※ isWritingUrlToTag
はBoolean型であり、書き込みを制御するために作ったもの
まとめ
NFCタグを読み取って水の購入ページを開くことができた!
普通に NFC Tools アプリ使って書き込んだらええやん案件ですが、
興味から勉強していくのは楽しいですね。
これで水を買うのが楽しくなりそう。
参考にした記事
Discussion