diff --git a/core/mqtt_client.py b/core/mqtt_client.py index 0620158..d7783ec 100644 --- a/core/mqtt_client.py +++ b/core/mqtt_client.py @@ -2,6 +2,7 @@ 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 --- diff --git a/generate_exe.py b/generate_exe.py new file mode 100644 index 0000000..7170581 --- /dev/null +++ b/generate_exe.py @@ -0,0 +1,57 @@ +import subprocess +import sys + +def generate_executable(): + """Prompts for a version and generates a single-file executable.""" + + # 1. Ask the user for the version number + version = "" + while not version: + version = input("Enter the version for the executable (e.g., 4.1): ") + if not version: + print("Version cannot be empty. Please try again.") + + executable_name = f"SwapStationDashboard_v{version}" + print(f"Generating executable with name: {executable_name}") + + # Check if pyinstaller is installed + try: + subprocess.run([sys.executable, "-m", "PyInstaller", "--version"], check=True, capture_output=True) + except (subprocess.CalledProcessError, FileNotFoundError): + print("PyInstaller is not found. Please install it using: pip install pyinstaller") + return + + print("Starting executable generation...") + + # 2. Use the version to create the command + command = [ + sys.executable, + "-m", "PyInstaller", + f"--name={executable_name}", + "--onefile", + "--icon=assets/icon.ico", + "--add-data=logo;logo", + "--add-data=assets;assets", + "--add-data=proto;proto", + "--hidden-import=paho.mqtt", + "--hidden-import=google.protobuf", + "--hidden-import=PyQt6", + "--hidden-import=PyQt6.Qt6", + "--hidden-import=PyQt6.sip", + "--hidden-import=setuptools", + "main.py" + ] + + try: + # 3. Execute the command + subprocess.run(command, check=True) + print("\n✅ Executable generated successfully!") + print(f"Look for '{executable_name}.exe' in the 'dist' folder.") + except subprocess.CalledProcessError as e: + print("\n❌ An error occurred during executable generation.") + print(f"Command failed with return code: {e.returncode}") + except FileNotFoundError: + print("\n❌ Error: The 'main.py' file was not found. Please run this script from the project's root directory.") + +if __name__ == "__main__": + generate_executable() \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py index 348f87b..1f0c6f3 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -3,6 +3,7 @@ import json import os import sys import time +import uuid from functools import partial from PyQt6.QtCore import pyqtSignal, QThread, Qt, QPropertyAnimation, QEasingCurve, QSettings, pyqtSlot @@ -1061,6 +1062,7 @@ class MainWindow(QMainWindow): decoded_payload = periodicData() decoded_payload.ParseFromString(payload) data_dict = MessageToDict(decoded_payload, preserving_proto_field_name=True) + # print(data_dict) self._log_periodic_to_terminal(decoded_payload, data_dict) self.update_main_dashboard(data_dict) if self.save_logs_checkbox.isChecked(): @@ -1314,6 +1316,7 @@ class MainWindow(QMainWindow): print("✅ Configuration saved.") # ========================================================== + client_id = f"SwapStationDashboard-{str(uuid.uuid4())}" self.mqtt_thread = QThread() self.mqtt_client = MqttClient(broker, port, user, password, client_id) self.mqtt_client.moveToThread(self.mqtt_thread) @@ -1428,36 +1431,52 @@ class MainWindow(QMainWindow): except Exception as e: print(f"Error updating dashboard: {e}") + def _log_periodic_to_terminal(self, decoded_payload, data_dict): """Formats and prints the periodic data to the terminal as a clean table.""" try: current_time = datetime.datetime.fromtimestamp(decoded_payload.ts).strftime('%Y-%m-%d %H:%M:%S') device_id = data_dict.get("deviceId", "N/A") + backup_supply = "ON" if decoded_payload.backupSupplyStatus == 1 else "OFF" + station_sdc = decoded_payload.stationDiagnosticCode # --- Main Information --- print("\n\033[1m" + "="*50 + " PERIODIC DATA " + "="*50 + "\033[0m") - print(f"\033[1m Timestamp:\033[0m {current_time} | \033[1mDevice ID:\033[0m {device_id}") - print(f"\033[1m Backup Supply:\033[0m {decoded_payload.backupSupplyStatus} | \033[1mStation SDC:\033[0m {decoded_payload.stationDiagnosticCode}") + print(f"\033[1m Timestamp:\033[0m {current_time} | \033[1mDevice ID:\033[0m {device_id}") + print(f"\033[1m Backup Supply:\033[0m {backup_supply} | \033[1mStation SDC:\033[0m {station_sdc}") print("-" * 120) # --- Table Header --- - header = "| {:^7} | {:^18} | {:^8} | {:^8} | {:^7} | {:^10} | {:^10} | {:^12} | {:^10} |" - print(header.format("Chamber", "Battery ID", "Present", "Charging", "SOC", "Voltage", "Current", "Temp (°C)", "Door")) + header = "| {:^7} | {:^18} | {:^8} | {:^8} | {:^7} | {:^10} | {:^10} | {:^10} | {:^10} | {:^10} |" + print(header.format("Chamber", "Battery ID", "Present", "Charging", "SOC", "Voltage", "Current", "Slot Temp", "Bat Temp", "Door")) print("-" * 120) # --- Table Rows --- - row_format = "| {:^7} | {:<18} | {:^8} | {:^8} | {:>5}% | {:>8} V | {:>8} A | {:>10}°C | {:^10} |" - for i, chamber in enumerate(data_dict.get("slotLevelPayload", []), start=1): + row_format = "| {:^7} | {:<18} | {:^8} | {:^8} | {:>5}% | {:>8}V | {:>8}A | {:>8}°C | {:>8}°C | {:^10} |" + for i, chamber_data in enumerate(data_dict.get("slotLevelPayload", []), start=1): + is_present = chamber_data.get("batteryPresent") == 1 + is_charging = chamber_data.get("chargingStatus") == 1 + is_door_open = chamber_data.get("doorStatus") == 1 + + # Get and format the Slot Temperature + slot_temp_raw = chamber_data.get('slotTemperature', 'N/A') + slot_temp_celsius = f"{slot_temp_raw / 10:.1f}" if isinstance(slot_temp_raw, int) else "N/A" + + # Get and format the Battery Temperature + battery_temp_raw = chamber_data.get('batteryMaxTemp', 'N/A') + battery_temp_celsius = f"{battery_temp_raw / 10:.1f}" if isinstance(battery_temp_raw, int) else "N/A" + print(row_format.format( i, - chamber.get('batteryIdentification', 'N/A'), - "✅" if chamber.get("batteryPresent") == 1 else "❌", - "✅" if chamber.get("chargingStatus") == 1 else "❌", - chamber.get('soc', 'N/A'), - chamber.get('batVoltage', 'N/A'), - chamber.get('current', 'N/A'), - chamber.get('batteryTemp', 'N/A'), - "OPEN" if chamber.get("doorStatus") == 1 else "CLOSED" + chamber_data.get('batteryIdentification', 'N/A'), + "✅" if is_present else "❌", + "✅" if is_charging else "❌", + chamber_data.get('soc', 'N/A'), + chamber_data.get('batVoltage', 'N/A'), + chamber_data.get('current', 'N/A'), + slot_temp_celsius, + battery_temp_celsius, + "Open" if is_door_open else "Closed" )) print("=" * 120 + "\n") diff --git a/utils.py b/utils.py deleted file mode 100644 index 5af210c..0000000 --- a/utils.py +++ /dev/null @@ -1,13 +0,0 @@ -import sys -import os - -def resource_path(relative_path): - """ Get absolute path to resource, works for dev and for PyInstaller """ - try: - # PyInstaller creates a temp folder and stores path in _MEIPASS - base_path = sys._MEIPASS - except Exception: - # If not running as a bundled exe, use the normal script path - base_path = os.path.abspath(".") - - return os.path.join(base_path, relative_path) \ No newline at end of file