読者です 読者をやめる 読者になる 読者になる

Raspberry Pi 2にて、FeliCa読み取り時にPowerOffし、Slackへ通知する

Raspberry Pi Python

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になるなど不安定だったため

 

仕様

  1. パソリをRaspberry Pi2に挿入し、systemdによりFeliCa認識サービスを起動する
  2. パソリにFeliCaをかざす
  3. Raspberry Pi2上のACT LEDをheartbeatにして、PowerOffの開始を通知する
  4. Slackへ通知する
  5. 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ヶ所に書いていました。

 
今回いろいろと試してみたところ、/etc/udev/rules.d/の下に置いた場合はlibpafeがパソリを認識しなかったため、/lib/udev/rules.d/の下へ置くことにしました。

あとは、libpafeの公式サイトにある内容を元に、

  • NAMEキーを使用したらlibpafeでパソリを認識できなかった
  • idProduct006c02e1のものはパソリ 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スクリプトをサービス化するにあたり、以下の方法を考えました。

 
どちらの方が良いのか分かりませんでしたが、serviceファイルでのRestart設定を試してみたかったことから、案2を採用しました。

 
serviceファイルの内容としては

  • [Service]セクションで繰り返しの設定を実施
    • Restartにon-failureを指定し、FeliCaの認識に成功した時以降は動作させないように設定
    • RestartSecに5を指定し、5秒後の再実行とした
      • 再実行の頻度が高いのは良くないかなと感じたため
      • その5秒の間に、「FeliCaを接触 > 離す」をしてしまうと認識しないけど、FeliCaを接触し続ける前提で考えておく
  • Pythonはdirenvで設定したPython3を使うように指定
  • USBデバイス認識対象のdeviceファイルは、dev-bus-usb-001-005.deviceとした
    • USBデバイス挿入時は、上記の他にsys-devices-platform-bcm2708_usb-usb1-1\x2d1-1\x2d1.4.deviceファイルもできるけど、ポート依存っぽいので避けた

としました。

/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の場合は以下の点に注意が必要でした。

上記を踏まえるとこんな感じになります。

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です。

 

ソースコード

今回作成した、

  • /lib/udev/rules.d/60-libpafe.rules
  • /etc/systemd/system/rc-s320.service
  • felica_poweroff_service.py

GitHubへ上げておきました。
thinkAmi-sandbox/rpi-felica_poweroff