""" utilities_screen.py - Device Utilities Screen This module contains the UtilitiesScreen class which provides device management functionality including device scanning, firmware updates, driver installation, and purchased items management. """ from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, QMessageBox, QScrollArea, QTableWidget, QTableWidgetItem, QHeaderView, QProgressBar, QLineEdit, QAbstractItemView ) from PyQt5.QtCore import Qt, pyqtSignal, QTimer from PyQt5.QtGui import QPixmap, QFont, QIcon, QColor import os from src.config import UXUI_ASSETS, WINDOW_SIZE, BACKGROUND_COLOR, DongleModelMap from src.controllers.device_controller import DeviceController from src.services.device_service import check_available_device from ..config import FW_DIR class UtilitiesScreen(QWidget): """ Utilities Screen Class Provides device management functionality with two main pages: 1. Utilities Page: Device connection, firmware updates, driver installation 2. Purchased Items Page: Download and manage purchased AI models Signals: back_to_selection: Emitted when user clicks back button Attributes: device_controller (DeviceController): Controller for device operations current_page (str): Current page being displayed ("utilities" or "purchased_items") device_table (QTableWidget): Table displaying connected devices purchased_table (QTableWidget): Table displaying purchased items progress_bar (QProgressBar): Progress bar for operations status_label (QLabel): Status message display """ # Signals for navigation back_to_selection = pyqtSignal() def __init__(self, parent=None): """ Initialize the UtilitiesScreen. Args: parent: Optional parent widget. """ super().__init__(parent) self.device_controller = DeviceController(self) self.current_page = "utilities" # Track current page: "utilities" or "purchased_items" self.init_ui() def set_device_controller(self, device_controller): """ Replace the local DeviceController with a shared instance. Call this from AppController after all screens are created so that UtilitiesScreen and MainWindow share the same device connection state. Args: device_controller: Shared DeviceController instance from MainWindow. """ self.device_controller = device_controller def init_ui(self): """ Initialize the user interface. Creates the main layout with: - Header with navigation buttons and logo - Content container with switchable pages (utilities and purchased items) """ # Basic window setup self.setGeometry(100, 100, *WINDOW_SIZE) self.setStyleSheet("background-color: #F5F7FA;") # Main layout self.main_layout = QVBoxLayout(self) self.main_layout.setContentsMargins(20, 20, 20, 20) self.main_layout.setSpacing(20) # Header with back button and logo header_frame = self.create_header() self.main_layout.addWidget(header_frame) # Create main content container self.content_container = QFrame(self) self.content_container.setStyleSheet(""" QFrame { background-color: white; border-radius: 10px; border: 1px solid #E0E0E0; } """) content_layout = QVBoxLayout(self.content_container) content_layout.setContentsMargins(20, 20, 20, 20) content_layout.setSpacing(20) # Create containers for both pages self.utilities_page = QWidget() self.purchased_items_page = QWidget() # Set up utilities page self.setup_utilities_page() # Set up purchased items page self.setup_purchased_items_page() # Add pages to content container content_layout.addWidget(self.utilities_page) content_layout.addWidget(self.purchased_items_page) # Initially show utilities page self.utilities_page.show() self.purchased_items_page.hide() # Add content container to main layout self.main_layout.addWidget(self.content_container, 1) # Note: Auto-refresh disabled to prevent blocking # QTimer.singleShot(500, self.refresh_devices) def create_header(self): """ Create the header section with navigation elements. Builds a header frame containing: - Back button for returning to selection screen - Title label showing current page - Navigation buttons for switching between Utilities and Purchased Items pages - Kneron logo on the right side Returns: QFrame: The header frame widget. """ header_frame = QFrame(self) header_frame.setStyleSheet("background-color: #2C3E50; border-radius: 0px;") header_frame.setFixedHeight(60) header_layout = QHBoxLayout(header_frame) header_layout.setContentsMargins(20, 0, 20, 0) # Back button back_button = QPushButton("←", self) back_button.setFixedSize(40, 40) back_button.setStyleSheet(""" QPushButton { background-color: transparent; border: none; color: white; font-size: 20px; } QPushButton:hover { background-color: rgba(255, 255, 255, 0.1); border-radius: 20px; } """) back_button.clicked.connect(self.back_to_selection.emit) header_layout.addWidget(back_button, alignment=Qt.AlignLeft) # Title self.title_label = QLabel("Utilities", self) self.title_label.setStyleSheet("color: white; font-size: 24px; font-weight: bold;") header_layout.addWidget(self.title_label, alignment=Qt.AlignCenter) # Navigation buttons nav_container = QFrame() nav_layout = QHBoxLayout(nav_container) nav_layout.setContentsMargins(0, 0, 0, 0) nav_layout.setSpacing(10) # Utilities button self.utilities_button = QPushButton("Utilities", self) self.utilities_button.setStyleSheet(""" QPushButton { background-color: #3498DB; color: white; border: none; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:hover { background-color: #2980B9; } QPushButton:disabled { background-color: #3498DB; color: white; } """) self.utilities_button.clicked.connect(self.show_utilities_page) nav_layout.addWidget(self.utilities_button) # Purchased Items button self.purchased_items_button = QPushButton("Purchased Items", self) self.purchased_items_button.setStyleSheet(""" QPushButton { background-color: transparent; color: #BDC3C7; border: none; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:hover { color: white; } QPushButton:disabled { background-color: #3498DB; color: white; } """) self.purchased_items_button.clicked.connect(self.show_purchased_items_page) nav_layout.addWidget(self.purchased_items_button) header_layout.addWidget(nav_container) # Logo logo_label = QLabel(self) logo_path = os.path.join(UXUI_ASSETS, "Assets_png/kneron_logo.png") if os.path.exists(logo_path): logo_pixmap = QPixmap(logo_path) scaled_logo = logo_pixmap.scaled(104, 40, Qt.KeepAspectRatio, Qt.SmoothTransformation) logo_label.setPixmap(scaled_logo) header_layout.addWidget(logo_label, alignment=Qt.AlignRight) return header_frame def setup_utilities_page(self): """ Set up the utilities page layout. Creates the utilities page with: - Device connection section for managing connected devices - Status section showing operation progress and device status """ utilities_layout = QVBoxLayout(self.utilities_page) utilities_layout.setContentsMargins(0, 0, 0, 0) utilities_layout.setSpacing(20) # Device connection section device_section = self.create_device_section() utilities_layout.addWidget(device_section) # Status section status_section = self.create_status_section() utilities_layout.addWidget(status_section) def setup_purchased_items_page(self): """ Set up the purchased items page layout. Creates the page for displaying and downloading purchased AI models and packages from the Kneron store. """ purchased_items_layout = QVBoxLayout(self.purchased_items_page) purchased_items_layout.setContentsMargins(0, 0, 0, 0) purchased_items_layout.setSpacing(20) # Purchased items section purchased_items_section = self.create_purchased_items_section() purchased_items_layout.addWidget(purchased_items_section) def create_purchased_items_section(self): """ Create the purchased items section. Returns: QFrame: A frame containing the purchased items table and action buttons. """ purchased_section = QFrame() purchased_section.setStyleSheet(""" QFrame { background-color: white; border-radius: 8px; border: 1px solid #E0E0E0; } """) purchased_layout = QVBoxLayout(purchased_section) purchased_layout.setContentsMargins(15, 15, 15, 15) purchased_layout.setSpacing(15) # Title title_label = QLabel("Your Purchased Items", purchased_section) title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2C3E50;") purchased_layout.addWidget(title_label) # Description desc_label = QLabel("Select items to download to your device", purchased_section) desc_label.setStyleSheet("font-size: 14px; color: #7F8C8D;") purchased_layout.addWidget(desc_label) # Items table (5 columns, "Action" column removed) self.purchased_table = QTableWidget() self.purchased_table.setColumnCount(5) self.purchased_table.setHorizontalHeaderLabels([ "Select", "Product", "Model", "Current Version", "Compatible Dongles" ]) # Set column widths header = self.purchased_table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) # Checkbox column header.setSectionResizeMode(1, QHeaderView.Stretch) header.setSectionResizeMode(2, QHeaderView.Stretch) header.setSectionResizeMode(3, QHeaderView.ResizeToContents) header.setSectionResizeMode(4, QHeaderView.Stretch) # Set table height self.purchased_table.setMinimumHeight(300) # Enable row selection self.purchased_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.purchased_table.setStyleSheet(""" QTableWidget { background-color: white; color: #2C3E50; border: 1px solid #E0E0E0; border-radius: 8px; gridline-color: #E0E0E0; } QTableWidget::item { padding: 8px; border-bottom: 1px solid #E0E0E0; } QTableWidget::item:selected { background-color: #3498DB; color: white; } QHeaderView::section { background-color: #3498DB; color: white; padding: 8px; border: none; font-weight: bold; } """) purchased_layout.addWidget(self.purchased_table) # Add mock data for demonstration self.populate_mock_purchased_items() # Download buttons button_layout = QHBoxLayout() button_layout.setSpacing(10) refresh_button = QPushButton("Refresh Items", purchased_section) refresh_button.setMinimumHeight(40) refresh_button.setStyleSheet(""" QPushButton { background-color: #3498DB; color: white; border: none; border-radius: 5px; padding: 10px 15px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #2980B9; } QPushButton:pressed { background-color: #1F618D; } """) refresh_button.clicked.connect(self.populate_mock_purchased_items) button_layout.addWidget(refresh_button) download_button = QPushButton("Download Selected Items", purchased_section) download_button.setMinimumHeight(40) download_button.setStyleSheet(""" QPushButton { background-color: #2ECC71; color: white; border: none; border-radius: 5px; padding: 10px 15px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #27AE60; } QPushButton:pressed { background-color: #1E8449; } """) download_button.clicked.connect(self.download_selected_items) button_layout.addWidget(download_button) purchased_layout.addLayout(button_layout) return purchased_section def populate_mock_purchased_items(self): """ Populate the purchased items table with mock data. Clears the existing table and fills it with sample purchased items for demonstration purposes. In production, this would fetch real data from the server. """ # Clear the table self.purchased_table.setRowCount(0) # Mock data for demonstration mock_items = [ { "product": "KL720 AI Package", "model": "Face Detection", "version": "1.2.3", "dongles": "KL720, KL730" }, { "product": "KL520 AI Package", "model": "Object Detection", "version": "2.0.1", "dongles": "KL520" }, { "product": "KL720 AI Package", "model": "Pose Estimation", "version": "1.5.0", "dongles": "KL720, KL730, KL830" }, { "product": "KL630 AI Package", "model": "Image Classification", "version": "3.1.2", "dongles": "KL630, KL720" }, { "product": "KL830 AI Package", "model": "Semantic Segmentation", "version": "1.0.0", "dongles": "KL830" } ] # Add data to table for i, item in enumerate(mock_items): self.purchased_table.insertRow(i) # Create checkbox item checkbox_item = QTableWidgetItem() checkbox_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsSelectable) checkbox_item.setCheckState(Qt.Unchecked) self.purchased_table.setItem(i, 0, checkbox_item) self.purchased_table.setItem(i, 1, QTableWidgetItem(item["product"])) self.purchased_table.setItem(i, 2, QTableWidgetItem(item["model"])) self.purchased_table.setItem(i, 3, QTableWidgetItem(item["version"])) self.purchased_table.setItem(i, 4, QTableWidgetItem(item["dongles"])) def download_item(self, row): """ Download a specific item from the purchased items list. Args: row (int): The row index of the item to download. """ product = self.purchased_table.item(row, 1).text() model = self.purchased_table.item(row, 2).text() # Show progress bar self.show_progress(f"Downloading {product} - {model}...", 0) # Simulate download process for i in range(1, 11): progress = i * 10 QTimer.singleShot(i * 300, lambda p=progress: self.update_progress(p)) # Complete download QTimer.singleShot(3000, lambda: self.handle_download_complete(product, model)) def handle_download_complete(self, product, model): """ Handle download completion for a single item. Args: product (str): The product name that was downloaded. model (str): The model name that was downloaded. """ self.hide_progress() QMessageBox.information(self, "Download Complete", f"{product} - {model} has been downloaded successfully!") def download_selected_items(self): """ Download all selected/checked items from the purchased items list. Iterates through the table to find checked items and initiates download for each. Shows progress bar during the download process. """ selected_rows = set() # Check for selected items for row in range(self.purchased_table.rowCount()): if self.purchased_table.item(row, 0).checkState() == Qt.Checked: selected_rows.add(row) if not selected_rows: QMessageBox.warning(self, "No Selection", "Please select at least one item to download") return # Show progress bar self.show_progress(f"Downloading {len(selected_rows)} items...", 0) # Simulate download process total_items = len(selected_rows) for i, row in enumerate(selected_rows): product = self.purchased_table.item(row, 1).text() model = self.purchased_table.item(row, 2).text() progress = int((i / total_items) * 100) # Update progress bar self.update_progress(progress) self.progress_title.setText(f"Downloading {product} - {model}... ({i+1}/{total_items})") # Simulate download delay QTimer.singleShot((i+1) * 1000, lambda p=product, m=model: self.status_label.setText(f"Downloaded {p} - {m}")) # Complete all downloads QTimer.singleShot((total_items+1) * 1000, self.handle_all_downloads_complete) def update_download_progress(self, progress, message): """ Update the download progress display. Args: progress (int): The progress percentage (0-100). message (str): The status message to display. """ self.update_progress(progress) self.progress_title.setText(message) def handle_all_downloads_complete(self): """ Handle completion of all selected downloads. Hides the progress bar and shows a success message to the user. """ self.hide_progress() QMessageBox.information(self, "Downloads Complete", "All selected items have been downloaded successfully!") def create_device_section(self): """ Create the device connection section. Builds a section containing: - Device table showing connected Kneron devices with their details - Action buttons for refresh, register, update firmware, and install drivers Returns: QFrame: The device section frame widget. """ device_section = QFrame(self) device_section.setStyleSheet(""" QFrame { background-color: white; border-radius: 8px; border: 1px solid #E0E0E0; } """) device_layout = QVBoxLayout(device_section) device_layout.setContentsMargins(15, 15, 15, 15) device_layout.setSpacing(15) # Title title_label = QLabel("Device Connection", self) title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2C3E50;") device_layout.addWidget(title_label) # Description desc_label = QLabel("Connect and manage your Kneron devices", self) desc_label.setStyleSheet("font-size: 14px; color: #7F8C8D;") device_layout.addWidget(desc_label) # Create device table self.device_table = QTableWidget() self.device_table.setColumnCount(6) self.device_table.setHorizontalHeaderLabels([ "Device Type", "Port ID", "Firmware Version", "KN Number", "Link Speed", "Status" ]) # Enable row selection mode self.device_table.setSelectionBehavior(QTableWidget.SelectRows) self.device_table.setSelectionMode(QTableWidget.SingleSelection) self.device_table.setStyleSheet(""" QTableWidget { background-color: white; color: #2C3E50; border: 1px solid #E0E0E0; border-radius: 8px; gridline-color: #E0E0E0; } QTableWidget::item { padding: 8px; border-bottom: 1px solid #E0E0E0; } QTableWidget::item:selected { background-color: #3498DB; color: white; } QHeaderView::section { background-color: #3498DB; color: white; padding: 8px; border: none; font-weight: bold; } """) # Set header properties header = self.device_table.horizontalHeader() for i in range(6): header.setSectionResizeMode(i, QHeaderView.Stretch) # Add the table to a scroll area table_scroll = QScrollArea() table_scroll.setWidgetResizable(True) table_scroll.setStyleSheet(""" QScrollArea { border: none; background-color: transparent; } """) table_scroll.setWidget(self.device_table) device_layout.addWidget(table_scroll) # Connect selection changed signal self.device_table.itemSelectionChanged.connect(self.on_device_selection_changed) # Button layout button_layout = QHBoxLayout() button_layout.setSpacing(10) # Refresh button refresh_button = QPushButton("Refresh Devices", self) refresh_button.setMinimumHeight(40) refresh_button.setStyleSheet(""" QPushButton { background-color: #3498DB; color: white; border: 2px solid #2980B9; border-radius: 5px; padding: 10px 15px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #2980B9; } QPushButton:pressed { background-color: #1F618D; } """) refresh_button.clicked.connect(self.refresh_devices) button_layout.addWidget(refresh_button) # Register button register_button = QPushButton("Register Device", self) register_button.setMinimumHeight(40) register_button.setStyleSheet(""" QPushButton { background-color: #2ECC71; color: white; border: 2px solid #27AE60; border-radius: 5px; padding: 10px 15px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #27AE60; } QPushButton:pressed { background-color: #1E8449; } """) register_button.clicked.connect(self.register_device) button_layout.addWidget(register_button) # Update firmware button update_button = QPushButton("Update Firmware", self) update_button.setMinimumHeight(40) update_button.setStyleSheet(""" QPushButton { background-color: #F39C12; color: white; border: 2px solid #D35400; border-radius: 5px; padding: 10px 15px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #D35400; } QPushButton:pressed { background-color: #A04000; } """) update_button.clicked.connect(self.update_firmware) button_layout.addWidget(update_button) # Install Driver button install_driver_button = QPushButton("Install Driver", self) install_driver_button.setMinimumHeight(40) install_driver_button.setStyleSheet(""" QPushButton { background-color: #9B59B6; color: white; border: 2px solid #8E44AD; border-radius: 5px; padding: 10px 15px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #8E44AD; } QPushButton:pressed { background-color: #7D3C98; } """) install_driver_button.clicked.connect(self.install_drivers) button_layout.addWidget(install_driver_button) device_layout.addLayout(button_layout) return device_section def create_status_section(self): """ Create the status section for displaying operation status. Builds a section containing: - Status label showing current device status or operation result - Progress bar for long-running operations (hidden by default) Returns: QFrame: The status section frame widget. """ status_section = QFrame(self) status_section.setStyleSheet(""" QFrame { background-color: white; border-radius: 8px; border: 1px solid #E0E0E0; } """) status_layout = QVBoxLayout(status_section) status_layout.setContentsMargins(15, 15, 15, 15) status_layout.setSpacing(15) # Title title_label = QLabel("Device Status", self) title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2C3E50;") status_layout.addWidget(title_label) # Status message self.status_label = QLabel("No devices found", self) self.status_label.setStyleSheet("font-size: 14px; color: #7F8C8D;") status_layout.addWidget(self.status_label) # Progress section self.progress_section = QFrame(self) self.progress_section.setVisible(False) self.progress_section.setStyleSheet(""" QFrame { background-color: #F8F9FA; border-radius: 5px; border: 1px solid #E0E0E0; padding: 10px; } """) progress_layout = QVBoxLayout(self.progress_section) progress_layout.setContentsMargins(10, 10, 10, 10) progress_layout.setSpacing(10) self.progress_title = QLabel("Operation in progress...", self) self.progress_title.setStyleSheet("font-size: 14px; font-weight: bold; color: #2C3E50;") progress_layout.addWidget(self.progress_title) self.progress_bar = QProgressBar(self) self.progress_bar.setRange(0, 100) self.progress_bar.setValue(0) self.progress_bar.setTextVisible(True) self.progress_bar.setStyleSheet(""" QProgressBar { border: 1px solid #E0E0E0; border-radius: 5px; background-color: white; text-align: center; height: 20px; } QProgressBar::chunk { background-color: #3498DB; border-radius: 5px; } """) progress_layout.addWidget(self.progress_bar) status_layout.addWidget(self.progress_section) return status_section def refresh_devices(self): """ Refresh and scan for connected Kneron devices. Clears the device table and scans for available devices using the Kneron Plus SDK. Populates the table with device information including model, port ID, firmware version, KN number, link speed, and status. Returns: bool: True if devices were found, False otherwise. """ try: # Clear the table self.device_table.setRowCount(0) # Show progress self.show_progress("Scanning for devices...", 0) # Get the devices device_descriptors = check_available_device() # Update progress self.update_progress(50) # Display the devices in the table if device_descriptors.device_descriptor_number > 0: devices = device_descriptors.device_descriptor_list for i, device in enumerate(devices): self.device_table.insertRow(i) # Product ID to Device Model mapping product_id_hex = hex(device.product_id) # Map the product_id to device model name using DongleModelMap device_model = DongleModelMap.get(product_id_hex, product_id_hex) model_item = QTableWidgetItem(device_model) self.device_table.setItem(i, 0, model_item) # Device ID (Port ID) port_id = str(device.usb_port_id) usb_id = QTableWidgetItem(port_id) self.device_table.setItem(i, 1, usb_id) # Try to get firmware_version from system_info firmware_version = "-" try: if device.is_connectable: import kp # Connect to device and get system info device_group = kp.core.connect_devices(usb_port_ids=[port_id]) system_info = kp.core.get_system_info( device_group=device_group, usb_port_id=device.usb_port_id ) # Get firmware version from system_info object if system_info and hasattr(system_info, 'firmware_version'): # firmware_version is an object, get its string representation fw_version = system_info.firmware_version if hasattr(fw_version, 'firmware_version'): # Extract version number, remove dict format version_str = fw_version.firmware_version # If version is in dict format, extract the value if isinstance(version_str, dict) and 'firmware_version' in version_str: firmware_version = version_str['firmware_version'] else: firmware_version = version_str else: # Convert object to string and clean up format version_str = str(fw_version) # Try to extract version number from string import re match = re.search(r'"firmware_version":\s*"([^"]+)"', version_str) if match: firmware_version = match.group(1) else: firmware_version = version_str except Exception as e: print(f"Error getting firmware version: {e}") # Firmware firmware = QTableWidgetItem(firmware_version) self.device_table.setItem(i, 2, firmware) # KN Number kn_number = QTableWidgetItem(str(device.kn_number)) self.device_table.setItem(i, 3, kn_number) # Link Speed link_speed_str = "Unknown" if hasattr(device, 'link_speed'): # Extract SPEED_XXX part from full link_speed string full_speed = str(device.link_speed) if "SUPER" in full_speed: link_speed_str = "SUPER" elif "HIGH" in full_speed: link_speed_str = "HIGH" elif "FULL" in full_speed: link_speed_str = "FULL" else: # Try to extract KP_USB_SPEED_XXX part import re match = re.search(r'KP_USB_SPEED_(\w+)', full_speed) if match: link_speed_str = match.group(1) link_speed = QTableWidgetItem(link_speed_str) self.device_table.setItem(i, 4, link_speed) # Status status = QTableWidgetItem("Connected" if device.is_connectable else "Not Available") status.setForeground(Qt.green if device.is_connectable else Qt.red) self.device_table.setItem(i, 5, status) # Hide progress self.hide_progress() # Update status device_count = self.device_table.rowCount() if device_count > 0: self.status_label.setText(f"Found {device_count} device(s)") self.status_label.setStyleSheet("font-size: 14px; color: #27AE60; font-weight: bold;") else: self.status_label.setText("No devices found") self.status_label.setStyleSheet("font-size: 14px; color: #E74C3C;") return device_count > 0 except Exception as e: print(f"Error refreshing devices: {e}") self.hide_progress() self.status_label.setText(f"Error: {str(e)}") self.status_label.setStyleSheet("font-size: 14px; color: #E74C3C;") return False def register_device(self): """ Register the currently selected device. Checks if a device is selected and initiates the registration process. Currently shows a placeholder message as this feature is planned for a future update. """ selected_rows = self.device_table.selectedItems() if not selected_rows: QMessageBox.warning(self, "Warning", "Please select a device to register") return # In a real application, you would implement the device registration logic here QMessageBox.information(self, "Info", "Device registration functionality will be implemented in a future update") def update_firmware(self): """ Update firmware for the currently selected device. Loads and flashes the SCPU and NCPU firmware files to the selected Kneron device. Shows progress during the update process. Raises: Displays error message if no device is selected, firmware files are not found, or update fails. """ try: # Check if a device is selected selected_rows = self.device_table.selectionModel().selectedRows() if not selected_rows: QMessageBox.warning(self, "Warning", "Please select a device to update firmware") return # Get selected device information row_index = selected_rows[0].row() device_model = self.device_table.item(row_index, 0).text() # Device model port_id = self.device_table.item(row_index, 1).text() # Port ID # Show progress bar self.show_progress(f"Updating firmware for {device_model}...", 0) # Connect to device import kp device_group = kp.core.connect_devices(usb_port_ids=[int(port_id)]) # Build firmware file paths scpu_fw_path = os.path.join(FW_DIR, device_model, "fw_scpu.bin") ncpu_fw_path = os.path.join(FW_DIR, device_model, "fw_ncpu.bin") # Check if firmware files exist if not os.path.exists(scpu_fw_path) or not os.path.exists(ncpu_fw_path): self.hide_progress() QMessageBox.critical(self, "Error", f"Firmware files not found for {device_model}") return # Update progress self.update_progress(30) # Load firmware kp.core.load_firmware_from_file( device_group=device_group, scpu_fw_path=scpu_fw_path, ncpu_fw_path=ncpu_fw_path ) # Update progress self.update_progress(100) # Show success message QMessageBox.information(self, "Success", f"Firmware for {device_model} has been updated successfully") except Exception as e: self.hide_progress() QMessageBox.critical(self, "Error", f"Error updating firmware: {str(e)}") finally: self.hide_progress() def install_drivers(self): """ Install drivers for all supported Kneron devices. Iterates through all supported Kneron product IDs and installs the corresponding Windows drivers. Shows progress during installation. """ try: # Show progress bar self.show_progress("Installing Kneron Device Drivers...", 0) # List all product IDs import kp product_ids = [ kp.ProductId.KP_DEVICE_KL520, kp.ProductId.KP_DEVICE_KL720_LEGACY, kp.ProductId.KP_DEVICE_KL720, kp.ProductId.KP_DEVICE_KL630, kp.ProductId.KP_DEVICE_KL730, kp.ProductId.KP_DEVICE_KL830 ] success_count = 0 total_count = len(product_ids) # Install each driver for i, product_id in enumerate(product_ids): try: # Update progress bar progress = int((i / total_count) * 100) self.update_progress(progress) self.progress_title.setText(f"Installing [{product_id.name}] driver...") # Install driver kp.core.install_driver_for_windows(product_id=product_id) success_count += 1 # Update status message self.status_label.setText(f"Successfully installed {product_id.name} driver") except kp.ApiKPException as exception: error_msg = f"Error: install {product_id.name} driver failed, error msg: [{str(exception)}]" self.status_label.setText(error_msg) QMessageBox.warning(self, "Driver Installation Error", error_msg) # Complete installation self.update_progress(100) self.hide_progress() # Show result message if success_count == total_count: QMessageBox.information(self, "Success", "All Kneron device drivers installed successfully!") else: QMessageBox.information(self, "Partial Success", f"Installed {success_count} out of {total_count} drivers. Check the status for details.") except Exception as e: self.hide_progress() error_msg = f"Error during driver installation: {str(e)}" self.status_label.setText(error_msg) QMessageBox.critical(self, "Error", error_msg) def show_progress(self, title, value): """ Show the progress section with the specified title and initial value. Args: title (str): The title/message to display above the progress bar. value (int): The initial progress value (0-100). """ self.progress_title.setText(title) self.progress_bar.setValue(value) self.progress_section.setVisible(True) def update_progress(self, value): """ Update the progress bar to the specified value. Args: value (int): The progress value (0-100). """ self.progress_bar.setValue(value) def hide_progress(self): """Hide the progress section and reset it to default state.""" self.progress_section.setVisible(False) def on_device_selection_changed(self): """ Handle device selection changes in the device table. Updates the status label to show the currently selected device information and ensures the entire row is highlighted. """ selected_rows = self.device_table.selectionModel().selectedRows() if selected_rows: # Get the selected row index row_index = selected_rows[0].row() # Update the status label to show which device is selected device_model = self.device_table.item(row_index, 0).text() device_id = self.device_table.item(row_index, 1).text() self.status_label.setText(f"Selected device: {device_model} (ID: {device_id})") # Ensure the entire row is highlighted self.device_table.selectRow(row_index) def show_utilities_page(self): """ Switch to the utilities page view. Updates button styles to show utilities as active and purchased items as inactive. Shows the utilities page and hides the purchased items page. """ self.utilities_button.setStyleSheet(""" QPushButton { background-color: #3498DB; color: white; border: none; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:hover { background-color: #2980B9; } QPushButton:disabled { background-color: #3498DB; color: white; } """) self.purchased_items_button.setStyleSheet(""" QPushButton { background-color: transparent; color: #BDC3C7; border: none; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:hover { color: white; } QPushButton:disabled { background-color: #3498DB; color: white; } """) self.utilities_page.show() self.purchased_items_page.hide() self.title_label.setText("Utilities") self.current_page = "utilities" def show_purchased_items_page(self): """ Switch to the purchased items page view. Updates button styles to show purchased items as active and utilities as inactive. Shows the purchased items page and hides the utilities page. """ self.purchased_items_button.setStyleSheet(""" QPushButton { background-color: #3498DB; color: white; border: none; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:hover { background-color: #2980B9; } QPushButton:disabled { background-color: #3498DB; color: white; } """) self.utilities_button.setStyleSheet(""" QPushButton { background-color: transparent; color: #BDC3C7; border: none; border-radius: 5px; padding: 5px 10px; font-weight: bold; } QPushButton:hover { color: white; } QPushButton:disabled { background-color: #3498DB; color: white; } """) self.utilities_page.hide() self.purchased_items_page.show() self.title_label.setText("Purchased Items") self.current_page = "purchased_items"