SwapStation_Dashboard/core/mqtt_client.py

231 lines
9.5 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.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)