部屋の温度をモニタリングして、エアコンを自動でON,OFFする。

#部屋の温度をモニタリングして、エアコンを自動でON,OFFする。

エアコンの問題点を指摘している記事[^8]を見かけ、既にエアコンを赤外線でコントロールしていたこともあって、自動でエアコンをON,OFFまたは温度を上げ下げしてくれるしてくれる置き型デバイスを作ることにした。

>安物エアコンの問題点
>冷たい空気は下に行くので、足元が寒くなりがちです。上と下とで3度ぐらいは平気で違ったりします。
>また、エアコンによってはエアコン自身の冷たさをセンサーが拾ってしまい、全然冷やしてくれなくなる欠陥機種もあります。


まず、エアコンを赤外線でコントロールする
以前の記事、「Raspberry Pi 3 で部屋の赤外線受信できる機器をコントロール」を見てください。
https://qiita.com/_kazuya/items/62a9a13a4ac140374ce8

その後、

+ 温度センサーをArduino UNOから読み取る
+ 温度が設定値を超えたら、Raspberry Pi3にシリアル通信経由でコマンド送信
+ Raspberry Pi3から赤外線信号送信

Arduinoを使う理由としては長めのUSBシリアルケーブルを用意すればある程度自由におく場所を決められるからです。
エアコン付近に取り付けているRaspberry Pi3からArduinoはテーブルの上くらいまで降ろしています。

### 温度センサーをArduinoから読み取る

LM35DZを使用

配線は
Arduinoのanalog0と5VとGNDへ接続

![配線.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110060/bbdd099b-bc2e-a6fd-2a92-ce197fff6746.png)


Arduinoのスケッチ

```C
// 温度センサーの値をシリアル通信で送信するプログラム.
// to Arduino UNO code by kazuyayuda.

int sensPin = A0; 
int sensVal = 0;
byte s_val;

void setup() {
  // serial setting 19200 bps
  Serial.begin(19200);
}

void loop() {
  float temp;
  if(Serial.available() > 0){
    s_val = Serial.read();

    if(s_val == 'T'){                         //T is Get Temp
      sensVal = analogRead(sensPin);          //get num from Analog0Pin
      temp    = modTemp(sensVal);             //convert from sensVal
      Serial.println(temp);                   //send result 

    }
  }
}

//Temp - convert from Analog0Pin
float modTemp(int analog_val){
float volt  = 5;                                   //base volt
float tempS = ((volt * analog_val) / 1024) * 100;  //convert //analoginput max 1023 so use 1024 //100(摂氏)
return tempS;                                      //temp ok

}

```

仕様に沿って摂氏に変換[^5]

アナログ端子から読み取れる最大値が1023なので、1024で除算し、正規化を行う。

摂氏変換式
$V : 5V$
$A : analog0の数値$

$$\frac{V \times A}{1024}\times 100$$

```
((5volt * analog0からの数値) / 1024) * 100;  
```

### ArduinoをRaspberryPi3とシリアル通信

ライブラリのインストール

```bash

#initial
sudo apt update

#pyserial
sudo apt -y install python3-pip
sudo pip3 install pyserial

#com
echo please check tty
dmesg | grep tty

```

```python
# code by kazuyayuda.
# -*- coding: utf-8 -*-
import serial
import time
from datetime import datetime 
import subprocess
import numpy as np

class TempAlert():

    def arduino_serial(Serialt):
        #open serial
        Serialt = serial.Serial('/dev/ttyACM0', 19200)
        time.sleep(3.5)
        return Serialt

    def get_process(self,St):
        n=1000
        filter_n=10
        Temp_box = []
        #Arduino is need value stabilization so 3 times loop.
        for num in range(n):
            #Get Temp from Arduino UNO Command : T
            St.write(b'T')
            Temp_box.append(float(St.readline()))

        usl_3=(sum(Temp_box) / len(Temp_box))+3*np.std(Temp_box)
        lsl_3=(sum(Temp_box) / len(Temp_box))-3*np.std(Temp_box)

        for num in range(filter_n):
            St.write(b'T')
            time.sleep(0.5)
            f_val=float(St.readline())
            if f_val > usl_3:
                print("abnormal val.") 
                filter_n -= 1
            elif f_val < lsl_3:
                print("abnormal val.") 
                filter_n -= 1
            else:
                Temp += f_val
        return float(Temp/filter_n)
        #close serial
        St.close()

if __name__ == "__main__":

    t = TempAlert()

    #serial open
    Stinfo = t.arduino_serial()

    for i in range(48): # auto exit process after 1day. 
        #get temp
        gettmp = t.get_process(Stinfo)
        contmp = round(gettmp)

        if contemp < 25:
            res = subprocess.call('sh hoton.sh')
            time.sleep(1800) #30 min.
        elif contemp > 25:
            res = subprocess.call('sh coldon.sh')
            time.sleep(1800) #30 min.
        else:
            print("no process.")
```

### 解説

各種ライブラリインポート

```python

import serial
import time
from datetime import datetime 
import subprocess
import numpy as np
```

シリアル通信初期化

```python
    def arduino_serial(Serialt):
        #open serial
        Serialt = serial.Serial('/dev/ttyACM0', 19200)
        time.sleep(3.5)
        return Serialt
```

### 値のばらつき
以前なんの処理もせずに温度センサを使っていたが稀に原因不明の外れ値が発生する事が分かった、今回それに対処してみたいと思う。

![3CBC01C6-39BB-49E0-B5B2-F22580A57E05.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110060/4a8c98c6-8416-6dc6-d18c-ca64a6dc0f92.jpeg)

センサー工学
V. 4.1.0
熊本大学工学部・機械システム工学科 鳥越 一平
3章の誤差の分布によると、[^9]

>系統的誤差は，精密なキャリブレーションと誤差要因の理論的・物理的な考察によって 除かれる.一方，偶然誤差は，原因不明ないしは予測不能であるが，多くの場合に共通の 統計的性質を有しており，統計学的な処理が可能である.

> 多くの測定で測定値の極限分布が正規分布に近い形を持つのは，複数の誤差要因が重なって起こる誤差は，個々の誤差要因の分布形状に関わらず，要因数が増えるにつれて，正規分布に近づくという性質(中心極限定理)があるためである.

とあるので、正規分布に対して3σ法を適用するのは問題ないはず。

また、V:センサ値にばらつきがある場合の対策として、複数の値をとって平均をとる
加えて、この平均値を利用して、3σ法を用いて、外れ値を除外する。[^6]
（ちなみにこの程度の規模なら中央値や最頻値をとるぐらいで良いかもですが^o^）

まず、値をn回取得し、センサの平均値を得る。

$$\bar{V}=\frac{1}{n}\sum_{i=1}^n V_i$$

必要に応じてnを決めれば良い、ここではn=1000で設定した。

$$\bar{V}=\frac{1}{1000}\sum_{i=1}^{1000} V_i$$

上記で得られた平均値を利用して、3σ法を適用する。

３σ法を適用するには、以下の手順で、



$V_i$が以下の条件を満たすとき、$V_i$を外れ値とする。

$$V_i > \mu + 3\sigma$$

$\mu$は$V$の平均値$\bar{V}$、$\sigma$は$V$の標準偏差である。

標準偏差は以下の式で求められる。

$$SD=\sqrt{\frac{1}{n}\sum_{i=1}^{n}(V_i-\bar{V})^2}$$

また、平均と標準偏差から$3σ$の値は、

$$3\sigma=3 \times SD$$
で求められる。

今回はnumpyを用いて、標準偏差を求め3σを求める。[^7]

```python:3σ

(3 * np.std(Temp_box)
```

また、下限値と上限値を定める

```python:下限値上限値
    
usl_3=(sum(Temp_box) / len(Temp_box))+3*np.std(Temp_box)
lsl_3=(sum(Temp_box) / len(Temp_box))-3*np.std(Temp_box)
```

以下は実装例↓

```python:get_process

    def get_process(self,St):
        n=1000
        filter_n=10
        Temp_box = []
        #Arduino is need value stabilization so 10 times loop.
        for num in range(n):
            #Get Temp from Arduino UNO Command : T
            St.write(b'T')
            Temp_box.append(float(St.readline()))

        usl_3=(sum(Temp_box) / len(Temp_box))+3*np.std(Temp_box)
        lsl_3=(sum(Temp_box) / len(Temp_box))-3*np.std(Temp_box)

        for num in range(filter_n):
            St.write(b'T')
            time.sleep(0.5)
            f_val=float(St.readline())
            if f_val > usl_3:
                print("abnormal val.") 
                filter_n -= 1
            elif f_val < lsl_3:
                print("abnormal val.") 
                filter_n -= 1
            else:
                Temp += f_val
        return float(Temp/filter_n)
        #close serial
        St.close()
```

最後に30分毎に温度を比較して、エアコンをコントロールする。

```python

    if contemp < 25:
        res = subprocess.call('sh hoton.sh')
        time.sleep(1800) #30 min.
    elif contemp > 25:
        res = subprocess.call('sh coldon.sh')
        time.sleep(1800) #30 min.
    else:
        print("no process.")
```

ちなみに私もまだまだここをどうするのがベストかは実験中。後日追記します！
この条件は、自分にとってより快適になるように設定すれば良い。
もう少し工夫するなら、温度を上げ下げする操作を入れるとより利便性が上がるでしょう。

### 参考文献[^8][^5][^6][^7][^1][^2][^3][^4]

[^1]: 「Slack経由で家の外からエアコンをon, offできる装置を、Raspberry Piで作ってみた。（しかも御坂美琴ちゃんが応答してくれる）」http://qiita.com/KAKY/items/55e6c54fa2073cdc0bbe
[^2]: 「Raspberry Pi 3 model B でエアコンをコントロールした話」http://s4kr4.hatenablog.com/entry/2016/07/24/114444
[^3]: 「Raspberry PiでLIRCをインストールする」http://qiita.com/Library/items/35eec18fbe11387be6d5
[^4]: 「Raspberry Pi 3 Model Bで赤外線受信センサーと赤外線 LEDでリモコン信号を送受信する方法」http://www.neko.ne.jp/~freewing/raspberry_pi/raspberry_pi_3_lirc_ir_remote_control/
[^5]: 「LM35DZ 温度センサIC」 https://www.switch-science.com/catalog/2508/
[^6]: 「ポアソン分布に従う時系列データから3σ法を用いて異常検知する」 https://qiita.com/FooQoo/items/3cb92f4a5bc0fedd1fd0
[^7]: 「正規分布の標準偏差と信頼区間の計算について」　https://friedrice-mushroom.hatenablog.com/entry/2019/03/17/193332
[^8]: 「安物エアコンの温度制御を改善するハック」https://qiita.com/rukihena/items/7f84f036abee2b5c0492
[^9]: 「センサー工学 V. 4.1.0 熊本大学工学部・機械システム工学科 鳥越 一平」 http://www.mech.kumamoto-u.ac.jp/Info/lab/sensor/lect/Sensor_tor_v41.pdf