これまで、ESP8266や、ESP32を使って、スマホからBlynk経由で、温度・湿度・気圧などを確認してきたが、まずは、同じ事をRaspberry Piで試してみる。Raspberry Pi上のI2CにBMP280を接続して、SSHでつないだターミナル上に、温度・湿度・気圧をprintするところまでは、こちらで作成した。
Blynkでのアカウント作成等は、ネットに一杯情報があるので、ここでは省く。
目次
新しいTokenを一つ取得する
スマホのBlynkアプリから、新しいデバイスを一つ登録する。残念ながらRaspberry Pi Zero Wは、ハードウエアの選択肢になかったので、Raspberry Pi 3Bをとりあえず選ぶ。これが正しいのかどうかわからないが、まずこれでやってみる。デバイス名は、”RaspberryPiZero001”としておく。作成すると、登録したメールアドレスにTokenが届くので、コピーしておく。
Auth Token : b738***********************2c1c
RaspberryにBlynkの環境をインストールして、Tokenを登録する。
Arduinoの時は、コンパイル済みのライブラリをIDEから読み込むだけで設定できたが、Raspberryの場合は、ソースからのコンパイルが必要らしい。
他の方の記事を参考にさせて頂き、
- $ cd
- $ mkdir -p src/blynk
- $ cd src/blynk
- $ wget https://github.com/blynkkk/blynk-library/releases/download/v0.5.4/Blynk_Release_v0.5.4.zip
- $ unzip Blynk_Release_v0.5.4.zip
- $ cd libraries/Blynk/linux
- $ make clean all target=raspberry
を実施してみたが、make の途中でエラーで終了。wiringpi関連のヘッダーが無いとか・・・。改めて、Makefileの中を見てみたら、ちゃんと書いてあるでは無いか。
1 2 3 4 5 6 7 |
# # To build on RaspberryPi: # 1. Install WiringPi: http://wiringpi.com/download-and-install/ # 2. Run: # make target=raspberry # sudo blynk --token=YourAuthToken # |
との事なので、まずは、WiringPiをインストールする。念のために、dpkg –l で、wiringpiがインストールされていないことを確認してみた。確かに入っていなかった。($ gpio –v でも確かめられる)
指示されたホームページを見ると、gitを使ってインストールすることが推奨のようなので、gitが入っているか確認したら、入っていた($ git –version)。下記のコマンドでwiringPiを gitでダウンロードする
1 2 3 4 5 6 7 8 |
yy@pizw001:~ $ git clone git://git.drogon.net/wiringPi Cloning into 'wiringPi'... remote: Counting objects: 1177, done. remote: Compressing objects: 100% (980/980), done. remote: Total 1177 (delta 821), reused 213 (delta 142) Receiving objects: 100% (1177/1177), 369.88 KiB | 234.00 KiB/s, done. Resolving deltas: 100% (821/821), done. yy@pizw001:~ $ |
1 2 |
$ cd wiringPi ./build |
でビルド実行。正常に終了したっぽいので、再度Blynkのディレクトリに戻って、makeを実行。target=raspberryというのが重要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ cd ./blynk/libraries/Blynk/linux $ make clean all target=raspberry $.... コンパイル終了 $ ls -l total 104 -rwxr-xr-x 1 yy yy 30472 Dec 23 14:08 blynk -rw-rw-r-- 1 yy yy 4127 Sep 5 21:14 BlynkApiLinux.h -rw-rw-r-- 1 yy yy 5285 Sep 5 21:14 BlynkApiWiringPi.h -rw-rw-r-- 1 yy yy 1300 Sep 5 21:14 BlynkOptionsParser.h -rw-rw-r-- 1 yy yy 3768 Sep 5 21:14 BlynkSocket.h -rwxr-xr-x 1 yy yy 410 Sep 5 21:14 build.sh -rw-rw-r-- 1 yy yy 1026 Sep 5 21:14 main.cpp -rw-r--r-- 1 yy yy 32584 Dec 23 14:08 main.o -rw-rw-r-- 1 yy yy 1901 Sep 5 21:14 Makefile -rw-rw-r-- 1 yy yy 584 Sep 5 21:14 README.md $ |
このように、blynkという実行ファイルが出来たので、Makefileに書いてあった通り、先程取得したTokenを登録する。
1 2 3 4 5 6 7 8 9 10 |
$ sudo ./blynk --token=b7**************************1c [0] ___ __ __ / _ )/ /_ _____ / /__ / _ / / // / _ \/ '_/ /____/_/\_, /_//_/_/\_\ /___/ v0.5.4 on Linux [6] Connecting to blynk-cloud.com:80 [213] Ready (ping: 91ms). |
正常っぽい。これで、Raspberry Pi Zeroが Blynkサーバーと接続できる環境が整った。sshでつないだ端末には、プロンプトが帰ってこないが、これはこのblynkプログラムを表のプロセスで起動したから。裏で起動することにする。
- 追記:あとで考えたら、backgroundでの起動は駄目。makeされた結果のblynkというプログラムは、main.cppをコンパイルした結果だから、BLYNK_WRITEのprintfの書き出し先としてstdoutを使っており、backgroundで動かす事は前提としていない。このblynkはあくまでも一つのプログラムであり、Python などから呼べるライブラリでは無い。当たり前・・・・。
- ESP32で書いていたようなCのコードを書けば、Raspberry Piでも同様の事ができることは明確だが、ここでは、何とかして、Pythonのコードから、Blynkを使えるように、色々調べてみることが目的。
スマホ側で、接続を確認する
次にスマホのBlynkアプリを立ち上げて、今接続したRaspberryPiが接続されているかどうかを確認する。正しく、先程設定した名前で登録されているのが確認できる。ただ、まだ何も送信していないので、この状態では何も出来ないのは当然。(他のESP01やESP32は、以前遊んでいて登録した物だが今は使っていないので、ずいぶん前からOfflineになっている)
スマホからRaspberry PiのGPIOピンをOn/Offする
スマホのBlynk画面から新しくボタンを作成してGPIO24ピンに設定する。
Blynkのホーム画面に戻って、▷ボタンを押して開始した後、表示されたボタン(GPIO-24)をクリックする毎に、Raspberry Pi ZeroのGPIO-24番(ヘッダの18番ピン)が、High/ Lowと変化するのが確認出来た。
ちなみに、TeratermのSSHで繋がっているシェルプロンプトから、GPIOを制御するためには、gpioコマンドを使う。現時点では何もSyncをさせていないので、gpioコマンドで High/Lowを変えても、Blynkのスマホ画面上のステータスが変化するわけでは無い。
1 2 3 |
$ gpio -g mode 24 out → GPIO 24を、出力に設定 $ gpio -g write 24 1 → これでHigh $ gpio -g write 24 0 → これでLow |
次に、スマホのblynkアプリからRaspberry Piに何らかの数字を送ってみる。一旦、Blynkのアプリから「□」ボタンをクリックして接続を止め、新しくスライダーを追加してみる。スライダーを Virtual Pin 1 (V1)に設定して、0から1024までの値を送るように設定した。
←矢印で戻ったのち、▷で再開して、下記画面のスライダーを左右に動かして見ると、止める毎にRaspberryの繋がったSSHターミナルの標準出力に、main.cppの、BLYNK_WRITE(V1)関数で定義された通り、’Got a value ….’の表示がなされる。
1 2 3 4 |
Got a value: 242.74576 Got a value: 723.90253 Got a value: 1023 Got a value: 446.47882 |
ここまでは、サンプルで提供された main.cppの動作を確認しただけ。
PythonでBlynkへつなぎたい
Pythonから繋ぐためのライブラリがあった。
ネットでBlynkとRaspberryPi関連の情報を探したが、殆どが、C言語でコードを書くもので、また、シェルプログラムで処理する例もあったが、Pythonで制御するものは余り見つからなかった。無理かな?と思っていたら、次のページを見つけた。
このリンクに、Python2あるいは3からBlynkへ接続するためのライブラリが登録されていた。pip3でインストールしてみた。正常に 0.1.3 バージョンがインストールできた。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ pip3 install blynk-library-python Collecting blynk-library-python Downloading https://www.piwheels.org/simple/blynk-library-python/blynk_library_python-0.1.3-py3-none-any.whl Installing collected packages: blynk-library-python Successfully installed blynk-library-python-0.1.3 $ pip3 list DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning. beautifulsoup4 (4.5.3) <strong>blynk-library-python (0.1.3)</strong> 以下略・・・・ $ |
Pythonでのコーディングサンプルは、ここにある。
これを元に、一行目に #!/usr/bin/python3 を追加し、Authenticationのキーに、自分が今回取得したBlynkからのTokenの文字列を記入して、Raspberry Pi上に blynk,pyというファイルで保存した。 ファイルの実行権を chmod +x で追加した後、./blynk.py と起動すると、とりあえず、動作した。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ ./blynk.py ___ __ __ / _ )/ /_ _____ / /__ / _ / / // / _ \/ '_/ /____/_/\_, /_//_/_/\_\ /___/ Give Blynk a Github star! => https://github.com/vshymanskyy/blynk-library-python TCP: Connecting to blynk-cloud.com:80 Blynk connection successful, authenticating... Access granted, happy Blynking! Current V1 value: 294.76273 Current V1 value: 0 Current V1 value: 1023 <font color="#ff0000"><strong>Warning: Digital/Analog pins not supported. Please use Virtual pins Warning: Digital/Analog pins not supported. Please use Virtual pins Warning: Digital/Analog pins not supported. Please use Virtual pins</strong></font> |
スライダーによる、RaspberryへのV1ピン経由の数値送りは、問題なく動作したが、GPIO 24で設定した実ピンに対しての出力は、上記のように、Warningが出て、動作しなかった。この Pythonライブラリは、実ピンに対する処理はサポートされていないようだ。
実際のGPIOへ出力するためには、一旦Virtual Pinで値を受けてから、Pythonのプログラムの中で、物理GPIOピンへの出力処理をしてあげる必要があるようだ。下記のように、Virtual pin 0をアサインして、コードを追加したところ動作した
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 |
#!/usr/bin/python3 import BlynkLib import time import RPi.GPIO as GPIO BLYNK_AUTH = 'b7*************1c' GPIO.setmode(GPIO.BCM) GPIO.setup(24, GPIO.OUT) # Initialize Blynk blynk = BlynkLib.Blynk(BLYNK_AUTH) # Register Virtual Pins @blynk.VIRTUAL_WRITE(1) def my_write_handler(value): print('Current V1 value: {}'.format(value)) @blynk.VIRTUAL_WRITE(0) def my_write_handler(value): print('Current V0 value: {}'.format(value)) if value == '1': GPIO.output(24, True) else: GPIO.output(24, False) @blynk.VIRTUAL_READ(2) def my_read_handler(): # this widget will show some time in seconds.. blynk.virtual_write(2, time.ticks_ms() // 1000) # Start Blynk (this call should never return) blynk.run() |
という事で、これでとりあえず、スマホのBlynkアプリから、Virtual Pin設定した値を、Raspberryに送り、Pythonのプログラムによって処理できることは確認出来た。
RaspberryのPythonプログラムからBlynkへデータを送る
Raspberryから、スマホのBlynkアプリへデータを送るには、VIRTUAL_READを使う必要がある。スマホのBlynkに、新たに “Value Display”を追加して、Virtual Pin 2 (V2)をアサインする。また、読み取り周期は5秒とする(READING RATE 5sec)
▷で作動させると、下記のように、Raspberryの ‘time’から、”time.ticks_ms() //1000 “の値が5秒毎に送られ、表示させるのが確認できる。(ちなみに、//の演算子は「切り捨て除算」)
Pythonを使ってBMP280から読んで補正した温度・湿度・気圧データを、Blynkへ送る
次は、Raspberryに接続されたBMP280をPythonで制御し、得られた測定データ(温度、湿度、気圧)をBlynk経由でスマホへ送って表示&記録する。
例えば、5秒毎に温度・湿度・気圧を測定して、それを3つのVirtual PinとしてBlynkに送るという事をやりたいが、普通にVirtual Readを使うと、スマホ側の読み出しトリガを基準に一つのチャンネルの読み出しトリガが発生して、一つのデータを送る・・・という事は問題なくできる。 ただ、一つのトリガで3つの測定データを3つのVirtual Pinに対して送る事が、今までのようなイベント型のトリガ方式で可能なのか疑問。
一定のタイマーをRaspberry側で測定して、その間隔毎に、Blynkにデータを送り続ける形にするのが望ましいと思う。それを実現するためには、今のコードの@blynk.VIRTUAL_READ(2)
def my_read_handler():….
のやり方では駄目と思われる。
@マークのデコレーターについて
上記の例に於ける、@blynk.VIRTUAL_READ()は、Pythonのデコレーターと呼ばれるもので、「ある関数を修飾するための関数」とその仕組みを指す。この仕組によって、既存関数の処理の前後に自分自身の処理を付け加えることができる。これにより、ライブラリとして提供されている関数を呼んだ時に、自動的に実行される処理を追加できるという事。関数にコードを足すこと無く、機能を追加することができる。
「デコレーターとは、”関数を引数に取り, 引き換えに新たな関数を返すcallable(*)”である」
今回の使い方では、予め定義されている blynk.VIRTUAL_READ()や VIRTUAL_WRITE()そのものには手を付けないで、そのWrapperのような形で、それらの関数を利用しているという事になるのかと思う。 便利だけど、何となくピンと来ない。
Raspberryからのデータ送信周期について
先程の例のように、スマホのBlynkアプリで、Virtual Readの周期が決まる場合、このアプリを起動していない場合は、データがBlynkに送られないのでは、と思われる(要調査)。
代わりに、Raspberry PiのPythonプログラムの中で、タイマーを設けて、そのタイマーが一定時間を経過する毎に、Virtual_Readを実行すれば、Blynkのサーバーに送られるように作りたい。(以前、ESPの時はそのように実施した。その時には、ESPに用意していある、blynk_timer.setInterval(5000L, sendUptime)を使って 5秒毎に ‘sendUptime’関数が呼ばれるようにした。
これと同様に事を実現するには、Raspberry Piの Pythonに用意されている、threadingを使うと良さそう。
1 2 3 4 5 6 7 8 9 10 11 |
import threading def sensorDataSend(): print(time.ticks_ms() //1000) blynk.virtual_write(2, time.ticks_ms() // 1000) t=threading.Timer(5, sensorDataSend) t.start() return t=threading.Timer(5, sensorDataSend) t.start() |
この例のように、5秒毎に新しく threadを作り、それを実行するという処理で実現できる。関数内で定義したthreadは実行が終わると消滅するので、毎回作らなくてはいけない。関数の外にある threadの定義は、最初に一回目を実行するために必要。何となく、スッキリしないが、このページで少し説明されているので参考になる。(さらにこのページ)
試行錯誤して、こんな感じで、5秒毎に温度、気圧、湿度をBMP280で測定して、Blynkへ送るPythonプログラムが、とりあえず出来て、所望の動作をしている。 SSHのターミナルへも、温度、気圧、湿度を出力している。
メインプログラム (blynk.py)
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 |
#!/usr/bin/python3 import BlynkLib import time import RPi.GPIO as GPIO from bmp280 import get_bmp280_data, setup_bmp280 import threading BLYNK_AUTH = 'b7*************************c1c' GPIO.setmode(GPIO.BCM) GPIO.setup(24, GPIO.OUT) # Initialize Blynk blynk = BlynkLib.Blynk(BLYNK_AUTH) setup_bmp280() # Register Virtual Pins @blynk.VIRTUAL_WRITE(1) def my_write_handler(value): print('Current V1 value: {}'.format(value)) @blynk.VIRTUAL_WRITE(0) def my_write_handler(value): print('Current V0 value: {}'.format(value)) if value == '1': GPIO.output(24, True) else: GPIO.output(24, False) def sensorDataSend(): t, p, h = get_bmp280_data() blynk.virtual_write(3, t) blynk.virtual_write(4, p) blynk.virtual_write(5, h) print('temp = %.2f, pressure = %.2f, humidity = %.2f' % (t, p, h)) t=threading.Timer(5, sensorDataSend) t.start() return t=threading.Timer(5, sensorDataSend) t.start() # Start Blynk (this call should never return) blynk.run() |
BMP280モジュール (bmp280.py)
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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
#!/usr/bin/python3 # #coding: utf-8 from smbus import SMBus import time from time import sleep bus_number = 1 i2c_address = 0x76 bus = SMBus(bus_number) digT = [] digP = [] digH = [] t_fine = 0.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] temp_comp = compensate_T(temp_raw) pres_comp = compensate_P(pres_raw) hum_comp = compensate_H(hum_raw) return temp_comp, pres_comp, hum_comp 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) return 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 return temperature 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 return 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) def setup_bmp280(): setup() get_calib_param() def get_bmp280_data(): return readData() |
とりあえず、これで、
「Raspberry Pi Zero WHにのGPIOの制御や、I2C接続したBMP280の温度・気圧・湿度のデータを、Blynkに5秒毎に送って表示する処理を Python3で実現する」
という、当初の目的は達成出来た。めでたし、めでたし。