refactor(DBNMQTTool): tkinter → PySide6
- main.py: 完整重写为 PySide6 (Qt for Python) - requirements.txt: 加 PySide6>=6.6.0 - 使用 Qt 信号/槽替代 tkinter 回调 - 界面更现代化,Fusion 风格 - 分组框 (QGroupBox) 布局,QSplitter 分栏
This commit is contained in:
+341
-286
@@ -1,283 +1,346 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
DBN MQTT Tool — DLD960 IoT MQTT 设备管理工具
|
DBN MQTT Tool — DLD960 IoT MQTT 设备管理工具
|
||||||
|
跨平台桌面软件 (PySide6)
|
||||||
跨平台桌面软件 (tkinter)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import ttk, messagebox, scrolledtext
|
|
||||||
from threading import Thread
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
# 确保项目路径可导入
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
|
QLabel, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem,
|
||||||
|
QTabWidget, QTextEdit, QGroupBox, QGridLayout, QCheckBox,
|
||||||
|
QSplitter, QMessageBox, QHeaderView, QFrame,
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import Qt, QTimer, Signal, Slot
|
||||||
|
from PySide6.QtGui import QFont, QColor, QTextCursor
|
||||||
|
|
||||||
from dbn_mqtt_tool.mqtt_client import MqttClient, BrokerConfig
|
from dbn_mqtt_tool.mqtt_client import MqttClient, BrokerConfig
|
||||||
from dbn_mqtt_tool.device_manager import DeviceManager, DeviceInfo
|
from dbn_mqtt_tool.device_manager import DeviceManager
|
||||||
from dbn_mqtt_tool.protocol import (
|
from dbn_mqtt_tool.protocol import (
|
||||||
CMD_DEV_INFO_QUERY, CMD_SSC_NET_QUERY, CMD_IOT_NET_QUERY,
|
CMD_DEV_INFO_QUERY, CMD_SSC_NET_QUERY, CMD_IOT_NET_QUERY,
|
||||||
CMD_IOT_TOPIC_QUERY, CMD_LOOP_PARAM_QUERY,
|
CMD_IOT_TOPIC_QUERY, CMD_LOOP_PARAM_QUERY,
|
||||||
CMD_SSC_NET_SET, CMD_IOT_NET_SET, CMD_IOT_TOPIC_SET,
|
CMD_SSC_NET_SET, CMD_IOT_NET_SET, CMD_IOT_TOPIC_SET,
|
||||||
CMD_LOOP_PARAM_SET, CMD_REPORT_CONFIG,
|
|
||||||
CMD_PWD_VERIFY, CMD_PWD_SET, CMD_FACTORY_RESET, CMD_DEVICE_RESET,
|
CMD_PWD_VERIFY, CMD_PWD_SET, CMD_FACTORY_RESET, CMD_DEVICE_RESET,
|
||||||
CMD_DEV_SERIAL_SET,
|
ERROR_MSGS,
|
||||||
CMD_LOOP_DATA, CMD_EVENT_REPORT, CMD_HEARTBEAT,
|
|
||||||
ERROR_MSGS, FREQ_LEVELS, OUTPUT_MODES,
|
|
||||||
data_ssc_net_set, data_iot_net_set, data_iot_topic_set,
|
data_ssc_net_set, data_iot_net_set, data_iot_topic_set,
|
||||||
data_loop_param_set, data_report_config,
|
data_pwd_verify, data_pwd_set,
|
||||||
data_pwd_verify, data_pwd_set, data_dev_serial_set,
|
|
||||||
topic_data_loop, topic_data_event, topic_status,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DBNMqttTool(tk.Tk):
|
class MainWindow(QMainWindow):
|
||||||
"""主窗口"""
|
_mqtt_status = Signal(bool, str)
|
||||||
|
_mqtt_msg = Signal(str, str, object)
|
||||||
|
_devices_changed = Signal()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.setWindowTitle("DBN MQTT Tool — DLD960 IoT 设备管理")
|
||||||
self.title("DBN MQTT Tool — DLD960 IoT 设备管理")
|
self.resize(1280, 800)
|
||||||
self.geometry("1200x750")
|
self.setMinimumSize(960, 640)
|
||||||
self.minsize(900, 600)
|
|
||||||
|
|
||||||
self._mqtt = MqttClient()
|
self._mqtt = MqttClient()
|
||||||
self._devmgr = DeviceManager()
|
self._devmgr = DeviceManager()
|
||||||
|
|
||||||
# MQTT 回调
|
self._mqtt.on_status_change(lambda c, m: self._mqtt_status.emit(c, m))
|
||||||
self._mqtt.on_status_change(self._on_mqtt_status)
|
self._mqtt.on_message(lambda t, s, p: self._mqtt_msg.emit(t, s, p))
|
||||||
self._mqtt.on_message(self._on_mqtt_message)
|
self._devmgr.on_change(lambda: self._devices_changed.emit())
|
||||||
|
|
||||||
# 设备列表变化
|
self._mqtt_status.connect(self._on_status)
|
||||||
self._devmgr.on_change(self._refresh_device_list)
|
self._mqtt_msg.connect(self._on_message)
|
||||||
|
self._devices_changed.connect(self._refresh_devices)
|
||||||
self._pending: dict[int, str] = {} # msg_id → 命令名
|
|
||||||
self._log_lines: list[str] = []
|
|
||||||
|
|
||||||
self._build_ui()
|
self._build_ui()
|
||||||
|
self._log("DBN MQTT Tool 已启动")
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# UI 构建
|
# UI 构建
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
def _build_ui(self):
|
def _build_ui(self):
|
||||||
# -- 顶栏:连接 --
|
central = QWidget()
|
||||||
top_bar = ttk.Frame(self, padding=5)
|
self.setCentralWidget(central)
|
||||||
top_bar.pack(fill=tk.X)
|
root = QVBoxLayout(central)
|
||||||
|
root.setContentsMargins(8, 8, 8, 8)
|
||||||
|
root.setSpacing(6)
|
||||||
|
|
||||||
ttk.Label(top_bar, text="Broker:").pack(side=tk.LEFT, padx=(0, 5))
|
# -- 顶栏 --
|
||||||
self._host_var = tk.StringVar(value="121.37.20.199")
|
root.addWidget(self._build_top_bar())
|
||||||
ttk.Entry(top_bar, textvariable=self._host_var, width=16).pack(side=tk.LEFT, padx=2)
|
|
||||||
ttk.Label(top_bar, text="Port:").pack(side=tk.LEFT, padx=(5, 2))
|
|
||||||
self._port_var = tk.IntVar(value=1883)
|
|
||||||
ttk.Entry(top_bar, textvariable=self._port_var, width=6).pack(side=tk.LEFT, padx=2)
|
|
||||||
ttk.Label(top_bar, text="用户:").pack(side=tk.LEFT, padx=(5, 2))
|
|
||||||
self._user_var = tk.StringVar()
|
|
||||||
ttk.Entry(top_bar, textvariable=self._user_var, width=10).pack(side=tk.LEFT, padx=2)
|
|
||||||
ttk.Label(top_bar, text="密码:").pack(side=tk.LEFT, padx=(5, 2))
|
|
||||||
self._pass_var = tk.StringVar()
|
|
||||||
ttk.Entry(top_bar, textvariable=self._pass_var, width=10, show="*").pack(side=tk.LEFT, padx=2)
|
|
||||||
|
|
||||||
self._btn_connect = ttk.Button(top_bar, text="连接", command=self._toggle_connect)
|
# -- 主体 --
|
||||||
self._btn_connect.pack(side=tk.LEFT, padx=10)
|
splitter = QSplitter(Qt.Horizontal)
|
||||||
|
|
||||||
self._conn_status = ttk.Label(top_bar, text="● 未连接", foreground="red")
|
left = self._build_device_panel()
|
||||||
self._conn_status.pack(side=tk.LEFT, padx=10)
|
splitter.addWidget(left)
|
||||||
|
|
||||||
# -- 主体:左右分栏 --
|
right = QTabWidget()
|
||||||
main = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
|
right.addTab(self._build_info_tab(), "设备信息")
|
||||||
main.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
right.addTab(self._build_config_tab(), "参数配置")
|
||||||
|
right.addTab(self._build_data_tab(), "实时数据")
|
||||||
|
right.addTab(self._build_log_tab(), "日志")
|
||||||
|
splitter.addWidget(right)
|
||||||
|
|
||||||
# 左侧:设备列表
|
splitter.setSizes([280, 960])
|
||||||
left = ttk.Frame(main, width=280)
|
root.addWidget(splitter, 1)
|
||||||
main.add(left, weight=0)
|
|
||||||
|
|
||||||
ttk.Label(left, text="设备列表", font=("", 11, "bold")).pack(anchor=tk.W, pady=(0, 5))
|
def _build_top_bar(self) -> QWidget:
|
||||||
self._dev_tree = ttk.Treeview(left, columns=("model",), show="tree headings", height=14)
|
bar = QWidget()
|
||||||
self._dev_tree.heading("#0", text="设备序列码")
|
layout = QHBoxLayout(bar)
|
||||||
self._dev_tree.heading("model", text="型号")
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self._dev_tree.column("#0", width=140)
|
layout.setSpacing(6)
|
||||||
self._dev_tree.column("model", width=80)
|
|
||||||
self._dev_tree.pack(fill=tk.BOTH, expand=True)
|
|
||||||
self._dev_tree.bind("<<TreeviewSelect>>", self._on_device_select)
|
|
||||||
|
|
||||||
btn_frame = ttk.Frame(left)
|
layout.addWidget(QLabel("Broker:"))
|
||||||
btn_frame.pack(fill=tk.X, pady=5)
|
self._host_edit = QLineEdit("121.37.20.199")
|
||||||
ttk.Button(btn_frame, text="查询设备信息", command=self._query_dev_info).pack(side=tk.LEFT, padx=2)
|
self._host_edit.setMaximumWidth(140)
|
||||||
ttk.Button(btn_frame, text="查询全部", command=self._query_all).pack(side=tk.LEFT, padx=2)
|
layout.addWidget(self._host_edit)
|
||||||
|
|
||||||
# 右侧:标签页
|
layout.addWidget(QLabel("Port:"))
|
||||||
right = ttk.Frame(main)
|
self._port_edit = QLineEdit("1883")
|
||||||
main.add(right, weight=1)
|
self._port_edit.setMaximumWidth(60)
|
||||||
|
layout.addWidget(self._port_edit)
|
||||||
|
|
||||||
self._notebook = ttk.Notebook(right)
|
layout.addWidget(QLabel("用户:"))
|
||||||
self._notebook.pack(fill=tk.BOTH, expand=True)
|
self._user_edit = QLineEdit()
|
||||||
|
self._user_edit.setMaximumWidth(90)
|
||||||
|
layout.addWidget(self._user_edit)
|
||||||
|
|
||||||
self._tab_info = self._add_info_tab()
|
layout.addWidget(QLabel("密码:"))
|
||||||
self._tab_config = self._add_config_tab()
|
self._pass_edit = QLineEdit()
|
||||||
self._tab_data = self._add_data_tab()
|
self._pass_edit.setMaximumWidth(90)
|
||||||
self._tab_log = self._add_log_tab()
|
self._pass_edit.setEchoMode(QLineEdit.Password)
|
||||||
|
layout.addWidget(self._pass_edit)
|
||||||
|
|
||||||
def _add_info_tab(self):
|
self._btn_conn = QPushButton("连接")
|
||||||
f = ttk.Frame(self._notebook, padding=10)
|
self._btn_conn.setFixedWidth(70)
|
||||||
self._notebook.add(f, text="设备信息")
|
self._btn_conn.clicked.connect(self._toggle_connect)
|
||||||
|
layout.addWidget(self._btn_conn)
|
||||||
|
|
||||||
self._info_text = scrolledtext.ScrolledText(f, height=18, width=60, state=tk.DISABLED,
|
self._status_label = QLabel("● 未连接")
|
||||||
font=("Consolas", 10))
|
self._status_label.setStyleSheet("color: red; font-weight: bold;")
|
||||||
self._info_text.pack(fill=tk.BOTH, expand=True)
|
layout.addWidget(self._status_label)
|
||||||
return f
|
|
||||||
|
|
||||||
def _add_config_tab(self):
|
layout.addStretch()
|
||||||
f = ttk.Frame(self._notebook, padding=10)
|
return bar
|
||||||
self._notebook.add(f, text="参数配置")
|
|
||||||
|
|
||||||
row = 0
|
def _build_device_panel(self) -> QWidget:
|
||||||
# -- 网口配置 --
|
w = QWidget()
|
||||||
ttk.Label(f, text="▸ SSC 网络配置", font=("", 10, "bold")).grid(row=row, column=0, columnspan=4, sticky=tk.W, pady=(0, 5))
|
layout = QVBoxLayout(w)
|
||||||
row += 1
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
for label, var_name, col in [
|
|
||||||
("设备 IP", "ssc_ip", 0), ("子网掩码", "ssc_mask", 1),
|
|
||||||
("网关", "ssc_gw", 2), ("LSSC IP", "ssc_lssc", 3),
|
|
||||||
("DNS", "ssc_dns", 0), ("端口", "ssc_port", 1),
|
|
||||||
]:
|
|
||||||
ttk.Label(f, text=label + ":").grid(row=row, column=col * 2, sticky=tk.E, padx=2, pady=1)
|
|
||||||
var = tk.StringVar()
|
|
||||||
setattr(self, f"_var_{var_name}", var)
|
|
||||||
ttk.Entry(f, textvariable=var, width=15).grid(row=row, column=col * 2 + 1, sticky=tk.W, padx=2, pady=1)
|
|
||||||
if col == 3 or (col == 1 and var_name == "ssc_port"):
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
ttk.Button(f, text="查询 SSC 网络", command=self._query_ssc_net).grid(row=row, column=0, pady=5, padx=2, sticky=tk.W)
|
layout.addWidget(QLabel("<b>设备列表</b>"))
|
||||||
ttk.Button(f, text="设置 SSC 网络", command=self._set_ssc_net).grid(row=row, column=1, pady=5, padx=2, sticky=tk.W)
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
# -- IoT 配置 --
|
self._dev_tree = QTreeWidget()
|
||||||
ttk.Separator(f, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=4, sticky=tk.EW, pady=10)
|
self._dev_tree.setHeaderLabels(["设备序列码", "型号"])
|
||||||
row += 1
|
self._dev_tree.setRootIsDecorated(False)
|
||||||
ttk.Label(f, text="▸ IoT 网络配置", font=("", 10, "bold")).grid(row=row, column=0, columnspan=4, sticky=tk.W, pady=(0, 5))
|
self._dev_tree.header().setStretchLastSection(True)
|
||||||
row += 1
|
self._dev_tree.itemSelectionChanged.connect(self._on_dev_select)
|
||||||
for label, var_name in [
|
layout.addWidget(self._dev_tree, 1)
|
||||||
|
|
||||||
|
btn_row = QHBoxLayout()
|
||||||
|
btn_query = QPushButton("查询设备信息")
|
||||||
|
btn_query.clicked.connect(self._query_dev_info)
|
||||||
|
btn_row.addWidget(btn_query)
|
||||||
|
|
||||||
|
btn_all = QPushButton("查询全部")
|
||||||
|
btn_all.clicked.connect(self._query_all)
|
||||||
|
btn_row.addWidget(btn_all)
|
||||||
|
|
||||||
|
layout.addLayout(btn_row)
|
||||||
|
return w
|
||||||
|
|
||||||
|
def _build_info_tab(self) -> QWidget:
|
||||||
|
w = QWidget()
|
||||||
|
layout = QVBoxLayout(w)
|
||||||
|
self._info_text = QTextEdit()
|
||||||
|
self._info_text.setReadOnly(True)
|
||||||
|
self._info_text.setFont(QFont("Consolas", 10))
|
||||||
|
layout.addWidget(self._info_text)
|
||||||
|
return w
|
||||||
|
|
||||||
|
def _build_config_tab(self) -> QWidget:
|
||||||
|
w = QWidget()
|
||||||
|
layout = QVBoxLayout(w)
|
||||||
|
|
||||||
|
# -- SSC 网络 --
|
||||||
|
g1 = QGroupBox("SSC 网络配置")
|
||||||
|
g1_layout = QGridLayout(g1)
|
||||||
|
fields_ssc = [
|
||||||
|
("设备 IP", "ssc_ip"), ("子网掩码", "ssc_mask"),
|
||||||
|
("网关", "ssc_gw"), ("LSSC IP", "ssc_lssc"),
|
||||||
|
("DNS", "ssc_dns"), ("端口", "ssc_port"),
|
||||||
|
]
|
||||||
|
for i, (label, name) in enumerate(fields_ssc):
|
||||||
|
g1_layout.addWidget(QLabel(label + ":"), i // 2, (i % 2) * 2)
|
||||||
|
edit = QLineEdit()
|
||||||
|
edit.setMaximumWidth(140)
|
||||||
|
g1_layout.addWidget(edit, i // 2, (i % 2) * 2 + 1)
|
||||||
|
setattr(self, f"_edit_{name}", edit)
|
||||||
|
|
||||||
|
btn_row = QHBoxLayout()
|
||||||
|
b1 = QPushButton("查询")
|
||||||
|
b1.clicked.connect(self._query_ssc_net)
|
||||||
|
b2 = QPushButton("设置")
|
||||||
|
b2.clicked.connect(self._set_ssc_net)
|
||||||
|
btn_row.addWidget(b1)
|
||||||
|
btn_row.addWidget(b2)
|
||||||
|
btn_row.addStretch()
|
||||||
|
g1_layout.addLayout(btn_row, 3, 0, 1, 4)
|
||||||
|
layout.addWidget(g1)
|
||||||
|
|
||||||
|
# -- IoT 网络 --
|
||||||
|
g2 = QGroupBox("IoT 网络配置")
|
||||||
|
g2_layout = QGridLayout(g2)
|
||||||
|
fields_iot = [
|
||||||
("MQTT Host", "iot_host"), ("MQTT Port", "iot_port"),
|
("MQTT Host", "iot_host"), ("MQTT Port", "iot_port"),
|
||||||
("Client ID", "iot_cid"), ("用户名", "iot_user"), ("密码", "iot_pass"),
|
("Client ID", "iot_cid"), ("用户名", "iot_user"),
|
||||||
|
("密码", "iot_pass"),
|
||||||
|
]
|
||||||
|
for i, (label, name) in enumerate(fields_iot):
|
||||||
|
g2_layout.addWidget(QLabel(label + ":"), i, 0)
|
||||||
|
edit = QLineEdit()
|
||||||
|
edit.setMinimumWidth(200)
|
||||||
|
if "pass" in name:
|
||||||
|
edit.setEchoMode(QLineEdit.Password)
|
||||||
|
g2_layout.addWidget(edit, i, 1)
|
||||||
|
setattr(self, f"_edit_{name}", edit)
|
||||||
|
|
||||||
|
btn_row2 = QHBoxLayout()
|
||||||
|
b3 = QPushButton("查询")
|
||||||
|
b3.clicked.connect(self._query_iot_net)
|
||||||
|
b4 = QPushButton("设置")
|
||||||
|
b4.clicked.connect(self._set_iot_net)
|
||||||
|
btn_row2.addWidget(b3)
|
||||||
|
btn_row2.addWidget(b4)
|
||||||
|
btn_row2.addStretch()
|
||||||
|
g2_layout.addLayout(btn_row2, len(fields_iot), 0, 1, 2)
|
||||||
|
layout.addWidget(g2)
|
||||||
|
|
||||||
|
# -- Topic --
|
||||||
|
g3 = QGroupBox("IoT Topic 配置")
|
||||||
|
g3_layout = QGridLayout(g3)
|
||||||
|
self._chk_cid = QCheckBox("Client ID 启用")
|
||||||
|
self._chk_cid.setChecked(True)
|
||||||
|
g3_layout.addWidget(self._chk_cid, 0, 0, 1, 2)
|
||||||
|
|
||||||
|
g3_layout.addWidget(QLabel("Topic 发布:"), 1, 0)
|
||||||
|
self._edit_topic_pub = QLineEdit()
|
||||||
|
self._edit_topic_pub.setMinimumWidth(250)
|
||||||
|
g3_layout.addWidget(self._edit_topic_pub, 1, 1)
|
||||||
|
|
||||||
|
g3_layout.addWidget(QLabel("Topic 订阅:"), 2, 0)
|
||||||
|
self._edit_topic_sub = QLineEdit()
|
||||||
|
g3_layout.addWidget(self._edit_topic_sub, 2, 1)
|
||||||
|
|
||||||
|
btn_row3 = QHBoxLayout()
|
||||||
|
b5 = QPushButton("查询")
|
||||||
|
b5.clicked.connect(self._query_iot_topic)
|
||||||
|
b6 = QPushButton("设置")
|
||||||
|
b6.clicked.connect(self._set_iot_topic)
|
||||||
|
btn_row3.addWidget(b5)
|
||||||
|
btn_row3.addWidget(b6)
|
||||||
|
btn_row3.addStretch()
|
||||||
|
g3_layout.addLayout(btn_row3, 3, 0, 1, 2)
|
||||||
|
layout.addWidget(g3)
|
||||||
|
|
||||||
|
# -- 控制 --
|
||||||
|
g4 = QGroupBox("控制命令")
|
||||||
|
g4_layout = QGridLayout(g4)
|
||||||
|
g4_layout.addWidget(QLabel("密码:"), 0, 0)
|
||||||
|
self._edit_pwd = QLineEdit()
|
||||||
|
self._edit_pwd.setMaximumWidth(100)
|
||||||
|
self._edit_pwd.setEchoMode(QLineEdit.Password)
|
||||||
|
g4_layout.addWidget(self._edit_pwd, 0, 1)
|
||||||
|
g4_layout.addWidget(QLabel("新密码:"), 0, 2)
|
||||||
|
self._edit_new_pwd = QLineEdit()
|
||||||
|
self._edit_new_pwd.setMaximumWidth(100)
|
||||||
|
self._edit_new_pwd.setEchoMode(QLineEdit.Password)
|
||||||
|
g4_layout.addWidget(self._edit_new_pwd, 0, 3)
|
||||||
|
|
||||||
|
btn_row4 = QHBoxLayout()
|
||||||
|
for label, slot in [
|
||||||
|
("验证密码", self._do_pwd_verify), ("设置密码", self._do_pwd_set),
|
||||||
|
("出厂初始化", self._do_factory_reset), ("设备复位", self._do_device_reset),
|
||||||
]:
|
]:
|
||||||
ttk.Label(f, text=label + ":").grid(row=row, column=0, sticky=tk.E, padx=2, pady=1)
|
btn = QPushButton(label)
|
||||||
var = tk.StringVar()
|
btn.clicked.connect(slot)
|
||||||
setattr(self, f"_var_{var_name}", var)
|
btn_row4.addWidget(btn)
|
||||||
w = ttk.Entry(f, textvariable=var, width=30)
|
btn_row4.addStretch()
|
||||||
if "pass" in var_name.lower():
|
g4_layout.addLayout(btn_row4, 1, 0, 1, 4)
|
||||||
w.configure(show="*")
|
layout.addWidget(g4)
|
||||||
w.grid(row=row, column=1, columnspan=3, sticky=tk.W, padx=2, pady=1)
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
ttk.Button(f, text="查询 IoT 网络", command=self._query_iot_net).grid(row=row, column=0, pady=5, padx=2, sticky=tk.W)
|
layout.addStretch()
|
||||||
ttk.Button(f, text="设置 IoT 网络", command=self._set_iot_net).grid(row=row, column=1, pady=5, padx=2, sticky=tk.W)
|
return w
|
||||||
row += 1
|
|
||||||
|
|
||||||
# -- Topic 配置 --
|
def _build_data_tab(self) -> QWidget:
|
||||||
ttk.Separator(f, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=4, sticky=tk.EW, pady=10)
|
w = QWidget()
|
||||||
row += 1
|
layout = QVBoxLayout(w)
|
||||||
ttk.Label(f, text="▸ IoT Topic 配置", font=("", 10, "bold")).grid(row=row, column=0, columnspan=4, sticky=tk.W, pady=(0, 5))
|
|
||||||
row += 1
|
|
||||||
self._var_cid_enable = tk.BooleanVar(value=True)
|
|
||||||
ttk.Checkbutton(f, text="Client ID 启用", variable=self._var_cid_enable).grid(row=row, column=0, columnspan=2, sticky=tk.W)
|
|
||||||
row += 1
|
|
||||||
for label, var_name in [("Topic 发布", "topic_pub"), ("Topic 订阅", "topic_sub")]:
|
|
||||||
ttk.Label(f, text=label + ":").grid(row=row, column=0, sticky=tk.E, padx=2, pady=1)
|
|
||||||
var = tk.StringVar()
|
|
||||||
setattr(self, f"_var_{var_name}", var)
|
|
||||||
ttk.Entry(f, textvariable=var, width=40).grid(row=row, column=1, columnspan=3, sticky=tk.W, padx=2, pady=1)
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
ttk.Button(f, text="查询 Topic", command=self._query_iot_topic).grid(row=row, column=0, pady=5, padx=2, sticky=tk.W)
|
layout.addWidget(QLabel("<b>线圈传感数据 (loop_data)</b>"))
|
||||||
ttk.Button(f, text="设置 Topic", command=self._set_iot_topic).grid(row=row, column=1, pady=5, padx=2, sticky=tk.W)
|
self._loop_text = QTextEdit()
|
||||||
row += 1
|
self._loop_text.setReadOnly(True)
|
||||||
|
self._loop_text.setFont(QFont("Consolas", 10))
|
||||||
|
layout.addWidget(self._loop_text, 2)
|
||||||
|
|
||||||
# -- 控制区 --
|
layout.addWidget(QLabel("<b>事件上报 (event_report)</b>"))
|
||||||
ttk.Separator(f, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=4, sticky=tk.EW, pady=10)
|
self._event_text = QTextEdit()
|
||||||
row += 1
|
self._event_text.setReadOnly(True)
|
||||||
ttk.Label(f, text="▸ 控制命令", font=("", 10, "bold")).grid(row=row, column=0, columnspan=4, sticky=tk.W, pady=(0, 5))
|
self._event_text.setFont(QFont("Consolas", 10))
|
||||||
row += 1
|
layout.addWidget(self._event_text, 1)
|
||||||
self._var_pwd = tk.StringVar()
|
return w
|
||||||
ttk.Label(f, text="密码:").grid(row=row, column=0, sticky=tk.E, padx=2)
|
|
||||||
ttk.Entry(f, textvariable=self._var_pwd, width=10, show="*").grid(row=row, column=1, sticky=tk.W, padx=2)
|
|
||||||
self._var_new_pwd = tk.StringVar()
|
|
||||||
ttk.Label(f, text="新密码:").grid(row=row, column=2, sticky=tk.E, padx=2)
|
|
||||||
ttk.Entry(f, textvariable=self._var_new_pwd, width=10, show="*").grid(row=row, column=3, sticky=tk.W, padx=2)
|
|
||||||
row += 1
|
|
||||||
ttk.Button(f, text="验证密码", command=self._do_pwd_verify).grid(row=row, column=0, pady=5, padx=2)
|
|
||||||
ttk.Button(f, text="设置密码", command=self._do_pwd_set).grid(row=row, column=1, pady=5, padx=2)
|
|
||||||
ttk.Button(f, text="出厂初始化", command=self._do_factory_reset).grid(row=row, column=2, pady=5, padx=2)
|
|
||||||
ttk.Button(f, text="设备复位", command=self._do_device_reset).grid(row=row, column=3, pady=5, padx=2)
|
|
||||||
|
|
||||||
return f
|
def _build_log_tab(self) -> QWidget:
|
||||||
|
w = QWidget()
|
||||||
|
layout = QVBoxLayout(w)
|
||||||
|
self._log_text = QTextEdit()
|
||||||
|
self._log_text.setReadOnly(True)
|
||||||
|
self._log_text.setFont(QFont("Consolas", 9))
|
||||||
|
layout.addWidget(self._log_text, 1)
|
||||||
|
|
||||||
def _add_data_tab(self):
|
btn_clear = QPushButton("清空")
|
||||||
f = ttk.Frame(self._notebook, padding=10)
|
btn_clear.clicked.connect(self._log_text.clear)
|
||||||
self._notebook.add(f, text="实时数据")
|
layout2 = QHBoxLayout()
|
||||||
|
layout2.addStretch()
|
||||||
# 线圈数据
|
layout2.addWidget(btn_clear)
|
||||||
ttk.Label(f, text="▸ 线圈传感数据 (loop_data)", font=("", 10, "bold")).pack(anchor=tk.W)
|
layout.addLayout(layout2)
|
||||||
self._loop_text = scrolledtext.ScrolledText(f, height=10, width=80, state=tk.DISABLED,
|
return w
|
||||||
font=("Consolas", 10))
|
|
||||||
self._loop_text.pack(fill=tk.BOTH, expand=True, pady=5)
|
|
||||||
|
|
||||||
# 事件
|
|
||||||
ttk.Label(f, text="▸ 事件上报 (event_report)", font=("", 10, "bold")).pack(anchor=tk.W)
|
|
||||||
self._event_text = scrolledtext.ScrolledText(f, height=6, width=80, state=tk.DISABLED,
|
|
||||||
font=("Consolas", 10))
|
|
||||||
self._event_text.pack(fill=tk.BOTH, expand=True, pady=5)
|
|
||||||
|
|
||||||
return f
|
|
||||||
|
|
||||||
def _add_log_tab(self):
|
|
||||||
f = ttk.Frame(self._notebook, padding=10)
|
|
||||||
self._notebook.add(f, text="日志")
|
|
||||||
|
|
||||||
self._log_text = scrolledtext.ScrolledText(f, height=18, width=60, state=tk.DISABLED,
|
|
||||||
font=("Consolas", 9))
|
|
||||||
self._log_text.pack(fill=tk.BOTH, expand=True)
|
|
||||||
|
|
||||||
ttk.Button(f, text="清空", command=self._clear_log).pack(anchor=tk.E, pady=5)
|
|
||||||
return f
|
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# MQTT 操作
|
# MQTT
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
def _toggle_connect(self):
|
def _toggle_connect(self):
|
||||||
if self._mqtt.connected:
|
if self._mqtt.connected:
|
||||||
self._mqtt.disconnect()
|
self._mqtt.disconnect()
|
||||||
else:
|
else:
|
||||||
config = BrokerConfig(
|
cfg = BrokerConfig(
|
||||||
host=self._host_var.get(),
|
host=self._host_edit.text(),
|
||||||
port=self._port_var.get(),
|
port=int(self._port_edit.text() or "1883"),
|
||||||
username=self._user_var.get(),
|
username=self._user_edit.text(),
|
||||||
password=self._pass_var.get(),
|
password=self._pass_edit.text(),
|
||||||
)
|
)
|
||||||
self._mqtt.connect(config)
|
self._mqtt.connect(cfg)
|
||||||
self._log(f"正在连接 {config.host}:{config.port} ...")
|
self._log(f"正在连接 {cfg.host}:{cfg.port} ...")
|
||||||
|
|
||||||
def _on_mqtt_status(self, connected: bool, msg: str):
|
@Slot(bool, str)
|
||||||
self.after(0, lambda: self._update_conn_status(connected, msg))
|
def _on_status(self, connected: bool, msg: str):
|
||||||
|
|
||||||
def _update_conn_status(self, connected: bool, msg: str):
|
|
||||||
if connected:
|
if connected:
|
||||||
self._btn_connect.configure(text="断开")
|
self._btn_conn.setText("断开")
|
||||||
self._conn_status.configure(text="● 已连接", foreground="green")
|
self._status_label.setText("● 已连接")
|
||||||
|
self._status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||||
else:
|
else:
|
||||||
self._btn_connect.configure(text="连接")
|
self._btn_conn.setText("连接")
|
||||||
self._conn_status.configure(text="● 未连接", foreground="red")
|
self._status_label.setText("● 未连接")
|
||||||
|
self._status_label.setStyleSheet("color: red; font-weight: bold;")
|
||||||
self._log(msg)
|
self._log(msg)
|
||||||
|
|
||||||
def _on_mqtt_message(self, topic: str, dev_serial: str, payload: dict):
|
@Slot(str, str, object)
|
||||||
self.after(0, lambda: self._handle_message(topic, dev_serial, payload))
|
def _on_message(self, topic: str, dev_serial: str, payload: dict):
|
||||||
|
|
||||||
def _handle_message(self, topic: str, dev_serial: str, payload: dict):
|
|
||||||
cmd = payload.get("cmd", "")
|
cmd = payload.get("cmd", "")
|
||||||
ts = datetime.fromtimestamp(payload.get("ts", 0)).strftime("%H:%M:%S")
|
ts = datetime.fromtimestamp(payload.get("ts", 0)).strftime("%H:%M:%S")
|
||||||
|
|
||||||
@@ -289,26 +352,25 @@ class DBNMqttTool(tk.Tk):
|
|||||||
if code == 0:
|
if code == 0:
|
||||||
if cmd == CMD_DEV_INFO_QUERY:
|
if cmd == CMD_DEV_INFO_QUERY:
|
||||||
self._devmgr.update_from_dev_info(dev_serial, data)
|
self._devmgr.update_from_dev_info(dev_serial, data)
|
||||||
self._show_info(data)
|
self._show_json(data)
|
||||||
elif cmd in (CMD_SSC_NET_QUERY, CMD_IOT_NET_QUERY, CMD_IOT_TOPIC_QUERY):
|
elif cmd in (CMD_SSC_NET_QUERY, CMD_IOT_NET_QUERY, CMD_IOT_TOPIC_QUERY, CMD_LOOP_PARAM_QUERY):
|
||||||
self._show_info({cmd: data})
|
self._show_json({cmd: data})
|
||||||
elif cmd == CMD_LOOP_PARAM_QUERY:
|
|
||||||
self._show_info({cmd: data})
|
|
||||||
else:
|
else:
|
||||||
self._show_info({"error": f"code={code} {pmsg} ({ERROR_MSGS.get(code, '?')})"})
|
self._show_json({"error": f"code={code} {pmsg} ({ERROR_MSGS.get(code, '?')})"})
|
||||||
|
|
||||||
elif "/dev/data/loop" in topic:
|
elif "/dev/data/loop" in topic:
|
||||||
data = payload.get("data", {})
|
data = payload.get("data", {})
|
||||||
self._devmgr.update_loop_data(dev_serial, data)
|
self._devmgr.update_loop_data(dev_serial, data)
|
||||||
self._log(f"[{ts}] {dev_serial} ← loop_data ({len(data.get('channels', []))} 通道)")
|
chs = len(data.get("channels", []))
|
||||||
self._append_data(self._loop_text, json.dumps(payload, indent=2, ensure_ascii=False))
|
self._log(f"[{ts}] {dev_serial} ← loop_data ({chs} 通道)")
|
||||||
|
self._append_text(self._loop_text, json.dumps(payload, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
elif "/dev/data/event" in topic:
|
elif "/dev/data/event" in topic:
|
||||||
data = payload.get("data", {})
|
data = payload.get("data", {})
|
||||||
self._devmgr.update_event(dev_serial, data)
|
self._devmgr.update_event(dev_serial, data)
|
||||||
events = data.get("events", [])
|
evt_count = len(data.get("events", []))
|
||||||
self._log(f"[{ts}] {dev_serial} ← event_report ({len(events)} 事件)")
|
self._log(f"[{ts}] {dev_serial} ← event_report ({evt_count} 事件)")
|
||||||
self._append_data(self._event_text, json.dumps(payload, indent=2, ensure_ascii=False))
|
self._append_text(self._event_text, json.dumps(payload, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
elif "/dev/status" in topic:
|
elif "/dev/status" in topic:
|
||||||
data = payload.get("data", {})
|
data = payload.get("data", {})
|
||||||
@@ -320,18 +382,16 @@ class DBNMqttTool(tk.Tk):
|
|||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
def _selected_dev(self) -> Optional[str]:
|
def _selected_dev(self) -> Optional[str]:
|
||||||
sel = self._dev_tree.selection()
|
items = self._dev_tree.selectedItems()
|
||||||
if sel:
|
return items[0].text(0).lstrip("●○ ") if items else None
|
||||||
return self._dev_tree.item(sel[0], "text")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _require_dev(self) -> Optional[str]:
|
def _require_dev(self) -> Optional[str]:
|
||||||
sn = self._selected_dev()
|
sn = self._selected_dev()
|
||||||
if not sn:
|
if not sn:
|
||||||
messagebox.showwarning("提示", "请先在左侧选择一个设备")
|
QMessageBox.warning(self, "提示", "请先选择一个设备")
|
||||||
return None
|
return None
|
||||||
if not self._mqtt.connected:
|
if not self._mqtt.connected:
|
||||||
messagebox.showwarning("提示", "请先连接 MQTT Broker")
|
QMessageBox.warning(self, "提示", "请先连接 MQTT Broker")
|
||||||
return None
|
return None
|
||||||
return sn
|
return sn
|
||||||
|
|
||||||
@@ -340,10 +400,10 @@ class DBNMqttTool(tk.Tk):
|
|||||||
if not sn:
|
if not sn:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
msg_id = self._mqtt.send_command(sn, cmd, data)
|
mid = self._mqtt.send_command(sn, cmd, data)
|
||||||
self._log(f"→ {sn} {cmd} (msg_id={msg_id})")
|
self._log(f"→ {sn} {cmd} (msg_id={mid})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("发送失败", str(e))
|
QMessageBox.critical(self, "发送失败", str(e))
|
||||||
|
|
||||||
def _query_dev_info(self):
|
def _query_dev_info(self):
|
||||||
self._send_cmd(CMD_DEV_INFO_QUERY)
|
self._send_cmd(CMD_DEV_INFO_QUERY)
|
||||||
@@ -364,12 +424,12 @@ class DBNMqttTool(tk.Tk):
|
|||||||
|
|
||||||
def _set_ssc_net(self):
|
def _set_ssc_net(self):
|
||||||
self._send_cmd(CMD_SSC_NET_SET, data_ssc_net_set(
|
self._send_cmd(CMD_SSC_NET_SET, data_ssc_net_set(
|
||||||
dev_ip=self._var_ssc_ip.get(),
|
dev_ip=self._edit_ssc_ip.text(),
|
||||||
subnet_mask=self._var_ssc_mask.get(),
|
subnet_mask=self._edit_ssc_mask.text(),
|
||||||
route_ip=self._var_ssc_gw.get(),
|
route_ip=self._edit_ssc_gw.text(),
|
||||||
lssc_ip=self._var_ssc_lssc.get(),
|
lssc_ip=self._edit_ssc_lssc.text(),
|
||||||
dns=self._var_ssc_dns.get(),
|
dns=self._edit_ssc_dns.text(),
|
||||||
port=int(self._var_ssc_port.get() or "0"),
|
port=int(self._edit_ssc_port.text() or "0"),
|
||||||
))
|
))
|
||||||
|
|
||||||
def _query_iot_net(self):
|
def _query_iot_net(self):
|
||||||
@@ -377,11 +437,11 @@ class DBNMqttTool(tk.Tk):
|
|||||||
|
|
||||||
def _set_iot_net(self):
|
def _set_iot_net(self):
|
||||||
self._send_cmd(CMD_IOT_NET_SET, data_iot_net_set(
|
self._send_cmd(CMD_IOT_NET_SET, data_iot_net_set(
|
||||||
host=self._var_iot_host.get(),
|
host=self._edit_iot_host.text(),
|
||||||
port=int(self._var_iot_port.get() or "1883"),
|
port=int(self._edit_iot_port.text() or "1883"),
|
||||||
client_id=self._var_iot_cid.get(),
|
client_id=self._edit_iot_cid.text(),
|
||||||
username=self._var_iot_user.get(),
|
username=self._edit_iot_user.text(),
|
||||||
password=self._var_iot_pass.get(),
|
password=self._edit_iot_pass.text(),
|
||||||
))
|
))
|
||||||
|
|
||||||
def _query_iot_topic(self):
|
def _query_iot_topic(self):
|
||||||
@@ -389,60 +449,67 @@ class DBNMqttTool(tk.Tk):
|
|||||||
|
|
||||||
def _set_iot_topic(self):
|
def _set_iot_topic(self):
|
||||||
self._send_cmd(CMD_IOT_TOPIC_SET, data_iot_topic_set(
|
self._send_cmd(CMD_IOT_TOPIC_SET, data_iot_topic_set(
|
||||||
client_id_enable=self._var_cid_enable.get(),
|
client_id_enable=self._chk_cid.isChecked(),
|
||||||
topic_pub=self._var_topic_pub.get(),
|
topic_pub=self._edit_topic_pub.text(),
|
||||||
topic_sub=self._var_topic_sub.get(),
|
topic_sub=self._edit_topic_sub.text(),
|
||||||
))
|
))
|
||||||
|
|
||||||
def _do_pwd_verify(self):
|
def _do_pwd_verify(self):
|
||||||
pwd = self._var_pwd.get()
|
pwd = self._edit_pwd.text()
|
||||||
if not pwd:
|
if not pwd:
|
||||||
messagebox.showwarning("提示", "请输入密码")
|
QMessageBox.warning(self, "提示", "请输入密码")
|
||||||
return
|
return
|
||||||
self._send_cmd(CMD_PWD_VERIFY, data_pwd_verify(pwd))
|
self._send_cmd(CMD_PWD_VERIFY, data_pwd_verify(pwd))
|
||||||
|
|
||||||
def _do_pwd_set(self):
|
def _do_pwd_set(self):
|
||||||
old = self._var_pwd.get()
|
old = self._edit_pwd.text()
|
||||||
new = self._var_new_pwd.get()
|
new = self._edit_new_pwd.text()
|
||||||
if not old or not new:
|
if not old or not new:
|
||||||
messagebox.showwarning("提示", "请输入旧密码和新密码")
|
QMessageBox.warning(self, "提示", "请输入旧密码和新密码")
|
||||||
return
|
return
|
||||||
self._send_cmd(CMD_PWD_SET, data_pwd_set(old, new))
|
self._send_cmd(CMD_PWD_SET, data_pwd_set(old, new))
|
||||||
|
|
||||||
def _do_factory_reset(self):
|
def _do_factory_reset(self):
|
||||||
if not messagebox.askyesno("确认", "确定要恢复出厂设置吗?此操作不可撤销!"):
|
r = QMessageBox.question(self, "确认", "确定要恢复出厂设置吗?\n此操作不可撤销!",
|
||||||
return
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
self._send_cmd(CMD_FACTORY_RESET)
|
if r == QMessageBox.Yes:
|
||||||
|
self._send_cmd(CMD_FACTORY_RESET)
|
||||||
|
|
||||||
def _do_device_reset(self):
|
def _do_device_reset(self):
|
||||||
if not messagebox.askyesno("确认", "确定要复位设备吗?设备将重启。"):
|
r = QMessageBox.question(self, "确认", "确定要复位设备吗?设备将重启。",
|
||||||
return
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
self._send_cmd(CMD_DEVICE_RESET)
|
if r == QMessageBox.Yes:
|
||||||
|
self._send_cmd(CMD_DEVICE_RESET)
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# UI 更新
|
# UI 更新
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
def _refresh_device_list(self):
|
@Slot()
|
||||||
|
def _refresh_devices(self):
|
||||||
tree = self._dev_tree
|
tree = self._dev_tree
|
||||||
current = set(tree.get_children(""))
|
existing = {}
|
||||||
devices = self._devmgr.devices
|
for i in range(tree.topLevelItemCount()):
|
||||||
|
item = tree.topLevelItem(i)
|
||||||
|
sn = item.text(0).lstrip("●○ ")
|
||||||
|
existing[sn] = item
|
||||||
|
|
||||||
for sn, dev in devices.items():
|
for sn, dev in self._devmgr.devices.items():
|
||||||
iid = sn
|
|
||||||
status = "●" if dev.online else "○"
|
status = "●" if dev.online else "○"
|
||||||
display = f"{status} {sn}"
|
|
||||||
model = dev.model or "?"
|
model = dev.model or "?"
|
||||||
if iid in current:
|
if sn in existing:
|
||||||
tree.item(iid, text=display, values=(model,))
|
existing[sn].setText(0, f"{status} {sn}")
|
||||||
current.discard(iid)
|
existing[sn].setText(1, model)
|
||||||
|
del existing[sn]
|
||||||
else:
|
else:
|
||||||
tree.insert("", tk.END, iid=iid, text=display, values=(model,))
|
item = QTreeWidgetItem([f"{status} {sn}", model])
|
||||||
|
tree.addTopLevelItem(item)
|
||||||
|
|
||||||
for stale in current:
|
for sn, item in existing.items():
|
||||||
tree.delete(stale)
|
idx = tree.indexOfTopLevelItem(item)
|
||||||
|
tree.takeTopLevelItem(idx)
|
||||||
|
|
||||||
def _on_device_select(self, event):
|
def _on_dev_select(self):
|
||||||
sn = self._selected_dev()
|
sn = self._selected_dev()
|
||||||
if sn and sn in self._devmgr.devices:
|
if sn and sn in self._devmgr.devices:
|
||||||
dev = self._devmgr.devices[sn]
|
dev = self._devmgr.devices[sn]
|
||||||
@@ -458,40 +525,28 @@ class DBNMqttTool(tk.Tk):
|
|||||||
"online": dev.online,
|
"online": dev.online,
|
||||||
"last_seen": dev.last_seen.strftime("%Y-%m-%d %H:%M:%S") if dev.last_seen else "—",
|
"last_seen": dev.last_seen.strftime("%Y-%m-%d %H:%M:%S") if dev.last_seen else "—",
|
||||||
}
|
}
|
||||||
self._show_info(info)
|
self._show_json(info)
|
||||||
|
|
||||||
def _show_info(self, data: dict):
|
def _show_json(self, data: dict):
|
||||||
self._info_text.configure(state=tk.NORMAL)
|
self._info_text.setPlainText(json.dumps(data, indent=2, ensure_ascii=False))
|
||||||
self._info_text.delete("1.0", tk.END)
|
|
||||||
self._info_text.insert(tk.END, json.dumps(data, indent=2, ensure_ascii=False))
|
|
||||||
self._info_text.configure(state=tk.DISABLED)
|
|
||||||
|
|
||||||
def _append_data(self, widget: scrolledtext.ScrolledText, text: str):
|
def _append_text(self, widget: QTextEdit, text: str):
|
||||||
widget.configure(state=tk.NORMAL)
|
|
||||||
ts = datetime.now().strftime("%H:%M:%S")
|
ts = datetime.now().strftime("%H:%M:%S")
|
||||||
widget.insert(tk.END, f"--- {ts} ---\n{text}\n\n")
|
widget.append(f"--- {ts} ---\n{text}\n")
|
||||||
widget.see(tk.END)
|
widget.moveCursor(QTextCursor.End)
|
||||||
widget.configure(state=tk.DISABLED)
|
|
||||||
|
|
||||||
def _log(self, msg: str):
|
def _log(self, msg: str):
|
||||||
ts = datetime.now().strftime("%H:%M:%S")
|
ts = datetime.now().strftime("%H:%M:%S")
|
||||||
line = f"[{ts}] {msg}"
|
self._log_text.append(f"[{ts}] {msg}")
|
||||||
self._log_lines.append(line)
|
self._log_text.moveCursor(QTextCursor.End)
|
||||||
self._log_text.configure(state=tk.NORMAL)
|
|
||||||
self._log_text.insert(tk.END, line + "\n")
|
|
||||||
self._log_text.see(tk.END)
|
|
||||||
self._log_text.configure(state=tk.DISABLED)
|
|
||||||
|
|
||||||
def _clear_log(self):
|
|
||||||
self._log_text.configure(state=tk.NORMAL)
|
|
||||||
self._log_text.delete("1.0", tk.END)
|
|
||||||
self._log_text.configure(state=tk.DISABLED)
|
|
||||||
self._log_lines.clear()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = DBNMqttTool()
|
app = QApplication(sys.argv)
|
||||||
app.mainloop()
|
app.setStyle("Fusion")
|
||||||
|
win = MainWindow()
|
||||||
|
win.show()
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user