# # 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.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): # if rc == 0: # self._is_connected = True # Set flag on success # print("Connection to MQTT Broker successful!") # self.connection_status_changed.emit(True, "✅ Connected") # self.connected.emit() # else: # # This block now handles the failure message, but not the disconnect signal # error_message = f"Connection failed (Code: {rc})" # if rc == 5: # error_message = "❌ Not Authorized: Check username and password." # print(f"Failed to connect: {error_message}") # self.connection_status_changed.emit(False, error_message) # # The on_disconnect callback will handle the disconnected signal # def on_disconnect(self, client, userdata, flags, rc, properties): # # --- MODIFIED: This entire block is now protected by the flag --- # if not self._is_connected and rc != 0: # # This is a connection failure, on_connect already handled the message # pass # else: # # This is a true disconnection from an active session # print("Disconnected from MQTT Broker.") # self.connection_status_changed.emit(False, "💔 Disconnected") # # This logic now runs only ONCE per disconnect/failure event # if self._is_connected or rc != 0: # self.disconnected.emit() # self._is_connected = False # 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) # print(f"Attempting to connect to {self.broker}:{self.port}...") # 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) # # def on_subscribe(self, client, userdata, mid, reason_code_list, properties): # # """Callback function for when the broker responds to a subscription request.""" # # if reason_code_list[0].is_failure: # # print(f"❌ Broker rejected subscription: {reason_code_list[0]}") # # else: # # print(f"✅ Broker accepted subscription with QoS: {reason_code_list[0].value}") # # --- (The rest of the file remains the same) --- # @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() # In core/mqtt_client.py import socket from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot import paho.mqtt.client as mqtt class MqttClient(QObject): # Sends connection state (bool) and a message (str) connection_status_changed = pyqtSignal(bool, str) # Generic signals for success or failure/disconnection connected = pyqtSignal() disconnected = pyqtSignal() # Sends topic (str) and payload (bytes) when a message is received message_received = pyqtSignal(str, bytes) def __init__(self, broker, port, user, password, client_id): super().__init__() self.broker = broker self.port = port self._is_connected = False # Flag to track connection state 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 def on_connect(self, client, userdata, flags, rc, properties): if rc == 0: self._is_connected = True print("Connection to MQTT Broker successful!") self.connection_status_changed.emit(True, "✅ Connected") self.connected.emit() else: # Report the specific error from the broker error_message = f"Connection failed (Code: {rc})" if rc == 5: error_message = "❌ Not Authorized: Check username and password." print(f"Failed to connect: {error_message}") self.connection_status_changed.emit(False, error_message) # The on_disconnect callback will handle the generic 'disconnected' signal def on_disconnect(self, client, userdata, flags, rc, properties): # Only show the generic "Disconnected" message if we were actually connected before. if self._is_connected: print("Disconnected from MQTT Broker.") self.connection_status_changed.emit(False, "💔 Disconnected") # Always emit the generic disconnected signal to trigger cleanup in the main window. self.disconnected.emit() self._is_connected = False def on_message(self, client, userdata, msg): self.message_received.emit(msg.topic, msg.payload) @pyqtSlot() def connect_to_broker(self): print(f"Attempting to connect to {self.broker}:{self.port}...") try: self.client.connect(self.broker, self.port, 60) self.client.loop_start() except Exception as e: # Catch any exception during the initial connect call msg = f"Connection Error: {e}" print(f"❌ {msg}") self.connection_status_changed.emit(False, msg) self.disconnected.emit() @pyqtSlot() def disconnect_from_broker(self): if self.client: self.client.loop_stop() self.client.disconnect() def subscribe_to_topic(self, topic): self.client.subscribe(topic) def publish_message(self, topic, payload): self.client.publish(topic, payload)