mqtt_client.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. import paho.mqtt.client as mqtt
  2. import json
  3. import logging
  4. from typing import Callable
  5. class MQTTClient:
  6. def __init__(self, on_message_callback: Callable[[str, dict], None]):
  7. self.on_message_callback = on_message_callback
  8. self.client = mqtt.Client()
  9. self.client.on_connect = self._on_connect
  10. self.client.on_disconnect = self._on_disconnect
  11. self.client.on_message = self._on_message
  12. self.connected = False
  13. self._pending_topics: list = []
  14. def connect(self, broker: str, port: int = 1883):
  15. self.disconnect()
  16. try:
  17. logging.info(f"MQTT正在连接 {broker}:{port}")
  18. self.client.connect(broker, port, 60)
  19. self.client.loop_start()
  20. except Exception as e:
  21. logging.error(f"MQTT连接失败 {broker}:{port}: {e}")
  22. def disconnect(self):
  23. self.connected = False
  24. try:
  25. self.client.loop_stop()
  26. self.client.disconnect()
  27. except Exception:
  28. pass
  29. def subscribe(self, topics):
  30. self._pending_topics.extend([t for t in topics if t not in self._pending_topics])
  31. if not self.connected:
  32. return
  33. for topic in topics:
  34. self.client.subscribe(topic)
  35. def unsubscribe(self, topic):
  36. self._pending_topics = [t for t in self._pending_topics if t != topic]
  37. if not self.connected:
  38. return
  39. self.client.unsubscribe(topic)
  40. def _on_connect(self, client, userdata, flags, rc):
  41. if rc == 0:
  42. self.connected = True
  43. logging.info(f"MQTT已连接,正在订阅 {len(self._pending_topics)} 个topic")
  44. for topic in self._pending_topics:
  45. client.subscribe(topic)
  46. else:
  47. logging.warning(f"MQTT broker拒绝连接,返回码: {rc}")
  48. def _on_disconnect(self, client, userdata, rc):
  49. self.connected = False
  50. if rc != 0:
  51. logging.warning(f"MQTT意外断开,rc={rc}")
  52. def _on_message(self, client, userdata, msg):
  53. try:
  54. payload_str = msg.payload.decode('utf-8')
  55. if not payload_str.strip():
  56. return
  57. payload = json.loads(payload_str)
  58. if payload is not None:
  59. self.on_message_callback(msg.topic, payload)
  60. else:
  61. logging.debug("收到 null payload,忽略")
  62. except Exception as e:
  63. logging.exception(f"MQTT消息解析错误")