ラズパイで温度湿度気圧を測る

#モチベーション
センサを使った何かをやってみたかったから、温度・湿度・気圧あたりでも計測してみるか。

#使用したもの
・RaspberryPiZeroW
・BME280センサモジュール(ピンヘッダ実装済み)

#仕様
・5分に1回、温度・湿度・気圧を測ってcsvで直近50回のデータを保存しておく。
・csvからグラフを描画してpng画像として保存しておく。
・png画像をWebサーバにあげてどこからでも確認できるようにする。
・うろ覚えだけど、たしかI2Cを有効にする設定をしないといけなかった気がするので、その辺は各自調べてくださいね。

#事前準備
I2Cの有効化とPythonでI2Cを扱うための準備をするよ。

##I2C有効化
```:I2C有効化
pi@raspberrypi:~ $sudo raspi-config
```

![2019-09-08 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216625/5954c41d-4bdd-82b3-787f-6a11275f6ded.png)
「5」を選択

![2019-09-08 (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216625/3ca52333-b851-2ebb-c3d1-6bd172f34a89.png)
「P5」を選択

![2019-09-08 (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216625/6944d57b-474d-10d8-4892-a1fdcd6cca28.png)
「Yes」を選択

![2019-09-08 (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216625/32577ed4-1d9e-a105-acf9-95d8e072005d.png)
「Ok」で完了


##必要なモジュールのダウンロード
```:2つダウンロード
pi@raspberrypi:~ $sudo apt-get install i2c-tools
pi@raspberrypi:~ $sudo apt-get install python-smbus
```

準備終了。

#ブレッドボード
![bme280_ブレッドボード.png](https://qiita-image-store.s3.amazonaws.com/0/216625/7b6d10a8-3113-6511-46df-bfa43bd702dc.png)

#回路図
![bme280_回路図.png](https://qiita-image-store.s3.amazonaws.com/0/216625/ad931f7e-4309-5fff-b45e-cb0f15cd0b45.png)

#ソースコード(計測・csv保存)
参考にリンクを貼っておいたけど、スイッチサイエンスさんがサンプルコードを公開しているのでそれに付け足していく。
直近50回分(5分*50回=250分前まで)の計測データだけcsvでそれぞれ保存してる。
形式は「年,月,日,時,分,計測値\n」
csvの保存先は/var/www/html/

```python:bme280_sample.py

#Save the temperature, humidity, and atmospheric pressure obtained from bme280 in csv
#csv format:
#year,month,date,hour,min,val
#coding: utf-8
#2017/09/23
#2017/10/26

from smbus import SMBus
import time
import csv
import datetime

bus_number  = 1
i2c_address = 0x76
MAX_DATA=50

bus = SMBus(bus_number)

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

t_fine = 0.0

d=datetime.datetime.today()

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
	ary_pressure=["0,0,0,0,0,0\n" for i in range(MAX_DATA)]
	
	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)  

	print("pressure : %7.2f hPa" % (pressure/100))

	fp=open("/var/www/html/pressure.csv","rb")
	line=fp.readline()
	while line:
		ary_pressure.pop(0)
		ary_pressure.append(line)
		line=fp.readline()
	ary_pressure.pop(0)
	ary_pressure.append(str(d.year)+","+str(d.month)+","+str(d.day)+","+str(d.hour)+","+str(d.minute)+","+str(pressure/100)+"\n")
	fp.close()
	
	fp=open("/var/www/html/pressure.csv","w")
	fp.writelines(ary_pressure)
	fp.close()

def compensate_T(adc_T):
	global t_fine
	ary_temp=["0,0,0,0,0,0\n" for i in range(MAX_DATA)]
	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
	print("temp : %-6.2f " % (temperature))

	fp=open("/var/www/html/temp.csv","rb")
	line=fp.readline()
	while line:
		ary_temp.pop(0)
		ary_temp.append(line)
		line=fp.readline()
	ary_temp.pop(0)
	ary_temp.append(str(d.year)+","+str(d.month)+","+str(d.day)+","+str(d.hour)+","+str(d.minute)+","+str(temperature)+"\n")
	fp.close()
	
	fp=open("/var/www/html/temp.csv","w")
	fp.writelines(ary_temp)
	fp.close()

def compensate_H(adc_H):
	global t_fine
	ary_hum=["0,0,0,0,0,0\n" for i in range(MAX_DATA)]
	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
	print("hum : %6.2f %%" % (var_h))
	
	fp=open("/var/www/html/hum.csv","rb")
	line=fp.readline()
	while line:
		ary_hum.pop(0)
		ary_hum.append(line)
		line=fp.readline()
	ary_hum.pop(0)
	ary_hum.append(str(d.year)+","+str(d.month)+","+str(d.day)+","+str(d.hour)+","+str(d.minute)+","+str(var_h)+"\n")
	fp.close()
	
	fp=open("/var/www/html/hum.csv","w")
	fp.writelines(ary_hum)
	fp.close()

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

```

#ソースコード(png画像生成・サーバに送信)
FTPサーバにpng画像あげて、自宅にいなくても自宅の温度とかがわかる。
温度が低すぎたり、湿度が不快だったらLINEにメッセージを飛ばす。
(LINEにメッセージを飛ばすことについては解説しません。)
こちらも生成した画像は/var/www/html/に保存。
グラフの描画にはmatplotlibを使ってる。

###修正箇所
・ラベルが見切れてしまう問題を修正しました。
・気圧があまり変化しなかったとき、y軸が指数表記になってしまうことがあったのを最近修正しました。

```python:env_graph.py
#Graph the temperature, humidity, pressure and upload it to the FTP server.
#coding: utf-8
#2017/09/23
#2017/10/04
#2017/10/26

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import csv
from pylab import *
from ftplib import FTP
import re
import requests
import os

from linebot import LineBotApi
from linebot.models import TextSendMessage

MAX_RANGE=50
URL_PATH="/var/www/html/"

def drawingTemp():
    
    temp=[0 for i in range(MAX_RANGE)]
    date=[0 for i in range(MAX_RANGE)]
    span=[]

    for i in range(MAX_RANGE):
        span.append(1000/MAX_RANGE*i)

    fp=open(URL_PATH+"temp.csv","rb")
    reader=csv.reader(fp)
    for row in reader:
        temp.pop(0)
        temp.append(float(row[5]))
        date.pop(0)
        #month/date_hour:minute
        date.append("%02d"%int(row[1])+"/"+"%02d"%int(row[2])+"_"+"%02d"%int(row[3])+":"+"%02d"%int(row[4]))
    fp.close()
    
    if(temp[MAX_RANGE-1]<=20.0):
    	sendMsgToLine("WARNING:temp is under 20C!!")

    fig=plt.figure(figsize=(16,9))
    ax=fig.add_subplot(1,1,1)
    plt.title("Temperature")
    plt.gca().get_yaxis().get_major_formatter().set_useOffset(False)
    plt.xlabel("month/date_hour:minute")
    plt.ylabel("degree Celsius")
    plt.plot(temp,color="m")
    plt.xticks(range(MAX_RANGE),date)
    grid(True)
    labels=ax.set_xticklabels(date,rotation=90,fontsize="small")

    plt.savefig(URL_PATH+"temp.png",bbox_inches="tight")

def drawingPressure():
    
    pressure=[0 for i in range(MAX_RANGE)]
    date=[0 for i in range(MAX_RANGE)]
    span=[]

    for i in range(MAX_RANGE):
        span.append(1000/MAX_RANGE*i)

    fp=open(URL_PATH+"pressure.csv","rb")
    reader=csv.reader(fp)
    for row in reader:
        pressure.pop(0)
        pressure.append(float(row[5]))
        date.pop(0)
        #month/date_hour:minute
        date.append("%02d"%int(row[1])+"/"+"%02d"%int(row[2])+"_"+"%02d"%int(row[3])+":"+"%02d"%int(row[4]))
    fp.close()

    #Warn if the air pressure acquired in the last 6 times keeps falling
    flg=True
    for i in range(MAX_RANGE-6,MAX_RANGE-1):
        if(pressure[i]<pressure[i+1]):
            flg=False
            break
    if(flg):
        sendMsgToLine("WARNING:Barometric pressure keeps falling for 30 consecutive minutes")

    fig=plt.figure(figsize=(16,9))
    ax=fig.add_subplot(1,1,1)
    plt.title("Pressure")
    plt.gca().get_yaxis().get_major_formatter().set_useOffset(False)
    plt.xlabel("month/date_hour:minute")
    plt.ylabel("Hectopascals")
    plt.plot(pressure,color="c")
    plt.xticks(range(MAX_RANGE),date)
    grid(True)
    labels=ax.set_xticklabels(date,rotation=90,fontsize="small")

    plt.savefig(URL_PATH+"pressure.png",bbox_inches="tight")

def drawingHum():
    
    hum=[0 for i in range(MAX_RANGE)]
    date=[0 for i in range(MAX_RANGE)]
    span=[]

    for i in range(MAX_RANGE):
        span.append(1000/MAX_RANGE*i)

    fp=open(URL_PATH+"hum.csv","rb")
    reader=csv.reader(fp)
    for row in reader:
        hum.pop(0)
        hum.append(float(row[5]))
        date.pop(0)
        #month/date_hour:minute
        date.append("%02d"%int(row[1])+"/"+"%02d"%int(row[2])+"_"+"%02d"%int(row[3])+":"+"%02d"%int(row[4]))
    fp.close()
    
    if(hum[MAX_RANGE-1]>=70.0):
    	sendMsgToLine("WARNING:Hum is over 70%!!")
    	
    if(hum[MAX_RANGE-1]<40.0):
    	sendMsgToLine("WARNING:Hum is under 40%!!")
        
    fig=plt.figure(figsize=(16,9))
    ax=fig.add_subplot(1,1,1)
    plt.title("Humidity")
    plt.gca().get_yaxis().get_major_formatter().set_useOffset(False)
    plt.xlabel("month/date_hour:minute")
    plt.ylabel("percent")
    plt.plot(hum,color="g")
    plt.xticks(range(MAX_RANGE),date)
    grid(True)
    labels=ax.set_xticklabels(date,rotation=90,fontsize="small")

    plt.savefig(URL_PATH+"hum.png",bbox_inches="tight")

def sendMsgToLine(msg):
    Channel_Access_Token="XXXXXXXXXX"
    User_Id="XXXXXXXXXX"
    Line_Bot_Api=LineBotApi(Channel_Access_Token)
    Line_Bot_Api.push_message(User_Id,TextSendMessage(text=msg))

print("start drawing Temp...")
drawingTemp()
print("successful")

print("start drawing Pressure...")
drawingPressure()
print("successful")

print("start drawing Hum...")
drawingHum()
print("successful")

print("connect to http server...")
ftp=FTP("FTP_Server","usrid","passwd")
print("successful")

#only first time
#send to html
#fp=open("../../var/www/html/index.html","rb")
#ftp.storbinary("STOR public_html/index.html",fp)
#fp.close()

#send temp to http
print("send temp.png to http server...")
fp=open(URL_PATH+"temp.png","rb")
ftp.storbinary("STOR public_html/temp.png",fp)
fp.close()
print("successful")

#send pressure to http
print("send pressure.png to http server...")
fp=open(URL_PATH+"pressure.png","rb")
ftp.storbinary("STOR public_html/pressure.png",fp)
fp.close()
print("successful")

#send hum to http
print("send hum.png to http server...")
fp=open(URL_PATH+"hum.png","rb")
ftp.storbinary("STOR public_html/hum.png",fp)
fp.close()
print("successful")

ftp.close()
print("all complete!")
```
#実行結果
俺の部屋の環境はこうです。
温度
![temp.png](https://qiita-image-store.s3.amazonaws.com/0/216625/896f0702-9669-3604-cd50-c651df3818bc.png)
気圧
![pressure.png](https://qiita-image-store.s3.amazonaws.com/0/216625/96056230-620c-20c3-b1ba-d0c1451ccee1.png)
湿度
![hum.png](https://qiita-image-store.s3.amazonaws.com/0/216625/a662515f-16fa-5a2f-d97e-71f70405855c.png)

#参考
[BME280搭載　温湿度・気圧センサモジュール ピンヘッダ実装済](https://www.switch-science.com/catalog/2323/)
[BME280サンプルコード](https://github.com/SWITCHSCIENCE/BME280/blob/master/Python27/bme280_sample.py)
[matplotlibで軸のオフセット表記を禁止させる](http://minus9d.hatenablog.com/entry/2016/04/21/215532)
[fritzingで使えるbme280のパーツ](https://sites.google.com/site/takafuminakahrm1017/fritzing/)