Raspberry PiとNFCでスマートフォンでタッチするとリクエストを送るようにする

![IMG_3137.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/00f3b6ae-f700-f661-ee98-d16e6ac865a2.jpeg)
# 必要なもの
**Raspberry Pi 2 model B v1.2**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/a221d92d-1f44-3063-abb2-a0210c58aa35.png)
**PaSoRi RC-S380**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/06e3d2b6-e9ca-ba89-2be7-b2237fbb8064.png)
**TP-Link TL-WN725N**
（Raspberry Pi 2 model BにWifiが搭載されていないため）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/2fd352e2-0712-7d05-c264-d89e16de5c62.png)
**電磁スピーカー**
PCのビープ音を出すスピーカーを使いました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/5c8c98c7-fcdd-f9eb-f7ce-3274d523abb0.png)
[電磁スピーカー Amazon URL](https://www.amazon.co.jp/gp/product/B08B1DCYS2/ref=ppx_yo_dt_b_asin_title_o09_s00?ie=UTF8&psc=1)

# Raspberry Pi にOSをインストールする

下記サイトから Raspberry Pi Imager をインストール
[Raspberry Pi OS](https://www.raspberrypi.com/software/)

Raspberry Pi Imagerを起動したら、「OSを選ぶ」から「Raspberry Pi OS」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/b0dd8dac-94f6-1ec0-ab61-687d83028bb2.png)
「ストレージ」からはRaspberry Piに読み込ませるmicro sdカードを選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/03ba6f54-f78d-ec28-5222-13ae4061cde6.png)
「歯車」マークから詳細な設定を行う
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/4bfe0d09-487c-d6fc-459a-8595be6c9eb2.png)
設定は下記のように設定
```
SSHを有効化する
    有効化
ユーザー名とパスワードを設定する
    ユーザー名： user
    パスワード： password
Wi-Fiを設定する
    SSID：　接続先のSSIDを設定 （※ Wi-Fiドングルが2.4GHz帯のWi-Fiしか認識できないので注意）
    パスワード： 接続先のパスワード
    Wifiを使う国： JP
ロケール設定する
    タイムゾーン： Asia/Tokyo
    キーボードレイアウト： jp
```
その他項目はデフォルトのまま「保存」を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/ad60f5b8-77df-b46d-d74e-21ae65164dac.png)
設定が完了したら、「書き込む」を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/e2d9f528-d085-6e06-4668-540d4d15d45c.png)

# TP-Link TL-WN725 のドライバインストール

新しめのWi-Fiドングルの場合、この作業は必要なさそうです。
Raspberry PiにディスプレイとLANケーブルとキーボードを接続して作業します。

パッケージを最新状態に更新
```
sudo apt update
```
インストール
```
sudo apt -y upgrade
```
カーネルヘッダをインストール
```
sudo apt install raspberrypi-kernel-headers git
```
ドライバをコンパイルしてインストール
```
git clone https://github.com/lwfinger/rtl8188eu.git
cd rtl8188eu/
make all
sudo make install
```
/etc/wpa_supplicant/wpa_supplicant.conf に設定を記載
```
sudo vi /etc/network/interfaces.d/wlan0

# 下記を追加
allow-hotplug wlan0
iface wlan0 inet manual
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
```
raspberry-pi の再起動
```
sudo reboot
```

これで、Wi-Fiドングルが認識されるようになります。

参考：https://heavymoon.org/2022/02/05/raspberrypi-tp-link-tl-wn725n/

# SSH で Raspberry Pi に接続

ping で Raspberry Pi のIPを調べる
```
ping raspberrypi.local
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/fe99a1c5-aa84-36a8-8168-c00f39c481ca.png)

SSHで接続
```
ssh user@192.168.0.188
# OSを書き込む際に設定したユーザー名を設定
# ssh ${ユーザー名}＠${ラズパイのIP}
```

参考：https://sukiburo.jp/setup-raspberry-pi/

# NFCリーダーとの接続

NFCが認識されているか確認
```
lsusb
```
Sony Corp. RC-380 が認識されていればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/43740015-c744-c432-fa76-a78962e354c2.png)
nfcpy のインストール
```
sudo pip3 install nfcpy
```

サンプルを実行してNFCが機能するか確認
nfcpyのサンプルをgit cloneする
```
git clone https://github.com/nfcpy/nfcpy.git
```
実行
```
sudo python3 nfcpy/examples/tagtool.py show
```
スマホやNFCカードが読み込まれると下記のようなログが出ます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/f2870350-bb7a-4cc8-2018-9d2020987ee7.png)

sudo 無しで実行できるように設定
```
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 --reload-rules
sudo reboot
```
index.py でアプリを作成
```
vi index.py
```
下記をコピペ
※ 交通系のICカード、iPhone、Android のみ反応します。
※ iPhone の場合、エクスプレスカードに設定した交通系のICが読み取られます

```
import binascii
import nfc
import requests

class MyCardReader(object):
    # iPhoneのエクスプレスカードにアクセスするための処理
    def on_startup(self, targets):
        for target in targets:
            target.sensf_req=bytearray.fromhex("0000030000")
        return targets

    def on_connect(self, tag):
        #タッチ時の処理
        print("--- Touched")

        #タグ情報を全て表示
        print(tag)

        #IDmのみ取得して表示
        self.idm = binascii.hexlify(tag._nfcid)
        print("IDm: {idm}", str(self.idm))

        return True

    def read_id(self):
        clf = nfc.ContactlessFrontend('usb')
        try:
            # 交通系IDまたはモバイルSuicaのみに限定
            clf.connect(rdwr={'targets':['212F'], 'on-startup': self.on_startup, 'on-connect': self.on_connect})
        finally:
            clf.close()


if __name__ == '__main__':
    cr = MyCardReader()
    while True:
        #最初に表示
        print("--- Please Touch")

        #タッチ待ち
        cr.read_id()

        #リリース時の処理
        print("--- Released")
```
実行
```
python3 index.py
```
IDｍがログに出力されていればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/323c2977-ec5a-bbfe-9e0e-5cbb97d09860.png)

参考：https://monomonotech.jp/kurage/raspberrypi/nfc.html
　　　https://qiita.com/xshell/items/55302a588b5927dde6b6

# Google Apps Script（GAS）との連携

リクエスト元を作成するために、スプレッドシートにて「拡張機能」→「Apps Script」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/e4d066c5-0483-7dd6-68e3-057ef15e9a30.png)
「デプロイ」→「新しいデプロイ」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/84ee80e1-cc18-2998-a432-2bf3ab258671.png)
「種類の選択」→「ウェブアプリ」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/2e141378-b3cf-ed2c-8d03-8d89e21f7291.png)
下記項目を入力し「デプロイ」を押下
```
ウェブアプリ	
	自分
アクセスできるユーザー	
	全員
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/2c543171-9288-c51a-7a20-4eaa11de155d.png)
デプロイが完了すると URL が発行されますので、メモって「完了」を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/cd1c2a99-5216-323e-8e11-4b20d42a40ac.png)
ソースコードを下記のように記述
リクエストが成功すると、status: '200' を返却します。
※ getSheetByNameのシート名を適宜書き換えてください
```
function doPost(e) {
  const idm = e.parameter.idm
  Logger.log('IDm: %s', idm)
  // シートオブジェ
  const sh = SpreadsheetApp.getActive().getSheetByName('シート名')
  sh.getRange(`A1`).setValue(idm)
  // レスポンス
  const output = ContentService.createTextOutput()
  output.setContent(JSON.stringify({status: '200'}))
  return output
}
```

「デプロイ」→「デプロイを管理」を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/f542a9de-13d4-3eac-561b-921cce3ad003.png)
「鉛筆マーク」を押下し、バージョンを「新バージョン」を選択し「デプロイ」を押下
※ この方法じゃないとURLが変わってしまうため
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/03711bc3-0fee-3b4c-3bd6-16c34d54a1e1.png)
NFCアプリの index.py を下記に書き換える
```
import binascii
import nfc
import requests

class MyCardReader(object):
    # スプレッドシートにリクエストを送る
    def request_gas(self, idm):
        url = 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' # 先程生成したGASのURLを設定
        res = requests.post(url, data={'idm': idm}).json()
        print('http status: {status}'.format(status=res['status']))

    # iPhoneのエクスプレスカードにアクセスするための処理
    def on_startup(self, targets):
        for target in targets:
            target.sensf_req=bytearray.fromhex("0000030000")
        return targets

    def on_connect(self, tag):
        #タッチ時の処理
        print("--- Touched")

        #タグ情報を全て表示
        print(tag)

        #IDmのみ取得して表示
        self.idm = binascii.hexlify(tag._nfcid)
        print("IDm: {idm}", str(self.idm))

        # リクエスト
        self.request_gas(str(self.idm))

        return True


    def read_id(self):
        clf = nfc.ContactlessFrontend('usb')
        try:
            # 交通系IDまたはモバイルSuicaのみに限定
            clf.connect(rdwr={'targets':['212F'], 'on-startup': self.on_startup, 'on-connect': self.on_connect})
        finally:
            clf.close()

if __name__ == '__main__':
    cr = MyCardReader()
    while True:
        #最初に表示
        print("--- Please Touch")

        #タッチ待ち
        cr.read_id()

        #リリース時の処理
        print("--- Released")
```

index.py を実行して、スマートフォンをかざす
ログに「http status: 200」が出力されていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/bf849b58-6133-5d59-7715-f9f805baf32b.png)
スプレッドシートにIdmが書き込まれていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/78d47b73-7432-ecbf-e3c6-5003c39d1235.png)

# ブザー音を鳴らす

ブザーの端子の位置を替える。
端子がツメに引っかかっているだけなので、マイナスドライバーなどでツメを持ち上げてあげれば簡単に抜けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/ff708bdb-85a8-71a3-8060-9e8cfefccc7a.png)
スピーカーのプラス端子を12番目にマイナス端子を14番目に接続する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/d64e866e-9395-28b6-191b-bdb9cb6a5ab7.png)
GPIO 画像：https://www.raspberrypi.com/documentation//computers/os.html#gpio-pinout

NFC読み取りの際に作成した index.py を修正
```
import binascii
import nfc
import requests
import buzzer

bz = buzzer.Buzzer()

class MyCardReader(object):
    # スプレッドシートにリクエストを送る
    def request_gas(self, idm):
        url = 'https://script.google.com/macros/s/AKfycbxV6byms_z8wFbfzF_Od6Pr2hVBsjXUm_j9PcrW9daH_GqdvgBcaHlvzar2lZu9zFgutg/exec' # 先程生成したGASのURLを設定
        res = requests.post(url, data={'idm': idm}).json()
        print('http status: {status}'.format(status=res['status']))

    # iPhoneのエクスプレスカードにアクセスするための処理
    def on_startup(self, targets):
        for target in targets:
            target.sensf_req=bytearray.fromhex("0000030000")
        return targets

    def on_connect(self, tag):
        #タッチ時の処理
        print("--- Touched")
        bz.touch()

        #タグ情報を全て表示
        print(tag)

        #IDmのみ取得して表示
        self.idm = binascii.hexlify(tag._nfcid)
        print("IDm: {idm}", str(self.idm))

        # リクエスト
        self.request_gas(str(self.idm))

        return True


    def read_id(self):
        clf = nfc.ContactlessFrontend('usb')
        try:
            # 交通系IDまたはモバイルSuicaのみに限定
            clf.connect(rdwr={'targets':['212F'], 'on-startup': self.on_startup, 'on-connect': self.on_connect})
        finally:
            clf.close()


if __name__ == '__main__':
    cr = MyCardReader()
    while True:
        #最初に表示
        print("--- Please Touch")

        #タッチ待ち
        cr.read_id()

        #リリース時の処理
        print("--- Released")
```

ブザーを鳴らすために下記のクラスを buzzer.py で作成
```
vi buzzer.py
```
下記をコピペ
```
import pigpio
from gpiozero import TonalBuzzer
from gpiozero.pins.pigpio import PiGPIOFactory
from time import sleep

# BUZZERのピン
BUZZER_PIN = 18

# TONEレベル
TONE_LV1 = "C4"
TONE_LV2 = "D4"
TONE_LV3 = "G4"
TONE_LV4 = "B4"
TONE_LV5 = "E5"

class Buzzer(object):
    def __init__(self):
        factory = PiGPIOFactory()
        self.bz = TonalBuzzer(BUZZER_PIN, pin_factory=factory)

    # ブザー音を鳴らす
    def ring(self, note, time):
        self.bz.play(note)
        sleep(float(time))
        self.bz.stop()

    # タッチ時
    def touch(self):
        self.ring(TONE_LV5, '0.2')

```
GPIOを制御するために、pigpioを有効化します
```
sudo pigpiod
```
動作確認
```
python3 index.py
```

タッチするとブザー音がなるようになっています。

# Raspberry Pi 起動時に自動的にアプリを立ち上げる
NFCアプリをサービス化させる
```
sudo vi /usr/lib/systemd/system/nfc-touch.service
```

下記内容を記載
ExecStartに NFCアプリのパスを設定
```
[Unit]
Description=NFC Touch

[Service]
ExecStart=/usr/bin/python3 /home/user/index.py
Restart=always
Type=simple

[Install]
WantedBy=multi-user.target
```
サービスの起動
```
sudo systemctl daemon-reload
sudo systemctl start nfc-touch.service
```
サービスの起動確認
```
sudo systemctl status nfc-touch.service
```
Active: の項目が active(running) となっていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418022/b6941a10-c50d-4d6b-c31c-998b416b067f.png)
サービスの有効化
```
sudo systemctl enable nfc-touch.service
```
pigpioを起動時から有効にするためサービスを有効化
```
sudo systemctl enable pigpiod.service
```
Raspberry Pi を再起動
```
sudo reboot
```

これで電源を入れる際にNFCタッチアプリが起動するようになりました。