@m_seki の

I like ruby tooから引っ越し

Raspberry Piのshutdownボタンの代わりになるドングルを考えたよ!

Raspberry Piをクルマで使おうとしたんだけど、電源切るのが大変。
shutdownボタンを作る例も試したんだけど、ちゃんとしたケース(?)に入れるとか考えるといろいろめんどくさい。
そこで不要なUSBメモリをドングルにして、shutdown鍵にすることにしました。

作りたいものの雰囲気

  • ラズパイのUSBにドングルを挿し、10秒以内に抜いたらshutdownを実行する。
  • 10秒を超えてたら、shutdownはしないよ!
  • なんどもSDカード壊してるから、shutdownが完了したのがわかるようにしたいぜ。

ちょっと調べてたら、ラスパイ本体のLED(ACTランプ)はプログラムで制御できるらしい。
これを利用して、点滅の様子を変えてみようかな。

LEDの操作

本体のLEDのうち、ACTを操作できる。(Raspberry Pi 2ならPWRも)

/sys/class/leds/led0 以下にあるファイルに読み書きして制御します。
表示モードの変更はtrigger、点灯時間、消灯時間の設定はdelay_on, delay_offに書き込むことで指示します。

triggerはいろいろあります。実験したのは以下のもの。

  • mmc0 - SDカードと連動。(デフォルト?)
  • heartbeat - 心臓っぽい点滅
  • timer - 点灯と消灯の繰り返し。長さが指定できる
  • none - brightnessを変更して好き放題いじる


250msec点灯、125msec消灯の繰り返しなら、triggerをtimerにして、delay_on, offで指定します。

echo timer > /sys/class/leds/led0/trigger
echo 250 > /sys/class/leds/led0/delay_on
echo 125 > /sys/class/leds/led0/delay_off

心臓の鼓動ならこんな感じ。

echo heartbeat > /sys/class/leds/led0/trigger

もとに戻すときはこう。

echo mmc0 > /sys/class/leds/led0/trigger

どれもsudoで実行しないとダメですよ。たぶん。

ACTによる情報提供は特別なハードウェアを繋がなくても良いところがオススメです。

USBの抜き差しをつかまえる

udevっていう仕組みがあって、いろんな機器の抜き差しをつかまえることができます。
udevadmのmonitorサブコマンドを起動してから、USB機器を抜き差しするといろんイベントが飛んでる様子が見えます。

% udevadm monitor 
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

KERNEL[2960.713366] remove   /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0 (bsg)
KERNEL[2960.713639] remove   /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0/scsi_generic/sg0 (scsi_generic)
KERNEL[2960.713813] remove   /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0 (scsi_device)
KERNEL[2960.713974] remove   /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0 (scsi_disk)
KERNEL[2960.714171] remove   /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1 (block)
KERNEL[2960.714358] remove   /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
KERNEL[2960.714564] remove   /devices/platform/soc/20980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0 (scsi)
...

udevの設定はrulesというファイルに記述します。
以下は追加したルール(/etc/udev/rules.d/99-mykey.rules )です。

ATTRS{idVendor}=="0781", ATTRS{serial}=="200607753009D74236F6", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/home/pi/halt_key_add.rb"
ATTRS{idVendor}=="0781", ATTRS{serial}=="200607753009D74236F6", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/home/pi/halt_key_remove.rb"

ルールは属性==値でパターンを、属性=値でアクションを記述します。この例は機器のベンダーとシリアル番号、USB機器であり、抜き差しのイベント(追加と削除)でパターンを記述し、
RUN+=でアクションを指定します。

ベンダー番号とシリアル番号はudevadm infoを使って調べました。
私の環境ではUSBメモリを挿すとsdaに割り当てられるみたい。
そこで次のようにしてベンダー番号、シリアル番号を探しました。

udevadm info -a -p $(udevadm info -q path -n /dev/sda)

このコマンドの意味はよくわからないけど、なんか動きました。

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

...

looking at parent device '/devices/platform/soc/20980000.usb/usb1/1-1/1-1.2':
    KERNELS=="1-1.2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="1.2"
    ATTRS{idVendor}=="0781" ★ これ
    ATTRS{speed}=="480"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="4"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="200mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="0110"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{serial}=="200607753009D74236F6" ★ これ
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="639"
...

みなさんがやるときは、ドングルにしたいUSBメモリを挿した状態でシリアル番号等を調べ、rulesに書き加えてね。

結合してみる

単にLEDの制御だけならshでよかったんだけど、10秒以内なら、っていうコードを書くためにRubyでやることにしました。

halt_key_add.rbは追加時のアクションです。

#!/usr/bin/ruby
File.write("/sys/class/leds/led0/trigger", "heartbeat")
File.write("/tmp/haltkey", Time.now.to_s)

点滅を心臓っぽくして、足跡ファイルを書き込みます。これは10秒すぎたかどうかを調べるためのファイルです。

次のhalt_key_remove.rbは削除時のアクションです。

#!/usr/bin/ruby
footprint = File.mtime("/tmp/haltkey") rescue Time.at(1)
if Time.now - footprint < 10
  File.write("/sys/class/leds/led0/trigger", "timer")
  File.write("/sys/class/leds/led0/delay_on", "250")
  File.write("/sys/class/leds/led0/delay_off", "125")
  system("/sbin/halt") # 実験中はコメントアウトするといいよ!
else
  File.write("/sys/class/leds/led0/trigger", "mmc0")
end

足跡ファイルと現在の時刻の差分を比べて処理を切り替えています。
10秒以内なら、モードをtimerに、250msec点灯125msec消灯の点滅をさせたままhaltさせます。
時間を超えていたら、SDカードの状態を表すmmc0モードに切り替えます。

なお、実験中はhaltされると不便なので、コメントアウトしておいたほうが良いです。

できたこと

ドングルを挿して10秒経たずに抜いたらshutdownするドングルができた。
halt時にも早めの点滅が繰り返されるので、本当に停止したことがわかりやすいです。

できてないこと

ドングルを挿して10秒すぎたらmmc0モードに戻したいのだけど、10秒後のトリガーはないのでひとまず保留。
Rindaを使えばすんなり書けそうな気もする。

追記

大丈夫とはなにか!
LEDの脈拍が安定してから抜けばshutdownしました。安定する前に抜くとなにもなし。抜いたイベントがこないのかなー。リアルな感じ。