创建项目框架

This commit is contained in:
clinton 2025-07-14 16:10:38 +08:00
commit d74d46705d
22 changed files with 357 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pyc

0
README.md Normal file
View File

89
poetry.lock generated Normal file
View File

@ -0,0 +1,89 @@
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "paho-mqtt"
version = "2.1.0"
description = "MQTT version 5.0/3.1.1 client class"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"},
{file = "paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834"},
]
[package.extras]
proxy = ["pysocks"]
[[package]]
name = "pyside6"
version = "6.5.3"
description = "Python bindings for the Qt cross-platform application and UI framework"
optional = false
python-versions = "<3.12,>=3.7"
groups = ["main"]
files = [
{file = "PySide6-6.5.3-cp37-abi3-macosx_11_0_universal2.whl", hash = "sha256:be53e7c64710fc4307afd33147e241a06cd97b18fae887ee611d8d4b373dbb04"},
{file = "PySide6-6.5.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:84f3d3e278e5ea00f1558ac7e1eeb382bba1df7732bdb025ee654e7b4b3cd451"},
{file = "PySide6-6.5.3-cp37-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:48f4579ca49225cfff8f512178551bdf6aa9031198527f71799bcc061a0f2327"},
{file = "PySide6-6.5.3-cp37-abi3-win_amd64.whl", hash = "sha256:aaaf5acfaaf9575740df03ee1aa706e2f38d8fcca2255acbbd3a5701f6f2f416"},
]
[package.dependencies]
PySide6-Addons = "6.5.3"
PySide6-Essentials = "6.5.3"
shiboken6 = "6.5.3"
[[package]]
name = "pyside6-addons"
version = "6.5.3"
description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
optional = false
python-versions = "<3.12,>=3.7"
groups = ["main"]
files = [
{file = "PySide6_Addons-6.5.3-cp37-abi3-macosx_11_0_universal2.whl", hash = "sha256:047162b158ee929d43c21cdc3ac48e75fec612f2e5492b317190fac98d2de5c6"},
{file = "PySide6_Addons-6.5.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e5bc1fa95351182dc2c003e07320d5509218ccc0840d10197d7d452aa5de5d2e"},
{file = "PySide6_Addons-6.5.3-cp37-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:be0dcfb15d44c2973c3c122058f1df8c3c9d93abd4170534e06dbf986aa30e26"},
{file = "PySide6_Addons-6.5.3-cp37-abi3-win_amd64.whl", hash = "sha256:dd1d294d48798bd297bde02d3ea02f313a86e38ed3944519228466bdfb537961"},
]
[package.dependencies]
PySide6-Essentials = "6.5.3"
shiboken6 = "6.5.3"
[[package]]
name = "pyside6-essentials"
version = "6.5.3"
description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
optional = false
python-versions = "<3.12,>=3.7"
groups = ["main"]
files = [
{file = "PySide6_Essentials-6.5.3-cp37-abi3-macosx_11_0_universal2.whl", hash = "sha256:4d9c95ded938e557052fc67efe68d57108856df141a1b499497fd7999419e3eb"},
{file = "PySide6_Essentials-6.5.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:45580138be91f5fdcefb4d28dadb56d3640eb658575af97b49057e10c22a024d"},
{file = "PySide6_Essentials-6.5.3-cp37-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:8244bc185b0243ba7c4491033e592b247e44a63d69213e9a45ee38e87e0f1f90"},
{file = "PySide6_Essentials-6.5.3-cp37-abi3-win_amd64.whl", hash = "sha256:f928b98ec349c87f9ccc63a482917779f59fa646893722c53c2fe2a1e4f335e0"},
]
[package.dependencies]
shiboken6 = "6.5.3"
[[package]]
name = "shiboken6"
version = "6.5.3"
description = "Python/C++ bindings helper module"
optional = false
python-versions = "<3.12,>=3.7"
groups = ["main"]
files = [
{file = "shiboken6-6.5.3-cp37-abi3-macosx_11_0_universal2.whl", hash = "sha256:faaca92dcbbf26c0ae13f189746c38482e40859e0897b0ed4dee5e04f69fda71"},
{file = "shiboken6-6.5.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4cdda98df511243c40f1dd4d9eac25a7191c2583ac673147ecdae0ffa3b9223f"},
{file = "shiboken6-6.5.3-cp37-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:1bc928ca9f1c1d16ff8fe0585627738a15552bb3329c04fca2c74a443618a6b3"},
{file = "shiboken6-6.5.3-cp37-abi3-win_amd64.whl", hash = "sha256:a013367e38a12b3f69ba02e79f133df4fba8d21b55a78c6999cdb31c25609524"},
]
[metadata]
lock-version = "2.1"
python-versions = ">=3.11,<3.12"
content-hash = "0bce6e6df23bae4a0db8ab108812470658b0e0b1d792f279a8447835f5a9e1de"

28
pyproject.toml Normal file
View File

@ -0,0 +1,28 @@
[project]
name = "mqtt-acess"
version = "0.1.0"
description = ""
authors = [
{name = "clinton", email = "clinton_luo@qq.com"}
]
readme = "README.md"
requires-python = ">=3.11,<3.12"
dependencies = [
"PySide6>=6.5,<6.6",
"PySide6-Addons>=6.5,<6.6",
"PySide6-Essentials>=6.5,<6.6",
"shiboken6>=6.5,<6.6",
"paho-mqtt (>=2.1.0,<3.0.0)"
]
[tool.poetry]
packages = [{include = "mqtt_acess", from = "src"}]
[[tool.poetry.source]]
name = "mirrors"
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/"
priority = "supplemental"
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"

View File

View File

@ -0,0 +1 @@
# config模块

View File

@ -0,0 +1,8 @@
class Settings:
MQTT_BROKER = "localhost"
MQTT_PORT = 1883
MQTT_USERNAME = ""
MQTT_PASSWORD = ""
MQTT_TLS = False
MQTT_TOPICS = [("test/topic", 0)]
PROTOCOL_TYPE = "json"

18
src/mqtt_acess/main.py Normal file
View File

@ -0,0 +1,18 @@
from PySide6.QtWidgets import QApplication
from ui.main_window import MainWindow
from mqtt.client import MqttClient
from parser.factory import ParserFactory
from responder.alarm import AlarmResponder
import sys
def main():
app = QApplication(sys.argv)
mqtt_client = MqttClient()
parser_factory = ParserFactory()
responder = AlarmResponder()
window = MainWindow(mqtt_client, parser_factory, responder)
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

View File

@ -0,0 +1 @@
# mqtt模块

View File

@ -0,0 +1,33 @@
import paho.mqtt.client as mqtt
from config.settings import Settings
class MqttClient:
def __init__(self):
self.client = mqtt.Client()
if Settings.MQTT_USERNAME:
self.client.username_pw_set(Settings.MQTT_USERNAME, Settings.MQTT_PASSWORD)
if Settings.MQTT_TLS:
self.client.tls_set()
self.client.on_connect = self.on_connect
self.client.on_message = None # 由外部设置
def connect(self):
self.client.connect(Settings.MQTT_BROKER, Settings.MQTT_PORT)
self.client.loop_start()
def disconnect(self):
self.client.loop_stop()
self.client.disconnect()
def subscribe(self, topic, qos=0):
self.client.subscribe(topic, qos)
def publish(self, topic, payload, qos=0):
self.client.publish(topic, payload, qos)
def set_on_message(self, callback):
self.client.on_message = callback
def on_connect(self, client, userdata, flags, rc):
for topic, qos in Settings.MQTT_TOPICS:
client.subscribe(topic, qos)

View File

@ -0,0 +1 @@
# parser模块

View File

@ -0,0 +1,3 @@
class IParser:
def parse(self, data: bytes) -> dict:
raise NotImplementedError("必须实现parse方法")

View File

@ -0,0 +1,8 @@
from .json_parser import JsonParser
class ParserFactory:
def get_parser(self, protocol_type: str):
if protocol_type == "json":
return JsonParser()
# 可扩展更多协议
raise ValueError(f"不支持的协议类型: {protocol_type}")

View File

@ -0,0 +1,6 @@
import json
from .base import IParser
class JsonParser(IParser):
def parse(self, data: bytes) -> dict:
return json.loads(data.decode("utf-8"))

View File

@ -0,0 +1 @@
# responder模块

View File

@ -0,0 +1,7 @@
from .base import IResponder
class AlarmResponder(IResponder):
def respond(self, parsed_data: dict):
# 示例:如果数据中有"alarm"字段则打印报警
if parsed_data.get("alarm"):
print("报警:", parsed_data["alarm"])

View File

@ -0,0 +1,3 @@
class IResponder:
def respond(self, parsed_data: dict):
raise NotImplementedError("必须实现respond方法")

View File

@ -0,0 +1 @@
# ui模块

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>658</width>
<height>498</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QWidget" name="horizontalWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>640</width>
<height>480</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1920</width>
<height>1200</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout"/>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'mainWindow.ui'
##
## Created by: Qt User Interface Compiler version 6.5.3
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QSizePolicy, QWidget)
class Ui_Form(object):
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName(u"Form")
Form.resize(658, 498)
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth())
Form.setSizePolicy(sizePolicy)
self.horizontalLayout_2 = QHBoxLayout(Form)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalWidget = QWidget(Form)
self.horizontalWidget.setObjectName(u"horizontalWidget")
sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.horizontalWidget.sizePolicy().hasHeightForWidth())
self.horizontalWidget.setSizePolicy(sizePolicy1)
self.horizontalWidget.setMinimumSize(QSize(640, 480))
self.horizontalWidget.setMaximumSize(QSize(1920, 1200))
self.horizontalLayout = QHBoxLayout(self.horizontalWidget)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout_2.addWidget(self.horizontalWidget)
self.retranslateUi(Form)
QMetaObject.connectSlotsByName(Form)
# setupUi
def retranslateUi(self, Form):
Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
# retranslateUi

View File

@ -0,0 +1,43 @@
from PySide6.QtWidgets import QMainWindow, QTextEdit, QPushButton, QVBoxLayout, QWidget
from config.settings import Settings
class MainWindow(QMainWindow):
def __init__(self, mqtt_client, parser_factory, responder):
super().__init__()
self.setWindowTitle("MQTT在线测试平台")
self.resize(600, 400)
self.mqtt_client = mqtt_client
self.parser = parser_factory.get_parser(Settings.PROTOCOL_TYPE)
self.responder = responder
self.text_edit = QTextEdit()
self.btn_connect = QPushButton("连接MQTT")
self.btn_disconnect = QPushButton("断开MQTT")
self.btn_connect.clicked.connect(self.connect_mqtt)
self.btn_disconnect.clicked.connect(self.disconnect_mqtt)
layout = QVBoxLayout()
layout.addWidget(self.text_edit)
layout.addWidget(self.btn_connect)
layout.addWidget(self.btn_disconnect)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def connect_mqtt(self):
self.mqtt_client.set_on_message(self.on_message)
self.mqtt_client.connect()
self.text_edit.append("已连接MQTT")
def disconnect_mqtt(self):
self.mqtt_client.disconnect()
self.text_edit.append("已断开MQTT")
def on_message(self, client, userdata, msg):
try:
parsed = self.parser.parse(msg.payload)
self.text_edit.append(f"收到消息: {parsed}")
self.responder.respond(parsed)
except Exception as e:
self.text_edit.append(f"解析错误: {e}")

0
tests/__init__.py Normal file
View File