commit d74d46705d8c7c80f251c159572a130e90eec66c Author: clinton Date: Mon Jul 14 16:10:38 2025 +0800 创建项目框架 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..131a6eb --- /dev/null +++ b/poetry.lock @@ -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" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2fae015 --- /dev/null +++ b/pyproject.toml @@ -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" diff --git a/src/mqtt_acess/__init__.py b/src/mqtt_acess/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mqtt_acess/config/__init__.py b/src/mqtt_acess/config/__init__.py new file mode 100644 index 0000000..c2f7822 --- /dev/null +++ b/src/mqtt_acess/config/__init__.py @@ -0,0 +1 @@ +# config模块 \ No newline at end of file diff --git a/src/mqtt_acess/config/settings.py b/src/mqtt_acess/config/settings.py new file mode 100644 index 0000000..8e24e80 --- /dev/null +++ b/src/mqtt_acess/config/settings.py @@ -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" \ No newline at end of file diff --git a/src/mqtt_acess/main.py b/src/mqtt_acess/main.py new file mode 100644 index 0000000..c9b61df --- /dev/null +++ b/src/mqtt_acess/main.py @@ -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() \ No newline at end of file diff --git a/src/mqtt_acess/mqtt/__init__.py b/src/mqtt_acess/mqtt/__init__.py new file mode 100644 index 0000000..f31ba58 --- /dev/null +++ b/src/mqtt_acess/mqtt/__init__.py @@ -0,0 +1 @@ +# mqtt模块 \ No newline at end of file diff --git a/src/mqtt_acess/mqtt/client.py b/src/mqtt_acess/mqtt/client.py new file mode 100644 index 0000000..21606d3 --- /dev/null +++ b/src/mqtt_acess/mqtt/client.py @@ -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) \ No newline at end of file diff --git a/src/mqtt_acess/parser/__init__.py b/src/mqtt_acess/parser/__init__.py new file mode 100644 index 0000000..c8aa2ab --- /dev/null +++ b/src/mqtt_acess/parser/__init__.py @@ -0,0 +1 @@ +# parser模块 \ No newline at end of file diff --git a/src/mqtt_acess/parser/base.py b/src/mqtt_acess/parser/base.py new file mode 100644 index 0000000..4526fbf --- /dev/null +++ b/src/mqtt_acess/parser/base.py @@ -0,0 +1,3 @@ +class IParser: + def parse(self, data: bytes) -> dict: + raise NotImplementedError("必须实现parse方法") \ No newline at end of file diff --git a/src/mqtt_acess/parser/factory.py b/src/mqtt_acess/parser/factory.py new file mode 100644 index 0000000..2b3c993 --- /dev/null +++ b/src/mqtt_acess/parser/factory.py @@ -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}") \ No newline at end of file diff --git a/src/mqtt_acess/parser/json_parser.py b/src/mqtt_acess/parser/json_parser.py new file mode 100644 index 0000000..cbf7379 --- /dev/null +++ b/src/mqtt_acess/parser/json_parser.py @@ -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")) \ No newline at end of file diff --git a/src/mqtt_acess/responder/__init__.py b/src/mqtt_acess/responder/__init__.py new file mode 100644 index 0000000..099a0f2 --- /dev/null +++ b/src/mqtt_acess/responder/__init__.py @@ -0,0 +1 @@ +# responder模块 \ No newline at end of file diff --git a/src/mqtt_acess/responder/alarm.py b/src/mqtt_acess/responder/alarm.py new file mode 100644 index 0000000..13bda74 --- /dev/null +++ b/src/mqtt_acess/responder/alarm.py @@ -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"]) \ No newline at end of file diff --git a/src/mqtt_acess/responder/base.py b/src/mqtt_acess/responder/base.py new file mode 100644 index 0000000..5b07ee2 --- /dev/null +++ b/src/mqtt_acess/responder/base.py @@ -0,0 +1,3 @@ +class IResponder: + def respond(self, parsed_data: dict): + raise NotImplementedError("必须实现respond方法") \ No newline at end of file diff --git a/src/mqtt_acess/ui/__init__.py b/src/mqtt_acess/ui/__init__.py new file mode 100644 index 0000000..97d0d71 --- /dev/null +++ b/src/mqtt_acess/ui/__init__.py @@ -0,0 +1 @@ +# ui模块 \ No newline at end of file diff --git a/src/mqtt_acess/ui/mainWindow.ui b/src/mqtt_acess/ui/mainWindow.ui new file mode 100644 index 0000000..ef69feb --- /dev/null +++ b/src/mqtt_acess/ui/mainWindow.ui @@ -0,0 +1,50 @@ + + + Form + + + + 0 + 0 + 658 + 498 + + + + + 0 + 0 + + + + Form + + + + + + + 0 + 0 + + + + + 640 + 480 + + + + + 1920 + 1200 + + + + + + + + + + diff --git a/src/mqtt_acess/ui/mainWindow_ui.py b/src/mqtt_acess/ui/mainWindow_ui.py new file mode 100644 index 0000000..ed85c01 --- /dev/null +++ b/src/mqtt_acess/ui/mainWindow_ui.py @@ -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 + diff --git a/src/mqtt_acess/ui/main_window.py b/src/mqtt_acess/ui/main_window.py new file mode 100644 index 0000000..2d2ec0b --- /dev/null +++ b/src/mqtt_acess/ui/main_window.py @@ -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}") \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29