feat: updated the styles

main
Kirubakaran 2025-08-27 00:33:05 +05:30
parent 9f8efb8aeb
commit 77ff85c541
6 changed files with 172 additions and 64 deletions

View File

@ -24,7 +24,7 @@ class CsvLogger(QObject):
self.num_slots = 9
def start_logging(self):
self.timer.start(100)
self.timer.start(10)
print(f"✅ CSV logging service started for session: {self.session_name}")
def _get_writer(self, device_id, file_group):

View File

@ -30,30 +30,32 @@ class MqttClient(QObject):
def on_connect(self, client, userdata, flags, rc, properties):
if rc == 0:
print("Connection to MQTT Broker successful!")
# --- MODIFIED EMIT: Send a success message ---
self.connection_status_changed.emit(True, "✅ Connected")
self.connected.emit()
# The app is connected, so we should NOT emit disconnected signals here.
else:
print(f"Failed to connect, return code {rc}\n")
# --- MODIFIED EMIT: Send a failure message ---
self.connection_status_changed.emit(False, f"❌ Connection failed (Code: {rc})")
self.stop_logging_signal.emit() # Emit the signal
self.disconnected.emit() # Connection failed, so we are disconnected
def on_disconnect(self, client, userdata, flags, rc, properties):
print("Disconnected from MQTT Broker.")
# Correctly emit signals for a disconnection
# Change the icon in the line below from 🔌 to 🔴 ❌ 🚫 💔
self.connection_status_changed.emit(False, "💔 Disconnected")
self.disconnected.emit()
self.stop_logging_signal.emit() # It's appropriate to stop logging here
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)
def on_disconnect(self, client, userdata, flags, rc, properties):
print("Disconnected from MQTT Broker.")
# Change the icon in the line below from 🔌 to 🔴 ❌ 🚫 💔
self.connection_status_changed.emit(False, "💔 Disconnected")
self.disconnected.emit()
# --- 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, 60)
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."
@ -77,7 +79,8 @@ class MqttClient(QObject):
print(f"Attempting to connect to {self.broker}:{self.port}...")
try:
# 1. Attempt to connect
self.client.connect(self.broker, self.port, 60)
# 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
@ -129,4 +132,5 @@ class MqttClient(QObject):
def cleanup(self):
print("Stopping MQTT network loop.")
self.client.loop_stop()
self.client.loop_stop()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

View File

@ -43,7 +43,7 @@ class MainWindow(QMainWindow):
super().__init__()
# self.setWindowIcon(QIcon("logo/v_logo.png"))
self.scale_factor = scale_factor
self.setWindowTitle("Battery Swap Station Dashboard v4.1")
self.setWindowTitle("Battery Swap Station Dashboard v4.2")
self.setWindowIcon(QIcon(resource_path("assets/icon.ico")))
self.settings = QSettings("VECMOCON", "BatterySwapDashboard")
@ -355,7 +355,7 @@ class MainWindow(QMainWindow):
title_box.addWidget(subtitle)
header.addLayout(title_box, 1)
badge = QLabel("Version 4.1")
badge = QLabel("Version 4.2")
badge.setObjectName("badge")
header.addWidget(badge, 0, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
@ -721,10 +721,15 @@ class MainWindow(QMainWindow):
default_log_filename = f"Station_Mqtt_Dashboard_log_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.csv"
# self.log_filename_input = QLineEdit(default_log_filename)
log_dir_layout = QHBoxLayout()
script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
script_dir = self.get_base_path()
default_log_dir = os.path.join(script_dir, "logs")
self.log_dir_input = QLineEdit(default_log_dir)
browse_btn = QPushButton("Browse")
# Add these lines after them to disable them:
self.log_dir_input.setEnabled(False)
self.log_dir_input.setToolTip("This is now set automatically to a 'logs' folder next to the application.")
browse_btn.clicked.connect(self.select_log_directory)
log_dir_layout.addWidget(self.log_dir_input)
log_dir_layout.addWidget(browse_btn)
@ -785,7 +790,7 @@ class MainWindow(QMainWindow):
top_bar_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum))
# top_bar_layout.addWidget(QLabel("Backup Supply:"))
self.backup_supply_indicator = QLabel("N/A")
self.backup_supply_indicator = QLabel("Backup")
self.backup_supply_indicator.setFixedSize(80, 25)
self.backup_supply_indicator.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.backup_supply_indicator.setStyleSheet(
@ -1133,11 +1138,11 @@ class MainWindow(QMainWindow):
click_count = self.swap_button_clicks[chamber_num]
if click_count == 1:
self.swap_sequence.append(chamber_num)
sender.setStyleSheet("background-color: #27ae60;")
sender.setStyleSheet("background-color: #05abd0;")
elif click_count == 2:
self.swap_sequence.append(chamber_num)
sender.setText(f"{chamber_num}")
sender.setStyleSheet("background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #27ae60, stop:0.5 #27ae60, stop:0.51 #2ecc71, stop:1 #2ecc71);")
sender.setStyleSheet("background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #1f7a9a, stop:0.5 #1f7a9a, stop:0.51 #1f7a9a, stop:1 #1f7a9a);")
elif click_count >= 3:
self.swap_sequence = [num for num in self.swap_sequence if num != chamber_num]
self.swap_button_clicks[chamber_num] = 0
@ -1286,7 +1291,7 @@ class MainWindow(QMainWindow):
self.mqtt_thread.wait()
if self.save_logs_checkbox.isChecked():
self.start_csv_logger()
self.reset_dashboard_ui()
broker = self.broker_input.text()
user = self.username_input.text()
password = self.password_input.text()
@ -1331,11 +1336,23 @@ class MainWindow(QMainWindow):
if self.mqtt_client: self.mqtt_client.disconnect_from_broker()
def start_csv_logger(self):
base_log_dir = self.log_dir_input.text()
# REMOVED: Your old path finding method is not reliable for packaged apps.
# script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# default_log_dir = os.path.join(script_dir, "logs")
# <-- MODIFIED: Use the correct, reliable method to get the app's base directory
app_dir = self.get_base_path()
# print(f"Application base directory: {app_dir}")
# Create a 'logs' sub-directory for better organization
base_log_dir = os.path.join(app_dir, 'logs')
# Ensure the log directory exists
os.makedirs(base_log_dir, exist_ok=True)
session_folder_name = f"session_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
self.logger_thread = QThread()
# Pass BOTH the base directory and the session name to the logger
self.csv_logger = CsvLogger(base_log_dir, session_folder_name)
self.csv_logger.moveToThread(self.logger_thread)
@ -1343,6 +1360,16 @@ class MainWindow(QMainWindow):
self.logger_thread.started.connect(self.csv_logger.start_logging)
self.logger_thread.start()
def get_base_path(self):
"""Gets the base path for the application, whether it's a script or a frozen exe."""
if getattr(sys, 'frozen', False):
# If packaged, the base path is the directory of the executable
base_path = os.path.dirname(sys.executable)
else:
# If run as a normal script, the base path is the script's directory
base_path = os.path.dirname(os.path.abspath(__file__))
return base_path
def stop_csv_logger(self):
if self.logger_thread and self.logger_thread.isRunning():
self.csv_logger.stop_logging()
@ -1391,6 +1418,7 @@ class MainWindow(QMainWindow):
for i, slot_data in enumerate(slot_payloads):
if i < len(self.chamber_widgets):
self.chamber_widgets[i].update_data(slot_data)
# print("Updating chamber", i+1, slot_data)
if (i+1) in self.swap_buttons:
is_present = slot_data.get("batteryPresent") == 1
self.swap_buttons[i+1].setStyleSheet("background-color: #2ecc71;" if is_present else "")

View File

@ -111,9 +111,12 @@ def get_light_theme_styles(scale=1.0):
font-weight: bold;
border-radius: {int(4*scale)}px;
}}
#ChamberOpenDoorButton:hover {{ background-color: #607d8b; }}
#ChamberChgOnButton:hover {{ background-color: #52be80; }}
#ChamberChgOffButton:hover {{ background-color: #cd6155; }}
#ChamberOpenDoorButton {{ background-color: #607d8b; }}
#ChamberChgOnButton {{ background-color: #52be80; }}
#ChamberChgOffButton {{ background-color: #cd6155; }}
#ChamberOpenDoorButton:hover {{ background-color: #485c64; }}
#ChamberChgOnButton:hover {{ background-color: #04d45d; }}
#ChamberChgOffButton:hover {{ background-color: #d42318; }}
/* Status & alarms */
QLabel[status="present"] {{ background-color: #2ecc71; color: white; border-radius: {int(4*scale)}px; padding: {int(3*scale)}px; }}
@ -331,12 +334,12 @@ def get_dark_theme_styles(scale=1.0):
font-weight: bold;
border-radius: {int(4*scale)}px;
}}
#ChamberOpenDoorButton {{ background-color: #3C3C3C; }}
#ChamberChgOnButton {{ background-color: #3C3C3C; }}
#ChamberChgOffButton {{ background-color: #3C3C3C; }}
#ChamberOpenDoorButton:hover {{ background-color: #607d8b; }}
#ChamberChgOnButton:hover {{ background-color: #52be80; }}
#ChamberChgOffButton:hover {{ background-color: #cd6155; }}
#ChamberOpenDoorButton {{ background-color: #607d8b; }}
#ChamberChgOnButton {{ background-color: #52be80; }}
#ChamberChgOffButton {{ background-color: #cd6155; }}
#ChamberOpenDoorButton:hover {{ background-color: #485c64; }}
#ChamberChgOnButton:hover {{ background-color: #04d45d; }}
#ChamberChgOffButton:hover {{ background-color: #d42318; }}
#ConnectButton, #DisconnectButton {{
padding: {int(6 * scale)}px {int(16 * scale)}px;

View File

@ -1,13 +1,13 @@
# --- REPLACE the entire content of ui/widgets.py with this ---
from PyQt6.QtWidgets import (
QGroupBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QFrame, QPushButton, QFormLayout
QGroupBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QFrame, QPushButton, QFormLayout, QSizePolicy
)
from PyQt6.QtCore import Qt, pyqtSignal # <-- IMPORT pyqtSignal
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QFont
class ChamberWidget(QGroupBox):
# --- ADD SIGNALS HERE ---
# Signals for button presses
open_door_requested = pyqtSignal()
chg_on_requested = pyqtSignal()
chg_off_requested = pyqtSignal()
@ -20,21 +20,24 @@ class ChamberWidget(QGroupBox):
main_layout = QVBoxLayout(self)
main_layout.setSpacing(max(2, int(4 * scale)))
# --- Battery ID ---
id_layout = QHBoxLayout()
id_layout.addWidget(QLabel("BAT ID: "))
self.id_field = self._create_data_field(scale)
self.id_field.setObjectName("BatIdField") # Set object name for styling
self.id_field.setObjectName("BatIdField")
id_layout.addWidget(self.id_field)
main_layout.addLayout(id_layout)
# --- Main Data Columns ---
columns_layout = QHBoxLayout()
# == Left Column: Battery Info ==
battery_form_layout = QFormLayout()
battery_form_layout.setVerticalSpacing(max(2, int(4 * scale)))
self.battery_status_label = self._create_status_label("ABSENT", scale)
self.battery_status_label.setProperty("status", "absent")
# --- SET OBJECT NAMES FOR EACH DATA FIELD ---
self.soc_field = self._create_data_field(scale)
self.soc_field.setObjectName("DataField")
@ -46,7 +49,6 @@ class ChamberWidget(QGroupBox):
self.battery_fault_field = self._create_data_field(scale)
self.battery_fault_field.setObjectName("DataField")
# --- END OF OBJECT NAMES ---
battery_form_layout.addRow("Status:", self.battery_status_label)
battery_form_layout.addRow("SOC:", self.soc_field)
@ -54,35 +56,35 @@ class ChamberWidget(QGroupBox):
battery_form_layout.addRow("Temp:", self.temp_field)
battery_form_layout.addRow("Fault:", self.battery_fault_field)
# == Separator ==
separator_line = QFrame()
separator_line.setFrameShape(QFrame.Shape.VLine)
separator_line.setFrameShadow(QFrame.Shadow.Sunken)
# == Right Column: Charger Info ==
charger_form_layout = QFormLayout()
charger_form_layout.setVerticalSpacing(max(2, int(4 * scale)))
self.charger_status_label = self._create_status_label("OFF", scale)
self.charger_status_label.setProperty("status", "absent")
# --- SET OBJECT NAMES FOR EACH DATA FIELD ---
self.slot_temp_field = self._create_data_field(scale)
self.slot_temp_field.setObjectName("DataField")
self.chg_temp_field = self._create_data_field(scale)
self.chg_temp_field.setObjectName("DataField")
self.door_status_field = self._create_data_field(scale)
self.door_status_field.setObjectName("DoorStatusField")
self.charger_fault_field = self._create_data_field(scale)
self.charger_fault_field.setObjectName("DataField")
# --- END OF OBJECT NAMES ---
self.current_field = self._create_data_field(scale)
self.current_field.setObjectName("DataField")
self.slot_temp_field = self._create_data_field(scale)
self.slot_temp_field.setObjectName("DataField")
charger_form_layout.addRow("Chg Status:", self.charger_status_label)
charger_form_layout.addRow("Chg Temp:", self.chg_temp_field)
charger_form_layout.addRow("Slot Temp:", self.slot_temp_field)
charger_form_layout.addRow("Door Status:", self.door_status_field)
charger_form_layout.addRow("Fault:", self.charger_fault_field)
charger_form_layout.addRow("Current:", self.current_field)
charger_form_layout.addRow("Slot Temp:", self.slot_temp_field)
columns_layout.addLayout(battery_form_layout)
columns_layout.addWidget(separator_line)
@ -91,7 +93,17 @@ class ChamberWidget(QGroupBox):
main_layout.addStretch()
# --- Door status and buttons are on one line ---
button_layout = QHBoxLayout()
# Part 1: Door Status (fixed size on the left)
button_layout.addWidget(QLabel("Door Status:"))
self.door_status_field = self._create_data_field(scale)
self.door_status_field.setObjectName("DoorStatusField")
self.door_status_field.setMaximumWidth(int(140 * scale))
button_layout.addWidget(self.door_status_field)
# Part 2: Buttons
self.open_door_btn = QPushButton("OPEN DOOR")
self.chg_on_btn = QPushButton("CHG ON")
self.chg_off_btn = QPushButton("CHG OFF")
@ -107,9 +119,9 @@ class ChamberWidget(QGroupBox):
button_layout.addWidget(self.open_door_btn)
button_layout.addWidget(self.chg_on_btn)
button_layout.addWidget(self.chg_off_btn)
main_layout.addLayout(button_layout)
# ... (the rest of the class is unchanged) ...
def _create_status_label(self, text, scale):
label = QLabel(text)
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
@ -117,20 +129,25 @@ class ChamberWidget(QGroupBox):
return label
def _create_data_field(self, scale):
field = QLineEdit("N/A")
field = QLineEdit(" ")
field.setReadOnly(True)
field.setFont(QFont("Arial", max(7, int(8 * scale))))
return field
def update_data(self, data):
if data.get("batteryPresent") == 1:
# Retrieve battery and charger presence with fallback keys
battery_present_status = data.get("batteryPresent")
# <-- MODIFIED: Now checks for 'chargerPresent' key from your payload
charger_on_status = data.get("chargerPresent", data.get("chargerOn", data.get("chgStatus", 0)))
if battery_present_status == 1:
self.battery_status_label.setText("PRESENT")
self.battery_status_label.setProperty("status", "present")
else:
self.battery_status_label.setText("ABSENT")
self.battery_status_label.setProperty("status", "absent")
if data.get("chargerPresent") == 1:
if charger_on_status == 1:
self.charger_status_label.setText("CHARGER ON")
self.charger_status_label.setProperty("status", "present")
else:
@ -141,17 +158,70 @@ class ChamberWidget(QGroupBox):
widget.style().unpolish(widget)
widget.style().polish(widget)
self.id_field.setText(data.get("batteryIdentification", "N/A"))
self.id_field.setText(data.get("batteryIdentification", " "))
self.soc_field.setText(f'{data.get("soc", 0)}%')
self.voltage_field.setText(f'{data.get("voltage", 0) / 1000.0:.2f} V')
self.temp_field.setText(f'{data.get("batteryMaxTemp", 0) / 10.0:.1f} °C')
self.battery_fault_field.setText(str(data.get("batteryFaultCode", 0)))
self.slot_temp_field.setText(f'{data.get("slotTemperature", 0) / 10.0:.1f} °C')
self.chg_temp_field.setText(f'{data.get("chargerTemp", 0) / 10.0:.1f} °C')
# <-- MODIFIED: Now checks for 'chargerMaxTemp' key from your payload
charger_temp = data.get("chargerMaxTemp", data.get("chargerTemp", data.get("chgTemp", 0)))
self.chg_temp_field.setText(f'{charger_temp / 10.0:.1f} °C')
self.charger_fault_field.setText(str(data.get("chargerFaultCode", 0)))
self.current_field.setText(f'{data.get("current", 0) / 1000.0:.2f} A')
slot_temp = data.get("slotTemperature", data.get("slotTemp", 0))
self.slot_temp_field.setText(f'{slot_temp / 10.0:.1f} °C')
door_status = "OPEN" if data.get("doorStatus") == 1 else "CLOSED"
self.door_status_field.setText(door_status)
if door_status == "OPEN":
self.door_status_field.setStyleSheet("background-color: #2E7D32; color: white; border-radius: 3px;")
else:
self.door_status_field.setStyleSheet("background-color: #C62828; color: white; border-radius: 3px;")
# def update_data(self, data):
# if data.get("batteryPresent") == 1:
# self.battery_status_label.setText("PRESENT")
# self.battery_status_label.setProperty("status", "present")
# else:
# self.battery_status_label.setText("ABSENT")
# self.battery_status_label.setProperty("status", "absent")
# if data.get("chargerOn") == 1:
# self.charger_status_label.setText("CHARGER ON")
# self.charger_status_label.setProperty("status", "present")
# else:
# self.charger_status_label.setText("CHARGER OFF")
# self.charger_status_label.setProperty("status", "absent")
# for widget in [self.battery_status_label, self.charger_status_label]:
# widget.style().unpolish(widget)
# widget.style().polish(widget)
# self.id_field.setText(data.get("batteryIdentification", " "))
# self.soc_field.setText(f'{data.get("soc", 0)}%')
# self.voltage_field.setText(f'{data.get("voltage", 0) / 1000.0:.2f} V')
# self.temp_field.setText(f'{data.get("batteryMaxTemp", 0) / 10.0:.1f} °C')
# self.battery_fault_field.setText(str(data.get("batteryFaultCode", 0)))
# self.chg_temp_field.setText(f'{data.get("chargerTemp", 0) / 10.0:.1f} °C')
# self.charger_fault_field.setText(str(data.get("chargerFaultCode", 0)))
# self.current_field.setText(f'{data.get("current", 0) / 1000.0:.2f} A')
# self.slot_temp_field.setText(f'{data.get("slotTemperature", 0) / 10.0:.1f} °C')
# door_status = "OPEN" if data.get("doorStatus") == 1 else "CLOSED"
# self.door_status_field.setText(door_status)
# # --- ADDED: Set color based on door status ---
# if door_status == "OPEN":
# self.door_status_field.setStyleSheet("background-color: #2E7D32; color: white; border-radius: 3px;")
# else: # CLOSED
# self.door_status_field.setStyleSheet("background-color: #C62828; color: white; border-radius: 3px;")
def reset_to_default(self):
"""Resets all fields in this chamber widget to their default state."""
self.battery_status_label.setText("ABSENT")
@ -159,17 +229,20 @@ class ChamberWidget(QGroupBox):
self.charger_status_label.setText("CHARGER OFF")
self.charger_status_label.setProperty("status", "absent")
# Re-apply the stylesheet for the status labels
for widget in [self.battery_status_label, self.charger_status_label]:
widget.style().unpolish(widget)
widget.style().polish(widget)
self.id_field.setText("N/A")
self.soc_field.setText("N/A")
self.voltage_field.setText("N/A")
self.temp_field.setText("N/A")
self.battery_fault_field.setText("N/A")
self.slot_temp_field.setText("N/A")
self.chg_temp_field.setText("N/A")
self.door_status_field.setText("N/A")
self.charger_fault_field.setText("N/A")
self.id_field.setText(" ")
self.soc_field.setText(" ")
self.voltage_field.setText(" ")
self.temp_field.setText(" ")
self.battery_fault_field.setText(" ")
self.chg_temp_field.setText(" ")
self.charger_fault_field.setText(" ")
self.current_field.setText(" ")
self.slot_temp_field.setText(" ")
self.door_status_field.setText(" ")
# --- ADDED: Reset the color ---
self.door_status_field.setStyleSheet("")