forked from masonhuang/KNEO-Academy
- device_service: Add timeout mechanism for device scanning - video_thread: Add timeout mechanism for camera opening - device_list: Fix height and hide scrollbars to prevent scroll issues - mainWindows: Adjust UI startup order, delay device refresh and camera start - utilities_screen: Temporarily disable auto refresh to prevent blocking - .gitignore: Add new ignore entries 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1037 lines
40 KiB
Python
1037 lines
40 KiB
Python
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
|
||
import kp
|
||
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):
|
||
# Signals for navigation
|
||
back_to_selection = pyqtSignal()
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.device_controller = DeviceController(self)
|
||
self.current_page = "utilities" # 追蹤當前頁面: "utilities" 或 "purchased_items"
|
||
self.init_ui()
|
||
|
||
def init_ui(self):
|
||
# Basic window setup
|
||
self.setGeometry(100, 100, *WINDOW_SIZE)
|
||
self.setStyleSheet(f"background-color: #F5F7FA;") # Light gray background
|
||
|
||
# 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)
|
||
|
||
# 創建主要內容容器
|
||
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)
|
||
|
||
# 創建兩個頁面的容器
|
||
self.utilities_page = QWidget()
|
||
self.purchased_items_page = QWidget()
|
||
|
||
# 設置 utilities 頁面
|
||
self.setup_utilities_page()
|
||
|
||
# 設置 purchased items 頁面
|
||
self.setup_purchased_items_page()
|
||
|
||
# 添加頁面到內容容器
|
||
content_layout.addWidget(self.utilities_page)
|
||
content_layout.addWidget(self.purchased_items_page)
|
||
|
||
# 初始顯示 utilities 頁面
|
||
self.utilities_page.show()
|
||
self.purchased_items_page.hide()
|
||
|
||
# 添加內容容器到主佈局
|
||
self.main_layout.addWidget(self.content_container, 1)
|
||
|
||
# Initialize with device refresh (暫時禁用自動刷新,避免阻塞)
|
||
# QTimer.singleShot(500, self.refresh_devices)
|
||
|
||
def create_header(self):
|
||
"""Create the header with back button and logo"""
|
||
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.setIcon(QIcon(os.path.join(UXUI_ASSETS, "Assets_png/back_arrow.png")))
|
||
back_button.setIconSize(QPixmap(os.path.join(UXUI_ASSETS, "Assets_png/back_arrow.png")).size())
|
||
back_button.setFixedSize(40, 40)
|
||
back_button.setStyleSheet("""
|
||
QPushButton {
|
||
background-color: transparent;
|
||
border: none;
|
||
}
|
||
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):
|
||
"""Create the utilities page"""
|
||
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):
|
||
"""Create the purchased items page"""
|
||
purchased_items_layout = QVBoxLayout(self.purchased_items_page)
|
||
purchased_items_layout.setContentsMargins(0, 0, 0, 0)
|
||
purchased_items_layout.setSpacing(20)
|
||
|
||
# 已購買項目區域
|
||
purchased_items_section = self.create_purchased_items_section()
|
||
purchased_items_layout.addWidget(purchased_items_section)
|
||
|
||
def create_purchased_items_section(self):
|
||
"""創建已購買項目區域"""
|
||
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_label = QLabel("Your Purchased Items", purchased_section)
|
||
title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2C3E50;")
|
||
purchased_layout.addWidget(title_label)
|
||
|
||
# 描述
|
||
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)
|
||
|
||
# 項目表格
|
||
self.purchased_table = QTableWidget()
|
||
# 修改為只有5列,移除 "Action" 列
|
||
self.purchased_table.setColumnCount(5)
|
||
self.purchased_table.setHorizontalHeaderLabels([
|
||
"Select", "Product", "Model", "Current Version", "Compatible Dongles"
|
||
])
|
||
|
||
# 設置行寬
|
||
header = self.purchased_table.horizontalHeader()
|
||
header.setSectionResizeMode(0, QHeaderView.ResizeToContents) # 勾選框列
|
||
header.setSectionResizeMode(1, QHeaderView.Stretch)
|
||
header.setSectionResizeMode(2, QHeaderView.Stretch)
|
||
header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
||
header.setSectionResizeMode(4, QHeaderView.Stretch)
|
||
|
||
# 設置表格高度
|
||
self.purchased_table.setMinimumHeight(300)
|
||
|
||
# 啟用整行選擇
|
||
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)
|
||
|
||
# 添加一些模擬數據
|
||
self.populate_mock_purchased_items()
|
||
|
||
# 下載按鈕
|
||
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):
|
||
"""填充模擬的已購買項目數據"""
|
||
# 清空表格
|
||
self.purchased_table.setRowCount(0)
|
||
|
||
# 模擬數據
|
||
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"
|
||
}
|
||
]
|
||
|
||
# 添加數據到表格
|
||
for i, item in enumerate(mock_items):
|
||
self.purchased_table.insertRow(i)
|
||
|
||
# 創建勾選框
|
||
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):
|
||
"""下載特定項目"""
|
||
product = self.purchased_table.item(row, 1).text()
|
||
model = self.purchased_table.item(row, 2).text()
|
||
|
||
# 顯示進度條
|
||
self.show_progress(f"Downloading {product} - {model}...", 0)
|
||
|
||
# 模擬下載過程
|
||
for i in range(1, 11):
|
||
progress = i * 10
|
||
QTimer.singleShot(i * 300, lambda p=progress: self.update_progress(p))
|
||
|
||
# 完成下載
|
||
QTimer.singleShot(3000, lambda: self.handle_download_complete(product, model))
|
||
|
||
def handle_download_complete(self, product, model):
|
||
"""處理下載完成"""
|
||
self.hide_progress()
|
||
QMessageBox.information(self, "Download Complete", f"{product} - {model} has been downloaded successfully!")
|
||
|
||
def download_selected_items(self):
|
||
"""下載所有勾選的項目"""
|
||
selected_rows = set()
|
||
|
||
# 檢查勾選的項目
|
||
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
|
||
|
||
# 顯示進度條
|
||
self.show_progress(f"Downloading {len(selected_rows)} items...", 0)
|
||
|
||
# 模擬下載過程
|
||
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)
|
||
|
||
# 更新進度條
|
||
self.update_progress(progress)
|
||
self.progress_title.setText(f"Downloading {product} - {model}... ({i+1}/{total_items})")
|
||
|
||
# 模擬下載延遲
|
||
QTimer.singleShot((i+1) * 1000, lambda p=product, m=model: self.status_label.setText(f"Downloaded {p} - {m}"))
|
||
|
||
# 完成所有下載
|
||
QTimer.singleShot((total_items+1) * 1000, self.handle_all_downloads_complete)
|
||
|
||
def update_download_progress(self, progress, message):
|
||
"""更新下載進度"""
|
||
self.update_progress(progress)
|
||
self.progress_title.setText(message)
|
||
|
||
def handle_all_downloads_complete(self):
|
||
"""處理所有下載完成"""
|
||
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"""
|
||
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"""
|
||
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 the list of devices"""
|
||
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)
|
||
|
||
# 嘗試獲取 system_info 中的 firmware_version
|
||
firmware_version = "-"
|
||
try:
|
||
if device.is_connectable:
|
||
# 連接設備並獲取系統信息
|
||
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
|
||
)
|
||
# 從 system_info 對象獲取固件版本
|
||
if system_info and hasattr(system_info, 'firmware_version'):
|
||
# firmware_version 是一個對象,獲取其字符串表示
|
||
fw_version = system_info.firmware_version
|
||
if hasattr(fw_version, 'firmware_version'):
|
||
# 提取版本號,移除字典格式
|
||
version_str = fw_version.firmware_version
|
||
# 如果版本是字典格式,提取其中的值
|
||
if isinstance(version_str, dict) and 'firmware_version' in version_str:
|
||
firmware_version = version_str['firmware_version']
|
||
else:
|
||
firmware_version = version_str
|
||
else:
|
||
# 將對象轉換為字符串並清理格式
|
||
version_str = str(fw_version)
|
||
# 嘗試從字符串中提取版本號
|
||
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'):
|
||
# 從完整的 link_speed 字符串中提取 SPEED_XXX 部分
|
||
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:
|
||
# 嘗試提取 KP_USB_SPEED_XXX 部分
|
||
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 selected device"""
|
||
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 selected device"""
|
||
try:
|
||
# 檢查是否有選擇設備
|
||
selected_rows = self.device_table.selectionModel().selectedRows()
|
||
if not selected_rows:
|
||
QMessageBox.warning(self, "警告", "請選擇要更新韌體的設備")
|
||
return
|
||
|
||
# 獲取選擇的設備資訊
|
||
row_index = selected_rows[0].row()
|
||
device_model = self.device_table.item(row_index, 0).text() # 設備型號
|
||
port_id = self.device_table.item(row_index, 1).text() # Port ID
|
||
|
||
# 顯示進度條
|
||
self.show_progress(f"正在更新 {device_model} 的韌體...", 0)
|
||
|
||
# 連接設備
|
||
device_group = kp.core.connect_devices(usb_port_ids=[int(port_id)])
|
||
|
||
# 構建韌體檔案路徑
|
||
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")
|
||
|
||
# 檢查韌體檔案是否存在
|
||
if not os.path.exists(scpu_fw_path) or not os.path.exists(ncpu_fw_path):
|
||
self.hide_progress()
|
||
QMessageBox.critical(self, "錯誤", f"找不到 {device_model} 的韌體檔案")
|
||
return
|
||
|
||
# 更新進度
|
||
self.update_progress(30)
|
||
|
||
# 載入韌體
|
||
kp.core.load_firmware_from_file(
|
||
device_group=device_group,
|
||
scpu_fw_path=scpu_fw_path,
|
||
ncpu_fw_path=ncpu_fw_path
|
||
)
|
||
|
||
# 更新進度
|
||
self.update_progress(100)
|
||
|
||
# 顯示成功訊息
|
||
QMessageBox.information(self, "成功", f"{device_model} 的韌體已成功更新")
|
||
|
||
except Exception as e:
|
||
self.hide_progress()
|
||
QMessageBox.critical(self, "錯誤", f"更新韌體時發生錯誤: {str(e)}")
|
||
finally:
|
||
self.hide_progress()
|
||
|
||
def install_drivers(self):
|
||
"""Install drivers for all supported Kneron devices"""
|
||
try:
|
||
# 顯示進度條
|
||
self.show_progress("Installing Kneron Device Drivers...", 0)
|
||
|
||
# 列出所有產品 ID
|
||
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)
|
||
|
||
# 安裝每個驅動程式
|
||
for i, product_id in enumerate(product_ids):
|
||
try:
|
||
# 更新進度條
|
||
progress = int((i / total_count) * 100)
|
||
self.update_progress(progress)
|
||
self.progress_title.setText(f"Installing [{product_id.name}] driver...")
|
||
|
||
# 安裝驅動程式
|
||
kp.core.install_driver_for_windows(product_id=product_id)
|
||
success_count += 1
|
||
|
||
# 更新狀態訊息
|
||
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)
|
||
|
||
# 完成安裝
|
||
self.update_progress(100)
|
||
self.hide_progress()
|
||
|
||
# 顯示結果訊息
|
||
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 bar with the given title and value"""
|
||
self.progress_title.setText(title)
|
||
self.progress_bar.setValue(value)
|
||
self.progress_section.setVisible(True)
|
||
|
||
def update_progress(self, value):
|
||
"""Update the progress bar value"""
|
||
self.progress_bar.setValue(value)
|
||
|
||
def hide_progress(self):
|
||
"""Hide the progress section"""
|
||
self.progress_section.setVisible(False)
|
||
|
||
def on_device_selection_changed(self):
|
||
"""Handle device selection changes"""
|
||
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):
|
||
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):
|
||
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"
|