ラズパイにUPS機能を組込む

ラズパイ1B+をネットワーク共有サーバーとして、バッテリーバックアップを行いながら常時起動しておくことを考えています。

以前、普段使いのノートPCのLinux(Ubuntu)立上げ後に、ネットワーク共有サーバー上に自動的に、Windowsファイルの一部をバックアップするsystemdのサービスを追加したことがあり、同様に常時起動のラズパイ1B+にバッテリー監視のサービスを起動しようと考えました。

今回の作業背景

何となく思いつきで購入した安価な製品で、ラズパイに装着できる制御ボードバッテリーのセットです。特に付属の説明も無いものでアマゾンで購入しました。

Raspi UPS HAT Board と呼ばれているようで、資料が公開されています。

この公開資料では色々と説明されていて、サンプルコードも多数提示されているのですが、何か詰が甘くて動作を検証しても思うような結果が出ません。そのまま手を加えなくても期待した結果だったモジュールは、C言語で記述された File:Ups-hat-c.zip だけでした。これはモジュールとして解説の手順でコンパイルすると、ups-read となります。

利用方法としては、引数を [付ける/付けない] により少し異なりますが、バッテリー電圧とバッテリー容量が取得できます。

解説の中には、root権限で実行するためにパスワード無しの sudo を冠して実行するための /etc/sudoers の追加記述がありますが、最新の raspbian では単純にユーザー権限のままでも実行できるようでした。

引数無しでは、バッテリー容量がパーセンテージで表した整数値のみで返されます。これはバッテリー制御ボードから直接受け取った情報を特定の関数で変換していると思われますが、ちょっとした落とし穴があって、0〜100 ではない場合があり、 割合を表す数値としてはありえない 120 とか 112 とかになっていることがあります。

引数には、vc が指定でき、両方指定すれば電圧と容量が、当てにならない小数点以下6桁で、3.498750V 9.480469% のように表示されます。10秒毎にリストにして確認してみましたが、電圧の変化と容量の変化にはある程度の相関関係があるようです。ラズパイの処理で負荷動作による揺れなのか、数値に振れが見受けられます。

ちなみに、0.000000% でも動作していますが、電圧値が、3.000000V を下回った頃に、一気に電力不足でシステムが停止しているようでした。容量が 5.000000% 以下になったのを見て正常な停止処理に移行させた方が良さそうです。

作ろうとしている私のネットワーク共有サーバーとしての動作環境は、ラズパイ1B+ 2TB 2.5インチ HDD で構成されています。フル充電から 5.0% まで容量が減衰する動作継続時間は、優に1時間を越えています。ある程度バッテリーが劣化しても余裕がありそうです。

先程の公開資料ですが、その中で提供されているサンプルは、ほぼ python で記述されていて、I2Cインタフェースからバッテリーの電圧と容量を取得するルーチンが紹介されています。


その中に UPS のバッテリーを監視して shutdown を起動するサンプルが次の項目に紹介されています。

Driver and Sample code

  • File:Rpi-ups-hat.zip
  • File:UserManual.pdf

zip を解凍すると、rpi-ups-hat ディレクトリ下に example.py と raspiupshat.so があります。raspiupshat.so が、UPSのバッテリー電圧と容量を取得できるライブラリです。

example.py では、そのライブラリを利用して、無限ループで容量をチェックして、5% を切ると shutdown コマンドが起動される例として記述されています。

残念なことに、os.system(“sudo shutdown”) の行で、ライブラリ os が import されていないため、エラーメッセージを表示して終了してしまいます。

UserManual.pdf は、英語と中国語?で書かれた利用条件の説明です。その中で wiringPi のインストールが必須として書かれていますが、最新の raspbian では事前にセットアップされているようです。

これを流用させて頂き、UPS制御を組込む予定です。


systemd のサービスとして起動する方法については、次のサイトを参考に制作しますが、一度私のノートPCで作成した経験があるので、自分の備忘録も見返しながらの作業になります。

参考にしたサイトは、作り方を事細かく説明してくれています。


ここからが作業の本題

Raspbian 起動時に、一度だけ起動して常時バッテリーを監視して、容量が 5% を下回った時に shutdown プロセスを起動させます。

systemd に登録するサービスの指示書が、ユニットファイルとのことです。

パッケージ名: UPS-Watch-shut で、ユニットファイル名: ups-watching.service としてみました。

サービス(ユニットファイル)の登録は、 /etc/systemd/system/ups-watching.service で、この中には登録するサービスの情報を決められた書式で記述します。

そして実際に動作させるプログラムは、パッケージ名で管理するディレクトリに記述するようで、 /opt/UPS-Watch-shut/bin に集めるようです。バッテリーの情報を取得するライブラリ raspiupshat.so もここに置きます。

systemd の動作を確認する雛形作り

参考サイトがカレントで作成して、それを該当するディレクトリにコピーして、所有者を root に変更したり、アクセス権の変更をしているので、私もその方法を踏襲します。

$ vi ups-watching.service
[Unit] 
Description = UPS Watching AutoRun
After=network-online.target remote-fs.target nss-lookup.target
ConditionPathExists=/opt/UPS-Watch-shut/bin

[Service]
ExecStart=/opt/UPS-Watch-shut/bin/startup-1st.sh
Restart=no
Type=simple

[Install]
WantedBy=multi-user.target
$ sudo cp ups-watching.service /etc/systemd/system
$ sudo chown root:root /etc/systemd/system/ups-watching.service
$ sudo chmod 644 /etc/systemd/system/ups-watching.service
$ vi startup-1st.sh
#!/bin/sh
exec /usr/bin/env python /opt/UPS-Watch-shut/bin/ups-shutdown.py
$ sudo mkdir -p /opt/UPS-Watch-shut/bin
$ sudo chmod 755 /opt/UPS-Watch-shut/bin
$ sudo cp startup-1st.sh /opt/UPS-Watch-shut/bin
$ sudo chown root:root /opt/UPS-Watch-shut/bin/startup-1st.sh
$ sudo chmod 755 /opt/UPS-Watch-shut/bin/startup-1st.sh

実際に動作するサンプルプログラム

最終的に起動されるサンプルプログラムは、bash や ruby または python でも構わないのでしょうけど、参考サイトにある例をそのまま丸コピーしてきました。検証作業の説明も丁寧に行われているので、見比べるのにも最適なので利用させて頂いています。例では python で組まれた1秒毎にメッセージを書き出す無限ループの処理で、ups-shutdown.py として、次の記述です。

$ vi ups-shutdown.py
#!/usr/bin/env python
import sys
from time import sleep
if __name__ == '__main__':
  while True:
    print "Hello world %s" % (sys.argv)
    sys.stdout.flush()
    sleep(1)
$ sudo cp ups-shutdown.py /opt/UPS-Watch-shut/bin
$ sudo chown root:root /opt/UPS-Watch-shut/bin/ups-shutdown.py
$ sudo chmod 755 /opt/UPS-Watch-shut/bin/ups-shutdown.py 

サービスとして定義している ups-watching.service により startup-1st.sh が起動されます。その中で単純にサンプルプログラムの ups-shutdown.py を起動しています。

作成したユニットファイルやサンプルプログラムの確認

作成したユニットファイルやパッケージ内のプログラムの所有者とアクセス権の確認をしておきます。

$ ls -l /opt/UPS-Watch-shut
合計 4
drwxr-xr-x 2 root root 4096  1月  24 22:51 bin
$ ls -l /opt/UPS-Watch-shut/bin
合計 8
-rwxr-xr-x 1 root root  76  1月 24 22:49 startup-1st.sh
-rwxr-xr-x 1 root root 174  1月 24 22:51 ups-shutdown.py
$ ls -l /etc/systemd/system/ups-watching.service
-rw-r--r-- 1 root root 272 1月 24 22:47 /etc/systemd/system/ups-watching.service

茶色の部分の root ユーザーと 755 及び 644 が確認できました。

サンプルプログラムの単体動作検証

無限ループでメッセージを書き出すサンプルプログラムを単体で起動して、 [Ctrl+C] で強制停止して動作の確認をしておきます。

$ sudo /opt/UPS-Watch-shut/bin/ups-shutdown.py
Hello world ['/opt/UPS-Watch-shut/bin/ups-shutdown.py']
Hello world ['/opt/UPS-Watch-shut/bin/ups-shutdown.py']
Hello world ['/opt/UPS-Watch-shut/bin/ups-shutdown.py']
Hello world ['/opt/UPS-Watch-shut/bin/ups-shutdown.py']
Hello world ['/opt/UPS-Watch-shut/bin/ups-shutdown.py']
Hello world ['/opt/UPS-Watch-shut/bin/ups-shutdown.py']
Hello world ['/opt/UPS-Watch-shut/bin/ups-shutdown.py']
^CTraceback (most recent call last):
  File "/opt/UPS-Watch-shut/bin/ups-shutdown.py", line 8, in 
    sleep(1)
KeyboardInterrupt

単体では問題なくメッセージが書き出されるようです。


ユニットファイルを systemd に登録

ここからは、systemd へのサービス(ユニットファイル)の登録と検証作業です。ユニットファイル等を追加や変更した後には、必ず次のコマンド sudo systemctl daemon-reload を実行します。

そして一緒にステータスも確認しておきます。

$ sudo systemctl daemon-reload
$ sudo systemctl status ups-watching.service
● ps-watching.service - UPS Watching AutoRun
   Loaded: loaded (/etc/systemd/system/note-startup-1st.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

ステータスで表示される Loaded: 行のカッコ内の1つ目と2つ目のセミコロンの間の disabled となっているのは、自動起動が無効になっているということのようで、自動起動できるように設定します。

自動起動が行われるように設定

$ sudo systemctl enable ups-watching.service
Created symlink /etc/systemd/system/multi-user.target.wants/ups-watching.service → /etc/systemd/system/ups-watching.service.
$ sudo systemctl status ups-watching.service
● ups-watching.service - UPS Watching AutoRun
Loaded: loaded (/etc/systemd/system/ups-watching.service; enabled; vendor preset: enabled)
Active: inactive (dead)

ユニットファイルの定義内に、[ Install ] の WantedBy= で定義しているユニットにリンクが張られ、先ほどの /etc/systemd/system/ups-watching.service; の後の disabledenabled に変わっています。

これで再立ち上げで自動実行されるようになっているはずですが、その前にコマンドで起動して確認したり、停止させる操作をして動作を確認します。

systemd の手動起動と停止の確認

ユニットファイル名を指定したコマンド sudo systemctl start ups-watching.service で起動できます。

$ sudo systemctl start ups-watching.service
$ sudo systemctl status ups-watching.service
 ups-watching.service - UPS Watching AutoRun
Loaded: loaded (/etc/systemd/system/ups-watching.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2019-01-24 23:21:56 JST; 13s ago
Main PID: 28546 (python)
CGroup: /system.slice/ups-watching.service
└─28546 python /opt/UPS-Watch-shut/bin/ups-shutdown.py

1月 24 23:22:01 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:22:02 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:22:03 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:22:04 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu

起動されて1秒毎にメッセージが書きだされているのが確認できます。

次は、sudo systemctl stop ups-watching.service コマンドで停止して結果をステータスで確認してみます。

$ sudo systemctl stop ups-watching.service
$ [
ups-watching.service - UPS Watching AutoRun
Loaded: loaded (/etc/systemd/system/ups-watching.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Thu 2019-01-24 23:36:05 JST; 10s ago
Process: 28546 ExecStart=/opt/UPS-Watch-shut/bin/startup-1st.sh (code=killed, signal=TERM)
Main PID: 28546 (code=killed, signal=TERM)

1月 24 23:36:00 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:36:01 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:36:02 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:36:03 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:36:04 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:36:05 rpi1-disk startup-1st.sh[28546]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shu
1月 24 23:36:05 rpi1-disk systemd[1]: Stopping UPS Watching AutoRun...
1月 24 23:36:05 rpi1-disk systemd[1]: Stopped UPS Watching AutoRun.

Active: の行が Active: inactive (dead) になり、プログラムは停止していることが確認できます。

システム再起動による自動起動の確認

いよいよ待望の Linux の立上げによる自動起動の確認になります。ノートPCを再起動してステータスを確認します。

$ sudo systemctl status ups-watching.service
 ups-watching.service - UPS Watching AutoRun
Loaded: loaded (/etc/systemd/system/ups-watching.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2019-01-24 23:50:23 JST; 3min 43s ago
Main PID: 374 (python)
CGroup: /system.slice/ups-watching.service
└─374 python /opt/UPS-Watch-shut/bin/ups-shutdown.py

1月 24 23:53:57 rpi1-disk startup-1st.sh[374]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shutd
1月 24 23:53:58 rpi1-disk startup-1st.sh[374]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shutd
1月 24 23:53:59 rpi1-disk startup-1st.sh[374]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shutd
1月 24 23:54:00 rpi1-disk startup-1st.sh[374]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shutd
1月 24 23:54:01 rpi1-disk startup-1st.sh[374]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shutd
1月 24 23:54:02 rpi1-disk startup-1st.sh[374]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shutd
1月 24 23:54:03 rpi1-disk startup-1st.sh[374]: Hello world ['/opt/UPS-Watch-shut/bin/ups-shutd

問題なく起動されているようです


本題のUPSバッテリー監視とシャットダウン

システムの起動で一緒に監視プログラムを立上げることが可能なのを確認できました。

シャットダウンプログラム

提供されていた python のサンプルプログラムに少し手を加えて、単体ではシャットダウンが行われることを確認できました。これを起動されることを確認したサンプルと同じ名称 ups-shutdown.py で作成して置き換えます。それとバッテリー情報収集用のライブラリ raspiupshat.so を同じ場所に配置します。

$ cat /opt/UPS-Watch-shut/bin/ups-shutdown.py
#!/usr/bin/env python

# Raspi UPS Hat

# We only provide 2 interface to get battery information;
#
#Interface 1:
#Function: get current battery voltage
#Return value: battery voltage;
#float getv();

#Interface 2:
#Function:    get battery capacity
#Return value: 0~100+
#float getsoc();
#

import sys
# import Raspi UPS Hat library
import raspiupshat
import os
import time
import datetime

# init Raspi UPS Hat
raspiupshat.init();

# Get info
now = datetime.datetime.now()
print("{0:%Y-%m-%d %H:%M:%S} ; ".format(now) + "Voltage:%5.2fV ; Battery:%5i%%" % (raspiupshat.getv(), raspiupshat.getsoc()))

if raspiupshat.getsoc() >= 100:
        print "Battery FULL"
if raspiupshat.getsoc() < 20:
        print "Battery LOW"
while 1:
        if raspiupshat.getsoc() < 5:
                print "System will shutdown now,bye!"
                os.system("sudo shutdown -h 0")  
        # now = datetime.datetime.now()
        # print("{0:%H:%M:%S} ; ".format(now) + "Voltage:%5.2fV ; Battery:%5i%%" % (raspiupshat.getv(), raspiupshat.getsoc()))
        # time.sleep(10)

無限ループ内の最後の3行は、確認時に10秒毎に時間とバッテリー電圧値、バッテリー容量を表示するためのものです。必要なら先頭のコメント ‘#’ を消すことで表示できます。

起動の確認サンプルから監視プログラムへ置き換えと検証

次にユニットファイルを修正して、サンプルからバックアッププログラムに置き換えて、コマンドで実行してステータスを確認してみます。

$ ls -l /opt/UPS-Watch-shut/bin/
-rw-r--r-- 1 root root 7416 1月 25 11:07 raspiupshat.so
-rwxr-xr-x 1 root root   76 1月 24 22:49 startup-1st.sh
-rwxr-xr-x 1 root root  987 1月 25 11:06 ups-shutdown.py

$ sudo systemctl stop ups-watching.service
$ sudo systemctl daemon-reload
$ sudo systemctl start ups-watching.service
$ sudo systemctl status ups-watching.service
 ups-watching.service - UPS Watching AutoRun
Loaded: loaded (/etc/systemd/system/ups-watching.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2019-01-26 08:57:28 JST; 5h 36min ago
Main PID: 367 (python)
CGroup: /system.slice/ups-watching.service
└─367 python /opt/UPS-Watch-shut/bin/ups-shutdown.py

1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:31:08 ; Voltage: 4.34V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:31:18 ; Voltage: 4.35V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:31:28 ; Voltage: 4.35V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:31:38 ; Voltage: 4.35V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:31:48 ; Voltage: 4.35V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:31:58 ; Voltage: 4.35V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:32:08 ; Voltage: 4.35V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:32:18 ; Voltage: 4.35V ; Battery: 100%
1月 26 14:32:48 rpi1-disk startup-1st.sh[367]: 14:32:28 ; Voltage: 4.35V ; Battery: 100%

上記の結果では、実際に shutdown 動作を行う python のプログラムに置き換えましたが、その中にコメントで記述している部分 ‘#’ のコメントを取り除き、メッセージ書き出しのステートメントを有効にし組込みました。

systemd の動作に不慣れで、正しく理解できていないのですが、systemctl status コマンドでは、処理中で書き出されたメッセージが直接表示されているわけではないようで、別に journalctl -u コマンドで表示できるようです。

書き出されたメッセージが、キューかバッファのような所に蓄積されていて、 systemctl status の実行時に移されて、journalctl -u で見られる場所に蓄積していくような感じです。そのため、最新の情報を確認するためには、systemctl status を先に実行してから journalctl -u を実行して確認する必要があるようです。

メッセージの各行先頭には、systemctl status で移された日時が記述されているようで、前回の確認から今回新しく加えられた行を判別することが可能なようです。なお、操作方法は less コマンドの操作と同じなので、‘/’ で文字列を検索したり、行をスキップしたり、‘1G’ で先頭行に移動したり、‘G’ で最終行に移動することもでき、上下の矢印キーで前後にメッセージを辿ることができます。

実際の動作検証作業

本番でのシステム終了動作を確認するために、ssh でリモート接続したまま、バッテリーの情報を 10 秒毎に表示して確認しましたが、5% になった後の1分後くらいにセッション切断のメッセージと共に、4% を表示して停止していました。

期待したシステム終了が行われているようで一安心です。メッセージが蓄積されている journalctl -u コマンドの結果例を次に示します。このコマンドは一般ユーザーで確認できるようです。

$ journalctl -u ups-watching.service

 1月 26 08:57:28 rpi1-disk systemd[1]: Started UPS Watching AutoRun.
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 2019-01-26 08:57:32 ; Voltage: 3.77V ; Battery:
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: Battery LOW
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 08:57:32 ; Voltage: 3.77V ; Battery:    5%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 08:57:42 ; Voltage: 3.78V ; Battery:    5%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 08:57:52 ; Voltage: 3.77V ; Battery:    5%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 08:59:54 ; Voltage: 3.77V ; Battery:    5%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:00:04 ; Voltage: 3.77V ; Battery:    5%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:00:14 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:00:24 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:00:34 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:00:44 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:00:54 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:01:05 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:01:15 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:01:25 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:01:35 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:01:45 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:01:55 ; Voltage: 3.78V ; Battery:    6%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:02:05 ; Voltage: 3.78V ; Battery:    7%
 1月 26 09:14:56 rpi1-disk startup-1st.sh[367]: 09:02:15 ; Voltage: 3.78V ; Battery:    7%
lines 1-22

今回のUPSバッテリー監視と shutdown では、特にプログラムやサービスについての依存関係は無いので、これで完了です。

今後の改良としては、不要なメッセージの停止と、例えば容量が 10% 減少したら日時とバッテリー電圧や容量を記録しておいたり、50% を下回って 10% 減少する毎に携帯電話にメールを飛ばすとかしてみたいと考えています。

メールについては、ちょっと対策方法が重たくて、簡単に実現できるのかの目処が立っていません。

投稿者:

sunao-mita

早期年金生活を選択した自由人です。 アウトドアが好きで、北海道の広々とした自然が大好きです。 特に山が好きなわけではないけど、何故か人の少ない北海道の山登りには惹かれてしまいます。 そして、日常から離れてのテント生活がとても好きです。