Bootstrap

使用wxPython开发mqtt客户端

本文章目的:

1.带你了解 paho-mqtt for python库的使用

2.带你了解wxPython库的使用

3.如何封装mqtt客户端和ui库

运行效果:

使用wxpython UI库开发MQTT客户端,最终效果如下:

操作:

运行后,先点击 连接服务器 按钮,然后填入主题消息内容,点击发布主题按钮

默认程序订阅主题: python/mqtt,fzb/event

准备备工作:

安装必要的库

pip install -U wxPython
pip install paho-mqtt

代码:

mqttClient.py

from paho.mqtt import client as mqtt_client
import random
import time
import logging
import json
import MyFrame


class mqttApp:
    """MQTT客户端使用类
    基于TCP传输 MQTT3.1.1
    """
    FIRST_RECONNECT_DELAY = 1
    RECONNECT_RATE = 2
    MAX_RECONNECT_COUNT = 12
    MAX_RECONNECT_DELAY = 60
    client_id = f'fzb-{random.randint(0, 1000)}'

    def __init__(self, topics: tuple = None):
        """创建MQTT对象,设置回调,初始化参数等
        """
        self.client = mqtt_client.Client(client_id = mqttApp.client_id, callback_api_version = mqtt_client.CallbackAPIVersion.VERSION2)
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.disconnect = self.on_disconnect
        self.topics = topics
        self.flag_exit = False
    
    def connect(self, broker = "broker.emqx.io", port=1883, keepalive=60):
        """创建MQTT 连接
        """
        # self.client.username_pw_set('emqx', 'public')
        self.client.connect(broker, port, keepalive)

    def subscribe(self, topic: str, qos: int = 0):
         """订阅主题
         """
         print(f"subscribe '{topic}' topic")
         self.client.subscribe(topic=topic, qos=qos)

    def publish(self, topic, payload=None, qos=0, retain=False):
         """发布主题
         """
         self.client.publish(topic=topic, payload=payload, qos=qos, retain=retain)     

    def on_connect(self, client, userdata, flags, rc, properties):
        """连接服务器成功回调
        """
        if rc == 0:
            print("connected to MQTT Broker!")
            if self.frame is not None:
                self.frame.m_staticText_status.SetLabelText("连接成功!")
                self.frame.m_button_connect.SetLabelText("断开连接")
            #订阅主题
            if self.topics is not None:
                 for topic in self.topics:
                      print(f"subscribe '{topic}' topic")
                      self.client.subscribe(topic)      
        else:
            if self.frame is not None:
                self.frame.m_staticText_status.SetLabelText("未连接")
                self.frame.m_button_connect.SetLabelText("连接服务器")
            print(f"failed to connect, return code {rc}")
    
    def on_message(self,client, userdata, msg):
        """订阅消息回调
        """
        print(f"received '{msg.payload.decode()}' from '{msg.topic}' topic")
        if self.frame is not None:
            self.frame.m_textCtrl_message.AppendText(f"received '{msg.payload.decode()}' from '{msg.topic}' topic\n")

    def on_disconnect(self, client, userdata, rc):
        """MQTT连接断开回调
        """
        print(f"disconnected with result code: {rc}")
        reconnect_count, reconnect_delay = 0, mqttApp.FIRST_RECONNECT_DELAY
        while reconnect_count < mqttApp.MAX_RECONNECT_COUNT:
            print(f"reconnecting in {reconnect_count} seconds...")
            time.sleep(reconnect_delay)

            try:
                client.reconnect()
                print("reconnected successfully!")
                return
            except Exception as err:
                print(f"{err}. reconnect failed, retrying...")

            reconnect_delay *= mqttApp.RECONNECT_RATE
            reconnect_delay = min(reconnect_delay, mqttApp.MAX_RECONNECT_DELAY)
            reconnect_count += 1
        print(f"reconnect failed after {reconnect_count} attempts.Exiting...")
        self.flag_exit = True
         
    def loop_forver(self):
         """开始进入堵塞循环,
         适用运行程序只有这一个应用
         """
         self.client.loop_forever()

    def loop_start(self):
         """开始一个线程循环,不堵塞主线程
            用户自己在主线程中维持程序运行
            如例程中main_loop()
         """
         self.client.loop_start()

    def loop_stop(self):
         """和 loop_start()配对使用
         """
         self.client.loop_stop()
    def set_frame_obj(self, obj:MyFrame.MyFrame = None):
        self.frame = obj
    

MyFrame.py

# -*- coding: utf-8 -*-

###########################################################################
## Python code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
## http://www.wxformbuilder.org/
##
## PLEASE DO *NOT* EDIT THIS FILE!
###########################################################################

import wx
import wx.xrc

import gettext
_ = gettext.gettext

###########################################################################
## Class MyFrame
###########################################################################

class MyFrame ( wx.Frame ):

    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"MQTT客户端"), pos = wx.DefaultPosition, size = wx.Size( 700,600 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        self.SetSizeHints( wx.Size( 700,600 ), wx.Size( -1,-1 ) )
        self.SetBackgroundColour( wx.Colour( 176, 230, 255 ) )

        bSizer_main = wx.BoxSizer( wx.VERTICAL )

        bSizer7 = wx.BoxSizer( wx.HORIZONTAL )

        self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, _(u"连接状态:"), wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText2.Wrap( -1 )

        bSizer7.Add( self.m_staticText2, 0, wx.ALL, 5 )

        self.m_staticText_status = wx.StaticText( self, wx.ID_ANY, _(u"未连接"), wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText_status.Wrap( -1 )

        bSizer7.Add( self.m_staticText_status, 0, wx.ALL, 5 )


        bSizer7.Add( ( 0, 0), 1, wx.EXPAND, 5 )

        self.m_button_connect = wx.Button( self, wx.ID_ANY, _(u"连接服务器"), wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer7.Add( self.m_button_connect, 0, wx.ALL, 5 )


        bSizer_main.Add( bSizer7, 1, wx.EXPAND, 5 )

        sbSizer4 = wx.StaticBoxSizer( wx.StaticBox( self, wx.ID_ANY, _(u"消息接收区域") ), wx.VERTICAL )

        self.m_textCtrl_message = wx.TextCtrl( sbSizer4.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.VSCROLL )
        sbSizer4.Add( self.m_textCtrl_message, 1, wx.ALL|wx.EXPAND, 5 )


        bSizer_main.Add( sbSizer4, 4, wx.EXPAND, 5 )

        bSizer_pub = wx.BoxSizer( wx.VERTICAL )

        bSizer6 = wx.BoxSizer( wx.HORIZONTAL )

        self.m_staticText7 = wx.StaticText( self, wx.ID_ANY, _(u"Qos:"), wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText7.Wrap( -1 )

        bSizer6.Add( self.m_staticText7, 0, wx.ALL, 10 )

        m_comboBox_qosChoices = [ _(u"0 最多一次"), _(u"1 至少一次"), _(u"2    仅一次"), wx.EmptyString ]
        self.m_comboBox_qos = wx.ComboBox( self, wx.ID_ANY, _(u"0 最多一次"), wx.DefaultPosition, wx.DefaultSize, m_comboBox_qosChoices, 0 )
        self.m_comboBox_qos.SetSelection( 0 )
        bSizer6.Add( self.m_comboBox_qos, 0, wx.ALL, 10 )

        self.m_radioBtn_retain = wx.RadioButton( self, wx.ID_ANY, _(u"Retain"), wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer6.Add( self.m_radioBtn_retain, 0, wx.ALL, 10 )


        bSizer6.Add( ( 0, 0), 1, wx.EXPAND, 10 )

        self.m_button_clear = wx.Button( self, wx.ID_ANY, _(u"清空"), wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer6.Add( self.m_button_clear, 0, wx.ALL, 10 )


        bSizer_pub.Add( bSizer6, 1, wx.EXPAND, 5 )

        self.m_staticText4 = wx.StaticText( self, wx.ID_ANY, _(u"主题:"), wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText4.Wrap( -1 )

        bSizer_pub.Add( self.m_staticText4, 0, wx.ALL, 10 )

        self.m_textCtrl_topic = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer_pub.Add( self.m_textCtrl_topic, 0, wx.ALL|wx.EXPAND, 10 )

        self.m_staticText5 = wx.StaticText( self, wx.ID_ANY, _(u"内容:"), wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText5.Wrap( -1 )

        bSizer_pub.Add( self.m_staticText5, 0, wx.ALL, 10 )

        self.m_textCtrl_payload = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer_pub.Add( self.m_textCtrl_payload, 0, wx.ALL|wx.EXPAND, 10 )

        self.m_button_pub = wx.Button( self, wx.ID_ANY, _(u"发布主题"), wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer_pub.Add( self.m_button_pub, 0, wx.ALL, 10 )


        bSizer_main.Add( bSizer_pub, 1, wx.EXPAND, 5 )


        self.SetSizer( bSizer_main )
        self.Layout()

        self.Centre( wx.BOTH )

        # Connect Events
        self.m_button_connect.Bind( wx.EVT_BUTTON, self.on_button_clicked_connect )
        self.m_button_clear.Bind( wx.EVT_BUTTON, self.on_button_clicked_clear )
        self.m_button_pub.Bind( wx.EVT_BUTTON, self.on_button_clicked_pub )

    def __del__( self ):
        pass


    # Virtual event handlers, override them in your derived class
    def on_button_clicked_connect( self, event ):
        if self.client is not None:
            #使用默认配置连接MQTT服务器
            self.client.connect()
            #开始事件循环线程
            self.client.loop_start()

        event.Skip()

    def on_button_clicked_clear( self, event ):
        self.m_textCtrl_message.Clear()
        event.Skip()

    def on_button_clicked_pub( self, event ):
        topic_str = self.m_textCtrl_topic.GetLineText(0)
        payload_str = self.m_textCtrl_payload.GetLineText(0)
        print(f"pub topic = {topic_str} payload = {payload_str}")
        self.client.publish(topic = str(topic_str), payload=str(payload_str))
        event.Skip()

    def set_mqtt_object(self, obj=None):
        self.client = obj


main.py

# -*- coding: utf-8 -*-

import wx
import mqttClient as mqtt
from MyFrame import MyFrame
import time

def main_loop():
    while True:
        pass

if __name__ == '__main__':
    print("start mqtt client application.....")
    #创建对象,并传递订阅主题
    client = mqtt.mqttApp(("python/mqtt","fzb/event"))
    #使用默认配置连接MQTT服务器
    # client.connect()
    #开始事件循环线程
    # client.loop_start()

    print("start wxPython application......")
    app = wx.App()
    frm = MyFrame(None)
    frm.set_mqtt_object(client)
    client.set_frame_obj(frm)
    frm.Show()
    app.MainLoop()

    #保持程序一直运行
    main_loop()
    client.loop_stop()

;