水耕栽培用にRaspberry Piで水温、室温、湿度、水位を計測してGoogleスプレッドシートにアップしてみた

#はじめに
<img src="https://qiita-image-store.s3.amazonaws.com/0/243160/c020db6d-1299-6c9c-489b-397626aaa818.jpeg" width=90%>

春も近づきそろそろ暖かくなってきました。種まきの季節だー、ということで
屋内水耕栽培にチャレンジしたく役立ちそうなものをラズパイで作ってみました。

##処理の流れ
1. ラズパイで水温、室温、湿度、水位を測定する。
2. 測定結果をSQliteでローカルDBに保存する。
3. 測定結果をWifiルーター経由でGoogleスプレッドシートにアップする。

GoogleスプレッドシートにあげちゃえばスマホでもPCでも何処からでも簡単にアクセスできます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/243160/ce9f7268-5d66-499a-0351-f3ce058e40a5.jpeg" width=25%>

アップロードされたデータをGoogleスプレッドシートでグラフにして可視化できるようにしました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/243160/15f25ca0-7a25-2ce3-dd12-77463cd96f04.jpeg" width=50%>

機器はこんな感じ
<img src="https://qiita-image-store.s3.amazonaws.com/0/243160/ec7bb598-1d7e-dec1-e3ff-be0e0dff39ce.jpeg" width=50%>

<img src="https://qiita-image-store.s3.amazonaws.com/0/243160/432cde1f-3ce1-ddbd-f661-ca90ff257c26.jpeg" width=50%>

##参考にさせて頂いたURL
他の方のラズパイの事例を参考に見様見真似で繋げました。
>[温度/湿度センサーDHT22をRaspberry Piで使用する方法](http://blog.livedoor.jp/victory7com/archives/48343379.html)
[Raspberry Piで1-Wireデジタル温度センサのDS18B20をPythonから使う](https://qiita.com/masato/items/cf5a27af696a27b73b86)
[ラズパイで気圧データをGoogleスプレッドシートに上げる方法](http://workpiles.com/2017/04/raspi-spreadsheet-api/)
[Raspberry PiでSQLiteに温度データを蓄えてみよう！](https://yuki-no-yabo.com/raspberry-pi-sqlite/)
[【ラズベリーパイ3】Pythonで超音波距離センサ(HC-SR04)の精度向上（気温考慮）](https://algorithm.joho.info/programming/python/raspberry3-ultrasonic-distance-sensor-hcsr04-temp/)

#環境(OS,Python)　
- Raspbian 9.3
- Python 2.7.13
 
![fig2.jpg](https://qiita-image-store.s3.amazonaws.com/0/243160/8368da0e-9c73-8500-4b2d-b9d63ba098d3.jpeg)

##Raspberry Piモデル
- Raspberry Pi2 Model B

##使用したセンサー
- 水温測定　 : 1-Wireステンレス防水温度センサ(DS18B20)
- 室温、湿度 : 温湿度センサーモジュール(DHT22)
- 水位測定　 : 超音波距離センサーモジュール(US-015)

水位の測り方は事前にパラメータで「容器の高さ」をセットして、距離センサ「US-015」で水面までの距離を測定し「水位＝容器高さ-測定値」で計算する方法をとりました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/243160/ddce9cb2-14d5-ee18-ba59-6e2115f326f1.jpeg" width=50%>

##配線図
超音波距離センサーの絵がHC-SR04なのはfrizingにUS-015が見当たらなかった為......
下記のPin位置に接続しました。

- #02 DCPower 5V
- #04 GND
- #11 GPIO17 US-015 TRIG
- #13 GPIO27 US-015 ECHO
- #32 GPIO12 DS18B20
- #36 GPIO16 DHT22

<img src="https://qiita-image-store.s3.amazonaws.com/0/243160/3814b5ce-4201-bebb-e57f-c0829fad3e6d.jpeg" width=50%>

#環境構築
センサーに必要なモジュールをgitから入手してインストールします。
自分は「<b>python_apps</b>」というフォルダを作ってそこに放り込みました。

```
$ cd
$ mkdir python_apps
$ cd ~/python_apps
```

###DHT22用にAdafruitのDHTライブラリをインストール
```
$ sudo git clone https://github.com/adafruit/Adafruit_Python_DHT.git
$ cd Adafruit_Python_DHT
$ sudo python setup.py install
```

参考サイト:[温度/湿度センサーDHT22をRaspberry Piで使用する方法](http://blog.livedoor.jp/victory7com/archives/48343379.html)

###DS18B20用にW1ThermSensorをインストール
```
$ sudo git clone https://github.com/timofurrer/w1thermsensor.git 
$ cd w1thermsensor
$ sudo python setup.py install
```
/boot/config.txtを修正して再起動
12はセンサーの繋がっているGPIOの番号

```tex:boot/confog.txt
#(修正前)
dtoverlay=w1-gpio
↓
#(修正後)
dtoverlay=w1-gpio,gpiopin=12
```

参考サイト:[Raspberry Piで1-Wireデジタル温度センサのDS18B20をPythonから使う](https://qiita.com/masato/items/cf5a27af696a27b73b86)


###GoogleスプレッドシートにアクセスできるようにAPI登録
参考サイト:[ラズパイで気圧データをGoogleスプレッドシートに上げる方法](http://workpiles.com/2017/04/raspi-spreadsheet-api/)の手順に沿ってAPIを有効化します。

1.Google API ConsoleでSheets APIを有効にする
 　　[Google API コンソール API登録ウィザード](https://accounts.google.com/ServiceLogin?service=cloudconsole&ltmpl=api&osid=1&passive=true&continue=https://console.developers.google.com/start/api?id%3Dsheets.googleapis.com)からAPIの登録
 　　新規プロジェクト「hydroponics」を作成してAPIを有効化

2.認証情報を作成する
 　　「<b>サービスアカウントID</b>」を控えます。
 　　「<b>認証用JSONファイル</b>」をダウンロードしてラズパイに配置します。
3.アップ先のスプレッドシートを作成
　　　シート名を「<b>sheet1</b>」で作成
　　　シートを作成したら共有ボタンを押して共有します。
　　　その際、上記「<b>サービスアカウントID</b>」を入力します。
　　　シートを表示した際のURLの「https：//docs.google.com/spreadsheets/<b>XXXXXXXXXXXXXX</b>」
　　　このXX部分が「<b>シートID</b>」です。後でプログラムに記述するので控えます。

4.Google Client Libraryをインストール

```
$ sudo pip install --upgrade google-api-python-client
```


#実行プログラム
プログラムを4本用意しました。
python_apps直下に「<b>hydroponics</b>」というフォルダを作ってそこに放り込みました。
ライブラリの配置も含めるとこんな感じ。
/home/pi/
　├ pyhton_apps/
　│　├ Adafruit_Python_DHT/
　　　├ hydroponics/
　　　│　　├<b>hydroponics.py</b>　←メイン処理
　　　│　　├<b>spreadsheet.py</b>　←スプレッドシート書込
　　　│　　├<b>sqlite_connect.py</b>　←SQLlite書込
　　　│　　├<b>us_015.py</b>　←US-015制御
　　　│　　└<b>認証用JSONファイル</b>(例:hoge-xxxxxx.json)
　　　└w1thermsensor

##ポイント

hydroponics.pyに設定情報を記述しています。実行環境ごとに書換えが必要です。

- 認証JSONファイル名
- シートID
- 容器の高さ
- DHT22接続先GPIOポート
- US-015接続先GPIOポート

##ソースコード

```Python:hyponics.py
# -*- coding: utf-8 -*-
import sys
import datetime
import time
import RPi.GPIO as GPIO
import sqlite3
sys.path.append('/home/pi/python_apps/')
from w1thermsensor import W1ThermSensor
from spredsheet import SpreadSheet
import Adafruit_DHT as DHT
from us_015 import US_015
from sqlite_connect import SqliteConnect

#-----------------------------------
#DS18B20で水温を計測する
#-----------------------------------
def DS18B20_result():
    sensor = W1ThermSensor()
    return sensor.get_temperature()

#-----------------------------------
#DHT22で室温、湿度を計測する
#DHT_GPIO 接続したGPIOポート
#-----------------------------------
def DHT22_result(DHT_GPIO):
    ## センサーの種類
    SENSOR_TYPE = DHT.DHT22

    ## 測定開始
    h,t = DHT.read_retry(SENSOR_TYPE, DHT_GPIO)
    return [round(t,4),round(h,4)]

#-----------------------------------
#US-015で水位を計測する 
# 水位＝容器高さ - 水面までの距離
# pPinTrig,pPinEcho GPIOポート
# pHight 容器高さ(cm)
# pTemp 室温
#-----------------------------------
def us_015_result(pPinTrig,pPinEcho,pHight,pTemp):
    instance = US_015(pPinTrig,pPinEcho)
    d = instance.result(pTemp)
    l = pHight - d
    return l

#-----------------------------------
#SQLiteへt0:日時,t1:水温、t2:室温、h:湿度 l:水位を書き込む 
#-----------------------------------
def sqlite_insert(t0,t1,t2,h,l):
    instance = SqliteConnect();
    instance.insert(t0,t1,t2,h,l)

#-----------------------------------
#TEST
#-----------------------------------
def test(t0,t1,t2,h,l):
    print("日付："+str(t0))
    print("水温：{:.4}".format(t1)+"°")
    print("気温：{:.4}".format(t2)+"°")
    print("湿度：{:.4}".format( h)+"%")
    print("水位：{:.4}".format( l)+"cm")

#-----------------------------------
#Google スプレッドシートへレコード追加
#-----------------------------------
def spreadSheet_insert(t0,t1,t2,h,l):
    KEY_FILENAME ='/home/pi/python_apps/hydroponics/【認証用JSONファイル名】'
    SHEET_ID ='【シートＩＤ】'
    APPEND_RANGE = 'Sheet1!A1:D1'
    APPEND_LENGTH = 5
   
    sheet = SpreadSheet(KEY_FILENAME,SHEET_ID,APPEND_RANGE,APPEND_LENGTH)
    sheet.append(["{0:%Y-%m-%d %H:%M:%S}".format(t0), t1, t2,h,l])

#-----------------------------------
#メイン処理
#-----------------------------------
def main():
    Hight =22.13 #容器の高さ(cm)
    GPIO_TRIG = 17 #US-015
    GPIO_ECHO = 27 #US-015

    GPIO_TEMP = 16 #DHT22

    #t0:日時,t1:水温、t2:室温、h:湿度 l:水位
    t0 = datetime.datetime.now()
    t1=0.0
    t2=0.0
    h=0.0
    l=0.0

    #水温計測
    t1 = DS18B20_result()

    #室温、湿度計測
    DHT22_array = DHT22_result(GPIO_TEMP)
    if DHT22_array is not None:
       t2 = DHT22_array[0]
       h  = DHT22_array[1]

    #水位計測
    l = us_015_result(GPIO_TRIG,GPIO_ECHO,Hight,t2)

    test(t0,t1,t2,h,l)

    #ローカルDBへ書き込み
    sqlite_insert(t0,t1,t2,h,l)

    #GoogleSpredSheetへ書き込み
    spreadSheet_insert(t0,t1,t2,h,l)

    #処理終了
    sys.exit()

#-----------------------------------
#実行ここから
#-----------------------------------
if __name__ == '__main__':
   main()
```

```Python:spreadseet.py
# -*- coding: utf-8 -*-
import httplib2
import numpy as np

from apiclient import discovery
from oauth2client.service_account import ServiceAccountCredentials

SCOPES = 'https://www.googleapis.com/auth/spreadsheets'

class SpreadSheet(object):
  def __init__(self, key_filename,sheet_id,range,length):
    self.sheetId       = sheet_id
    self.key_filename  = key_filename
    self.append_range  = range
    self.append_length = length 

    credentials = ServiceAccountCredentials.from_json_keyfile_name(key_filename, scopes=SCOPES)
    http_auth = credentials.authorize(httplib2.Http())
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?''version=v4')
    self.service = discovery.build('sheets', 'v4', http=http_auth, discoveryServiceUrl=discoveryUrl)

  def append(self, values):
    assert np.array(values).shape==(self.append_length,) , "The shape of value %s must be %s" % (np.array(values).shape,self.append_length)

    value_range_body = {'values':[values]}
    result = self.service.spreadsheets().values().append(spreadsheetId=self.sheetId, range=self.append_range, valueInputOption='USER_ENTERED', body=value_range_body).execute()
    #print(result)

if __name__ == '__main__':
  sheet = SpreadSheet("シートID")
  sheet.append(["test", "test", 0,1])
```

```python:sqlite_connect.py
# -*- coding: utf-8 -*-
import sqlite3

#-----------------------------------
#SQLite操作クラス 
#-----------------------------------
class SqliteConnect(object):

    #-----------------------------------
    #SQLiteへt0:日時,t1:水温、t2:室温、h:湿度 l:水位を書き込む 
    #-----------------------------------
    def insert(self,t0,t1,t2,h,l):
          # SQLiteのデータベースの名前と保管場所を指定。
          dbname = '/home/pi/python_apps/hydroponics/hydroponics_db.db'

          # データベース内のテーブルの名前
          dbtable = 'hydroponics'

          # SQLiteへの接続
          conn = sqlite3.connect(dbname)
          c = conn.cursor()

          # SQLiteにテーブルがあるかどうか確認するクエリ
          checkdb = c.execute("SELECT * FROM sqlite_master WHERE type='table' and name='%s'" % dbtable)
          # もしテーブルがなかったら新規でテーブルを作成する
          if checkdb.fetchone() == None:
              # クエリを実行
              c.execute("create table  %s(time default CURRENT_TIMESTAMP,water REAL,room REAL,humidity REAL,level REAL)" % dbtable)
              # 変更を保存する
              conn.commit()

          # 温度、湿度、タイムスタンプを保存。
          sql = 'insert into hydroponics (time, water, room,humidity,level) values (?,?,?,?,?)'
          data= (t0,t1,t2,h,l)
          c.execute(sql, data)
          conn.commit()

          #接続を切る
          conn.close()

if __name__ == '__main__':
     print(__name__)
```


```Python:us_015.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO
import time
import math

#-----------------------------------
#US-015 初期設定 TRIGとECHOのGPIOピン位置指定
#-----------------------------------
class US_015(object):
  def __init__(self, gpio_trig,gpio_echo):
      self.trig = gpio_trig
      self.echo = gpio_echo
      
  #-----------------------------------
  # HIGH or LOWの時計測
  #-----------------------------------
  def pulseIn(self,PIN, start=1, end=0):
      if start==0: end = 1
      t_start = 0
      t_end = 0
      # ECHO_PINがHIGHである時間を計測
      while GPIO.input(PIN) == end:
          t_start = time.time()
          
      while GPIO.input(PIN) == start:
          t_end = time.time()
      return t_end - t_start

  #-----------------------------------
  # 距離計測
  #-----------------------------------
  def calc_distance(self,TRIG_PIN, ECHO_PIN, num, v=34000): 
      list = None
      for i in range(num):
          # TRIGピンを0.3[s]だけLOW
          GPIO.output(TRIG_PIN, GPIO.LOW)
          time.sleep(0.3)
          # TRIGピンを0.00001[s]だけ出力(超音波発射)        
          GPIO.output(TRIG_PIN, True)
          time.sleep(0.00001)
          GPIO.output(TRIG_PIN, False)
          # HIGHの時間計測
          t = self.pulseIn(ECHO_PIN)
          # 距離[cm] = 音速[cm/s] * 時間[s]/2
          d = v * t/2
          distance = math.floor(d *100) /100
          #print(distance, "cm")
          if distance > 0 and distance < 1000:
             break
      # ピン設定解除
      GPIO.cleanup()
      return distance

  #-----------------------------------
  # 距離計測
  #-----------------------------------
  def result(self,temperature):
  
          # 音速[cm/s]
          v = 33150 + 60*temperature
              
          # ピン番号をGPIOで指定
          GPIO.setwarnings(False)
          GPIO.setmode(GPIO.BCM)
          # TRIG_PINを出力, ECHO_PINを入力
          GPIO.setup(self.trig,GPIO.OUT)
          GPIO.setup(self.echo,GPIO.IN)
          GPIO.setwarnings(False)
      
          # 距離計測(TRIGピン番号, ECHO_PIN番号, 計測回数, 音速[cm/s])
          d = self.calc_distance(self.trig, self.echo, 10, v)
          return d
          
if __name__ == '__main__':
      TRIG = 17
      ECHO = 27
      us015 = US_015(TRIG,ECHO)
      print us015.result(20)
```

#実行方法
手動で実行するとこんな感じ

```
$ cd python_apps/hydroponics/
$ python hydroponics.py
```

![fig4.jpg](https://qiita-image-store.s3.amazonaws.com/0/243160/dc92fdd9-35f0-38ac-ee13-4f9377216e40.jpeg)


実際はCrontab に登録して定期実行させました。
10分ごとに実行する設定を登録しました。

```tex:crontab
*/10 * * * * python /home/pi/python_apps/hydroponics/hydroponics.py
```

最初Cronで実行できずはまった。
ファイル名の絶対パスへ書換と実行時パス追加して動くようになりました。

#課題点
<b>水位に誤差がでる</b>
計測してみると水位の値が実際より約1cm~2cm低く表示されます。
　→ズレの量一定ぽい。ある程度計測して傾向掴めたら手動で補正する？(容器高さに加算する)
<b>水位が安定しない</b>
変動していない筈なのですが計測するたびに水位が0.5cm~1cm上下します。
狭い容器の反響なのか液体を測定しているからなのか原因不明。
水がない時は割と正確なので液体の測定の場合別途設定が必要？

#まとめ
おかしい......水耕栽培しようと思ったら野菜よりセンサーがいっぱい生えてきた。