以前、Raspberry Pi 2 + パソリ RC-S320 + libpafeにて、FeliCa読み取り時にPowerOffし、Slackへ通知したことがありました。
その後、
- PaSoRiを
RC-S380
へ更新した nfcpy
がPython3対応した
などがあったため、 nfcpy
を使い、同じことを実現したくなりました。
ただ、いくつかハマったところがあったため、メモを残します。
目次
- 環境
- ラズパイのセットアップ
- Pythonスクリプトの作成
- 動作確認
- ソースコード
環境
- Raspberry Pi 2 Model B
- Raspberry Pi OS 32-bit
- Released 2022-01-28
- OSインストール先のmicro SD カード
- KIOXIA KMSDER45N016G
- 前回と異なり、bootとrootの両方ともmicroSDに入れている
- PaSoRi RC-S380
- 任意のFeliCa (Suica/KURURUなど)
- systemd 247 (247.3-6+rpi1)
systemctl --version
にて確認
- Python 3.9.2
- Raspberry Pi OSのデフォルト
- nfcpy 1.0.4
- slack_sdk 3.15.2
- PyCharm Professional Edition
なお、以前の記事とは異なり、今回はSlack BotにてSlackへ通知します。
そのため、今回の記事を試す場合の前提として、
Bot token
を持つアプリを作成- アプリの権限に
chat:write
を設定
というSlack appを用意してあるものとします。
Basic app setup | Slack
ラズパイのセットアップ
macにて、Raspberry Pi Imager にてOSをmicroSDに書き込む
最近のラズパイでは Raspberry Pi Imager
を使ってOSをmicroSDに書き込むのが良さそうです。
https://www.raspberrypi.com/software/
今回はOSとして、 Raspberry Pi OS (32-bit)
、バージョンは Released 2022-01-28
を使用します。
なお、 Raspberry Pi Imager
実行後はmacからmicroSDが取り外されてしまいます。
ただ、次の作業を行うため、microSDを抜き差しして、macにmicroSDを認識させておきます。
Finderの 場所
に boot
と表示されていればOKです。
macにて、SSHを可能にするファイルを作成する
以下のコマンドでSSHを可能にするためのファイルを作成します。
% touch /Volumes/boot/ssh
これでmac上でのmicroSDに対する作業は完了したため、macからmicroSDを抜きます。
macからラズパイへSSH接続
microSDをラズパイに挿入してから電源を入れ、まずはパスワード認証でSSHできることを確認します。
% ssh pi@raspberrypi.local The authenticity of host 'raspberrypi.local (***:***:***:***:***:***:***:***)' can't be established. ECDSA key fingerprint is SHA256:***. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'raspberrypi.local,***:***:***:***:***:***:***:***' (ECDSA) to the list of known hosts. pi@raspberrypi.local's password: Linux raspberrypi 5.10.92-v7+ #1514 SMP Mon Jan 17 17:36:39 GMT 2022 armv7l ... pi@raspberrypi:~ $
ラズパイのログインを公開鍵認証でも可能にする
毎回パスワード認証するのは手間なので、公開鍵認証でもログインできるように設定します。
セキュリティなどを考えると公開鍵認証のみに絞ることも考えられますが、今回は試すだけなので併用する形でOKとしておきます。
macにて、SSH接続用のSSH鍵を生成する
macのターミナルでSSH鍵を生成します。今回はパスフレースなしでOKとします。
% ssh-keygen -t rsa -b 4096 -f ~/.ssh/pi_felica_rsa Enter passphrase (empty for no passphrase): <そのままEnter(パスフレースなし)>
SSHの公開鍵をラズパイに登録
ssh-copy-id
を使って、macからラズパイへ登録します。
% ssh-copy-id -i ~/.ssh/pi_felica_rsa.pub pi@raspberrypi.local /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "~/.ssh/pi_felica_rsa.pub" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys pi@raspberrypi.local's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh 'pi@raspberrypi.local'" and check to make sure that only the key(s) you wanted were added.
公開鍵でラズパイへSSHする
以下でログインできればOKです。
ssh -i ~/.ssh/pi_felica_rsa pi@raspberrypi.local
vimを入れる
インストールされているのは vim-tiny
なため、ふつうのvimに差し替えます。
RaspberryPi3のセットアップ続き〜VimやNFS設定 - Qiita
# 確認 $ dpkg -l | grep vim ii vim-common 2:8.2.2434-3+deb11u1 ii vim-tiny 2:8.2.2434-3+deb11u1 # アンインストール $ sudo apt-get --purge remove vim-common vim-tiny # インストール $ sudo apt-get install vim
ラズパイを固定IP化する
以前の通り、ラズパイを固定IP化します。
Python2 + Scapyで、Raspberry Pi 2 Model B をブリッジにできるか試してみた #router_jisaku - メモ的な思考的な
# 対象のファイルを開く $ vi /etc/dhcpcd.conf # 以下を末尾に追加 interface eth0 static ip_address=192.168.0.52/24 static routers=192.168.0.1 static domain_name_servers=192.168.0.1 # 有効化 $ sudo service dhcpcd reload sending signal HUP to pid 393
しばらく待つと、ラズパイでの入力受け付けるようになり、固定IP化されます。
Pythonスクリプトの作成
今回の開発方法について
今回は、PyCharmのremote sdk機能を使い
という形で開発してみます。
ラズパイのPythonバージョンを確認
以前は2系でしたが、現在は3.9系でした。
# ラズパイ上で確認 $ python --version Python 3.9.2
ラズパイのプロジェクトディレクトリを作成
今回使うPythonスクリプトは以下のディレクトリに入れるため、作成しておきます。
/home/pi/projects/rpi_felica_poweroff_with_nfcpy
PyCharmの SSH Interpreter まわりの設定を追加し、動作確認
今回、PyCharmmの SSH Interpreter 機能を使って、PyCharmでラズパイ上のファイルを動かしながら開発を行います。
まずはSSH Interpreter 機能が動作するかを確認します。
確認用ファイルを用意
ローカルの任意のディレクトリをPyCharmのプロジェクトルートとして、 <Project root>/src/main.py
ファイルを確認用ファイルとして作成します。
main.py
の実装は以下です。ラズパイ上で実行できれば Linux
と表示されるはずです。
import platform print(platform.system())
PyCharmの設定を追加
3箇所設定を追加します。
PyCharmで SSH Configurations を追加
まずはPyCharmにSSH設定を追加します。
- メニューから
Preference > Tools > SSH Configurations
を開く - 左上の
+
をクリック - 以下を入力
- Host:
192.168.0.52
- User name:
pi
- Authentication type:
Key pair
- Private key file:
上記で生成したSSH秘密鍵(ディレクトリより選択)
- Host:
Test Connection
ボタンをクリックし、接続が成功すればOK
PyCharmに SSH Interpreter を追加
続いて、PyCharmからラズパイのPythonインタプリタを扱えるよう、設定を追加します。
- メニューから
Preference > Project > Python Interpreter
を開く - 右の歯車マークから
Add
を選択 - 左側で
SSH Interperter
を選択し、右側でExisting server configuration
を選択、上記で作成したSSH設定を指定- 今回の例だと
pi@192.168.0.52:22
- 今回の例だと
Next
をクリック- 以下を設定し、
Finish
をクリック- Interpreter:
/usr/bin/python
(デフォルトのまま)- 今回はシステムのPythonを使うため
- Sync folders
- Local Path:
/path/to/rpi_felica_poweroff_with_nfcpy/src
- プロジェクトルートの下に作った
src
ディレクトリを指定
- プロジェクトルートの下に作った
- Remote Path:
/home/pi/projects/rpi_felica_poweroff_with_nfcpy
- Local Path:
Authmatically upload project file to the server
にチェックを入れる
- Interpreter:
実行設定を追加
最後に、PyCharmから実行するときの設定を追加します。
- メニューから
Run > Edit Configurations
をクリック - 左上の
+
をクリックし、Python
を選択 - 以下を入力し、
OK
をクリック- Name:
任意
- Script path: mac上の
src/main.py
を選択 - Python interpreter: 上記で作成した
Remote ***
を選択
- Name:
- PyCharmのindexingが走るので、しばらく待つ
動作確認
実行ボタンをクリックすると、以下が表示されることを確認します。
ssh://pi@192.168.0.52:22/usr/bin/python -u /home/pi/projects/rpi_felica_poweroff_with_nfcpy/main.py Linux
macのターミナルで実行した時と結果が異なるため、ラズパイ上で実行できていることが確認できました。
% python src/main.py Darwin
udevのrulesファイルを作成し、PaSoRi RC-S380をラズパイで扱えるようにする
rulesファイルの置き場所について
以前の記事では /lib/udev/rules.d/
の下に置いていました。
ただ、今回nfcpyを使ったところ、 /etc/udev/rules.d/
の下に置いても動作したため、 /etc
の方にします。
PaSoRiの idVendor と idProduct を確認
以前と同じく確認します。
$ dmesg | grep usb ... [ 4.363804] usb 1-1.2: New USB device found, idVendor=054c, idProduct=06c3, bcdDevice= 1.11 ... [ 4.363875] usb 1-1.2: Product: RC-S380/P [ 4.363903] usb 1-1.2: Manufacturer: SONY [ 4.363924] usb 1-1.2: SerialNumber: 0514577 ...
udevのrulesファイルを作成
nfcpyからPaSoRiを扱えるよう、以前 PaSoRi RC-S320を使ったときのrulesファイルを流用して作成します。
Raspberry Pi 2 + systemd + udevで、USBデバイス挿入時にサービスを起動する - メモ的な思考的な
sudo vi /etc/udev/rules.d/90-rc-s380.rules
として、以下を設定します。
SUBSYSTEM=="usb", ACTION=="add", ATTRS{idProduct}=="06c3", ATTRS{idVendor}=="054c", GROUP="plugdev", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rc-s380.service", NAME="pasori380"
変更点としては、
GROUP
をusb
からplugdev
へと変更- nfcpyのドキュメントに「the device is owned by 'root' but you are 'stephen'. also members of the 'root' group would be permitted. you could use 'sudo' but this is not recommended. it's better to add the device to the 'plugdev' group」とあったため
- 念のため、
groups pi
したところ、pi : pi adm dialout cdrom sudo audio video plugdev games users input render netdev spi i2c gpio lpadmin
だったので、plugdev
で大丈夫そう
SYSTEMD_WANTS
は後ほど作成するサービスの名前へと変更NAME
も、とりあえず変更
です。
sudo reboot
で再起動した後にSSHでログインしてみると、PaSoRiが認識されていました。
$ systemctl --type device -a --full UNIT LOAD ACTIVE SUB DESCRIPTION dev-bus-usb-001-004.device loaded active plugged RC-S380 ...
nfcpyでFeliCaを読み込む
ラズパイ上で nfcpy
をインストールします。
$ pip install nfcpy
先ほど用意した main.py
を以下のように書き換えます。
import binascii import nfc def on_connect(tag): idm = binascii.hexlify(tag._nfcid).decode('utf-8') print(idm) # ラズパイのACT LEDをheartbeatにする cmd = shlex.split("echo heartbeat") cmd_redirect = shlex.split("sudo tee /sys/class/leds/led0/trigger") p1 = subprocess.Popen(cmd, stdout=subprocess.PIPE) p2 = subprocess.Popen(cmd_redirect, stdin=p1.stdout) p1.stdout.close() p2.communicate()[0] if __name__ == "__main__": print('読み取りを開始します') with nfc.ContactlessFrontend('usb:054c:06c3') as cf: cf.connect(rdwr={'on-connect': on_connect})
main.py
をラズパイに転送した後に実行すると、
ssh://pi@192.168.0.52:22/usr/bin/python -u /home/pi/projects/rpi_felica_poweroff_with_nfcpy/main.py 読み取りを開始します
で停止します。
011***
また、ラズパイのACT LEDがheartbeatになります。
systemdのserviceファイルを作成する
以前同様、systemdでmain.pyをサービスとして動作させるようにします。
serviceファイルは以前のものを流用し、 sudo vi /etc/systemd/system/rc-s380.service
で作成します。
Raspberry Pi 2 + パソリ RC-S320 + libpafeにて、FeliCa読み取り時にPowerOffし、Slackへ通知する - メモ的な思考的な
[Unit] Description=Pasori RC-S380 service Requires=dev-bus-usb-001-004.device After=dev-bus-usb-001-004.device [Service] Type=simple User=pi Restart=on-failure RestartSec=5 ExecStart=/usr/bin/python /home/pi/projects/rpi_felica_poweroff_with_nfcpy/main.py
変更点としては
です。
サービスの動作確認
sudo reboot
で再起動後、 sytemctl
でサービスの状態を確認します。動作しているようです。
$ systemctl status rc-s380.service ● rc-s380.service - Pasori RC-S380 service Loaded: loaded (/etc/systemd/system/rc-s380.service; static) Active: active (running) since Mon 2022-03-28 07:17:34 BST; 1min 47s ago Main PID: 322 (python) Tasks: 2 (limit: 1597) CPU: 6.196s CGroup: /system.slice/rc-s380.service └─322 /usr/bin/python /home/pi/projects/rpi_felica_poweroff_with_nfcpy/main.py
この状態でPaSoRiにFeliCaを接触させると、ラズパイのACT LEDがheartbeatになります。
Slackへの通知とシャットダウン
通知に使うライブラリについて
以前は os/slacker
を使って通知していました。
https://github.com/os/slacker
しかし、現在ではアーカイブされています。
ただ、slackerのREADMEからSlack公式の python-slack-sdk
へのリンクが張られているため、今回は python-slack-sdk
を使ってみます。
- https://github.com/slackapi/python-slack-sdk
- Python で Slack API や Webhook を扱うなら公式 SDK(slack-sdk/slack-bolt)を使おう - Qiita
上記記事では WebClient
を使っていましたが、asyncioに対応したものがないのかを調べたところ、別の記事にてpython-slack-sdkでは対応している旨の記載がありました。
asyncio アプリ内から Slack にメッセージを投稿しよう - Qiita
せっかくなので、今回はasyncio版を試してみることにします。
まず、ラズパイ上で必要なものをインストールします。
なお、 python-dotenv
は必須ではありませんが、Slackのトークンを .env
ファイルから読み込むようにするために使っています。
$ pip install slack_sdk aiohttp python-dotenv
asyncio版のslack_sdkで通知とシャットダウンを実装
上記の記事に従い、slack_sdkでの通知を実装します。また、合わせてシャットダウン機能も追加します。
追加後の実装は以下のとおりです。
import binascii import os import shlex import subprocess import asyncio import nfc from dotenv import load_dotenv from slack_sdk.web.async_client import AsyncWebClient async def post(): client = AsyncWebClient(os.environ['SLACK_BOT_TOKEN']) response = await client.chat_postMessage( channel=os.environ['DESTINATION'], text=':zzz:', ) print(response) def on_connect(tag): idm = binascii.hexlify(tag._nfcid).decode('utf-8') print(idm) # ラズパイのACT LEDをheartbeatにする cmd = shlex.split("echo heartbeat") cmd_redirect = shlex.split("sudo tee /sys/class/leds/led0/trigger") p1 = subprocess.Popen(cmd, stdout=subprocess.PIPE) p2 = subprocess.Popen(cmd_redirect, stdin=p1.stdout) p1.stdout.close() p2.communicate()[0] # Slackにポストする asyncio.run(post()) # シャットダウン cmd_power_off = shlex.split("sudo systemctl poweroff") subprocess.Popen(cmd_power_off) if __name__ == "__main__": load_dotenv() print('読み取りを開始します') with nfc.ContactlessFrontend('usb:054c:06c3') as cf: cf.connect(rdwr={'on-connect': on_connect})
なお、 .env
ファイルから
を読み込むことから、 /src/.env
として環境に合わせて設定します。
SLACK_BOT_TOKEN= DESTINATION=
一通りの準備ができたので、
- mac上の
<プロジェクトルート>/src
以下をラズパイへデプロイ - ラズパイの再起動
をしておきます。
動作確認
すべての準備が整ったので、全体を通した動作確認をします。
まずはラズパイにSSH接続し、サービスが起動していることを確認します。
# macからSSH % ssh -i ~/.ssh/pi_felica_rsa pi@raspberrypi.local # ラズパイ上でサービスの動作確認 (動作中) $ systemctl status rc-s380.service ● rc-s380.service - Pasori RC-S380 service Loaded: loaded (/etc/systemd/system/rc-s380.service; static) Active: active (running) since Mon 2022-03-28 07:45:45 BST; 5min ago Main PID: 321 (python) Tasks: 2 (limit: 1597) CPU: 19.731s CGroup: /system.slice/rc-s380.service └─321 /usr/bin/python /home/pi/projects/rpi_felica_poweroff_with_nfcpy/main.py
- ラズパイのACT LEDをheartbeatになる
- 指定したチャンネル(ユーザーのDM)へSlackへ
:zzz:
のemojiが通知される - シャットダウンする
が行われます。
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/rpi_felica_poweroff_with_nfcpy