Raspberry Pi に環境センサ BME280 を I2C バスで接続して実現する環境モニタをいくつか運用しているのですが、その中の1つがセンサ不良で取得不能になることがあります(母艦は特に問題無い)。すぐに素子交換出来るような場所ではない場合、これが致命的な欠陥に直結することから、環境センサの 冗長化 を考えます。
I2Cは一組のバス上に複数のデバイスを並列に連結して利用することが可能で、今回のBME280の場合はSDO端子を違えることで、固有の2つのアドレスに振り分けることが出来ます。
Adafruit製モジュールの場合:
- 0x77 デフォルト
- 0x76 SDO-GND接続
中華製GY-BME280-3.3の場合(こちらを使用):
- 0x76 デフォルト
- 0x77 SDO-VCC接続
まずはこの要領をブレッドボード上で実現してみましょう。
次に母艦Rspberry Pi上で、i2ctools を使ってバスの状況を確認してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
pi@pi2b:~ $ 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 77 pi@pi2b:~ $ i2cdump -y 1 0x76 No size specified (using byte-data access) 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 80: 8f 70 89 14 61 47 b5 06 c4 6c 65 66 32 00 35 92 ?p??aG???lef2.5? 90: e1 d5 d0 0b 80 1e 6c ff f9 ff ac 26 0a d8 bd 10 ??????l.?.?&???? a0: 00 4b b9 00 00 00 00 00 00 00 00 00 33 00 00 c0 .K?.........3..? b0: 00 54 00 00 00 00 60 02 00 01 ff ff 1f 71 03 00 .T....`?.?..?q?. c0: 00 00 93 ff 00 00 00 00 04 00 00 00 00 00 00 00 ..?.....?....... d0: 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 `............... e0: 00 68 01 00 14 04 00 1e ad 41 ff ff ff ff ff ff .h?.??.??A...... f0: ff 00 04 00 93 60 00 4e 3f c0 81 5f a0 74 29 80 ..?.?`.N???_?t)? pi@pi2b:~ $ i2cdump -y 1 0x77 No size specified (using byte-data access) 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 80: 82 70 89 48 20 78 93 06 8b 6c d2 66 32 00 0d 95 ?p?H x???l?f2.?? 90: 42 d6 d0 0b 45 23 4f ff f9 ff ac 26 0a d8 bd 10 B???E#O.?.?&???? a0: 00 4b 78 00 00 00 00 00 00 00 00 00 33 00 00 c0 .Kx.........3..? b0: 00 54 00 00 00 00 60 02 00 01 ff ff 1f 71 03 00 .T....`?.?..?q?. c0: 00 00 93 ff 00 00 00 00 04 00 00 00 00 00 00 00 ..?.....?....... d0: 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 `............... e0: 00 65 01 00 14 0b 00 1e c7 41 ff ff ff ff ff ff .e?.??.??A...... f0: ff 00 04 04 93 60 00 46 ba 40 81 89 e0 76 83 80 ..???`.F?@???v?? |
それぞれの素子を読み取れていることがわかります。
続いてシステムの実装です。既に稼働しているシステムでは、Adafruit製の「Adafruit_Python_BME280」ライブラリを愛用していて、この中のオブジェクトを生成するクラスの定義をよく読んでみると、
1 2 3 4 5 6 7 8 9 |
class BME280(object): def __init__(self, t_mode=BME280_OSAMPLE_1, p_mode=BME280_OSAMPLE_1, h_mode=BME280_OSAMPLE_1, standby=BME280_STANDBY_250, filter=BME280_FILTER_off, address=BME280_I2CADDR, i2c=None, **kwargs): |
とあり、引数でI2Cアドレスを渡すだけで、目的の個体を呼び出すことが出来ます(ありがとう!)。
そこで、5分毎にデータを取得、それをThingSpeakに投げつける既存の仕組みをこの方法で拡張してみると
次のようになります(Pythonビギナーが見様見真似で書いたスクリプトにつきご勘弁を)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# Multiple BME280 on I2C Bus # from Adafruit_BME280 import * import urllib2, json, requests, time ts_key = "XXXXXXXXXXXXXXXX" altitude = 75 sensor1 = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8, address=0x76) degrees1 = sensor1.read_temperature() pascals1 = sensor1.read_pressure() humidity1 = sensor1.read_humidity() hectopascals1 = pascals1 / 100 hectpascals_zero1 = hectopascals1 + (hectopascals1 * 9.81 * altitude) / (287 * (273.15 + degrees1)) time.sleep(1) sensor2 = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8, address=0x77) degrees2 = sensor2.read_temperature() pascals2 = sensor2.read_pressure() humidity2 = sensor2.read_humidity() hectopascals2 = pascals2 / 100 hectpascals_zero2 = hectopascals2 + (hectopascals2 * 9.81 * altitude) / (287 * (273.15 + degrees2)) class ThingSpeak: def __init__(self,apiKey): self.apiKey = apiKey def regist(self): request = "field1=" + str(round(degrees1,2)) + \ "&field2=" + str(round(degrees2,2)) + \ "&field3=" + str(round(humidity1,2)) + \ "&field4=" + str(round(humidity2,2)) + \ "&field5=" + str(round(hectpascals_zero1,2)) + \ "&field6=" + str(round(hectpascals_zero2,2)) + \ "&field7=" + str(round(cputemp,2)) url = "https://api.thingspeak.com/update?key=" + self.apiKey headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} res = requests.post(url, headers=headers, data=request) print res return res def get_temp(): f = open("/sys/class/thermal/thermal_zone0/temp","r") tmp = 0 for t in f: tmp = t[:2]+"."+t[2:5] f.close() return float(tmp) cputemp = get_temp() print '-- Data --' print 'Timestamp1 = {0:0.3f}'.format(sensor1.t_fine) print 'Timestamp2 = {0:0.3f}'.format(sensor2.t_fine) print 'CPU Temp = {0:0.2f} deg C'.format(cputemp) print 'Temp1 = {0:0.2f} deg C'.format(degrees1) print 'Temp2 = {0:0.2f} deg C'.format(degrees2) print 'Humidity1 = {0:0.2f} %'.format(humidity1) print 'Humidity2 = {0:0.2f} %'.format(humidity2) print 'Pressure1 = {0:0.2f} hPa'.format(hectopascals1) print 'Pressure2 = {0:0.2f} hPa'.format(hectopascals2) print 'PressureZ1 = {0:0.2f} hPa'.format(hectpascals_zero1) print 'PressureZ2 = {0:0.2f} hPa'.format(hectpascals_zero2) print '-- ThingSpeak --' thingDevice = ThingSpeak(ts_key) thingDevice.regist() |
一晩運転した結果はこのようになりました。
意外と数値に個体差があるようです。ここまでネタが揃えば、二つの計測結果の平均化や、何らかの不具合で数値の異常や取得不能に対する例外処理を追加することで精度と安定性を向上させることが可能でしょう。
また、同様にいくつか稼働している、ESP8266ベースのシステムにもこのセンサ冗長化の仕組みを展開したいと考えています。
参考)
2017.11.24 追記
課題のセンサ不具合対応を織り込んだ最終版。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# Multiple BME280 on I2C Bus # from Adafruit_BME280 import * import urllib2, json, requests, time ts_key = "XXXXXXXXXXXXXXXX" altitude = 75 data1 = {'Temp':'0', 'Humid':'0', 'PressRaw':'0', 'PressZero':'0'} data2 = {'Temp':'0', 'Humid':'0', 'PressRaw':'0', 'PressZero':'0'} def setDatum(sen, dat): dat['Temp'] = sen.read_temperature() dat['Humid'] = sen.read_humidity() dat['PressRaw'] = sen.read_pressure() / 100 if dat['Temp'] != 0 and dat['PressRaw'] != 0: dat['PressZero'] = dat['PressRaw'] + (dat['PressRaw'] * 9.81 * altitude) / (287 * (273.15 + dat['Temp'])) else: dat['PressZero'] = 0 def get_CPUtemp(): f = open("/sys/class/thermal/thermal_zone0/temp","r") tmp = 0 for t in f: tmp = t[:2]+"."+t[2:5] f.close() return float(tmp) class ThingSpeak: def __init__(self,apiKey): self.apiKey = apiKey def regist(self): request = "field1=" + str(round(data1['Temp'],2)) + \ "&field2=" + str(round(data2['Temp'],2)) + \ "&field3=" + str(round(data1['Humid'],2)) + \ "&field4=" + str(round(data2['Humid'],2)) + \ "&field5=" + str(round(data1['PressZero'],2)) + \ "&field6=" + str(round(data2['PressZero'],2)) + \ "&field7=" + str(round(cputemp,2)) url = "https://api.thingspeak.com/update?key=" + self.apiKey headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} res = requests.post(url, headers=headers, data=request) print res return res try: sensor1 = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8, address=0x76) setDatum(sensor1, data1) except: print "Sensor #1 Failure" data1['Temp'] = data1['Humid'] = data1['PressRaw'] = data1['PressZero'] = 0 time.sleep(1) try: sensor2 = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8, address=0x77) setDatum(sensor2, data2) except: print "Sensor #2 Failure" data2['Temp'] = data2['Humid'] = data2['PressRaw'] = data2['PressZero'] = 0 cputemp = get_CPUtemp() print '-- Data --' print 'CPU Temp = {0:0.2f} deg C'.format(cputemp) print 'Temp1 = {0:0.2f} deg C'.format(data1['Temp']) print 'Temp2 = {0:0.2f} deg C'.format(data2['Temp']) print 'Humidity1 = {0:0.2f} %'.format(data1['Humid']) print 'Humidity2 = {0:0.2f} %'.format(data2['Humid']) print 'Pressure1 = {0:0.2f} hPa'.format(data1['PressRaw']) print 'Pressure2 = {0:0.2f} hPa'.format(data2['PressRaw']) print 'PressureZ1 = {0:0.2f} hPa'.format(data1['PressZero']) print 'PressureZ2 = {0:0.2f} hPa'.format(data2['PressZero']) print '-- ThingSpeak --' thingDevice = ThingSpeak(ts_key) thingDevice.regist() |
2017.12.19 さらに追記
ブレッドボードを離れ、市販のコネクタを流用した汎用量産版を製作。分離すればシングル動作も可能に。