feat: Improve terminal logging and add build script
parent
c296932450
commit
d660cc67f7
|
|
@ -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 ---
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
13
utils.py
13
utils.py
|
|
@ -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)
|
|
||||||
Loading…
Reference in New Issue