Raspberry piに特定のUSB機器を差し込むと電源を落とすデーモンをPythonで作る

ラズベリーパイは電源ボタンがない。
給電されると起動されるが、電源を切る場合はOSからシャットダウンをしなければならない。

モニタとキーボードがついていれば良いのだが、殆どの人はSSHで操作をしてラズパイ自体にはモニタなどつけないと思う。
パソコンで操作をすれば良いのだけれども、タズパイを常時動かしている場合にすぐ電源を落としたい時操作をする端末を起動させるのが煩わしい。

今まではいくつかの方法で運用を行っていた。
まずはAndroidからSSHクライアントで操作。
でもこれは小さな画面でコマンドを打つことはかなりの忍耐力と精神的成熟が必要で僕のような凡人には長く続けていくことは無理だった。

次に行ったのはソケット通信による操作。
とある文字列をソケット通信で受信をすればシャットダウンコマンドを実行するというもの。
これはこれで面白かった。
ソケット通信による認証や文字列の送信、デーモンの仕組みなどこの仕組みを作る上で勉強になった。

これについてはブログに載せている

これに関してはサーバー側の仕組みは出来たのだけれども、クライアント側のアプリを作る前に情熱が冷めたため作っていない。

それに一つ問題があった。
僕がいない時に電源を落とす方法がないということだ。
結局僕のクライアントをインストールした端末からボタンを押さなければならない。
妻が電源を落とさなければならない時、それはできないのだ(iPhoneだから(僕はiPhoneアプリ作る環境がない))

新しい仕様

というわけで新しい仕組みを考えた。
USB機器というのはベンダーIDとプロダクトIDという物を持っている。
メーカーと製品それぞれにIDを持っていて、識別できるらしい。

ならばUSB機器の情報を常に見張っていて、予め決めていたIDを持つ機器が差し込まれた時に処理を実行すればいい。

今回は一つ課題を貸した。
前回のソケット通信デーモンはPython2系で作成した。
今回は今後のことも考えてPython3系で作ることにした。

ただしデーモンを作るためのライブラリは2系のみしか対応していない。

だから今回はライブラリなしで自分でデーモンプロセスを作る仕組みを書いた。

デーモンとは起動したプロセスから子プロセスを作って、親プロセスはそのまま終了、
子プロセスだけで動くという仕組みで実現される。
これはForkと言われているのだけれども、wikipediaでforkの記事を見るとPythonでどうやるかの答えが書いてあるのだ。

それを参考にして作ってみた。
簡単だった。

用意するもの

USB機器(ただしUSBメモリを除く)

USBメモリで最初試したら、どういうわけか通電しているけどOSが落ちているときにUSBメモリを挿したら起動したため避けたほうがよい。
僕の場合、差し込んだら電源が落ちる ⇒ すぐ起動 ⇒ デーモンが起動して電源が落ちる

ということの繰り返しになった。

僕はUSBゲームパッドを使用している。

USB機器のID

お手持ちのパソコンにUSB機器を差し込んで、lsusbコマンドを実行する

今認識されているUSBデバイスの情報が表示されるので、自分が使いたい機器を特定して
ベンダーIDとプロダクトIDを控える。

lsusbコマンドだとIDは16進数で書かれているので、10進数に変換しておく

pyusbライブラリ

PythonでUSB機器を操作するために必要なライブラリ。
Python3でも使える。
pip経由で落とせる。ただし正式版ではないらしく評価版??みたいなものを落とす必要があるらしい。
以下のように–preオプションをつけることでインストールできる。

pip install pyusb –pre

 

ソースコード

#!/usr/bin/python3
import time
import os
import sys
import usb

#右がベンダー 左が製品
checkDev = (1035,25904)

def createDaemon():

    pid = os.fork()

    #親プロセスは終了します
    if pid > 0:
        f = open('/var/run/usbtrigger.pid','w')
        f.write(str(pid) + '\n')
        f.close()
        sys.exit()

    #子プロセスはUSB接続を待機する関数へ
    if pid == 0:
        daemonLooper()

def daemonLooper():

    while True:
        #USBのデバイスをリストにするが、二重リストになっているのでフラットにする。
        usblist = [ b.devices  for b in usb.busses()]
        usblist = [e for ilist in usblist for e in ilist]
        usblist = [(b.idVendor,b.idProduct) for b in usblist]        

        if checkDev in usblist:
            #該当のUSBが差し込まれたら、処理が走る。

            os.system('shutdown -h 1')
            break
        time.sleep(5)

if __name__ == '__main__':

    createDaemon()

checkDevのタプルには先ほど調べたIDを10進数にして入力する。
pyusbから得られるIDは10進数で、lsusbは16進数、その間を埋めるような変換処理を入れても良かったけれどめんどくさいので、ユーザーが自分で調べるようにした。

このスクリプトのファイル名は何でも良いが、pipファイルを書き出す箇所ではデーモン名と同じにする必要がある。

pidファイルは子プロセスが動いた時にそのpidを保持しておくファイル。
これがないとinit.dでstartは出来てもstopができなくなる。
pidだけが書かれたテキストファイルだが、最後に改行がないと読み込んでくれない。

time.sleepは5秒毎に処理を実施している事を表している。

頻繁に情報が来るわけでもないので、もっと長い間隔でもいいだろう。

shutdown -h 1はシャットダウンコマンド実行後一分後に電源を切るという意味。
0でも良いけれど、一応余裕をもって一分にしている。

Pythonでのforkの仕方は、os.forkを使う。
これを使うとプロセスがコピーされ、分身ができる。
フォークのように枝分かれする。

枝分かれしたうち、親プロセスの方はos.fork()の戻り値が子プロセスのpidになっている。
0以上であれば親プロセスということになるので、ifで分岐させてpidファイルに子プロセスのpidを書き込んだ後に終了する。

枝分かれしたうち、子プロセスの方は戻り値で0が帰ってくるので、
if文で無限ループになっている関数を実行する。

親プロセスが終了すると、子プロセスは孤児になりinitプロセスの養子に出される。
そうしてユーザーが与り知らぬバックグラウンドで処理がはしることになる。

C言語でもRubyでも似たような手法で実施することができる。

init.dへデーモンを登録

先ほどのソースを任意のファイル名で(先ほどのソースコード無いではusbtriggerを想定)保存して、
/usr/local/binあたりに置いておく。ユーザーはrootにして、実行可能にしておく(パーミッションは744とか)

デーモンの登録の方法は以前書いたモノを参考にして欲しい。
どこにスクリプトを置くのか、起動スクリプトの書き方はどうするのかはそちらに記してある。

 

 

スポンサードリンク

関連コンテンツ