fix: MqttClient 回退信号方案,添加 publish debug 日志追踪阻塞点

- 移除 QObject 基类和 QueuedConnection 信号方案
- publish() 直接调用 self._client.publish() 并打印前后日志
- 添加 print(flush=True) 确保 Windows 终端实时输出

目的是确认阻塞发生在:
  [MqttClient] publish topic=... qos=1  ← 打印后消失?
  [MqttClient] publish done mid=...      ← 这行能否出现?
This commit is contained in:
wangfq
2026-07-07 17:27:15 +08:00
parent baa82c895d
commit c98aa5b0bb
2 changed files with 41 additions and 37 deletions
+35 -35
View File
@@ -3,12 +3,12 @@ MQTT 客户端封装
"""
import json
import time
import sys
import threading
from typing import Callable, Optional
from dataclasses import dataclass
import paho.mqtt.client as mqtt
from PySide6.QtCore import QObject, Signal, Slot, Qt
from .protocol import parse_topic_dev_serial
@@ -23,64 +23,67 @@ class BrokerConfig:
keepalive: int = 60
class MqttClient(QObject):
"""DLD960 IoT MQTT 客户端 — QObject 基类确保信号线程安全"""
# 供外部监听
_status_notify = Signal(bool, str) # connected, message
_msg_notify = Signal(str, str, object) # topic, dev_serial, payload_dict
# 内部线程安全的 publish 队列
_publish_sig = Signal(str, str, int) # topic, json_data, qos
class MqttClient:
"""DLD960 IoT MQTT 客户端"""
def __init__(self):
super().__init__()
self._client: Optional[mqtt.Client] = None
self._config = BrokerConfig()
self._connected = False
self._lock = threading.Lock()
# 信号→槽: publish 通过排队连接确保在主线程的 paho context 执行
self._publish_sig.connect(self._do_publish, Qt.QueuedConnection)
self._message_callbacks: list[Callable[[str, str, dict], None]] = []
self._status_callbacks: list[Callable[[bool, str], None]] = []
@property
def connected(self) -> bool:
return self._connected
# ---- 公开回调 (兼容旧接口) ----
def on_status_change(self, callback: Callable[[bool, str], None]):
self._status_notify.connect(callback)
self._status_callbacks.append(callback)
def on_message(self, callback: Callable[[str, str, dict], None]):
"""回调参数: (topic, dev_serial, payload_dict)"""
self._msg_notify.connect(callback)
self._message_callbacks.append(callback)
# ---- MQTT callbacks (paho 网络线程) ----
def _notify_status(self, connected: bool, msg: str):
for cb in self._status_callbacks:
try:
cb(connected, msg)
except Exception:
pass
def _notify_message(self, topic: str, payload: dict):
dev_serial = parse_topic_dev_serial(topic) or ""
for cb in self._message_callbacks:
try:
cb(topic, dev_serial, payload)
except Exception:
pass
# ---- MQTT callbacks ----
def _on_connect(self, client, userdata, flags, reason_code, properties=None):
rc = reason_code if isinstance(reason_code, int) else reason_code.value
if rc == 0:
self._connected = True
self._status_notify.emit(True, "已连接到 Broker")
self._notify_status(True, "已连接到 Broker")
from .protocol import TOPIC_ALL_DEVICE_UP
client.subscribe(TOPIC_ALL_DEVICE_UP, qos=1)
else:
self._connected = False
self._status_notify.emit(False, f"连接失败 (rc={rc})")
self._notify_status(False, f"连接失败 (rc={rc})")
def _on_disconnect(self, client, userdata, *args):
self._connected = False
self._status_notify.emit(False, "已断开连接")
self._notify_status(False, "已断开连接")
def _on_message(self, client, userdata, msg: mqtt.MQTTMessage):
try:
payload = json.loads(msg.payload.decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError):
payload = {"_raw": msg.payload.hex()}
dev_serial = parse_topic_dev_serial(msg.topic) or ""
self._msg_notify.emit(msg.topic, dev_serial, payload)
self._notify_message(msg.topic, payload)
# ---- Public API ----
@@ -116,11 +119,17 @@ class MqttClient(QObject):
self._connected = False
def publish(self, topic: str, payload: dict, qos: int = 1):
"""线程安全的发布 — 通过 Qt 信号排队到主线程执行"""
"""发送 JSON 消息到指定 topic"""
if not self._client or not self._connected:
raise ConnectionError("MQTT 未连接")
data = json.dumps(payload, ensure_ascii=False)
self._publish_sig.emit(topic, data, qos)
print(f"[MqttClient] publish topic={topic} qos={qos}", flush=True)
try:
info = self._client.publish(topic, data, qos=qos)
print(f"[MqttClient] publish done mid={info.mid}", flush=True)
except Exception as e:
print(f"[MqttClient] publish EXCEPTION: {e}", flush=True)
raise
def subscribe(self, topic: str, qos: int = 1):
"""订阅主题"""
@@ -132,15 +141,6 @@ class MqttClient(QObject):
if self._client:
self._client.unsubscribe(topic)
@Slot(str, str, int)
def _do_publish(self, topic: str, data: str, qos: int):
"""实际的 paho publish 调用 — 始终在主线程执行"""
if self._client and self._connected:
try:
self._client.publish(topic, data, qos=qos)
except Exception as e:
print(f"[MqttClient] publish error: {e}")
def send_command(self, dev_serial: str, cmd: str, data: Optional[dict] = None, qos: int = 1) -> int:
"""向设备发送命令,返回 msg_id"""
from .protocol import build_request, get_topic_for_cmd