GPIO信号でシャットダウン

#１．はじめに

RaspberryPiのGPIO端子に信号が入力されると、シャットダウンする機能について解説します。既にいろいろなサンプルがありますが、ここでは、systemdからスクリプトを起動して、定期的にGPIOを監視する方法を説明します。

※本記事は、下名が[はてなブログで公開](https://hotch-potch.hatenadiary.jp/?page=1561640400)している内容を元に、一部内容を訂正したものになります。（十分試用して、問題無いようですのでこちらに転載しました）

##環境

- Raspberry Pi シリーズ  (1, 2, 3, 3+)
- OS
    - Raspbian
    - Ubuntu Linux 18.04 (ラズパイ版 32/64bit)

#２．つくりかた

##（１）ファイル構成

本記事では、下記のファイルを作成します。

| ファイル名 | 機能 |
|:--|:--|
| setPowerWatcher.sh | GPIO入力ピンの状態監視とシャットダウン処理 |
| setPowerWatcher.service | systemdのサービスファイル |

最後に、systemdにサービスとして登録します。


##（２）シェルスクリプト

「定数」の所は、環境に合わせて修正してください。

| 定数 | 機能 |
|:--|:--|
| SET_WATCH_PORT | GPIOピンにシャットダウン信号を入れる番号を指定 |
| SET_TRIGGER | 信号の立ち上がり・立ち下がりどちらで反応するかを設定 |
| SET_WATCH_INTERVAL | 指定した秒数ごとに、シャットダウン信号の状態を監視 |

ソース中の所々の[INSTALL DIR]は、お使いの環境に合わせて書き換えてください。

```sh:setPowerWatcher.sh
#!/bin/bash
#--------------------------------------------------------------------
# UPSの信号監視とシャットダウン
# 2019 by myasu
# MIT Licence
#--------------------------------------------------------------------
#
#install:
# $ sudo ln -s /home/pi/[INSTALL DIR]/setPowerWatcher.service /etc/systemd/system
# $ sudo systemctl enable setPowerWatcher.service
# $ sudo systemctl start setPowerWatcher.service
#--------------------------------------------------------------------
#ref:
#
# ※必ずbashを使うこと。shを使うと関数の宣言でエラーが出ます
#   https://yatta47.hateblo.jp/entry/2014/04/20/222707
#

#----------------------------------
#定数
#----------------------------------

#監視するポート(BMC番号で指定)
SET_WATCH_PORT=4

#監視モード
# 1:ダウンパルス、0:アップパルス
SET_TRIGGER=1

#監視ループの間隔(sec)
SET_WATCH_INTERVAL=1


#----------------------------------
#関数
#----------------------------------

#シャットダウンの開始処理
function ShutdownGoProc ( ) {
    #シャットダウン信号を検出したメッセージを表示
    echo "  ! Shutdown signal detected. ${MESSAGE}"

    #シャットダウン処理を開始
    if [ ! "$MODE_TEST" = 1 ]; then
        #瞬停防止のため2秒だけshutdownを待つ(-t 2)
        /sbin/shutdown -P -t 2
    fi
}

#シャットダウンの停止処理
function ShutdownStopProc ( ) {
    #シャットダウン信号を検出したメッセージを表示
    echo "  + Shutdown signal disappeared. ${MESSAGE}"

    #シャットダウン処理を中止
    if [ ! "$MODE_TEST" = 1 ]; then
        /sbin/shutdown -c
    fi

    #すぐにループ処理に戻らないよう、ウェイト
    sleep 1
}


#----------------------------------
#メイン処理
#----------------------------------

#実行中ユーザをチェック
if [ `/usr/bin/id -u` != "0" ]; then
    #rootでなければ異常終了
    echo " ! Please run as root."
    exit 1
fi

#主要コマンドの存在をチェック
if [ ! -x /usr/bin/gpio ]; then
    #見つからなければ異常終了
    echo " ! No 'gpio' Command."
    exit 1
fi

#自身のコマンド名を変数に入れる。usage用
CMDNAME=`basename $0`

#引数のチェックと動作フラグの生成
#https://shellscript.sunone.me/parameter.html
while getopts vt OPT
do
    case $OPT in
        "v" )
            #verboseモード
            MODE_VERBOSE=1 ;;
        "t" )
            #テストモード（シャットダウンはしない）
            MODE_TEST=1 ;;
        * )
            #それ以外の指定は、usageを表示
            echo "Usage: $CMDNAME [-v] [-t]" 1>&2
            exit 1 ;;
    esac
done

#動作モードのメッセージ
if [ ${SET_TRIGGER} -eq 1 ]; then
MESSAGE="(MODE: Down trigger)"
else
MESSAGE="(MODE: Up trigger)"
fi

#起動メッセージ
if [ "$MODE_VERBOSE" = 1 ]; then

  #メッセージ
  echo " * Monitor the GPIO signal and shutdown * ${MESSAGE}"

  #テストモードのメッセージ表示
  if [ "$MODE_TEST" = 1 ]; then
    echo '  - test mode (no shutdown)'
  fi
fi

#ポートの初期化
`/usr/bin/gpio -g mode ${SET_WATCH_PORT} in`

#監視ポートの状態変数の初期化と問い合わせ
STAT_PORT_PREV=`/usr/bin/gpio -g read ${SET_WATCH_PORT}`

#監視ループ
while :
do
    #監視ポートの状態を問い合わせ
    STAT_PORT_NOW=`/usr/bin/gpio -g read ${SET_WATCH_PORT}`

    if [ ${SET_TRIGGER} -eq 1 -a ${STAT_PORT_PREV} = "1"  -a  ${STAT_PORT_NOW} = "0" ]; then
        #ダウントリガで検知
        ShutdownGoProc
    elif [ ${SET_TRIGGER} -eq 0 -a ${STAT_PORT_PREV} = "0"  -a  ${STAT_PORT_NOW} = "1" ]; then
        #アップトリガで検知
        ShutdownGoProc 

    elif [ ${SET_TRIGGER} -eq 1 -a ${STAT_PORT_PREV} = "0"  -a  ${STAT_PORT_NOW} = "1" ]; then
        #アップトリガで検知
        ShutdownStopProc
    elif [ ${SET_TRIGGER} -eq 0 -a ${STAT_PORT_PREV} = "1"  -a  ${STAT_PORT_NOW} = "0" ]; then
        #ダウントリガで検知
        ShutdownStopProc 
    fi

    #監視ポートの状態変数に、現在の状態を代入
    STAT_PORT_PREV=${STAT_PORT_NOW}

    #ウェイト
    sleep ${SET_WATCH_INTERVAL}
done

exit 0

```

##（３）systemdに登録するserviceファイル

```ini:setPowerWatcher.service
[Unit]
Description=Monitor the GPIO signal and shutdown
After=local-fs.target
ConditionPathExists=/home/pi/[INSTALL DIR]

[Service]
ExecStart=/home/pi/[INSTALL DIR]/setPowerWatcher.sh
ExecStop=/bin/kill ${MAINPID}
Restart=on-failure
StartLimitInterval=60
StartLimitBurst=3
KillMode=mixed
Type=simple
User=root
Group=root

[Install]
WantedBy=multi-user.target
```

##（４）配線

前述（３）のプログラムにならって、GPIO4にリセットボタンを繋ぎます。

![配線例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145581/8a043652-3b38-f6d6-152a-cf5c656c9109.png)


#２．セットアップ手順

##（１）systemdへ登録
systemdに登録して、サービスを起動します。

```sh:コマンドライン
$ sudo ln -s /home/pi/[INSTALL DIR]/setPowerWatcher.service /etc/systemd/system
$ sudo systemctl enable /home/pi/[INSTALL DIR]/setPowerWatcher.service
```

##（２）serviceファイルを修正した時

下記のリロード操作をします。

```sh:コマンドライン
$ sudo systemctl daemon-reload
```

##（３）状態を確認

systemctl statusコマンドで、状態を確認します。
まだ起動していません

```sh:コマンドライン
$ systemctl status setPowerWatcher.service
● setPowerWatcher.service - Monitor the GPIO signal and shutdown
   Loaded: loaded (/home/pi/[INSTALL DIR]/setPowerWatcher.service; linked; vendor preset: enabled)
   Active: inactive (dead)
```

##（４）起動

```sh:コマンドライン
$ sudo systemctl start setPowerWatcher.service
```


##（５）状態を確認

左上の●が緑色になっていれば、正常に起動しています。

```sh:コマンドライン
$ systemctl status setPowerWatcher.service
● setPowerWatcher.service - Monitor the GPIO signal and shutdown
   Loaded: loaded (/home/pi/[INSTALL DIR]/setPowerWatcher.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2019-03-09 15:47:22 JST; 9s ago
 Main PID: 537 (setPowerWatcher)
   CGroup: /system.slice/setPowerWatcher.service
           tq537 /bin/bash /home/pi/[INSTALL DIR]/setPowerWatcher.sh
           mq564 sleep 1

Mar 09 15:47:22 raspizwh systemd[1]: Started Monitor the GPIO signal and shutdown.
```

##（６）動作確認

GPIOに信号を入れると、間もなくシャットダウンします
その際syslogには、下記のメッセージが残ります。

```sh:コマンドライン
Mar  9 15:58:13 raspizwh setPowerWatcher.sh[537]:   ! Shutdown signal detected. (MODE: Down trigger)
```

#おわりに

こんな感じで、外部信号からシャットダウンできるようになりました。
ご参考頂けると幸いです。