RaspberryPiからセンサーを死活監視する
温度とか気圧とかセンサーから取ってどうにかする奴はたくさん見てきたけど,センサーが正常に動作しているか(死活監視)をしている人はなかなかいないと思ったので今回実際にセンサーの死活監視をやってみた.今回はI2Cで接続するセンサーのみで検証する.
目次
- 使用するもの
- RaspberryPiの準備
- RaspberryPiとセンサーを回路で繋ぐ
- i2cdetectコマンドで接続されているかを確認する
- 死活監視方法
- プログラムでコマンドを実行し,標準出力を取得する
- レジスタ値とセンサー名の紐付け
- 死活監視
- 終わりに
使用するもの
- RaspberryPi3B+
- Macbookpro (ラズパイにリモートで接続するため)
- I2Cセンサー
- ADT7410
- BME280
- SHI31
RaspberryPiの準備
今回,死活監視には"i2cdetect"というLinux系OSで扱えるアクセスコマンドになるため, RaspberryPiにはRaspbianというOSをインストールする.
現時点ではこのようになっている.
raspbian 10.2 (Linux 10)
更に今回はPython3を使ってプログラムを作るため,インストールしておきたい.
(Python2系でも動作するとは思うが保証はできない)
$ sudo apt-get install python3
そして現在のVersionはこうなっている.
pi@raspberrypi:~ $ python3 --version
Python 3.7.3
RaspberryPiとセンサーを回路で繋ぐ
I2Cセンサーを適切に回路で繋ぐ.具体的な説明はここではしないため,詳細を知りたい方はこちらを参照してもらいたい.http://independence-sys.net/main/?p=3860 (これはBME280を接続している)
i2cdetectコマンドで接続されているかを確認する.
pi@raspberrypi:~ $ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- 45 -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
コマンドを実行するとこのような出力が得られる.ここにある45,48,76というのは繋がっているセンサーのレジスタ値である.I2Cではこのようにセンサーにレジスタ値を設定することでセンサーの判別を行なっている.
ちなみにレジスタとセンサーはこのようになっている.
- 45 = SHI31
- 48 = ADT7410
- 76 = BME280
つないでいるセンサーの数だけレジスタが表示されれば問題ない.
死活監視方法
上記のようなレジスタ値が取れているということ = センサーが正常に接続され,データを送ることができる状態
であるということである.
要するに,レジスタ値が表示されていることを判別することができればどれが生きて,どれが死んでいるのかを判別をすることができるということになる.
今回はそれを用いて死活監視を行なっていく.
プログラムでコマンドを実行し,標準出力を取得する
これにはsubprocessという機能を利用してコマンドをプログラムから実行する.
import subprocess
cmd = 'i2cdetect -y 1'
subprocess.Popen(cmd, stdout=subprocess.PIPE,shell=True).communicate()[0]
これを取得するために以下のように改良する.
import subprocess
def command(cmd):
return subprocess.Popen(cmd, stdout=subprocess.PIPE,shell=True).communicate()[0]
def main():
cmd = 'i2cdetect -y 1'
cmd_result = command(cmd)
標準出力からレジスタ値のみを取り出す
まずPython3ではsubprocessで得られた結果は全てByte型として扱われているのでこれをstr型へキャストする必要がある.
そしてstr型へ直した後スペース区切りで配列に格納をする.
str = cmd_result.decode("utf-8") # Byte to str
str2 = str.split() # スペースで区切る
str = list(e) # 配列に格納する
そして配列を以下の手順でレジスタ値のみにする.
- -- を消す
- a ~ fの文字を消す
- :がついている要素を消す
- str型をint型へキャストする
- 10以下の要素を消す
プログラムではこのようになる
def remove(str):
x = 0
while x <len(e):
try:
e.remove('--') # --を取り除く
except:
None
x += 1
x = 0
while x < len(e):
try:
e.remove('a')
e.remove('b')
e.remove('c')
e.remove('d')
e.remove('e')
e.remove('f')
e.remove('00:')
e.remove('10:')
e.remove('20:')
e.remove('30:')
e.remove('40:')
e.remove('50:')
e.remove('60:')
e.remove('70:')
except:
None
x += 1
int_e = list(map(int,e)) # str型からint型へキャストする
x = 0
while x < len(int_e): # 10以下の数字を削除
if int_e[x] <= 10:
int_e.pop(x)
x -= 1
x += 1
return int_e
実行結果するとこのようになる.
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '00:', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '10:', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '20:', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '30:', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '40:', '--', '--', '--', '--', '--', '45', '--', '--', '48', '--', '--', '--', '--', '--', '--', '--', '50:', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '60:', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '--', '70:', '--', '--', '--', '--', '--', '--', '76', '--']
ハイフンを取り除く
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '00:', '10:', '20:', '30:', '40:', '45', '48', '50:', '60:', '70:', '76']
レジスタ値以外の要素を取り除く
[45, 48, 76]
このようにすることで,期待通りレジスタ値のみを取得することができた.
あとはこれを用いて監視と結び付けていく.
レジスタ値とセンサー名の紐付け
しかし,レジスタ値だけではどのセンサーがついているかがプログラムからはわからない.
そこでCSVファイルを作り,そこにセンサーの名前を登録しておき,プログラムが自動的に読み取ってこれるようにした.
CSVファイルの中身は以下のようになっている
pi@raspberrypi:~$ cat test.csv
None,None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None
このCSVファイルに書かれている文字列を,(カンマ)区切りで配列にするのだが,その際,配列の要素数とレジスタ値が紐づけられるようにしておく.
そして,最初にプログラムを動作させた際はユーザーになんのセンサーがついているのか入力を求めるようにする.そして受け取ったセンサー名をCSVファイルへ上書きするという方法だ.
pi@raspberrypi:~/tearminal_test $ sudo python3 cmd_test.py
[45, 48, 76]
新しくセンサーを登録しますか?(y/n)
> y
[45, 48, 76]
レジスタ値:45 に接続されているセンサーはなんですか?
> SHI31
SHI31を登録しました
レジスタ値:48 に接続されているセンサーはなんですか?
> ADT7410
ADT7410を登録しました
レジスタ値:76 に接続されているセンサーはなんですか?
> BME280
BME280を登録しました
csvファイルを上書きしました。
もう一度CSVファイルを開いてみると...
pi@raspberrypi:~$ cat test.csv
None,None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,SHI31,None,None,
ADT7410,None,None,None,None,None,None,None
,None,None,None,None,None,None,None,None,
None,None,None,None,None,None,None,None,
None,None,None,None,BME280,None
しっりと入力したセンサーの名前が入っていることが確認できる.
死活監視
最後に死活監視のためのプログラムをかけば完了である.
プログラムはこのように記述した.
def csv(number):
name = csv_cmd.csv_read() #最新のcsvファイルを取得
x = 0
life = True
error='Error!! : ' #エラー文の雛形
while x < len(name[0]):
try
if name[0][number[x]] == 'None':
print(name[0][number[x]]+' : Error')
error += name[0][number[x]]
life = False
else:
print(name[0][number[x]]+' : OK')
except:
None
x += 1
if life == False: # 1つのセンサーでも問題があった場合
print('センサーに何らかの異常があり通信できません。メールとSlackで通知します。')
error += 'の接続ができません。'
sendGmail(error) # メール送信
slacksend(error) # Slackに送信
else: # 1つのセンサーも異常がなかった場合
print('問題はありません。')
text = 'センサーは全て正常に動作しています。'
sendGmail(text)
slacksend(text)
これを組んでプログラムを実行すると以下のようになる.
pi@raspberrypi:~/tearminal_test $ sudo python3 cmd_test.py
SHI31 : OK
ADT7410 : OK
BME280 : OK
問題はありません。
また異常があった際は上記のセンサー名の隣がOKがErrorになり,メールやSlackで通知が送られる.
今回はセンサーを回路から抜き,人工的に障害を発生させてみた.
今回は時間の都合上3つのセンサーまでしかできなかったが,理論上数が増えても同じ要領で監視が行えるものである.
終わりに
今回はI2Cセンサーのみの監視であったが,I2Cだけでなく他のセンサーの死活監視も行えるようになるのが望ましい.
また現在はセンサーが生きているか否かしか識別できないため,問題が起きた際にどこでどのような問題が起きているのかがわかるようなシステムを作れるとなおセンサーの監視精度が上がり,需要も増えるのではないかと思う
まだまだやらなくてはいけないことだらけだが,ひとつひとつ問題をつぶしていきたいでし