Sunday, February 4, 2018

Get Temperature & Humidity Data on PC Using Python | NodeMCU (Tutorial-7)

In the last post, we have seen how to measure Temperature and Humidity using NodeMCU and then publish this data to a MQTT Server, and then application on Android Phone Subscribe to these temperature and humidity values and display it on the Android screen.
In this post, I am going to extend the Subscription part to PC also, by this we can subscribe for the same topic using multiple clients on different platforms.
The language of choice in this case is Python, and we have already played with Python and MQTT earlier, plus we also know how to create beautiful User Interfaces using PyQt5 in Python.

Prerequisite to Understand this Post 
Before starting this post, you must understand, how to get started with MQTT using Python, and you can learn this from the following link.
Getting Started with MQTT Using Python
Next, you need to learn how to design user interfaces in Python Using PyQt5 and you can learn this from the following link.
Design GUI in Python Using PyQt5
Finally the most important part, in this post we will be publishing the temperature and humidity data using NodeMCU and DHT11 sensor, so to get the NodeMCU software, use the following link.
Get Temperature and Humidity Data on Android Phone | NodeMcu/ESP8266

The following is the User Interface which is developed in Python using PyQt5.
Python GUI as MQTT Client
Have a look at the following video, to view the Python User Interface, Publishing and Subscribing Topics.



Python Software
In this script, I am storing the server information such as Server Address, Server Port Number, Username and Password, as a Python Dictionary and storing it in 'settings.txt' file.
Now coming to the important code snippets.
The following function is called, when connect button on user interface is pressed.

def connect_with_server():
    global server_info
    client.username_pw_set(server_info["Username"], server_info["Password"])
    client.connect(server_info["Server_Address"], int(server_info["Server_Port"]), 60)
    client.loop_start()

 The following Callback function is called when this application successfully connected with server, this function also enables the controls which will be used after connection and it will also disables the controls which will not be used after connection is established.
# Callback Function on Connection with MQTT Server
def on_connect( client, userdata, flags, rc):
    print ("Connected with Code :" +str(rc))
    if rc == 0:
        # Subscribe Topic from here
        client.subscribe("home/#")
        # Enable Disconnect Button and Enable Others
        ui.connect_btn.setDisabled(True)
        # ui.server_add.setEnabled(False) Don't use this
        ui.server_add.setDisabled(True)
        ui.server_port.setDisabled(True)
        ui.username.setDisabled(True)
        ui.password.setDisabled(True)
        ui.disconnect_btn.setEnabled(True)
        ui.led_btn.setEnabled(True)
        ui.statusBar.setStatusTip("Connected")

The following Callback function is called when Subscribed topics are received on PC, in our case it will consist of temperature and humidity values, which are extracted from the payload and displayed on the user interface.

# Callback Function on Receiving the Subscribed Topic/Message
def on_message( client, userdata, msg):
    # print the message received from the subscribed topic
    #print ( str(msg.payload) )
    #print ( str(len(msg.payload) ))
    if len(msg.payload) == 5:
        message = msg.payload
        message = message.decode()      # default decoding utf-8
        temp, humidity = message.split(',')
        ui.temp.setText(temp)
        ui.humidity.setText(humidity)

The following function is called whenever led button is pressed, this function publishes the led state, and as you know NodeMCU has subscribed to this topic and it will change the state of the led based on the payload.

def led_state_loggle():
    global led_state
    if led_state ==  True:
        client.publish("led", bytes([0x00]))
        led_state = False
        ui.led_btn.setText("OFF")
    else:
        client.publish("led", bytes([0x01]))
        led_state = True
        ui.led_btn.setText("ON")

The above are some of the important snippets from the code, the complete Python code is given below.

import os
import codecs
import pickle
import paho.mqtt.client as mqtt
from PyQt5 import QtCore, QtGui, QtWidgets

global server_info
global led_state
led_state = False

filename = 'settings.txt'

def save_file():
    with open(filename, "wb") as myFile:
        pickle.dump(server_info, myFile)

if os.path.exists(filename):
    # Read Dictionary from this file
    with open(filename, "rb") as myFile:
        server_info = pickle.load(myFile)
else:
    # Create Dictionary Using Default parameters
    server_info = { "Server_Address":"m14.cloudmqtt.com", \
                    "Server_Port":"18410", \
                    "Username": "setsmjwc", \
                    "Password":"apDnKqHRgAjA"}
    save_file()


# Callback Function on Connection with MQTT Server
def on_connect( client, userdata, flags, rc):
    print ("Connected with Code :" +str(rc))
    if rc == 0:
        # Subscribe Topic from here
        client.subscribe("home/#")
        # Enable Disconnect Button and Enable Others
        ui.connect_btn.setDisabled(True)
        # ui.server_add.setEnabled(False) Don't use this
        ui.server_add.setDisabled(True)
        ui.server_port.setDisabled(True)
        ui.username.setDisabled(True)
        ui.password.setDisabled(True)
        ui.disconnect_btn.setEnabled(True)
        ui.led_btn.setEnabled(True)
        ui.statusBar.setStatusTip("Connected")

# Callback Function on Receiving the Subscribed Topic/Message
def on_message( client, userdata, msg):
    # print the message received from the subscribed topic
    #print ( str(msg.payload) )
    #print ( str(len(msg.payload) ))
    if len(msg.payload) == 5:
        message = msg.payload
        message = message.decode()      # default decoding utf-8
        temp, humidity = message.split(',')
        ui.temp.setText(temp)
        ui.humidity.setText(humidity)
    

def save_server_add():
    global server_info
    server_info["Server_Address"] = ui.server_add.text()
    save_file()

def save_server_port():
    server_info["Server_Port"] = ui.server_port.text()
    save_file()

def save_username():
    server_info["Username"] = ui.username.text()
    save_file()

def save_password():
    server_info["Password"] = ui.password.text()
    save_file()

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

def connect_with_server():
    global server_info
    client.username_pw_set(server_info["Username"], server_info["Password"])
    client.connect(server_info["Server_Address"], int(server_info["Server_Port"]), 60)
    client.loop_start()


def disconnect_with_server():
    client.loop_stop()
    client.disconnect()
    # Enable Connect Button and Disable Others
    ui.connect_btn.setEnabled(True)
    ui.server_add.setEnabled(True)
    ui.server_port.setEnabled(True)
    ui.username.setEnabled(True)
    ui.password.setEnabled(True)
    ui.disconnect_btn.setDisabled(True)
    ui.led_btn.setDisabled(True)
    ui.statusBar.setStatusTip("Not Connected")

def led_state_loggle():
    global led_state
    if led_state ==  True:
        client.publish("led", bytes([0x00]))
        led_state = False
        ui.led_btn.setText("OFF")
    else:
        client.publish("led", bytes([0x01]))
        led_state = True
        ui.led_btn.setText("ON")

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(306, 199)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.connect_btn = QtWidgets.QPushButton(self.centralwidget)
        self.connect_btn.setGeometry(QtCore.QRect(220, 10, 75, 31))
        self.connect_btn.setObjectName("connect_btn")
        self.disconnect_btn = QtWidgets.QPushButton(self.centralwidget)
        self.disconnect_btn.setEnabled(False)
        self.disconnect_btn.setGeometry(QtCore.QRect(220, 70, 75, 31))
        self.disconnect_btn.setObjectName("disconnect_btn")
        self.temp_lbl = QtWidgets.QLabel(self.centralwidget)
        self.temp_lbl.setGeometry(QtCore.QRect(10, 120, 69, 18))
        self.temp_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.temp_lbl.setAlignment(QtCore.Qt.AlignCenter)
        self.temp_lbl.setObjectName("temp_lbl")
        self.humid_lbl = QtWidgets.QLabel(self.centralwidget)
        self.humid_lbl.setGeometry(QtCore.QRect(110, 120, 69, 18))
        self.humid_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.humid_lbl.setAlignment(QtCore.Qt.AlignCenter)
        self.humid_lbl.setObjectName("humid_lbl")
        self.temp = QtWidgets.QLabel(self.centralwidget)
        self.temp.setGeometry(QtCore.QRect(10, 140, 31, 18))
        font = QtGui.QFont()
        font.setPointSize(10)
        font.setBold(True)
        font.setWeight(75)
        self.temp.setFont(font)
        self.temp.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.temp.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
        self.temp.setObjectName("temp")
        self.c_lbl = QtWidgets.QLabel(self.centralwidget)
        self.c_lbl.setGeometry(QtCore.QRect(50, 140, 21, 18))
        font = QtGui.QFont()
        font.setPointSize(10)
        font.setBold(True)
        font.setWeight(75)
        self.c_lbl.setFont(font)
        self.c_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.c_lbl.setAlignment(QtCore.Qt.AlignCenter)
        self.c_lbl.setObjectName("c_lbl")
        self.humidity = QtWidgets.QLabel(self.centralwidget)
        self.humidity.setGeometry(QtCore.QRect(110, 140, 31, 18))
        font = QtGui.QFont()
        font.setPointSize(10)
        font.setBold(True)
        font.setWeight(75)
        self.humidity.setFont(font)
        self.humidity.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.humidity.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
        self.humidity.setObjectName("humidity")
        self.percent_lbl = QtWidgets.QLabel(self.centralwidget)
        self.percent_lbl.setGeometry(QtCore.QRect(150, 140, 21, 18))
        font = QtGui.QFont()
        font.setPointSize(10)
        font.setBold(True)
        font.setWeight(75)
        self.percent_lbl.setFont(font)
        self.percent_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.percent_lbl.setAlignment(QtCore.Qt.AlignCenter)
        self.percent_lbl.setObjectName("percent_lbl")
        self.led_btn = QtWidgets.QPushButton(self.centralwidget)
        self.led_btn.setGeometry(QtCore.QRect(220, 110, 71, 51))
        self.led_btn.setObjectName("led_btn")
        self.led_btn.setEnabled(False)
        self.widget = QtWidgets.QWidget(self.centralwidget)
        self.widget.setGeometry(QtCore.QRect(10, 10, 71, 91))
        self.widget.setObjectName("widget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setObjectName("verticalLayout")
        self.server_add_lbl = QtWidgets.QLabel(self.widget)
        self.server_add_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.server_add_lbl.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
        self.server_add_lbl.setObjectName("server_add_lbl")
        self.verticalLayout.addWidget(self.server_add_lbl)
        self.server_port_lbl = QtWidgets.QLabel(self.widget)
        self.server_port_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.server_port_lbl.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
        self.server_port_lbl.setObjectName("server_port_lbl")
        self.verticalLayout.addWidget(self.server_port_lbl)
        self.username_lbl = QtWidgets.QLabel(self.widget)
        self.username_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.username_lbl.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
        self.username_lbl.setObjectName("username_lbl")
        self.verticalLayout.addWidget(self.username_lbl)
        self.password_lbl = QtWidgets.QLabel(self.widget)
        self.password_lbl.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.password_lbl.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
        self.password_lbl.setObjectName("password_lbl")
        self.verticalLayout.addWidget(self.password_lbl)
        self.widget1 = QtWidgets.QWidget(self.centralwidget)
        self.widget1.setGeometry(QtCore.QRect(90, 10, 111, 91))
        self.widget1.setObjectName("widget1")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.widget1)
        self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.server_add = QtWidgets.QLineEdit(self.widget1)
        self.server_add.setMaxLength(30)
        self.server_add.setObjectName("server_add")
        self.verticalLayout_2.addWidget(self.server_add)
        self.server_port = QtWidgets.QLineEdit(self.widget1)
        self.server_port.setMaxLength(30)
        self.server_port.setObjectName("server_port")
        self.verticalLayout_2.addWidget(self.server_port)
        self.username = QtWidgets.QLineEdit(self.widget1)
        self.username.setMaxLength(30)
        self.username.setObjectName("username")
        self.verticalLayout_2.addWidget(self.username)
        self.password = QtWidgets.QLineEdit(self.widget1)
        self.password.setMaxLength(30)
        self.password.setEchoMode(QtWidgets.QLineEdit.Password)
        self.password.setObjectName("password")
        self.verticalLayout_2.addWidget(self.password)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusBar = QtWidgets.QStatusBar(MainWindow)
        self.statusBar.setObjectName("statusBar")
        MainWindow.setStatusBar(self.statusBar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Weather Monitor"))
        self.connect_btn.setText(_translate("MainWindow", "Connect"))
        self.disconnect_btn.setText(_translate("MainWindow", "Disconnect"))
        self.temp_lbl.setText(_translate("MainWindow", "Temperature"))
        self.humid_lbl.setText(_translate("MainWindow", "Humidity"))
        self.temp.setText(_translate("MainWindow", "0"))
        self.c_lbl.setText(_translate("MainWindow", "C"))
        self.humidity.setText(_translate("MainWindow", "0"))
        self.percent_lbl.setText(_translate("MainWindow", "%"))
        self.led_btn.setText(_translate("MainWindow", "OFF"))
        self.server_add_lbl.setText(_translate("MainWindow", "Server Add :"))
        self.server_port_lbl.setText(_translate("MainWindow", "Server Port :"))
        self.username_lbl.setText(_translate("MainWindow", "Username :"))
        self.password_lbl.setText(_translate("MainWindow", "Password :"))
        # Main Program Starts from Here
        self.statusBar.setStatusTip("Not Connected")
        # Update Server Information
        global server_info
        self.server_add.setText(server_info["Server_Address"])
        self.server_port.setText(server_info["Server_Port"])
        self.username.setText(server_info["Username"])
        self.password.setText(server_info["Password"])
        # Button Press Events
        self.connect_btn.clicked.connect( connect_with_server )
        self.disconnect_btn.clicked.connect( disconnect_with_server )
        self.led_btn.clicked.connect( led_state_loggle )
        self.server_add.editingFinished.connect( save_server_add )
        self.server_port.editingFinished.connect( save_server_port )
        self.username.editingFinished.connect( save_username )
        self.password.editingFinished.connect( save_password )


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle('Fusion')
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

Most of the code is generated automatically using pyuic5.exe by providing Qt designed file as input, you can learn the complete steps of doing so by reading the post, Design User Interface in Python Using PyQt5.

No comments:

Post a Comment