pythonとArduinoで始めるIoT開発<部屋が寒いと思う皆さん！部屋の温度可視化してみませんか？>

## はじめに
私は最近部屋がすごく寒いなーと感じています.
一体今の部屋の温度は何度なんだろう？昨日と比べて何度違うんだろうと感じている人いませんか？
そんな人の悩みを解決すべく温度を可視化するシステムを構築してみましたのでご覧いただけると幸いです.
## システムの構成
>Arduino UNO(マイコンだったらなんでも大丈夫です.)
今回はArduinoUNOを使っていますが...
PC (Windows or Mac or Linux or ワンボード)
基本的になんでも大丈夫です.
温度センサ DHT11温度センサー

温度センサDHT11
![スクリーンショット 2021-01-24 22.10.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567360/640482eb-c31a-2de3-3419-d5f6767fb61e.png)

https://www.amazon.co.jp/HiLetgo-DHT11温度センサー-モジュール湿度センサーモジュール-デュポンラインと付属-Arduinoと互換/dp/B010GXAH4E/ref=pd_rhf_sc_p_img_1?_encoding=UTF8&psc=1&refRID=CC6QWPZYTYSVHDZE21KC

## 類似商品と今回のシステムの比較
はい.世の中には既存に色々なシステムがあります.その中でも今回はスイッチボットさんの温度計を紹介したいと思います.
![スクリーンショット 2021-01-24 22.05.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567360/97e092fa-6eae-20ed-58a9-d3479f72ef89.png)

https://www.amazon.co.jp/SwitchBot-スイッチボット-スイス製センサースマホで温度湿度管理-GoogleHome-IFTTT対応/dp/B07L4QNZVF/ref=sr_1_2?dchild=1&keywords=スイッチボット+温度計&qid=1611493523&sr=8-2

上の写真がスイッチボットさんの温度計になります.はいスマートでいいですね.本当にそう思います.
<b>が,しかし...</b>こちらの商品電池を交換しなくちゃならないらしいです.いや聞いた話なので本当かどうかはわかりませんが...
めんどくさがり屋の自分としては電池の交換はしたくないので自作をしました.
<b>既存システムとの差別化は電池を交換しなくていい所！</b>
<b>自分の部屋の温度を他人に知られない...笑(会社に)</b>
多分この２つが差別化できる所だと思います.


※スイッチボットさんと比較していますが,スイッチボットさんを誹謗中傷するものではございません.
あくまでも個人的な意見です.
## 回路図
![スクリーンショット 2021-01-24 21.32.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567360/7f215e86-099d-22f5-f39b-fa42a5bd89b7.png)

作った回路図は本当に対して難しくないので誰でも実践できると思うのでやってみてください.

## システム構成図
![スクリーンショット 2021-01-26 22.47.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567360/3960af9e-64b1-faa8-bc35-79c595ef2ef4.png)

## システムの説明
一応システムの説明をすると,
sensor.ino
<b>1,まず,マイコンで温度を計測する.</b>
sensor.py
<b>2,それをUSB(シリアル通信)を使ってpcで受け取る(pythonで表示).</b>
<b>3,pcで受け取ったデータをデータベースに書き込む</b>
大体,通信的なことも入れるとローカル上だと2秒に1回書き込んでいます.
外部のサーバを通すともう少し書き込みスピードは変わるかもしれません.
server.py
<b>4,書き込まれたデータをflaskで可視化する</b>
現在可視化対応しているもの:
・過去5日間の平均温度の可視化
・最新の温度計測結果の表示
設計だと,画面をリロードするとデータが更新される設計になっています.
<b>以上！</b>

本当に難しいことはなにもしていません.

## データベースの設計
設計っていうかただデータベースに送っているだけなので現状は大したことはありません.

| day        | time         | temperature |
|:-----------|------------:|:------------:|

こんな感じになっています.

<b>一つ注意点</b>
データは無限に入れられる分ではないのでそこはデータが溜まってきたときに
考えなければいけない問題の一つであるかなと考えています.

現状設計では考えていませんが,5ヶ月を超えたデータは消していこうかなと思っています.

## コード
### Arduino側のコード

一つだけ注意点というかこのコードは外部のライブラリを参照していますのでそのライブラリをインストールしないと使うことができません.
>https://github.com/adafruit/DHT-sensor-library

上記からダウンロードしてありがたく使わせていただきました.

#### sensor.ino
```arduino:sensor.ino
#include "DHT.h" //ライブラリインクルード
#define DHT_Pin 8 
#define DHT_Type DHT11 

DHT dht(DHT_Pin, DHT_Type); 
float tempC = 0.0f;
void setup() {
Serial.begin(9600);
dht.begin(); 
}
void loop() {
delay(2000);
tempC = dht.readTemperature();
if (isnan(humidity) || isnan(tempC)) {
return;
}
Serial.println(tempC);
}
//参考URL
//https://omoroya.com/arduino-lesson11/
```


### pythonシリアル通信

#### sensor.py
```python:sensor.py
import serial
import time
import datetime
import mysql.connector as mydb
ser = serial.Serial('/dev/tty.usbmodem14101', 9600)
conn = mydb.connect(
        host='127.0.0.1',
        port='3306',
        user='root',#ユーザidの記入
        password='',#パスワードの記入
        database='temperature'#データベースの名前を記入
    )
# コネクションが切れた時に再接続してくれるよう設定
conn.ping(reconnect=True)
# 接続できているかどうか確認
print(conn.is_connected())
while (1):
    s=str(ser.readline())
    s1=s.replace("b'", '')
    s2=s1.replace("r", '')
    s3=s2.replace("n", '')
    s4=s3.replace("\\", '')
    s5=s4.replace("'", '')
    dt_now = datetime.datetime.now()
    print("現在時刻:"+str(dt_now))
    print(str(s5)+"°")
    # コネクションが切れた時に再接続してくれるよう設定
    conn.ping(reconnect=True)
    # 接続できているかどうか確認
    print(conn.is_connected())
    cur = conn.cursor()
    cur.execute("INSERT INTO `test` (`day`, `temperature`)"+ "VALUES"+ "("+"'"+str(dt_now)+"'"+","+str(s5)+")")
    conn.commit()
    cur.close()
    conn.close()
    time.sleep(0.1)
```

<b>コードのワンポイント解説</b>

<b>win系だとCOMを使うらしいのでwinでやる方はお調べになると良いのでは？と思います.
MacやLinux OSで実装される方は下記の書き方で大丈夫だと思います.</b>

```
ser = serial.Serial('/dev/tty.usbmodem14101', 9600)
```

<b>シリアル通信やソケット通信を行うと不要な文字などもついてくるのでそちらを整形してあげるとうまく使えるようになります</b>

```
    s=str(ser.readline())
    s1=s.replace("b'", '')
    s2=s1.replace("r", '')
    s3=s2.replace("n", '')
    s4=s3.replace("\\", '')
    s5=s4.replace("'", '')
```
### 温度可視化(Flask)

#### server.py

```python:server.py
from flask import Flask, render_template #追加
import pymysql #追加
import time
import datetime
from flask import render_template, url_for
from flask import request
import numpy 
import json


app = Flask(__name__)

@app.route('/')
def hello():
    #db setting
    db = pymysql.connect(
            host='127.0.0.1',
            user='root',
            password='',
            db='temperature',
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
        )
    cur = db.cursor()
    cur1 = db.cursor()
    cur2 = db.cursor()
    cur3 = db.cursor()
    cur4 = db.cursor()
    cur5 = db.cursor()
    cur6 = db.cursor()
    day = datetime.date.today()
    day1 =  datetime.date.today() - datetime.timedelta(days=1)
    day2 =  datetime.date.today() - datetime.timedelta(days=2)
    day3 =  datetime.date.today() - datetime.timedelta(days=3)
    day4 =  datetime.date.today() - datetime.timedelta(days=4)
    day5 =  datetime.date.today() - datetime.timedelta(days=5)
    print(day1)
    print(day2)
    print(day3)
    print(day4)
    print(day5)
    #sql = "SELECT avg(temperature) as temperature FROM `temperature`"
    sql = "SELECT * FROM `temperature` WHERE `day`="+"'"+str(day)+"'" +"ORDER BY `time` DESC LIMIT 1"
    sql1="SELECT day , DATE_FORMAT(time, '%H') as time, avg(temperature) as avg FROM temperature WHERE day='2021-01-18' GROUP BY DATE_FORMAT(time, '%H');"
    tday1="select avg(temperature) as temperature from temperature WHERE day='"+str(day1)+"';"
    tday2="select avg(temperature) as temperature from temperature WHERE day='"+str(day2)+"';"
    tday3="select avg(temperature) as temperature from temperature WHERE day='"+str(day3)+"';"
    tday4="select avg(temperature) as temperature from temperature WHERE day='"+str(day4)+"';"
    tday5="select avg(temperature) as temperature from temperature WHERE day='"+str(day5)+"';"
    cur.execute(sql)
    cur1.execute(sql1)
    cur2.execute(tday1)
    cur3.execute(tday2)
    cur4.execute(tday3)
    cur5.execute(tday4)
    cur6.execute(tday5)
    members = cur.fetchall()
    members1 = cur1.fetchall()
    today1 = cur2.fetchall()
    today2 = cur3.fetchall()
    today3 = cur4.fetchall()
    today4 = cur5.fetchall()
    today5 = cur6.fetchall()
    cur.close()
    cur1.close()
    cur2.close()
    cur3.close()
    cur4.close()
    cur5.close()
    cur6.close()
    db.close()
    print(today1)
    print(today2)
    print(today3)
    print(today4)
    print(today5)
    return render_template('hello.html', title='トップページ', members=members,members1=members1,today1=today1,today2=today2,today3=today3,today4=today4,today5=today5) 

@app.route('/hello.html')
def hello1():
    #db setting
    db = pymysql.connect(
            host='127.0.0.1',
            user='root',
            password='',
            db='temperature',
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
        )
    cur = db.cursor()
    cur1 = db.cursor()
    cur2 = db.cursor()
    cur3 = db.cursor()
    cur4 = db.cursor()
    cur5 = db.cursor()
    cur6 = db.cursor()
    day = datetime.date.today()
    day1 =  datetime.date.today() - datetime.timedelta(days=1)
    day2 =  datetime.date.today() - datetime.timedelta(days=2)
    day3 =  datetime.date.today() - datetime.timedelta(days=3)
    day4 =  datetime.date.today() - datetime.timedelta(days=4)
    day5 =  datetime.date.today() - datetime.timedelta(days=5)
    print(day1)
    print(day2)
    print(day3)
    print(day4)
    print(day5)
    #sql = "SELECT avg(temperature) as temperature FROM `temperature`"
    sql = "SELECT * FROM `temperature` WHERE `day`="+"'"+str(day)+"'" +"ORDER BY `time` DESC LIMIT 1"
    sql1="SELECT day , DATE_FORMAT(time, '%H') as time, avg(temperature) as avg FROM temperature WHERE day='2021-01-18' GROUP BY DATE_FORMAT(time, '%H');"
    tday1="select avg(temperature) as temperature from temperature WHERE day='"+str(day1)+"';"
    tday2="select avg(temperature) as temperature from temperature WHERE day='"+str(day2)+"';"
    tday3="select avg(temperature) as temperature from temperature WHERE day='"+str(day3)+"';"
    tday4="select avg(temperature) as temperature from temperature WHERE day='"+str(day4)+"';"
    tday5="select avg(temperature) as temperature from temperature WHERE day='"+str(day5)+"';"
    cur.execute(sql)
    cur1.execute(sql1)
    cur2.execute(tday1)
    cur3.execute(tday2)
    cur4.execute(tday3)
    cur5.execute(tday4)
    cur6.execute(tday5)
    members = cur.fetchall()
    members1 = cur1.fetchall()
    today1 = cur2.fetchall()
    today2 = cur3.fetchall()
    today3 = cur4.fetchall()
    today4 = cur5.fetchall()
    today5 = cur6.fetchall()
    cur.close()
    cur1.close()
    cur2.close()
    cur3.close()
    cur4.close()
    cur5.close()
    cur6.close()
    db.close()
    print(today1)
    print(today2)
    print(today3)
    print(today4)
    print(today5)
    return render_template('hello.html', title='日別部屋の温度可視化', members=members,members1=members1,today1=today1,today2=today2,today3=today3,today4=today4,today5=today5) #変更

@app.route('/hello1.html')
def hello2():
    #db setting
    db = pymysql.connect(
            host='127.0.0.1',
            user='root',
            password='',
            db='temperature',
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
        )
    now = datetime.datetime.now()
    now1=now - datetime.timedelta(hours=1)
    now4=now - datetime.timedelta(hours=2)
    now6=now - datetime.timedelta(hours=3)
    now8=now - datetime.timedelta(hours=4)
    now3=now1.strftime('%H')#20
    now2=now4.strftime('%H')#19
    now5=now6.strftime('%H')#18
    now7=now8.strftime('%H')#17
    day = datetime.date.today()
    cur = db.cursor()
    cur1 = db.cursor()
    cur2 = db.cursor()
    cur3 = db.cursor()
    cur4 = db.cursor()
    t1day="SELECT * FROM `temperature` WHERE `day`="+"'"+str(day)+"'" +"ORDER BY `time` DESC LIMIT 1"
    t1day1="select avg(temperature) as temperature from temperature WHERE day="+"'"+str(day)+"'"+ "and time>="+"'"+str(now2)+":00:00"+"'"+" and time<="+"'"+str(now3)+":00:00"+"'"+";"
    t1day2="select avg(temperature) as temperature from temperature WHERE day="+"'"+str(day)+"'"+ "and time>="+"'"+str(now5)+":00:00"+"'"+" and time<="+"'"+str(now2)+":00:00"+"'"+";"
    t1day3="select avg(temperature) as temperature from temperature WHERE day="+"'"+str(day)+"'"+ "and time>="+"'"+str(now7)+":00:00"+"'"+" and time<="+"'"+str(now5)+":00:00"+"'"+";"
    t1day4="select avg(temperature) as temperature from temperature WHERE day="+"'"+str(day)+"'"+ "and time>="+"'"+str(now7)+":00:00"+"'"+" and time<="+"'"+str(now5)+":00:00"+"'"+";"
    cur.execute(t1day)
    cur1.execute(t1day1)
    cur2.execute(t1day2)
    cur3.execute(t1day3)
    cur4.execute(t1day4)
    
    test1 = cur1.fetchall()
    test2 = cur2.fetchall()
    test3 = cur3.fetchall()
    test4 = cur3.fetchall()
    test5 = cur.fetchall()

    cur.close()
    cur1.close()
    cur2.close()
    cur3.close()
    cur.close()
    db.close()
    print(t1day1)
    print(test1)
    print(t1day2)
    print(test2)
    print(t1day3)
    print(t1day4)
    return render_template('hello1.html', title='時別部屋の温度可視化', test1=test1,test2=test2,test3=test3,test4=test4,test5=test5)

if __name__ == '__main__':
    app.run(debug=True, port=8085)
```

<b>ピンポイントコード解説</b>
データベースの集計はこんな感じで集計を行いました.

```
select avg(temperature) as temperature from temperature WHERE day='"+str(day1)+"'
```

```
debug=True
```
flaskのデバックモードがオンになっていますので実際に運用する時はdebug=Falseにしましょう.

### 温度可視化(Flaskフロント)

#### hello.html（日別ページ）

```html:hello.html
{% extends "layout.html" %}
{% block content %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
<h1>Temperature-visualization</h1>
<nav aria-label="breadcrumb">
    <ol class="breadcrumb">
      <li class="breadcrumb-item"><a href="hello.html">日</a></li>
      <!--<li class="breadcrumb-item"><a href="hello.html">週</a></li>-->
      <!--<li class="breadcrumb-item"><a href="hello3.html">月</a></li>-->
    </ol>
  </nav>
  <h2>5日間の温度データ</h2>
  <canvas id="myLineChart"></canvas>
    <center>

        <br>
        <pre>
    <b>
        <b>現在の気温</b>
        日付:{% for member in members %}{{ member.day}}{% endfor %}
        時間：{% for member in members %}{{ member.time}}{% endfor %}
        現在の温度{% for member in members %}{{ member.temperature}}{% endfor %}°
        </pre>
    </b>
    </center>
  <script>
    var today1 = "{% for today1 in today1 %}{{today1.temperature}}{% endfor %}"
    var today2 = "{% for today2 in today2 %}{{today2.temperature}}{% endfor %}"
    var today3 = "{% for today3 in today3 %}{{today3.temperature}}{% endfor %}"
    var today4 = "{% for today4 in today4 %}{{today4.temperature}}{% endfor %}"
    var today5 = "{% for today5 in today5 %}{{today5.temperature}}{% endfor %}"
    var ctx = document.getElementById("myLineChart");
    var myLineChart = new Chart(ctx, {
      type: 'line',
      data: {
        labels: ['5日前', '4日前', '3日前', '2日前', '1日前'],
        datasets: [
          {
            label: '温度',
            data: [today5, today4, today3, today2, today1],
            borderColor: "rgba(0,0,255,1)",
            backgroundColor: "rgba(0,0,0,0)"
          }
        ],
      },
      options: {
        title: {
          display: true,
          text: '温度(時)'
        },
        scales: {
          yAxes: [{
            ticks: {
              suggestedMax: 40,
              suggestedMin: 0,
              stepSize: 10,
              callback: function(value, index, values){
                return  value +  '度'
              }
            }
          }]
        },
      }
    });
    </script>

{% endblock %}
```

<b>ピンポイントアドバイス</b>
flask上でjsにpythonの変数を移行する為には以下のやり方をするとできますので参考にしていただけると良いかなと思います.

```
  var today1 = "{% for today1 in today1 %}{{today1.temperature}}{% endfor %}"
  var today2 = "{% for today2 in today2 %}{{today2.temperature}}{% endfor %}"
  var today3 = "{% for today3 in today3 %}{{today3.temperature}}{% endfor %}"
  var today4 = "{% for today4 in today4 %}{{today4.temperature}}{% endfor %}"
  var today5 = "{% for today5 in today5 %}{{today5.temperature}}{% endfor %}"
```

#### hello1.html（時別ページ）

```html:hello1.html
{% extends "layout.html" %}
{% block content %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
<h1>Temperature-visualization</h1>
<nav aria-label="breadcrumb">
    <ol class="breadcrumb">
      <li class="breadcrumb-item"><a href="hello.html">週</a></li>
      <li class="breadcrumb-item"><a href="hello1.html">時</a></li>
      <!--<li class="breadcrumb-item"><a href="hello3.html">月</a></li>-->
    </ol>
  </nav>
  <h2>4時間の温度データ</h2>
  <canvas id="myLineChart"></canvas>
  <br>
  <br>
  <p>現在時刻が0時の場合には現在時刻のみが表示されます.</p>
    <center>

        <br>
        <pre>
    <b>
        <b>現在の気温</b>
        日付:{% for test5 in test5 %}{{ test5.day}}{% endfor %}
        時間：{% for test5 in test5 %}{{ test5.time}}{% endfor %}
        現在の温度{% for test5 in test5 %}{{ test5.temperature}}{% endfor %}°
        </pre>
    </b>
    </center>
  <script>
    var t1 = "{% for test1 in test1 %}{{test1.temperature}}{% endfor %}"
    var t2 = "{% for test2 in test2 %}{{test2.temperature}}{% endfor %}"
    var t3 = "{% for test3 in test3 %}{{test3.temperature}}{% endfor %}"
    var t4 = "{% for test4 in test4 %}{{test4.temperature}}{% endfor %}"
    var t = "{% for test5 in test5 %}{{test5.temperature}}{% endfor %}"
    var ctx = document.getElementById("myLineChart");
    var myLineChart = new Chart(ctx, {
      type: 'line',
      data: {
        labels: ['4時間前','3時間前', '2時間前', '１時間前','現在'],
        datasets: [
          {
            label: '温度',
            data: [t4, t3, t2, t1,t],
            borderColor: "rgba(0,0,255,1)",
            backgroundColor: "rgba(0,0,0,0)"
          }
        ],
      },
      options: {
        title: {
          display: true,
          text: '温度(時)'
        },
        scales: {
          yAxes: [{
            ticks: {
              suggestedMax: 40,
              suggestedMin: 0,
              stepSize: 10,
              callback: function(value, index, values){
                return  value +  '度'
              }
            }
          }]
        },
      }
    });
    </script>

{% endblock %}
```

#### layout.html

```html:layout.html
<html>
<head>
<title>{{ title }}</title>

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
<script src="http://guide.withabout.net/guide/gp332/459916/Chart.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
</head>
<body>

{% block content %}
{% endblock %}
</body>
</html>
```
グラフの可視化にはchart.jsを利用しています.この辺は皆さんでやる場合お好きなものをお使いになると良いのではないかなと思います.


## 画面UI
### 日別ページ
![スクリーンショット 2021-01-24 21.41.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567360/c1ad49bc-ae2f-7b92-6c48-c7c6a2fa64b1.png)
### 時別ページ
![スクリーンショット 2021-01-26 23.11.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567360/0c6c5a57-e61a-baea-331a-06f2ff01b911.png)


## 最後に
今回はスイッチボットさんの温度計を見て自分で自作してみました.手直しする部分は多いですが,案外実システムとwebシステムの融合って難しくないのでは？と感じられた方も多いのではないでしょうか.
皆さんも楽しいお家IoT開発をされてみてはいかがでしょうか.

一応コードはgithubにあげてありますのでそちらも確認いただけると幸いです.
>github
https://github.com/S-mishina/Temperature-visualization