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

# RaspberryPiからセンサーを死活監視する
温度とか気圧とかセンサーから取ってどうにかする奴はたくさん見てきたけど，センサーが正常に動作しているか(死活監視)をしている人はなかなかいないと思ったので今回実際にセンサーの死活監視をやってみた．今回はI2Cで接続するセンサーのみで検証する．

## 目次
- 使用するもの
- RaspberryPiの準備
- RaspberryPiとセンサーを回路で繋ぐ
- i2cdetectコマンドで接続されているかを確認する
- 死活監視方法
    - プログラムでコマンドを実行し，標準出力を取得する
    - レジスタ値とセンサー名の紐付け
    - 死活監視
- 終わりに

## 使用するもの
- RaspberryPi3B+
- Macbookpro (ラズパイにリモートで接続するため)
- I2Cセンサー
    - ADT7410
    - BME280
    - SHI31

## RaspberryPiの準備
今回，死活監視には"i2cdetect"というLinux系OSで扱えるアクセスコマンドになるため， RaspberryPiにはRasｐbianというOSをインストールする．
現時点ではこのようになっている．

`raspbian 10.2 (Linux 10)`

更に今回はPython3を使ってプログラムを作るため，インストールしておきたい．
(Python2系でも動作するとは思うが保証はできない)

`$ sudo apt-get install python3`

そして現在のVersionはこうなっている．

```shell
pi@raspberrypi:~ $ python3 --version
Python 3.7.3
```

## RaspberryPiとセンサーを回路で繋ぐ
I2Cセンサーを適切に回路で繋ぐ．具体的な説明はここではしないため，詳細を知りたい方はこちらを参照してもらいたい．http://independence-sys.net/main/?p=3860　(これはBME２８０を接続している)

## i2cdetectコマンドで接続されているかを確認する．

```terminal
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 --     
```

コマンドを実行するとこのような出力が得られる．ここにある４５，４８，７６というのは繋がっているセンサーのレジスタ値である．I2Cではこのようにセンサーにレジスタ値を設定することでセンサーの判別を行なっている．

ちなみにレジスタとセンサーはこのようになっている．

- 45 = SHI31
- 48 = ADT7410
- 76 = BME280

つないでいるセンサーの数だけレジスタが表示されれば問題ない．

# 死活監視方法
**上記のようなレジスタ値が取れているということ　＝　センサーが正常に接続され，データを送ることができる状態**
であるということである．
要するに，レジスタ値が表示されていることを判別することができればどれが生きて，どれが死んでいるのかを判別をすることができるということになる．
今回はそれを用いて死活監視を行なっていく．

## プログラムでコマンドを実行し，標準出力を取得する
これにはsubprocessという機能を利用してコマンドをプログラムから実行する．

```python
import subprocess

cmd = 'i2cdetect -y 1'
subprocess.Popen(cmd, stdout=subprocess.PIPE,shell=True).communicate()[0]
```

これを取得するために以下のように改良する．

```python
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型へ直した後スペース区切りで配列に格納をする．

```python
str = cmd_result.decode("utf-8") # Byte to str

str2 = str.split() # スペースで区切る
str = list(e) # 配列に格納する
```

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

1. --　を消す
2. a ~ fの文字を消す
3. ：がついている要素を消す
4. str型をint型へキャストする
5. １０以下の要素を消す

プログラムではこのようになる

```python
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): # １０以下の数字を削除
        if int_e[x] <= 10:
            int_e.pop(x)
            x -= 1
        x += 1

    return int_e
```

実行結果するとこのようになる．

```terminal
['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ファイルの中身は以下のようになっている

```terminal
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ファイルへ上書きするという方法だ．

```terminal
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ファイルを開いてみると．．．

```terminal
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
```

しっりと入力したセンサーの名前が入っていることが確認できる．

## 死活監視
最後に死活監視のためのプログラムをかけば完了である．

プログラムはこのように記述した．

```python
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:     # １つのセンサーでも問題があった場合
        print('センサーに何らかの異常があり通信できません。メールとSlackで通知します。')
        error += 'の接続ができません。'
        sendGmail(error)  # メール送信
        slacksend(error)  # Slackに送信
    else:                 # １つのセンサーも異常がなかった場合
        print('問題はありません。')
        text = 'センサーは全て正常に動作しています。'
        sendGmail(text)
        slacksend(text)
```

これを組んでプログラムを実行すると以下のようになる．

```terminal
pi@raspberrypi:~/tearminal_test $ sudo python3 cmd_test.py 
SHI31 : OK
ADT7410 : OK
BME280 : OK
問題はありません。
```
また異常があった際は上記のセンサー名の隣がOKがErrorになり，メールやSlackで通知が送られる．
今回はセンサーを回路から抜き，人工的に障害を発生させてみた．

<img width="803" alt="スクリーンショット 2020-01-16 0.16.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/b565d3dd-e177-41ea-9c6c-c795b80cdc98.png">

<img width="1203" alt="スクリーンショット 2020-01-16 0.15.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459889/668811ef-72be-c34c-e9fa-0b0521eab138.png">

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

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