前回まで/次回以降の取り組みはこちらから。
今日やっと、すべてのセンサのデータをRaspberry Piに渡せるようになりました。
今回の完成図
こんな感じ。
気温・湿度・大気圧・照度・土の水分量・給水タンクの水検知を表示してます。
左半分にはタイムラプス撮影した最新の写真を表示しとこうかと思ってます。
Arduinoスケッチ
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 | // あさがおIoT観察日記 Arduino側プログラム // 2020.05.21 V0.0.1 DHT11による温度・湿度取得(DHT11サンプルスケッチより作成) // V0.0.2 BME280気圧取得(BME280サンプルスケッチより作成) // 2020.05.23 V0.0.3 TEMT6000による照度取得 // 2020.05.24 V0.0.4 KeyStudio製センサで土の水分量取得 // 2020.06.02 V0.0.5 RaspberryPi側通信制御開発のため、一度シリアル送信するのを照度データだけに変更 // 今後の準備としてLED照明、ポンプ、水切れセンサ、ボタンSWに関する制御パートを追加 // 2020.06.06 V0.0.6 センサの数値をすべてシリアル送信するように変更 // データ「d」をシリアルで受信したらセンサデータを送信する boolean l = false; #define LED_pin 13 #define pump_pin 12 #define SW_B_pin 11 #define SW_R_pin 10 #define sensor_tank 2 #define sensor_water 1 #define sensor_bright 0 #include <Adafruit_Sensor.h> #include <DHT.h> #include <DHT_U.h> #define DHTPIN 2 // Digital pin connected to the DHT sensor #define DHTTYPE DHT11 // DHT 11 DHT_Unified dht(DHTPIN, DHTTYPE); #include <Wire.h> #include <SPI.h> #include <Adafruit_BME280.h> #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C uint32_t delayMS; int Alt = 120; void setup() { Serial.begin(9600); pinMode(LED_pin, OUTPUT); pinMode(pump_pin,OUTPUT); pinMode(SW_B_pin,INPUT_PULLUP); pinMode(SW_R_pin,INPUT_PULLUP); dht.begin(); delayMS = 100; bme.begin(0x76); } void loop() { double temp; double temp_0; double humid; double pres; double pres_0; int bright; int water; int tank; sensors_event_t event; //温度取得 dht.temperature().getEvent(&event); if (!isnan(event.temperature)) { temp = event.temperature; } //湿度取得 dht.humidity().getEvent(&event); if (!isnan(event.relative_humidity)) { humid = event.relative_humidity; } //気圧取得 pres = bme.readPressure() / 100; pres_0 = pres/(pow((1-((0.0065*Alt)/(bme.readTemperature()+0.0065*Alt+273.15))),5.257)); //海面更生 //照度取得 bright = analogRead(sensor_bright); //水分量取得 water = analogRead(sensor_water); //水やり判断 tank = analogRead(sensor_tank); //シリアル送信 if(Serial.available()>0){ byte val = Serial.read(); if(val=='d'){ l = !l; digitalWrite(LED_pin,l); Serial.println(temp); Serial.println(humid); Serial.println(pres_0); Serial.println(bright); Serial.println(water); Serial.println(tank); delay(100); } } } |
すべてのセンサデータを連続で送っていると「どれがどのセンサの値か」が分からなくなります。
今回は、Raspberry PiからArduinoに「d」という1文字を送り、Arduinoは「d」を受け取ったら各センサの値を順番に1回ずつ送信する仕様にしました。
ちなみにdetaの「d」です。
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 68 69 70 71 72 73 74 | from PyQt5.QtWidgets import QApplication from ui.mainwindow_0_0_5 import MainWindow from PyQt5.QtCore import * import time import datetime import serial serialport = "/dev/ttyACM0" class SerialCom: def __init__(self, serialport): self.sc = serial.Serial(serialport,9600) time.sleep(3) def data_read(self): rc_data = self.sc.readline() return rc_data def start_com(self): self.sc.write(b'd') def close(self): self.sc.close() def refresh_time(): #時刻更新 now = datetime.datetime.now() ct = "現在時刻:" + now.strftime('%Y/%m/%d %H:%M:%S') ui.labeltime.setText(ct) def refresh_sensor(): myserial.start_com() #温度更新 temp = myserial.data_read() ui.labeltemp.setText("気温[℃]: " + str(float(temp))) #湿度更新 humid = myserial.data_read() ui.labelhumid.setText("湿度[%]: " + str(float(humid))) #気圧更新 press = myserial.data_read() ui.labelpress.setText("大気圧[hPa]: " + str(float(press))) #照度更新 bright = myserial.data_read() ui.labelbright.setText("照度[Lx]: " + str("{0:4}".format(int(bright)))) #水分量更新 water= myserial.data_read() ui.labelwater.setText( "水分量: " + str("{0:}".format(int(water)))) #水タンク満水検知 tank= myserial.data_read() ui.labeltank.setText("タンク内水検知: " + str("{0:}".format(int(tank)))) if __name__=="__main__": import sys app = QApplication(sys.argv) ui = MainWindow() #ui.showFullScreen() myserial = SerialCom(serialport) timer1 = QTimer() timer1.timeout.connect(refresh_time) timer1.start(100) timer2 = QTimer() timer2.timeout.connect(refresh_sensor) timer2.start(1000) ui.show() sys.exit(app.exec_()) |
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 | # -*- coding: utf-8 -*- """ Module implementing MainWindow. """ from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QMainWindow from .Ui_mainwindow_0_0_4 import Ui_MainWindow class MainWindow(QMainWindow, Ui_MainWindow): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(MainWindow, self).__init__(parent) self.setupUi(self) @pyqtSlot() def on_pushButton_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet raise NotImplementedError |
つまづきポイント
またしょーもないところでハマったので、記録して晒しておきます。
※起こったこと・解決策・自分の理解したことを書いています。間違ってるかもしれません。
悲惨な間違いなどあれば教えていただけると大変助かります。
私pythonやオブジェクト指向プログラミングについてはまだまだ勉強中なので…。
で、今回ハマったのはシリアル通信。
当初、シリアル通信を担当する「Serial_com」クラスの__init__プロシージャでシリアル通信を始めるserial.Serial()命令を書いていました。
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 | serialport = "/dev/ttyACM0" class SerialCom: def __init__(self, serialport): self.sc = serial.Serial(serialport,9600) time.sleep(3) def data_read(self): rc_data = self.sc.readline() return rc_data def start_com(self): self.sc.write(b'd') def close(self): self.sc.close() def refresh_time(): #時刻更新 def refresh_sensor(): myserial = SerialCom(serialport) myserial.start_com() #温度更新 temp = myserial.data_read() ui.labeltemp.setText("気温[℃]: " + str(float(temp))) ... |
ところが、待てど暮らせどデータが送られてこない、それどころかフリーズして時計が止まる。
という症状が出てきて悩まされました。
問題を切り分けていくと、2つのことが分かりました。
- 通信開始→データ読み取りを2秒以上待てばフリーズしない
- millis命令の値が変わっていない
結論、
- シリアル通信開始命令のたびにArduinoがリセットされている
みたいです。
まず、シリアル通信を始めてからデータのやり取りをするまでに時間を置けばフリーズしないことが分かりました。1秒だとフリーズ、2秒以上ならデータが送られてきます。
次に、センサデータの代わりにArduinoのmillis()命令(スケッチが走り始めてからの時間を取得する命令)のデータを送ってみたところ、通信開始後2秒待つ状態で常にほとんど同じ値(1318~1319)が返ってきました。
どうやら、ArduinoとRaspberry Piがシリアル通信を始めるときに、Arduino側は毎回リセットがかかっているようです。
そのリセットにかかる時間はおそらく700ms程度、リセットしきらないうちに通信をしようとするとフリーズ、という具合です。
でも、センサの値を取得するためにArduinoに「d」という文字を送り、センサの値を受け取る、sensor_refreshという関数の中でSerialComクラスをインスタンス化している(≒シリアル通信を始める命令がある)以上どうしようもない…。
つまり、sensor_refreshという関数を呼び出すたびにArduinoはリセットされてしまう…。
一度始めたシリアル通信を止めない方法はないものか…。
…と悩むこと数時間(マジで数時間悩みました)、
メイン部分でインスタンス化したら、呼び出した関数でもそのまま使える
…っぽいことが分かりました。
というか、ダメもとでやってみたらエラーなく動いたっていうのが正確な所です。
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 | serialport = "/dev/ttyACM0" class SerialCom: def __init__(self, serialport): self.sc = serial.Serial(serialport,9600) time.sleep(3) def data_read(self): ... def start_com(self): ... def close(self): ... def refresh_time(): #時刻更新 now = datetime.datetime.now() ct = "現在時刻:" + now.strftime('%Y/%m/%d %H:%M:%S') ui.labeltime.setText(ct) def refresh_sensor(): myserial.start_com() #温度更新 ... if __name__=="__main__": import sys app = QApplication(sys.argv) ui = MainWindow() #ui.showFullScreen() myserial = SerialCom(serialport) #⇦ここでシリアル通信を始めても大丈夫だった timer1 = QTimer() timer1.timeout.connect(refresh_time) timer1.start(100) timer2 = QTimer() timer2.timeout.connect(refresh_sensor) timer2.start(1000) ui.show() sys.exit(app.exec_()) |
…実は正直まだインスタンス化とか、クラスとか、オブジェクト指向とか、いまいちよくわかっていません。
まだまだ勉強ですね。
まだまだ続きます。
コメント