BME280 で環境測定 (Raspberry Piに接続 & 記録)

#購入
Amazon から [GY-BME280-5VデジタルブレイクアウトIIC](https://amzn.to/2TDLwcW) を2つ買ってみた。300円引きで 979円x2個でした。![PSX_20190319_112118.jpg](https://qiita-image-store.s3.amazonaws.com/0/306041/c52ac7f2-c8a4-5193-eebc-5510f2894f40.jpeg)
I2C の端子しか出てませんが、SPI を使わないなら逆に分かり易くて良いですね。
[3つで 1,460円](https://amzn.to/2tYamGc) の方が安いみたいですが、これは中国から送られてくるから 1週間以上掛かるんでしょう。こっちは SPI 用の端子と、I2C 用の端子が上下に分けてありますね。

# 接続
[Raspberry Pi 3 Model B+ の設定のメモ](/jp_yen/items/55156ef4c571f6f25635) に書いたように BME280 を繋いでみました。
よく見かける方法はブレッドボードに挿す方法だけど、場所を取るので[ハーネス](http://eleshop.jp/shop/g/gAAC41A/)で繋いでみた。![PSX_20190319_112516.jpg](https://qiita-image-store.s3.amazonaws.com/0/306041/4c64c12d-0bb1-b30f-256b-25e0e0ade590.jpeg)
ピンの対応は
BME280 ↔ Raspberry Pi GPIO
1 (VCC) ↔ 1 (VIN +3V)
2 (GND) ↔ 6 (GND)
3 (SCL) ↔ 5 (SCL)
4 (SDA) ↔ 3 (SDA)
写真とは若干違ってますがこれでも動きました😅

# 動作確認
コマンドラインインターフェイスをインストールして

```shell-session
Ras1:~> sudo apt-get install i2c-tools
...
Ras1:~> i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- 76 --
```
あっちこっちで見かける、0x76 が見えてるから繋がってるという図。

BME280 というディレクトリを掘って cd。[SWITCHSCIENCE さんの公開している github](https://github.com/SWITCHSCIENCE/BME280) からスクリプトを拾ってくる。

```shell-sessionsion:
> mkdir BME280
> cd BME280
> wget https://raw.githubusercontent.com/SWITCHSCIENCE/BME280/master/Python27/bme280_sample.py
```
で、何も考えずにいきなり動かす。

```shell-session:
必要なモジュールを追加
Ras1:~/BME280> sudo apt install python-smbus
...
そして実行
Ras1:~/BME280> python ./bme280_sample.py
temp : 23.29  ℃
pressure : 1020.05 hPa
hum :  34.43 ％
Ras1:~/BME280>
```
おー、取れた取れた。湿度 34.43% って乾燥してるな。しかしこれ、複数ヶ所のデータを取ろうとしたら、前にセレクタを入れないといけないんじゃないか？ま、そういう時はこれみたいに精度は良くないけど 1-Wire で数珠つなぎかな。

# スクリプトをCVSファイルに記録するよう書き換え
python3 で動かそうとするとコケますね。どうも python3 には smbus2 が要るらしい。そして print 文の構文が変わってるとも。python3 で動くように、ちょいちょいいじって実行可能属性を付ける。

```shell-session:
必要なモジュールを追加
Ras1:~/BME280> sudo apt install python3-smbus
または
Ras1:~/BME280> sudo pip3 install smbus2
...
適当に書き換えて diff
Ras1:~/BME280> diff -u bme280_sample.py bme280_sample.py3
--- bme280_sample.py	2019-03-05 00:52:39.135103085 +0900
+++ bme280_sample.py3	2019-03-07 17:23:02.537739360 +0900
@@ -1,11 +1,18 @@
+#!/usr/bin/env /usr/bin/python3
 #coding: utf-8
 
+# for smbus2
+# sudo pip3 install smbus2
 from smbus2 import SMBus
-import time
+import datetime
+import os
 
 bus_number  = 1
 i2c_address = 0x76
 
+now = datetime.datetime.now()
+log_dir = '/var/log/BME280'
+logfile = now.strftime('%Y%m.csv')
 bus = SMBus(bus_number)
 
 digT = []
@@ -65,10 +72,16 @@
 	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)
+	time_t = int(now.timestamp())
+	temp = compensate_T(temp_raw)
+	pres = compensate_P(pres_raw)
+	humm = compensate_H(hum_raw)
+
+	return ('{:d},'.format(time_t) + \
+		' {:.2f},'.format(temp) + \
+		' {:.2f},'.format(pres) + \
+		' {:.2f}'.format(humm))
 
 def compensate_P(adc_P):
 	global  t_fine
@@ -90,9 +103,9 @@
 		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)  
+	pressure = (pressure + ((v1 + v2 + digP[6]) / 16.0)) / 100
+	return pressure
 
-	print "pressure : %7.2f hPa" % (pressure/100)
 
 def compensate_T(adc_T):
 	global t_fine
@@ -100,7 +113,7 @@
 	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) 
+	return temperature
 
 def compensate_H(adc_H):
 	global t_fine
@@ -114,7 +127,7 @@
 		var_h = 100.0
 	elif var_h < 0.0:
 		var_h = 0.0
-	print "hum : %6.2f ％" % (var_h)
+	return var_h
 
 
 def setup():
@@ -141,11 +154,13 @@
 
 if __name__ == '__main__':
 	try:
-		readData()
+		get_val = readData()
+		if not os.path.exists(log_dir):
+			os.makedirs(log_dir)
+		f = open(log_dir + "/" + logfile, 'a')
+		f.write(get_val+"\n")
+		f.close()
+
 	except KeyboardInterrupt:
 		pass
 
-
-
-
-
Ras1:~/BME280> chmod +x bme280_sample.py3
```
compensate_T, compensate_P, compensate_H の関数の中で値を補正して表示していたものを表示させず return で返して cvs 形式で、ファイルに吐くように変更、月毎にローテション。
実際の出力はこんな感じ

```shell-session:
Ras1:~> cat /var/log/BME280/201903.csv
1551815959, 24.55, 1019.27, 32.53
1551817282, 24.52, 1019.07, 32.49
1551817801, 24.60, 1019.43, 33.03
1551818402, 24.61, 1019.32, 33.20
```

# cron へ登録
cron で定期的に実行させるため crontab へ登録。
crontab は直接編集するのではなく、crontab -l してファイルに吐いてから編集し、crontab に読ませた方がいいです。編集をミスったときにやり直せる可能性がちょっとだけ高くなるので。

```shell-session:
Ras1:~/BME280> sudo crontab -l > crontab_root
Ras1:~/BME280> vi crontab_root 
Ras1:~/BME280> sudo crontab ./crontab_root
```
10分毎に実行するように登録した内容は... 

```crontab:crontab_root
*/10 * * * * /home/pi/BME280/bme280_sample.py3
```
以上、ログの出力まで完了！

## ログの時刻を見やすいようにする方法
ログを cat した時に時間が time_t だと見にくいので awk の一行野郎で

```shell-session:
> cat ログファイル
1552465201, 11.11, 1016.04, 32.03
1552465502, 11.27, 1016.06, 33.33
1552465801, 11.40, 1016.22, 35.32
1552466102, 11.37, 1016.37, 36.63
...
> awk -F, '{$1=strftime("%F %T",$1)}OFS=","' ログファイル
2019-03-13 17:20:01, 11.11, 1016.04, 32.03
2019-03-13 17:25:02, 11.27, 1016.06, 33.33
2019-03-13 17:30:01, 11.40, 1016.22, 35.32
2019-03-13 17:35:02, 11.37, 1016.37, 36.63
...
```
本当は OFS="," は BEGIN{} の中に書くべきですが、文字数の省略のためここで後ろに書いています。そして、真なので省略されたブロック {print} を実行します。きちんと BEGIN を使って書くとこんな感じ。

```shell-session:
> awk -F, 'BEGIN{OFS=","}{$1=strftime("%F %T",$1)}1'
> awk -F, 'BEGIN{OFS=","}{$1=strftime("%F %T",$1)}1{print}'
> awk -F, 'BEGIN{OFS=","}{$1=strftime("%F %T",$1);print}'
```
或いは awk の strftime コマンドは gnu awk の拡張なので AIX や HP-UX, Solaris などでは動きません。*BSD の nawk でも動きません。なので ruby や perl の一行野郎で書くと以下のような感じに。

```shell-session:
> ruby -F, -anle '$F[0]=Time.at($F[0].to_i).strftime("%F %T") ; puts $F.join(",")' ログファイル
> ruby -F, -anlpe '$F[0]=Time.at($F[0].to_i).strftime("%F %T") ; $_=$F.join(",")' ログファイル
> perl -F, -MPOSIX -anlE '$F[0]=strftime("%F %T",localtime($F[0])) ; say join(",",@F)' ログファイル

出力形式が変わっていいならもう少し縮めることも
> awk '$1=strftime("%F %T",$1)' ログファイル
> ruby -F, -anle '$F[0]=Time.at($F[0].to_i).strftime("%F %T") ; p $F.join(",")' ログファイル
> ruby -F, -anle '$F[0]=Time.at($F[0].to_i).strftime("%F %T") ; p $F' ログファイル
> perl -F, -MPOSIX -anlE '$F[0]=strftime("%F %T",localtime($F[0])) ; say @F' ログファイル
```
awk の短さは圧倒的ですね。ruby や perl の -F, も省略できますが詰まってしまって読み難いです。
ruby は数値を直接 strftime に食わせることはできず、Time.at で Time オブジェクトに変換してから strftime する必要があるらしい。perl は -MPOSIX が面倒くさい。

ワンライナーを覚えておくのも面倒なのでスクリプトに変換しておくと便利でしょう。

```perl:bme280_redable
> echo "#!`which perl`" > bme280_readable
> perl -MO=Deparse -Mstrict -MPOSIX -F, -anlwE '$F[0]=strftime("%F %T",localtime($F[0]));say join(",",@F)' >> bme280_readable
-e syntax OK
> chmod +x bme280_readable
> cat bme280_readable
#!/usr/bin/perl
BEGIN { $^W = 1; }
BEGIN { $/ = "\n"; $\ = "\n"; }
use POSIX;
use strict;
use feature 'current_sub', 'evalbytes', 'fc', 'postderef_qq', 'say', 'state', 'switch', 'unicode_strings', 'unicode_eval';
LINE: while (defined($_ = readline ARGV)) {
    chomp $_;
    our @F = split(/,/u, $_, 0);
    $F[0] = strftime('%F %T', localtime $F[0]);
    say join(',', @F);
}
```
feature 別にいらないし、while(<>) でいいし、chomp もいらないし自動変換は安全策なのでしょうが無駄が山盛りですね。defined があるから空行は読み飛ばすのかな？

ruby が良いなら

```ruby:bme280_readable.ry
> cat bme280_readable.ry
#!/usr/bin/ruby
STDIN.each_line {|line|
        s = line.split(",",2)
        puts Time.at(s[0].to_i).strftime("%F %T") + ", " + s[1]
}
```
GNU awk でも

```awk:bme280_readable.awk
> cat bme280_readable.awk
#!/usr/bin/awk -f
BEGIN{FS=",";OFS=","}
{
        $1 = strftime("%F %T",$1)
        print
}
```