On my Raspberry Pi, I am using rtl_433 to read the temperature and the humidity from the outdoor sensor of my weather station, then store them in a database and generate some graphs for the past 24 hours, seven days, 30 days and 365 days.
There is also a version of rtl_433 available here, which is compiled for Windows, and I wanted to build a simple GUI for it, so I can use another RTL-SDR stick that I had lying around, to monitor the data from the sensor in real-time and in a nice and decent way on my Windows PC.
When it comes to programming some GUI based applications for Windows, it often gets very time consuming and frustrating, especially if you are using C++, so that you wonder if it’s worth the effort for your idea.
After looking around for a while, trying to find some easier way to realize what I had in mind, I came across this tutorial. Since I had basically all the functionality already implemented in Python on my Raspberry Pi, I thought it would be a good idea to use Python and Qt for this. So I downloaded and installed Anaconda.
After some struggling, because the example in the tutorial uses PyQt4 and Anaconda came together with PyQt5, I finally had a first working version of my program. The GUI itself was easily created with Qt Designer, which already comes together with Anaconda.
But soon I noticed, what I already was suspecting of being a potential problem, actually showed up as a real problem. Since calling rtl_433 and waiting for it returning a result in the same thread that is used for the GUI, the GUI was not reacting while the call of rtl_433 was still active. The GUI did not react during this time.
I came around another example that shows you how to use threads to avoid exactly this particular problem. Again, that example also uses PyQt4 instead of PyQt5, so a bit more research and effort was necessary to get it running, but then there it was, my own cute little GUI for rtl_433!🙂
On my Raspberry Pi, I also upload the currently measured values for temperature, humidity and pressure to Weather Underground. Doing so requires an API-key, and since I already have one, I thought it would be nice to use it again to read the uploaded pressure back and display it together with the real-time values from rtl_433 to make the small weather station display complete. For the API-call, I am also using a separate thread in the same way, I do for calling rtl_433. Below, you can find the final code, which just requires replacing YOURAPIKEYHERE and YOURSTATIONIDHERE.
import sys import os import urllib2 import json from PyQt5 import uic from PyQt5.QtWidgets import QMainWindow, QApplication from PyQt5.QtCore import QTimer, QThread, pyqtSignal Ui_MainWindow, QtBaseClass = uic.loadUiType("Wetterstation.ui") class MyApp(QMainWindow): def __init__(self): super(MyApp, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.temp = '--.-' self.humi = '--.-' self.press = '---.-' self.ui.Temperatur.display(self.temp) self.ui.Luftfeuchtigkeit.display(self.humi) self.ui.Luftdruck.display(self.press) def timeout1(): try: self.MyThread1.start() self.MyTimer1.start(90000) except: self.temp = '--.-' self.humi = '--.-' self.ui.Temperatur.display(self.temp) self.ui.Luftfeuchtigkeit.display(self.humi) def timeout2(): try: self.MyThread2.start() self.MyTimer2.start(600000) except: self.press = '---.-' self.ui.Luftdruck.display(self.press) def done1(temp, humi): self.temp = temp self.humi = humi self.ui.Temperatur.display(self.temp) self.ui.Luftfeuchtigkeit.display(self.humi) def done2(press): self.press = press self.ui.Luftdruck.display(self.press) self.MyThread1 = rtl_433Thread() self.MyThread1.MySignal1.connect(done1) self.MyThread2 = PressureThread() self.MyThread2.MySignal2.connect(done2) self.MyTimer1 = QTimer() self.MyTimer1.timeout.connect(timeout1) self.MyTimer1.start(100) self.MyTimer2 = QTimer() self.MyTimer2.timeout.connect(timeout2) self.MyTimer2.start(100) def __del__(self): self.MyTimer1.stop() self.MyTimer2.stop() self.MyThread1.terminate() self.MyThread2.terminate() class rtl_433Thread(QThread): MySignal1 = pyqtSignal(str, str) def __init__(self): QThread.__init__(self) def __del__(self): self.wait() def run(self): try: process = os.popen('rtl_433 -f 868250000 -g 42 -p 37 -q -R 8 -T 65') str = process.read() process.close() if str: str = str.strip() str = str.replace('\t', '') str = str.replace('\n', ' ') data = str.split(' ') if data == 'Temperature:' and data == 'Humidity:': temp = data humi = data elif data == 'Humidity:' and data == 'Temperature:': temp = data humi = data elif not str: temp = '--.-' humi = '--.-' except: temp = '--.-' humi = '--.-' self.MySignal1.emit(temp, humi) class PressureThread(QThread): MySignal2 = pyqtSignal(str) def __init__(self): QThread.__init__(self) def __del__(self): self.wait() def run(self): try: f = urllib2.urlopen('http://api.wunderground.com/api/YOURAPIKEYHERE/geolookup/conditions/q/pws:YOURSTATIONIDHERE.json') json_string = f.read() parsed_json = json.loads(json_string) pressure_in = parsed_json['current_observation']['pressure_in'] f.close() press_float = float(pressure_in) press_float = press_float*33.8637526 press = '%.1f' % press_float except: press = '---.-' self.MySignal2.emit(press) if __name__ == "__main__": app = QApplication(sys.argv) window = MyApp() window.show() sys.exit(app.exec_())
If you want to build an executable and therefore being able to run it on a computer that has not installed Anaconda, you can additionally use PyInstaller to pack all the necessary runtime libraries together in one executable file.
pyinstaller --windowed -y -F --distpath="." -p C:\ProgramData\Anaconda2\Lib\site-packages\PyQt5 Wetterstation.py
In the end, I used Resource Hacker to change the icon of the generated executable, so that it is the same one like I use within the GUI.
Like you can see, the final executable is rather large, since the whole runtime environment for Python and Qt is included. But since the effort to create the program was quite acceptable and I am very happy with the result, I don’t see this as a real downside.