Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

RaspberryPiからセンサー(I2C)を死活監視する

More than 1 year has passed since last update.

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) # 配列に格納する

そして配列を以下の手順でレジスタ値のみにする.

  1. -- を消す
  2. a ~ fの文字を消す
  3. :がついている要素を消す
  4. str型をint型へキャストする
  5. 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で通知が送られる.
今回はセンサーを回路から抜き,人工的に障害を発生させてみた.

スクリーンショット 2020-01-16 0.16.32.png

スクリーンショット 2020-01-16 0.15.04.png

今回は時間の都合上3つのセンサーまでしかできなかったが,理論上数が増えても同じ要領で監視が行えるものである.

終わりに

今回はI2Cセンサーのみの監視であったが,I2Cだけでなく他のセンサーの死活監視も行えるようになるのが望ましい.
また現在はセンサーが生きているか否かしか識別できないため,問題が起きた際にどこでどのような問題が起きているのかがわかるようなシステムを作れるとなおセンサーの監視精度が上がり,需要も増えるのではないかと思う
まだまだやらなくてはいけないことだらけだが,ひとつひとつ問題をつぶしていきたいでし

cdsl
東京工科大学コンピュータサイエンス学部クラウド・分散システム研究室
https://www.tak-cslab.org/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away