SwapStation_Dashboard/core/mqtt_client.py

145 lines
5.7 KiB
Python

# In core/mqtt_client.py
import socket
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
import paho.mqtt.client as mqtt
import uuid
class MqttClient(QObject):
# --- MODIFIED SIGNAL: Now sends a bool and a string ---
connection_status_changed = pyqtSignal(bool, str)
message_received = pyqtSignal(str, bytes)
connection_error = pyqtSignal(str)
stop_logging_signal = pyqtSignal()
connected = pyqtSignal()
disconnected = pyqtSignal()
def __init__(self, broker, port, user, password, client_id):
super().__init__()
self.broker = broker
self.port = port
self._is_connected = False
self._reported_bad_creds = False
self._suppress_next_disconnect_notice = False
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id)
if user and password:
self.client.username_pw_set(user, password)
self.client.on_connect = self.on_connect
self.client.on_disconnect = self.on_disconnect
self.client.on_message = self.on_message
# self.client.on_subscribe = self.on_subscribe
def on_connect(self, client, userdata, flags, rc, properties=None, *args, **kwargs):
if rc == 0:
# success
self._is_connected = True
self._ever_connected = True
self._suppress_next_disconnect_notice = False
print("Connection to MQTT Broker successful!")
self.connection_status_changed.emit(True, "✅ Connected")
self.connected.emit()
else:
# auth or other failure — log ONCE, and suppress the auto "Disconnected" message that will follow
msg = "Bad user name or password" if rc == 5 else f"Connection failed (Code: {rc})"
print(f"Failed to connect: {msg}")
self.connection_status_changed.emit(False, msg)
# make sure we do NOT show a "Disconnected" notice right after this
self._suppress_next_disconnect_notice = True
# stop any retry loop immediately
try:
client.disconnect()
client.loop_stop()
except Exception:
pass
def on_disconnect(self, client, userdata, rc, properties=None, *args, **kwargs):
self._is_connected = False
if self._suppress_next_disconnect_notice or not self._ever_connected:
self._suppress_next_disconnect_notice = False
return
print("Disconnected from MQTT Broker.")
self.disconnected.emit()
self.connection_status_changed.emit(False, "Disconnected")
def on_message(self, client, userdata, msg):
# print(f"Received {len(msg.payload)} bytes of binary data from topic `{msg.topic}`")
self.message_received.emit(msg.topic, msg.payload)
# --- MODIFIED connect_to_broker METHOD ---
def connect_to_broker(self):
print(f"Attempting to connect to {self.broker}:{self.port}...")
try:
self.client.connect(self.broker, self.port, 120)
self.client.loop_start()
except socket.gaierror:
msg = "Host not found. Check internet."
print(f"❌ Connection Error: {msg}")
self.connection_status_changed.emit(False, f"{msg}")
except (socket.error, TimeoutError):
msg = "Connection failed. Server offline?"
print(f"❌ Connection Error: {msg}")
self.connection_status_changed.emit(False, f"{msg}")
except Exception as e:
msg = f"An unexpected error occurred: {e}"
print(f"{msg}")
self.connection_status_changed.emit(False, f"❌ Error")
def run(self):
"""
Connects to the broker and starts the network loop.
Handles all common connection errors gracefully.
"""
print(f"Attempting to connect to {self.broker}:{self.port}...")
try:
# 1. Attempt to connect
# print(f"Attempting to connect to {self.broker}:{self.port}...")
self.client.connect(self.broker, self.port, 120)
# 2. Run the blocking network loop
# This will run until self.client.disconnect() is called
self.client.loop_forever()
except socket.gaierror:
msg = "Host not found. Check the broker address or your internet connection."
print(f"{msg}")
self.connection_error.emit(msg) # Report error to the main window
except (socket.error, ConnectionRefusedError):
msg = "Connection refused. Is the server offline or the port incorrect?"
print(f"{msg}")
self.connection_error.emit(msg)
except TimeoutError:
msg = "Connection timed out. The server is not responding."
print(f"{msg}")
self.connection_error.emit(msg)
except Exception as e:
# Catch any other unexpected errors during connection or loop
msg = f"An unexpected error occurred: {e}"
print(f"{msg}")
self.connection_error.emit(msg)
@pyqtSlot()
def disconnect_from_broker(self):
"""Stops the MQTT client's network loop."""
if self.client:
self.client.loop_stop()
self.client.disconnect()
print("Stopping MQTT network loop.")
def subscribe_to_topic(self, topic): # Add qos parameter
print(f"Subscribing to topic: {topic}")
self.client.subscribe(topic)
def publish_message(self, topic, payload):
self.client.publish(topic, payload)
def cleanup(self):
print("Stopping MQTT network loop.")
self.client.loop_stop()