# 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()