Python
RaspberryPi
GoogleSpreadSheet
GoogleHome

ラズパイで温度・湿度・気圧をグーグルシートに保存してグーグルホームにしゃべらせた

Raspberry Pi Zero Wを手に40年ぶり(?)に電子工作にチャレンジしたので、その奮闘期(でもないけど)記します。いやぁー、ブレッドボードとか便利なものもあるんですね。

sensor201806.jpg

使ったもの

Raspberry Pi Zero W
Raspberry Pi キャラクタ液晶ディスプレイモジュール(秋月電子)
温湿度センサー モジュール AM2320
気圧センサー BMP180
タクトスイッチ

Google Home Mini

(BMP180は気温センサーもついていますが、温度・湿度はAM2320で取得しています。)

出来たこと

・室内の温度・湿度・気圧および6時間前との気圧変化イメージをリアルタイムに計測し、液晶ディスプレイに表示
・毎時の温度・湿度・気圧をGoogle スプレッドシートに自動保存
・タクトスイッチを押すと、現在の温度・湿度・気圧と6時間前の気圧変化をGoogle Home Miniが話してくれます

手順

液晶ディスプレイ、温湿度センサー、気圧センサーとも通信はI2Cですので、配線は非常に楽です。各センサーのSDA、SCL、VDD(3.3V)およびグランドを繋いでラズパイのおのおのの端子に接続するだけですね。また、タクトスイッチはVDD(3.3V)とGPIO24に繋いで、スイッチのオンオフをチェックしています。
また、今回はRaspberry Pi Zero Wを使ってますので、できるだけコンパクトに!ということで、他のPC(Windows10)からリモートで操作しています。

私が参考にしたサイトは以下のところです。

・Windowsからのリモート操作
Raspberry PiをWindowsPCから操作する方法 【CUI・GUI】

・I2Cセットアップ
I2Cを使う(設定編)

・AM2320導入
温湿度センサ AM2320を使う
AM2320でi2cdetectコマンドを投げたときに、アドレスが検出されないことがあります。何度かコマンドを繰り返していると値を返してくれるのですが、これで随分と悩んでしまいました。アドレスは0x5c
上からみて左1番ピンから
1番:VDD 2番:SDA 3番:GND 4番:SCL
です。

・BMP180導入
Raspberry Pi 2で気温・気圧・高度センサのBMP180を使う
この参照サイトにも書いてありますが、AdafruitからBMP用のPythonライブラリが提供されているのでそれを使います。アドレスは0x77
ピン配列はモジュールによって異なるかも。うちにあったやつは左1番ピンから
上からみて左1番ピンから
1番:NotConnect 2番:SDA 3番:SCL 4番:GND 5番:VDD
ですが、デバイスに記載があるので個々に確かめてください。

・液晶ディスプレイ導入
「カラー図解 最新 Raspberry Piで学ぶ電子工作」講談社を参考にさせていただいています。コードは以下のコードを参照してください。アドレスは0x3e
使用したのは秋月電子のキャラクタ液晶ディスプレイモジュールです。

左1番ピンから
1番:RST 2番:GND 3番:LED 4番:SCL 5番:SDA 6番:VDD
です。バックライト有りだと3番で制御できますが、私が購入したのはバックライト無しだったので、1番、3番は未接続です。

以上、各センサーのアドレスは以下のとおり


i2cdetect201806.gif

・Google スプレッドシートとの連携
Raspberry PiとIFTTTのMaker Webhooksを使って家事カウンターを作ってみた
IFTTT経由でGoogle スプレッドシートに書き込んでいきます。Google スプレッドシートは自宅ネットワーク以外から、例えばスマホからでも見ることができたり、家族とシートを共有したりできますので、とても便利です。グラフも見ることができますし。こんな風に。


googlespreadsheet.gif

・Google Home Miniとの連携
ラズパイZeroWから、Google Homeを喋らせる
連携するには、Google Home Miniに割り当てられているIPアドレスを知る必要があります。スマホに入っているGoogle Homeアプリのデバイス設定から確認できることになっていますが、今現在、バグで表示させることができません。(サポート談。)なので、私は
iNet - Network Scanner
というiPhoneアプリで確認しました。

コード

# -*- coding: utf-8 -*-
# AM2320-BMP085-LCD2320(AQM0802A)-Google sheet-Google Home

import smbus
import sys
import time
import requests
import os
import Adafruit_BMP.BMP085 as BMP085
import RPi.GPIO as GPIO
from datetime import datetime

i2c = smbus.SMBus(1) #2320
address = 0x5c #2320
sensor = BMP085.BMP085()

#Maker Webhooks
EVENT="LivingRoom Humidity-Temperature"
KEY="*****************"

#GPIOの初期化命令(トグルスイッチはラズパイのGPIO24に接続)
GPIO.setmode(GPIO.BCM)
GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

#液晶ディスプレイセッティング
address_aqm0802a = 0x3e
register_setting = 0x00
register_display = 0x40

chars_per_line = 8  #8x2行ディスプレイを使用
display_lines = 2
display_chars = chars_per_line*display_lines

position = 0
line = 0
disp="L******H"

#その他初期化
comment=""
hist=[1013,1013,1013,1013,1013,1013]
old_time = 99

#液晶ディスプレイ
def setup_aqm0802a(): 
    trials = 5
    for i in range(trials):
        try:
            i2c.write_i2c_block_data(address_aqm0802a, register_setting, [0x38, 0x39, 0x14, 0x70, 0x56, 0x6c])
            time.sleep(0.2)
            i2c.write_i2c_block_data(address_aqm0802a, register_setting, [0x38, 0x0d, 0x01])
            time.sleep(0.001)
            break
        except IOError:
            if i==trials-1:
                sys.exit()

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.write_byte_data(address_aqm0802a, register_setting, 0x01)
    time.sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.write_byte_data(address_aqm0802a, register_setting, 0xc0)
        time.sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.write_byte_data(address_aqm0802a, register_display, byte_data)
    position += 1

def check_writable(c):
    if c >= 0x20 and c <= 0x7d :
        return c
    else:
        return 0x20 # 空白文字

#Google Spread Sheetに書込み
def google_post(hum,tmp,prs):
    url = "https://maker.ifttt.com/trigger/" + EVENT + "/with/key/" + KEY
    data = { "value1": hum, "value2": tmp, "value3": prs }
    requests.post(url=url, data=data)

#Google Home miniに送信
def my_callback(channel):
    if channel == 24:
         os.system("node word0.js "+"現在の部屋の湿度は"+str(hum)+"%、温度は"+str(tmp)+"度、気圧は"+str(prs)+ "ヘクトパスカルです。"+comment)

def prs_check(hist5,hist0):
    global comment
    global disp

    if hist5 > hist0+9:
        comment="6時間で大きく気圧が上がっています"
        disp="L     >H"
    elif hist5 > hist0+4:
        comment="6時間でやや気圧が上がっています"
        disp="L    > H"
    elif hist5 > hist0+2:
        comment="6時間で少し気圧が上がっています"
        disp="L   >  H"
    elif hist5 < hist0-9:
        comment="6時間で大きく気圧が下がっています"
        disp="L<     H"
    elif hist5 < hist0-4:
        comment="6時間でやや気圧が下がっています"
        disp="L <    H"
    elif hist5 < hist0-2:
        comment="6時間で少し気圧が下がっています"
        disp="L  <   H"
    else:
        comment="気圧はほぼ安定しています"
        disp="L  ==  H"

#割り込み
GPIO.add_event_detect(24, GPIO.RISING, callback=my_callback, bouncetime=2000)

#液晶ディスプレイセッティング
setup_aqm0802a()


try:
    while True:
        try:
            i2c.write_i2c_block_data(address,0x00,[])
        except:
            pass
# 読み取り命令
        time.sleep(0.003)
        i2c.write_i2c_block_data(address,0x03,[0x00,0x04])

# データ受取(湿度・温度)
        time.sleep(0.015)
        block = i2c.read_i2c_block_data(address,0,6)
        hum = float(block[2] << 8 | block[3])/10
        tmp = float(block[4] << 8 | block[5])/10

        clear()
        s = "Hm:"+str(hum)+"%Tp:"+str(tmp)+"C"
        write_string(s)

        time.sleep(3) #3秒表示

# データ受取(気圧)
        prs = "{0:4s}".format(str(sensor.read_pressure()/100))
        s = " "+prs+"hPa"+disp
        write_string(s)

        time.sleep(2) #2秒表示

        now_time = datetime.now().strftime("%H") #今の「時」を取得
        if str(now_time) != str(old_time): #「時」が変わっていれば・・
            google_post(hum,tmp,prs) # Google Spreaed Sheetに書込み
            for i in range(0,5): #過去6時間分のデータを更新
                hist[i]=hist[i+1]
            hist[5]=int(prs)
            old_time = now_time
            prs_check(hist[5],hist[0]) #6時間前の気圧変化をチェック

except KeyboardInterrupt:
    pass

GPIO.cleanup()

個々のセンサーのセッティングや値のとり方は、上記のリンクを参照していただくとして、おのおのの連携の部分は以下のように行なっています。

・湿度、温度、気圧の測定
基本的に常時リアルタイムの値を測定、ディスプレイに表示しますので、whileのループで回しています。
ディスプレイは8x2行と小型なので、一度に全ては表示できない為、まず湿度・温度を表示後、3秒後に気圧とその6時間前との傾向を2秒表示して、湿度・温度表示に戻ります。
sensor2016-1.jpg
sensor2016-2.jpg

 #データ受取(湿度・温度)
        time.sleep(0.015)
        block = i2c.read_i2c_block_data(address,0,6)
        hum = float(block[2] << 8 | block[3])/10
        tmp = float(block[4] << 8 | block[5])/10

        clear()
        s = "Hm:"+str(hum)+"%Tp:"+str(tmp)+"C"
        write_string(s)

        time.sleep(3) #3秒表示

 #データ受取(気圧)
        prs = "{0:4s}".format(str(sensor.read_pressure()/100))
        s = " "+prs+"hPa"+disp
        write_string(s)

        time.sleep(2) #2秒表示

変数dispが6時間前との気圧傾向をディスプレイに表示する時のイメージです。

def prs_check(hist5,hist0)でGoogle Home miniにしゃべらせるワードと共に定義しています。一応、以下のように表現していますが、これがお天気予報士的に正しいかどうかは知りません(笑)

気圧変化が 表示(disp) しゃべるワード(comment)
9hpa > L >H 6時間で大きく気圧が上がっています
4hpa > L > H 6時間でやや気圧が上がっています
2hpa > L > H 6時間で少し気圧が上がっています
L == H 気圧はほぼ安定しています
2hpa < L < H 6時間で少し気圧が下がっています
4hpa < L < H 6時間でやや気圧が下がっています
9hpa < L< H 6時間で大きく気圧が下がっています

・Google Spread Sheetへの書込み
コードの最初の方にあるKEYはGoogle Spread Sheet作成時のキーを入力します。

#Maker Webhooks
EVENT="LivingRoom Humidity-Temperature"
KEY="*****************"

Google Spread Sheetへの書込みは毎時00分に書き込むようにしました。

        now_time = datetime.now().strftime("%H") #今の「時」を取得
        if str(now_time) != str(old_time): #「時」が変わっていれば・・
            google_post(hum,tmp,prs) # Google Spreaed Sheetに書込み

datetime.now().strftime("%H")で今の「時」を読み取り、今までの「時」と異なっていれば、Google Spread Sheetに湿度、温度、気圧を書き込みます。
それと同時に6時間前の気圧傾向をチェックし、disp変数やcomment変数の傾向を更新しています。

・タクトスイッチを押したときに、湿度・温度・気圧をGoogle Home miniにしゃべらせる
これに関しては、事前に上の参考サイトの記述にあるようにNode.js、npm、google-home-noifier等をインストールしておく必要があります。
タクトスイッチが押されるとGPIO24がHIGHになりますので、それを割り込み処理で飛ばします。

GPIO.add_event_detect(24, GPIO.RISING, callback=my_callback, bouncetime=2000)

my_callbackに飛ぶと、nodeを使って、しゃべらせたい言葉をGoogle Home miniに送ります。

def my_callback(channel):
    if channel == 24:
         os.system("node word0.js "+"現在の部屋の湿度は"+str(hum)+"%、温度は"+str(tmp)+"度、気圧は"+str(prs)+ "ヘクトパスカルです。"+comment)

ただし、タクトスイッチを押してから実際話し始めるには、「どっこいしょ」って感じで少しタイムラグがありますw

所感

まだ、動かし始めて1ヶ月くらいしか経っていませんが、天気と気圧の変化がリンクしているのがよくわかります。気圧下がる→雨が降る→湿度上がる、温度下がるみたいな。これを1年くらいデータ積上げていくと、家の環境がどう変化しているか新たな発見もあるかなと思ってます。