現在の室温をtweetしよう

連休に自宅サーバをこれまで愛用していたHP社製microserverからRaspberry Pi3へリプレイスしました。microserverでは地味にRAID1+0環境にしてたりして安心確実ストレージとなっているのですが、日常からHDDを4本も回してなくていいんじゃ?と言う事が事はじめです。とは言えストレージサーバとしてはとても信頼おける状態となっていますので、時折WakeOnLanで起こしてはバックアップ先として活躍して頂く予定で、普段使いはRaspberry Pi3につないだシングルのHDDでも問題ないよね、という感じです。

さて普通にLinux環境が動くRaspberry Piですのでサーバ構築はそれこそ鼻歌交じりで完了します。何も躓くところはありません。そしてコンパクトになり各種センサーを接続するGPIOが接続しやすく、となると色々センサー繋ぎたくなりますよね。

我が家ではCITIZENの電波時計をリビングや寝室に置いてあり、これには温湿度計が付いています。脱衣所や窓辺にはそれぞれ脱衣所の室温や外気温(屋根裏にも設置してあります)を測るための温度計を設置してありますが、それぞれ独立した温度計でその時々の温度しか知る事が出来ませんしその場に行かねばわかりません。

じゃあRaspberry Piを設置するリビング位Raspberry Piに計測させて遠隔でも見れるようにしたらいいよね。と言う事でこの連休に色々パーツを買いそろえて表題の通り室温をTweetするまで作りました。

さて、こんな事は実はすでに先人が多く通った道なのでGoogleで検索するといっぱい記事が見つかります。「Raspberry Pi BME280」あたりで検索してみてください。BME280は何かというと今回用いた温湿度センサーで1000円ちょっとする部品ですが、1チップで温度も湿度も気圧も測れてお得でおススメです。

今回Raspberry Pi以外に用意したものとしては

  • AE-BME280 (秋月電子製温湿度気圧センサー)
  • ブレッドボード(試作に大活躍)
  • ジャンパーピン(オス-メス)
  • ジャンパーピン(オス-オス)
  • 十字配線ユニバーサル基板

こんな感じです。その他にもLIS3DHと言う3軸加速度センサーも一緒に買って回路に組んでますが、今回の室温をTweetするのには関係ない部分なので割愛。

さて、回路を組んで試験する処までは こちらのページ がとても参考になります。むしろこちらのページだけで十分でこんなエントリー要らないくらい!

Raspberry Piでの準備

Raspberry Piに入れているOSにより準備が異なってきます。Raspbianであれば

$ sudo raspi-config

これで設定画面が開きますのでAdvanced Optionを選びます。

Advanced Optionの中にI2Cの設定がありますので、選択し有効化。これでI2Cが使えるようになります。

またその他としてI2Cを扱うツールをインストールする必要があります。

$ sudo apt-get -y install i2c-tools python-smbus

これでまずは温度を取得するのに必要なツールがインストールされました。ここで一度再起動しておきましょう。

I2C通信の仕組みを知る

アナログセンサーを接続してアナログデータを取ったりした事はありますが、I2Cを触るのは初めてでした。センサー単体で動かしてよしよし、って言うページはとても多く見つかるんですが欲張りにセンサー2つ以上繋ぎたい場合は?となるとなかなかページ見つかりません。仕方ないので I2Cについて調べたら なんの事はない、バス接続で良いと知りました。という訳で2つのセンサーを繋いでみよう、とこんな回路にして接続してみました。AE-BME280をI2C通信として用いるためにはJ3と書かれたジャンパを半田でショートさせる必要があります。ちなみにLIS3DHの場合は裏面のABC3つのジャンパをショートさせます。これらはそれぞれ添付のマニュアルにちゃんと記載がありますのでちゃんと確認しましょう。

なお今回両方のセンサーを繋ぐため以下のようになってますが、今回のお題である室温をtweetするだけであれば右側のセンサーは不要です。

センサー側の接続が済んだらRaspberry Piに接続しますがI2Cはホットプラグ可能ですので電源入れたままで繋いで問題ありません。接続端子間違えないようにしましょうね。例えば今回のBME280もLIS3DHも電源電圧は3.3Vですのでうっかり5Vの端子に繋いじゃったりすると壊れてしまう可能性もゼロではありません。

接続したらi2cdetectコマンドを用いてraspberry piで接続を確認してみると

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- 18 -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

ばっちり認識されました。0x76の方が今回用いる温度センサーで0x18の方が今回用いない加速度センサーです。と言う事でI2Cに対応しているセンサーであれば測りたいセンサーをこんな感じでどんどん繋いで行ける!って事ですね。便利!

温度データを取得する

さて、I2Cデバイスとして認識するところまで来ました。いよいよここからデータを取りだします。本当はデータシートを読んで頑張らなくては本来いけない所なんでしょうけれども、 SWITCH SCIENCEさんが用意して下さっているスクリプト をまずは使って動作確認します。

$ wget https://raw.githubusercontent.com/SWITCHSCIENCE/BME280/master/Python27/bme280_sample.py
$ sudo python bme280_sample.py
temp : 28.97  ℃
pressure : 1017.31 hPa
hum :  34.95 %

このままツイートさせてもいいのですが加工したいのでスクリプトを修正

#!/usr/bin/python
#coding: utf-8

import smbus
import time

import datetime
import locale

d = datetime.datetime.today()

bus_number  = 1
i2c_address = 0x76

bus = smbus.SMBus(bus_number)

digT = []
digP = []
digH = []

t_fine = 0.0
yymmdd = 0
hhmm = 0
temperature = 0
humidity = 0
pressure = 0

def writeReg(reg_address, data):
        bus.write_byte_data(i2c_address,reg_address,data)

def get_calib_param():
        calib = []

        for i in range (0x88,0x88+24):
                calib.append(bus.read_byte_data(i2c_address,i))
        calib.append(bus.read_byte_data(i2c_address,0xA1))
        for i in range (0xE1,0xE1+7):
                calib.append(bus.read_byte_data(i2c_address,i))

        digT.append((calib[1] << 8) | calib[0])
        digT.append((calib[3] << 8) | calib[2])
        digT.append((calib[5] << 8) | calib[4])
        digP.append((calib[7] << 8) | calib[6])
        digP.append((calib[9] << 8) | calib[8])
        digP.append((calib[11]<< 8) | calib[10])
        digP.append((calib[13]<< 8) | calib[12])
        digP.append((calib[15]<< 8) | calib[14])
        digP.append((calib[17]<< 8) | calib[16])
        digP.append((calib[19]<< 8) | calib[18])
        digP.append((calib[21]<< 8) | calib[20])
        digP.append((calib[23]<< 8) | calib[22])
        digH.append( calib[24] )
        digH.append((calib[26]<< 8) | calib[25])
        digH.append( calib[27] )
        digH.append((calib[28]<< 4) | (0x0F & calib[29]))
        digH.append((calib[30]<< 4) | ((calib[29] >> 4) & 0x0F))
        digH.append( calib[31] )

        for i in range(1,2):
                if digT[i] & 0x8000:
                        digT[i] = (-digT[i] ^ 0xFFFF) + 1

        for i in range(1,8):
                if digP[i] & 0x8000:
                        digP[i] = (-digP[i] ^ 0xFFFF) + 1

        for i in range(0,6):
                if digH[i] & 0x8000:
                        digH[i] = (-digH[i] ^ 0xFFFF) + 1

def readData():
        data = []
        for i in range (0xF7, 0xF7+8):
                data.append(bus.read_byte_data(i2c_address,i))
        pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
        temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
        hum_raw  = (data[6] << 8)  |  data[7]

        compensate_T(temp_raw)
        compensate_P(pres_raw)
        compensate_H(hum_raw)

def compensate_P(adc_P):
        global  t_fine
        pressure = 0.0

        v1 = (t_fine / 2.0) - 64000.0
        v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5]
        v2 = v2 + ((v1 * digP[4]) * 2.0)
        v2 = (v2 / 4.0) + (digP[3] * 65536.0)
        v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1] * v1) / 2.0)) / 262144
        v1 = ((32768 + v1) * digP[0]) / 32768

        if v1 == 0:
                return 0
        pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125
        if pressure < 0x80000000:
                pressure = (pressure * 2.0) / v1
        else:
                pressure = (pressure / v1) * 2
        v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
        v2 = ((pressure / 4.0) * digP[7]) / 8192.0
        pressure = pressure + ((v1 + v2 + digP[6]) / 16.0)
        global press
        press = pressure/100

def compensate_T(adc_T):
        global t_fine
        v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
        v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2]
        t_fine = v1 + v2
        temperature = t_fine / 5120.0
        global temp
        temp = temperature -4

def compensate_H(adc_H):
        global t_fine
        var_h = t_fine - 76800.0
        if var_h != 0:
                var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) * (digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h)))
        else:
                return 0
        var_h = var_h * (1.0 - digH[0] * var_h / 524288.0)
        if var_h > 100.0:
                var_h = 100.0
        elif var_h < 0.0:
                var_h = 0.0
        global hum
        hum = var_h

def setup():
        osrs_t = 1                      #Temperature oversampling x 1
        osrs_p = 1                      #Pressure oversampling x 1
        osrs_h = 1                      #Humidity oversampling x 1
        mode   = 3                      #Normal mode
        t_sb   = 5                      #Tstandby 1000ms
        filter = 0                      #Filter off
        spi3w_en = 0                    #3-wire SPI Disable

        ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode
        config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en
        ctrl_hum_reg  = osrs_h

        writeReg(0xF2,ctrl_hum_reg)
        writeReg(0xF4,ctrl_meas_reg)
        writeReg(0xF5,config_reg)

setup()
get_calib_param()
if __name__ == '__main__':
        try:
                readData()
        except KeyboardInterrupt:
                pass

        print d.strftime("%Y%m%d,%H:%M"),",%2.2f,%4.2f,%6.2f" % (temp, hum,press)

これで扱いやすいデータになりました。 所で実はここで校正データを入れてあります。114行目に"temp = temperature -4"とあり、ここの最後で値から4度引いています。これは何かというと組んだ回路で測定して誤差も少ない素晴らしいセンサーを使っているのですが、例えば近くにRaspberry Piと言う熱源がありますよね。ですのでここで表示される値が必ずしも我々の体感する室温か?というとたぶん高めに表示されると思います。ですのでいくつか温度計を用意して校正すると良いかと思います。

$ sudo python room_tweet.py
20160506,14:03 ,25.12,36.98,1017.93

いよいよtweetする部分

Raspberry Piからscriptでtweetするのには ttytter と言うのが流行っぽいです。でもリンク先を見に行くとTTYtter is dead.とか書いてますね(汗)

ttytterの使い方は こちらのページが詳しい です。

インストールしてまずは認証画面を開きます。 途中"Press RETURN/ENTER to continue “と言われるのでEnterを押しましょう。

$ sudo apt-get -y install ttytter
$ ttytter -ssl

すると認証用のURLが出てきますのでブラウザでその認証URLを開き、アプリケーション認証。出てくるPIN番号をまたTerminalに返してあげてください。

tweetするには

$ ttytter -ssl -status="tweet内容"

となりますので先ほどの温度取得pythonスクリプトで生成してあげればいいのですが他用途でもこの温度取得scriptは使っていますので(グラフを作成している)、別途簡単なshellscriptで先ほどのpythonスクリプトを叩いてtweetしています。

#!/bin/sh
export LANG=ja_JP.utf8
export LANGUAGE=japanese

 text=`sudo /path/room_tweet.py`
 time_value=`echo $text | awk -F, '{print $2}'`
 temp_value=`echo $text | awk -F, '{print $3}'`
 humidity_value=`echo $text | awk -F, '{print $4}'`
 hpa_value=`echo $text | awk -F, '{print $5}'`
 tweet="${time_value} 現在の室温は ${temp_value} 度、湿度は ${humidity_value} %、気圧は ${hpa_value} hPaです。"
 ttytter -ssl -status="${tweet}"

上記の通りsudoをscriptより実行していますのでroot_tweet.pyの実行はsudoersファイルを編集してNOPASSで実行できないと定期的に実行してくれません。あとはこれをcronで回すだけ! 気温測定のScriptとTweet部を別にしておく事でttytterが使えなくなったときの修正も楽ですし、特定の室温を超えたときだけtweetなんて条件文もちょちょいと書けていいかな。

こんな感じで定期的に室温をtweetしています。フォロワーさんの迷惑にならないように頻度は考えましょう。