feat: Improve terminal logging and add build script

main
Kirubakaran 2025-08-22 21:53:21 +05:30
parent c296932450
commit d660cc67f7
4 changed files with 91 additions and 27 deletions

View File

@ -2,6 +2,7 @@
import socket import socket
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
import uuid
class MqttClient(QObject): class MqttClient(QObject):
# --- MODIFIED SIGNAL: Now sends a bool and a string --- # --- MODIFIED SIGNAL: Now sends a bool and a string ---

57
generate_exe.py Normal file
View File

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

View File

@ -3,6 +3,7 @@ import json
import os import os
import sys import sys
import time import time
import uuid
from functools import partial from functools import partial
from PyQt6.QtCore import pyqtSignal, QThread, Qt, QPropertyAnimation, QEasingCurve, QSettings, pyqtSlot from PyQt6.QtCore import pyqtSignal, QThread, Qt, QPropertyAnimation, QEasingCurve, QSettings, pyqtSlot
@ -1061,6 +1062,7 @@ class MainWindow(QMainWindow):
decoded_payload = periodicData() decoded_payload = periodicData()
decoded_payload.ParseFromString(payload) decoded_payload.ParseFromString(payload)
data_dict = MessageToDict(decoded_payload, preserving_proto_field_name=True) data_dict = MessageToDict(decoded_payload, preserving_proto_field_name=True)
# print(data_dict)
self._log_periodic_to_terminal(decoded_payload, data_dict) self._log_periodic_to_terminal(decoded_payload, data_dict)
self.update_main_dashboard(data_dict) self.update_main_dashboard(data_dict)
if self.save_logs_checkbox.isChecked(): if self.save_logs_checkbox.isChecked():
@ -1314,6 +1316,7 @@ class MainWindow(QMainWindow):
print("✅ Configuration saved.") print("✅ Configuration saved.")
# ========================================================== # ==========================================================
client_id = f"SwapStationDashboard-{str(uuid.uuid4())}"
self.mqtt_thread = QThread() self.mqtt_thread = QThread()
self.mqtt_client = MqttClient(broker, port, user, password, client_id) self.mqtt_client = MqttClient(broker, port, user, password, client_id)
self.mqtt_client.moveToThread(self.mqtt_thread) self.mqtt_client.moveToThread(self.mqtt_thread)
@ -1428,36 +1431,52 @@ class MainWindow(QMainWindow):
except Exception as e: except Exception as e:
print(f"Error updating dashboard: {e}") print(f"Error updating dashboard: {e}")
def _log_periodic_to_terminal(self, decoded_payload, data_dict): def _log_periodic_to_terminal(self, decoded_payload, data_dict):
"""Formats and prints the periodic data to the terminal as a clean table.""" """Formats and prints the periodic data to the terminal as a clean table."""
try: try:
current_time = datetime.datetime.fromtimestamp(decoded_payload.ts).strftime('%Y-%m-%d %H:%M:%S') current_time = datetime.datetime.fromtimestamp(decoded_payload.ts).strftime('%Y-%m-%d %H:%M:%S')
device_id = data_dict.get("deviceId", "N/A") 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 --- # --- Main Information ---
print("\n\033[1m" + "="*50 + " PERIODIC DATA " + "="*50 + "\033[0m") 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 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 Backup Supply:\033[0m {backup_supply} | \033[1mStation SDC:\033[0m {station_sdc}")
print("-" * 120) print("-" * 120)
# --- Table Header --- # --- Table Header ---
header = "| {:^7} | {:^18} | {:^8} | {:^8} | {:^7} | {:^10} | {:^10} | {:^12} | {:^10} |" header = "| {:^7} | {:^18} | {:^8} | {:^8} | {:^7} | {:^10} | {:^10} | {:^10} | {:^10} | {:^10} |"
print(header.format("Chamber", "Battery ID", "Present", "Charging", "SOC", "Voltage", "Current", "Temp (°C)", "Door")) print(header.format("Chamber", "Battery ID", "Present", "Charging", "SOC", "Voltage", "Current", "Slot Temp", "Bat Temp", "Door"))
print("-" * 120) print("-" * 120)
# --- Table Rows --- # --- Table Rows ---
row_format = "| {:^7} | {:<18} | {:^8} | {:^8} | {:>5}% | {:>8} V | {:>8} A | {:>10}°C | {:^10} |" row_format = "| {:^7} | {:<18} | {:^8} | {:^8} | {:>5}% | {:>8}V | {:>8}A | {:>8}°C | {:>8}°C | {:^10} |"
for i, chamber in enumerate(data_dict.get("slotLevelPayload", []), start=1): 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( print(row_format.format(
i, i,
chamber.get('batteryIdentification', 'N/A'), chamber_data.get('batteryIdentification', 'N/A'),
"" if chamber.get("batteryPresent") == 1 else "", "" if is_present else "",
"" if chamber.get("chargingStatus") == 1 else "", "" if is_charging else "",
chamber.get('soc', 'N/A'), chamber_data.get('soc', 'N/A'),
chamber.get('batVoltage', 'N/A'), chamber_data.get('batVoltage', 'N/A'),
chamber.get('current', 'N/A'), chamber_data.get('current', 'N/A'),
chamber.get('batteryTemp', 'N/A'), slot_temp_celsius,
"OPEN" if chamber.get("doorStatus") == 1 else "CLOSED" battery_temp_celsius,
"Open" if is_door_open else "Closed"
)) ))
print("=" * 120 + "\n") print("=" * 120 + "\n")

View File

@ -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)