Raspberry Pi2でいろいろとやっている中で、コマンドを入力して電源OFFにするのが手間に感じたため、何とかしようと考えました。
Web上には、Raspberry Piにシャットダウンボタンを付けるなどの電子工作記事がありました。ただ、まずは手元にある機器で何とかしようと考え、パソリを使ってFeliCaを読み取った時に電源OFFするようにしてみました。
また、電源OFFをログに残すため、Slackへ通知するようにもしてみました。
目次
環境
- Raspberry Pi 2 Model B
- Raspbian 2015-05-05
- systemd 44
- direnv 2.6.0
- Python環境を分けない場合は無くても可
- Python 3.2.3 (デフォルト)
- pip 1.1
- virtualenv 13.0.3
- slacker 0.6.3
- パソリ RC-S320
- libpafe 0.0.8
- パソリが古いので、libpafeを使う。新しければnfcpyで良さそう(ただし、Python2.xでのみ動作)。
なお、環境については以下の点が前回までの記事とは異なります。
- Raspbianのバージョンが新しい
- ファームウェアのバージョンが古い
- 最近は
sudo rpi-update
すると、4系のファームウェアになってしまうことから、とりあえず3系で進めたいため
- 最近は
- systemdのバージョンが古い
- wheezy-backports版のsystemd 204ではログイン時に connection refusedになるなど不安定だったため
仕様
- パソリをRaspberry Pi2に挿入し、systemdによりFeliCa認識サービスを起動する
- パソリにFeliCaをかざす
- Raspberry Pi2上のACT LEDをheartbeatにして、PowerOffの開始を通知する
- Slackへ通知する
- Raspberry Pi2がPowerOffする
基本的なセットアップ
最近の記事の内容に従い、
- systemdまわり
- direnv + Python3まわり
- libpafeまわり
をセットアップします。
なお、rootファイルシステムをUSBメモリに置くのは、以前の記事を参考に実施済とします。
# とりあえず pi@raspberrypi ~ $ sudo apt-get update pi@raspberrypi ~ $ sudo apt-get upgrade # systemdまわり pi@raspberrypi ~ $ sudo apt-get install systemd systemd-sysv pi@raspberrypi ~ $ sudo reboot pi@raspberrypi ~ $ systemctl --type device # 動作確認 UNIT LOAD ACTIVE SUB JOB DESCRIPTION ... # direnv + Python3まわり pi@raspberrypi ~ $ sudo apt-get install python3-pip pi@raspberrypi ~ $ sudo pip-3.2 install virtualenv pi@raspberrypi ~ $ curl -L https://github.com/zimbatm/direnv/releases/download/v2.6.0/direnv.linux-arm -o direnv pi@raspberrypi ~ $ sudo install direnv /usr/local/bin pi@raspberrypi ~ $ echo 'eval "$(direnv hook bash)"' >> ~/.bashrc pi@raspberrypi ~ $ sudo reboot pi@raspberrypi ~ $ mkdir felica-poweroff pi@raspberrypi ~ $ cd felica-poweroff/ pi@raspberrypi ~/felica-poweroff $ echo layout python3 > .envrc direnv: error .envrc is blocked. Run `direnv allow` to approve its content. pi@raspberrypi ~/felica-poweroff $ direnv allow pi@raspberrypi ~/felica-poweroff $ pip freeze # 動作確認 wheel==0.24.0 # libpafeまわり pi@raspberrypi ~/felica-poweroff $ sudo apt-get install libusb-dev pi@raspberrypi ~/felica-poweroff $ wget http://homepage3.nifty.com/slokar/pasori/libpafe-0.0.8.tar.gz pi@raspberrypi ~/felica-poweroff $ tar xzvf libpafe-0.0.8.tar.gz pi@raspberrypi ~/felica-poweroff $ cd libpafe-0.0.8/ pi@raspberrypi ~/felica-poweroff/libpafe-0.0.8 $ ./configure pi@raspberrypi ~/felica-poweroff/libpafe-0.0.8 $ make pi@raspberrypi ~/felica-poweroff/libpafe-0.0.8 $ sudo make install pi@raspberrypi ~/felica-poweroff/libpafe-0.0.8 $ sudo ./tests/pasori_test # 動作確認 PaSoRi (RC-S320) ...
systemdと連携するためにudevのrulesファイル作成
rulesファイルの作成
過去記事を振り返ると、rulesファイルは以下の2ヶ所に書いていました。
/lib/udev/rules.d/
の下- libpafeでFeliCaを読んだ時に、この場所へ置いた
/etc/udev/rules.d/
の下- USBデバイス挿入時にサービスを起動する時に、この場所へ置いた
今回いろいろと試してみたところ、/etc/udev/rules.d/
の下に置いた場合はlibpafeがパソリを認識しなかったため、/lib/udev/rules.d/
の下へ置くことにしました。
あとは、libpafeの公式サイトにある内容を元に、
NAME
キーを使用したらlibpafeでパソリを認識できなかったidProduct
が006c
や02e1
のものはパソリ RC-S320のものではないため、削除TAG
キーとENV{SYSTEMD_WANTS}
キーで、systemdまわりの設定を実施
という点を考慮し、以下のようなファイルを作成しました。
/lib/udev/rules.d/60-libpafe.rules
ACTION!="add", GOTO="pasori_rules_end" SUBSYSTEM=="usb_device", GOTO="pasori_rules_start" SUBSYSTEM!="usb", GOTO="pasori_rules_end" LABEL="pasori_rules_start" ATTRS{idVendor}=="054c", ATTRS{idProduct}=="01bb", MODE="0664", GROUP="plugdev", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rc-s320.service" LABEL="pasori_rules_end"
動作確認
systemdによる認識
パソリのdeviceファイルが2つ、新しく認識されました。
# 再起動 pi@raspberrypi ~/felica-poweroff/libpafe-0.0.8 $ sudo reboot # 確認 pi@raspberrypi ~ $ systemctl --type device --all UNIT LOAD ACTIVE SUB JOB DESCRIPTION dev-bus-usb-001-005.device loaded active plugged 01bb ... sys-devices-platform-bcm2708_usb-usb1-1\x2d1-1\x2d1.4.device loaded active plugged 01bb
pasori_testの実行
sudoなしで実行できるか確認します。
pi@raspberrypi ~ $ ./felica-poweroff/libpafe-0.0.8/tests/pasori_test PaSoRi (RC-S320) ...
systemdのserviceファイルの作成
今回はPythonスクリプトによりFeliCaの認識を行いますが、そのPythonスクリプトをサービス化するにあたり、以下の方法を考えました。
案1
案2
どちらの方が良いのか分かりませんでしたが、serviceファイルでのRestart設定を試してみたかったことから、案2を採用しました。
serviceファイルの内容としては
[Service]
セクションで繰り返しの設定を実施- Pythonはdirenvで設定したPython3を使うように指定
- USBデバイス認識対象の
device
ファイルは、dev-bus-usb-001-005.device
とした- USBデバイス挿入時は、上記の他に
sys-devices-platform-bcm2708_usb-usb1-1\x2d1-1\x2d1.4.device
ファイルもできるけど、ポート依存っぽいので避けた
- USBデバイス挿入時は、上記の他に
としました。
/etc/systemd/system/rc-s320.service
[Unit] Description=Pasori RC-S320 service Requires=dev-bus-usb-001-005.device After=dev-bus-usb-001-005.device [Service] Type=simple Restart=on-failure RestartSec=5 ExecStart=/home/pi/felica-poweroff/.direnv/python-3.2.3/bin/python /home/pi/felica-poweroff/felica_poweroff_service.py
Pythonスクリプトの作成
パソリを介したFeliCaの認識
以前の記事でも使用した、libpafe + Pythonを使ってFeliCaを読み取ります。
Raspberry Pi + libpafe + Python + ctypesで、FeliCaのIDmを読む - メモ的な思考的な
ACT LEDをheartbeatにする方法
LEDでシャットダウン開始を通知したかったのですが、現時点では外付けで付けたくないと考えたところ、以下の記事にRaspberryPi2の基板にあるACT LEDを使えば良さそうでした。
Raspberry Piブログ : [コラム] Raspberry Pi 2のオンボードLEDを自由に光らせよう
コマンドラインでは、sudo echo heartbeat > /sys/class/leds/led0/trigger
のようにすれば良いのですが、Pythonの場合は以下の点に注意が必要でした。
- リダイレクト(
>
)が使えないので、tee
を使う - コマンドの実行については、
os.system
やcommands
は非推奨のため、subprocess.Popen
を使う - リダイレクトのような標準入出力が必要な場合は、Popenを2つ使う
- Popenに渡すコマンドは
shlex.split()
を使うと便利
上記を踏まえるとこんな感じになります。
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への通知
ライブラリを使うのが楽そうでしたので、slacker
を使うことにしました。
os/slacker
Slack APIのtokenは以下のURLの下、「Authentication」より取得できます。
Slack Web API | Slack
なお、
とやりたかったのですが、systemdのServiceの中でどうやってdirenvを使えばよいか分からなかったため、今回はコードに直書きする形を取りました。
slack = Slacker('<your token>') slack.chat.post_message('#general', 'poweroff now')
シャットダウン
シャットダウンにもいろいろとありますが、外からの見た目が分かりやすいよう、LANポートも消灯するようにpoweroff
を選びました。
単発のコマンドはPopen
のコンストラクタに渡すだけで動作します。
cmd_power_off = shlex.split("sudo systemctl poweroff") subprocess.Popen(cmd_power_off)
Pythonスクリプト全体
slackerのインストール
忘れないよう、pipでインストールします。
pi@raspberrypi ~/felica-poweroff $ pip install slacker
~/felica-poweroff/felica_poweroff_service.py
# -*- coding: utf-8 -*- import sys import datetime from ctypes import * import shlex import subprocess import time from slacker import Slacker # libpafe.hの77行目で定義 FELICA_POLLING_ANY = 0xffff SLACK_TOKEN = '<your token>' def read_felica_idm(): libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so") libpafe.pasori_open.restype = c_void_p pasori = libpafe.pasori_open() libpafe.pasori_init(pasori) libpafe.felica_polling.restype = c_void_p felica = libpafe.felica_polling(pasori, FELICA_POLLING_ANY, 0, 0) idm = c_uint16() libpafe.felica_get_idm.restype = c_void_p libpafe.felica_get_idm(felica, byref(idm)) # 認識できない時は'0'が入ってくる result = idm.value # READMEより、felica_polling()使用後はfree()を使う # なお、freeは自動的にライブラリに入っているもよう libpafe.free(felica) libpafe.pasori_close(pasori) return result def switch_act_led(trigger): cmd_echo = shlex.split("echo %s" % trigger) cmd_redirect = shlex.split("sudo tee /sys/class/leds/led0/trigger") p1 = subprocess.Popen(cmd_echo, stdout=subprocess.PIPE) p2 = subprocess.Popen(cmd_redirect, stdin=p1.stdout) p1.stdout.close() p2.communicate()[0] def write_log_file(msg): f = open('/home/pi/felica-poweroff/log.txt', 'a') now = datetime.datetime.now() f.write(now.strftime("%Y/%m/%d %H:%M:%S") + msg + '\n') f.close() if __name__ == '__main__': # FeliCaが無い時のログ出力 if read_felica_idm() == 0: write_log_file('FeliCa not found.') # Serviceを再実行するよう、0以外を返す sys.exit(1) # FeliCaを認識した時のログ出力 write_log_file('Found!') # heartbeat > 2秒待ち > microSDランプ の順に切替 switch_act_led('heartbeat') time.sleep(2) switch_act_led('mmc0') # Slackへの通知 slack = Slacker(SLACK_TOKEN) slack.chat.post_message('#general', 'poweroff now') # シャットダウン cmd_power_off = shlex.split("sudo systemctl poweroff") subprocess.Popen(cmd_power_off) # Serviceを再実行しないよう、0を返す sys.exit(0)
Pythonスクリプトの動作確認
Pythonスクリプトを動作させるとシャットダウンしてしまうことから、シャットダウンの手前に sys.exit(0)
をデバッグ用のコードとして入れてみて、動作を確認します。
# 再起動 pi@raspberrypi ~/felica-poweroff $ sudo reboot # FeliCaを置く前 pi@raspberrypi ~ $ systemctl status rc-s320.service rc-s320.service - Pasori RC-S320 service Loaded: loaded (/etc/systemd/system/rc-s320.service; static) Active: activating (auto-restart) (Result: exit-code) since Tue, 30 Jun 2015 19:00:26 +0900; 2s ago Process: 2096 ExecStart=/home/pi/felica-poweroff/.direnv/python-3.2.3/bin/python /home/pi/felica-poweroff/felica_poweroff_service.py (code=exited, status=1/FAILURE) CGroup: name=systemd:/system/rc-s320.service # FeliCaを置いた後 pi@raspberrypi ~ $ systemctl status rc-s320.service rc-s320.service - Pasori RC-S320 service Loaded: loaded (/etc/systemd/system/rc-s320.service; static) Active: inactive (dead) since Tue, 30 Jun 2015 19:01:42 +0900; 3s ago Process: 2170 ExecStart=/home/pi/felica-poweroff/.direnv/python-3.2.3/bin/python /home/pi/felica-poweroff/felica_poweroff_service.py (code=exited, status=0/SUCCESS) CGroup: name=systemd:/system/rc-s320.service Jun 30 19:01:38 raspberrypi sudo[2176]: root : TTY=unknown ; PWD=/ ; USER=root ; COMMAND=/usr/bin/tee /sys/class/leds/led0/trigger Jun 30 19:01:38 raspberrypi sudo[2176]: pam_unix(sudo:session): session opened for user root by (uid=0) Jun 30 19:01:38 raspberrypi python[2170]: heartbeat Jun 30 19:01:40 raspberrypi sudo[2179]: root : TTY=unknown ; PWD=/ ; USER=root ; COMMAND=/usr/bin/tee /sys/class/leds/led0/trigger Jun 30 19:01:40 raspberrypi sudo[2179]: pam_unix(sudo:session): session opened for user root by (uid=0) Jun 30 19:01:40 raspberrypi python[2170]: mmc0
Pythonスクリプトの動作確認が取れたところで、デバッグ用のコードを削除し、FeliCaを触れてみて動作を確認します。
- Raspberry Pi2が起動後、5秒おきにパソリのLEDが光る
- FeliCaをパソリに置く
- PowerOffが始まり、ACT LEDの点滅後、LANポートのLEDが消灯する
ということが確認できればOKです。
ソースコード
今回作成した、
をGitHubへ上げておきました。
thinkAmi-sandbox/rpi-felica_poweroff