Modulize the code structure & add utilites' UI element

This commit is contained in:
Masonmason 2025-03-20 20:51:30 +08:00
parent af5b3835d5
commit d58cec837b
21 changed files with 1895 additions and 1256 deletions

72
main.py
View File

@ -1,13 +1,75 @@
import sys import sys
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication, QStackedWidget
from src.views.mainWindows import MainWindow from src.views.mainWindows import MainWindow
from src.views.selection_screen import SelectionScreen
from src.views.login_screen import LoginScreen
from src.views.utilities_screen import UtilitiesScreen
from src.config import APP_NAME, WINDOW_SIZE from src.config import APP_NAME, WINDOW_SIZE
class AppController:
def __init__(self):
self.app = QApplication(sys.argv)
self.stack = QStackedWidget()
self.stack.setGeometry(100, 100, *WINDOW_SIZE)
self.stack.setWindowTitle(APP_NAME)
# Initialize screens
self.init_screens()
# Configure navigation signals
self.connect_signals()
# Start with selection screen
self.show_selection_screen()
def init_screens(self):
# Selection screen
self.selection_screen = SelectionScreen()
self.stack.addWidget(self.selection_screen)
# Login screen
self.login_screen = LoginScreen()
self.stack.addWidget(self.login_screen)
# Utilities screen
self.utilities_screen = UtilitiesScreen()
self.stack.addWidget(self.utilities_screen)
# Demo app (main window)
self.main_window = MainWindow()
self.stack.addWidget(self.main_window)
def connect_signals(self):
# Selection screen signals
self.selection_screen.open_utilities.connect(self.show_login_screen)
self.selection_screen.open_demo_app.connect(self.show_demo_app)
# Login screen signals
self.login_screen.login_success.connect(self.show_utilities_screen)
self.login_screen.back_to_selection.connect(self.show_selection_screen)
# Utilities screen signals
self.utilities_screen.back_to_selection.connect(self.show_selection_screen)
def show_selection_screen(self):
self.stack.setCurrentWidget(self.selection_screen)
def show_login_screen(self):
self.stack.setCurrentWidget(self.login_screen)
def show_utilities_screen(self):
self.stack.setCurrentWidget(self.utilities_screen)
def show_demo_app(self):
self.stack.setCurrentWidget(self.main_window)
def run(self):
self.stack.show()
return self.app.exec_()
def main(): def main():
app = QApplication(sys.argv) controller = AppController()
window = MainWindow() return controller.run()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,6 +1,7 @@
from enum import Enum from enum import Enum
import os import os
APPDATA_PATH = os.environ.get("LOCALAPPDATA") # APPDATA_PATH = os.environ.get("LOCALAPPDATA")
APPDATA_PATH = "/Users/mason/Developer/Kneron-Academy/test_images"
# 取得專案根目錄的絕對路徑並設定 UXUI_ASSETS 為絕對路徑 # 取得專案根目錄的絕對路徑並設定 UXUI_ASSETS 為絕對路徑
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
UXUI_ASSETS = os.path.join(PROJECT_ROOT, "uxui", "") UXUI_ASSETS = os.path.join(PROJECT_ROOT, "uxui", "")

View File

@ -1,32 +1,75 @@
import kp # src/controllers/device_controller.py
from typing import List, Dict from PyQt5.QtWidgets import QWidget, QListWidgetItem
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
import os
from src.services.device_service import check_available_device
from src.config import UXUI_ASSETS, DongleModelMap, DongleIconMap
class DeviceController: class DeviceController:
def __init__(self): def __init__(self, main_window):
self.main_window = main_window
self.selected_device = None
self.connected_devices = [] self.connected_devices = []
def scan_devices(self): def refresh_devices(self):
return kp.core.scan_devices() """Refresh the list of connected devices"""
try:
print("Refreshing devices...")
device_descriptors = check_available_device()
self.connected_devices = []
def connect_device(self, usb_port_id: int): if device_descriptors.device_descriptor_number > 0:
device_group = kp.core.connect_devices(usb_port_ids=[usb_port_id]) self.parse_and_store_devices(device_descriptors.device_descriptor_list)
kp.core.set_timeout(device_group=device_group, milliseconds=5000) self.display_devices(device_descriptors.device_descriptor_list)
return device_group return True
else:
self.main_window.show_no_device_gif()
return False
except Exception as e:
print(f"Error in refresh_devices: {e}")
return False
def load_firmware(self, device_group, product_id: int): def parse_and_store_devices(self, devices):
SCPU_FW_PATH = f'../../external/res/firmware/{product_id}/fw_scpu.bin' """Parse device information and store it"""
NCPU_FW_PATH = f'../../external/res/firmware/{product_id}/fw_ncpu.bin' for device in devices:
kp.core.load_firmware_from_file( product_id = hex(device.product_id).strip().lower()
device_group=device_group, dongle = DongleModelMap.get(product_id, "unknown")
scpu_fw_path=SCPU_FW_PATH, device.dongle = dongle
ncpu_fw_path=NCPU_FW_PATH
)
def parse_device_info(self, device) -> Dict: new_device = {
return {
'usb_port_id': device.usb_port_id, 'usb_port_id': device.usb_port_id,
'product_id': device.product_id, 'product_id': device.product_id,
'kn_number': device.kn_number 'kn_number': device.kn_number,
'dongle': dongle
} }
existing_device_index = next(
(index for (index, d) in enumerate(self.connected_devices)
if d['usb_port_id'] == new_device['usb_port_id']),
None
)
if existing_device_index is not None:
self.connected_devices[existing_device_index] = new_device
else:
self.connected_devices.append(new_device)
def get_selected_device(self):
"""Get the currently selected device"""
return self.selected_device
def select_device(self, device, list_item, list_widget):
"""Select a device and update UI"""
self.selected_device = device
print("Selected dongle:", device)
# Update list item visual selection
for index in range(list_widget.count()):
item = list_widget.item(index)
widget = list_widget.itemWidget(item)
widget.setStyleSheet("background: none;")
list_item_widget = list_widget.itemWidget(list_item)
list_item_widget.setStyleSheet("background-color: lightblue;")

View File

@ -0,0 +1,139 @@
# src/controllers/inference_controller.py
import os, queue, cv2, json
from PyQt5.QtWidgets import QMessageBox
from src.models.inference_worker import InferenceWorkerThread
from src.config import UTILS_DIR, FW_DIR
class InferenceController:
def __init__(self, main_window, device_controller):
self.main_window = main_window
self.device_controller = device_controller
self.inference_worker = None
self.inference_queue = queue.Queue(maxsize=10)
self.current_tool_config = None
def select_tool(self, tool_config):
"""Select an AI tool and configure inference"""
print("Selected tool:", tool_config.get("display_name"))
self.current_tool_config = tool_config
# Get mode and model name
mode = tool_config.get("mode", "")
model_name = tool_config.get("model_name", "")
# Load detailed model configuration
model_path = os.path.join(UTILS_DIR, mode, model_name)
model_config_path = os.path.join(model_path, "config.json")
if os.path.exists(model_config_path):
try:
with open(model_config_path, "r", encoding="utf-8") as f:
detailed_config = json.load(f)
tool_config = {**tool_config, **detailed_config}
except Exception as e:
print(f"Error reading model config: {e}")
# Get tool input type
input_info = tool_config.get("input_info", {})
tool_type = input_info.get("type", "video")
once_mode = True if tool_type == "image" else False
# Prepare input parameters
input_params = tool_config.get("input_parameters", {}).copy()
# Configure device-related settings
selected_device = self.device_controller.get_selected_device()
if selected_device:
input_params["usb_port_id"] = selected_device.get("usb_port_id", 0)
dongle = selected_device.get("dongle", "unknown")
# Verify device compatibility
compatible_devices = tool_config.get("compatible_devices", [])
if compatible_devices and dongle not in compatible_devices:
msgBox = QMessageBox(self.main_window)
msgBox.setIcon(QMessageBox.Warning)
msgBox.setWindowTitle("Device Incompatible")
msgBox.setText(f"The selected model does not support {dongle} device.\nSupported devices: {', '.join(compatible_devices)}")
msgBox.setStyleSheet("QLabel { color: white; } QMessageBox { background-color: #2b2b2b; }")
msgBox.exec_()
return False
# Configure firmware paths
scpu_path = os.path.join(FW_DIR, dongle, "fw_scpu.bin")
ncpu_path = os.path.join(FW_DIR, dongle, "fw_ncpu.bin")
input_params["scpu_path"] = scpu_path
input_params["ncpu_path"] = ncpu_path
else:
# Default device handling
devices = self.device_controller.connected_devices
if devices and len(devices) > 0:
input_params["usb_port_id"] = devices[0].get("usb_port_id", 0)
print("Warning: No device specifically selected, using first available device")
else:
input_params["usb_port_id"] = 0
print("Warning: No connected devices, using default usb_port_id 0")
# Handle file inputs for image/voice modes
if tool_type in ["image", "voice"]:
if hasattr(self.main_window, "destination") and self.main_window.destination:
input_params["file_path"] = self.main_window.destination
if tool_type == "image":
uploaded_img = cv2.imread(self.main_window.destination)
if uploaded_img is not None:
if not self.inference_queue.full():
self.inference_queue.put(uploaded_img)
print("Uploaded image added to inference queue")
else:
print("Warning: inference queue is full")
else:
print("Warning: Unable to read uploaded image")
else:
input_params["file_path"] = ""
print(f"Warning: {tool_type} mode requires a file input, but no file has been uploaded.")
# Add model file path
if "model_file" in tool_config:
model_file = tool_config["model_file"]
model_file_path = os.path.join(model_path, model_file)
input_params["model"] = model_file_path
print("Input parameters:", input_params)
# Stop existing inference worker if running
if self.inference_worker:
self.inference_worker.stop()
self.inference_worker = None
# Create new inference worker
self.inference_worker = InferenceWorkerThread(
self.inference_queue,
mode,
model_name,
min_interval=0.5,
mse_threshold=500,
once_mode=once_mode
)
self.inference_worker.input_params = input_params
self.inference_worker.inference_result_signal.connect(self.main_window.handle_inference_result)
self.inference_worker.start()
print(f"Inference worker started for module: {mode}/{model_name}")
# Start camera if needed
if tool_type == "video":
self.main_window.media_controller.start_camera()
else:
print("Tool mode is not video, camera not started")
return True
def add_frame_to_queue(self, frame):
"""Add a frame to the inference queue"""
if not self.inference_queue.full():
self.inference_queue.put(frame)
def stop_inference(self):
"""Stop the inference worker"""
if self.inference_worker:
self.inference_worker.stop()
self.inference_worker = None

View File

@ -0,0 +1,166 @@
import cv2
import os
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
from src.models.video_thread import VideoThread
from src.utils.image_utils import qimage_to_numpy
class MediaController:
def __init__(self, main_window, inference_controller):
self.main_window = main_window
self.inference_controller = inference_controller
self.video_thread = None
self.recording = False
self.recording_audio = False
self.recorded_frames = []
def start_camera(self):
"""Start the camera for video capture"""
if self.video_thread is None:
self.video_thread = VideoThread()
self.video_thread.change_pixmap_signal.connect(self.update_image)
self.video_thread.start()
print("Camera started")
else:
print("Camera already running")
def stop_camera(self):
"""Stop the camera"""
if self.video_thread is not None:
self.video_thread.stop()
self.video_thread = None
print("Camera stopped")
def update_image(self, qt_image):
"""Update the image display and pass to inference queue"""
try:
# Update canvas display
canvas_size = self.main_window.canvas_label.size()
scaled_image = qt_image.scaled(
canvas_size.width() - 20,
canvas_size.height() - 20,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.main_window.canvas_label.setPixmap(QPixmap.fromImage(scaled_image))
# Convert QImage to numpy array and add to inference queue
frame_np = qimage_to_numpy(qt_image)
self.inference_controller.add_frame_to_queue(frame_np)
except Exception as e:
print(f"Error in update_image: {e}")
def record_video(self, button=None):
"""Start or stop video recording"""
if not self.recording:
try:
self.recording = True
self.recorded_frames = []
print("Started video recording")
if button:
button.setStyleSheet("""
QPushButton {
background: rgba(255, 0, 0, 0.3);
border: 1px solid red;
border-radius: 10px;
}
""")
except Exception as e:
print(f"Error starting video recording: {e}")
else:
try:
self.recording = False
print("Stopped video recording")
if self.recorded_frames:
filename = QFileDialog.getSaveFileName(
self.main_window,
"Save Video",
"",
"Video Files (*.avi)"
)[0]
if filename:
height, width = self.recorded_frames[0].shape[:2]
out = cv2.VideoWriter(
filename,
cv2.VideoWriter_fourcc(*'XVID'),
20.0,
(width, height)
)
for frame in self.recorded_frames:
out.write(frame)
out.release()
print(f"Video saved to {filename}")
if button:
button.setStyleSheet("""
QPushButton {
background: transparent;
border: 1px transparent;
border-radius: 10px;
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 50);
}
""")
except Exception as e:
print(f"Error stopping video recording: {e}")
def record_audio(self, button=None):
"""Start or stop audio recording"""
if not self.recording_audio:
try:
self.recording_audio = True
print("Started audio recording")
if button:
button.setStyleSheet("""
QPushButton {
background: rgba(255, 0, 0, 0.3);
border: 1px solid red;
border-radius: 10px;
}
""")
except Exception as e:
print(f"Error starting audio recording: {e}")
else:
try:
self.recording_audio = False
print("Stopped audio recording")
if button:
button.setStyleSheet("""
QPushButton {
background: transparent;
border: 1px transparent;
border-radius: 10px;
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 50);
}
""")
except Exception as e:
print(f"Error stopping audio recording: {e}")
def take_screenshot(self):
"""Take a screenshot of the current frame"""
try:
if self.main_window.canvas_label.pixmap():
filename = QFileDialog.getSaveFileName(
self.main_window,
"Save Screenshot",
"",
"Image Files (*.png *.jpg)"
)[0]
if filename:
self.main_window.canvas_label.pixmap().save(filename)
print(f"Screenshot saved to {filename}")
except Exception as e:
print(f"Error taking screenshot: {e}")

View File

@ -0,0 +1,73 @@
import os, time, queue, numpy as np, importlib.util
from PyQt5.QtCore import QThread, pyqtSignal
from src.config import UTILS_DIR
def load_inference_module(mode, model_name):
"""Dynamically load an inference module"""
script_path = os.path.join(UTILS_DIR, mode, model_name, "script.py")
module_name = f"{mode}_{model_name}"
spec = importlib.util.spec_from_file_location(module_name, script_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class InferenceWorkerThread(QThread):
inference_result_signal = pyqtSignal(object)
def __init__(self, frame_queue, mode, model_name, min_interval=0.5, mse_threshold=500, once_mode=False):
super().__init__()
self.frame_queue = frame_queue
self.mode = mode
self.model_name = model_name
self.min_interval = min_interval
self.mse_threshold = mse_threshold
self._running = True
self.once_mode = once_mode
self.last_inference_time = 0
self.last_frame = None
self.cached_result = None
self.input_params = {}
# Dynamically load inference module
self.inference_module = load_inference_module(mode, model_name)
def run(self):
while self._running:
try:
frame = self.frame_queue.get(timeout=0.1)
except queue.Empty:
continue
current_time = time.time()
if current_time - self.last_inference_time < self.min_interval:
continue
if self.last_frame is not None:
mse = np.mean((frame.astype(np.float32) - self.last_frame.astype(np.float32)) ** 2)
if mse < self.mse_threshold and self.cached_result is not None:
self.inference_result_signal.emit(self.cached_result)
if self.once_mode:
self._running = False
break
continue
try:
result = self.inference_module.inference(frame, params=self.input_params)
except Exception as e:
print(f"Inference error: {e}")
result = None
self.last_inference_time = current_time
self.last_frame = frame.copy()
self.cached_result = result
self.inference_result_signal.emit(result)
if self.once_mode:
self._running = False
break
self.quit()
def stop(self):
self._running = False
self.wait()

View File

@ -0,0 +1,30 @@
import cv2
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QImage
class VideoThread(QThread):
change_pixmap_signal = pyqtSignal(QImage)
def __init__(self):
super().__init__()
self._run_flag = True
def run(self):
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
self._run_flag = False
while self._run_flag:
ret, frame = cap.read()
if ret:
# Convert to RGB format
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
height, width, channel = frame.shape
bytes_per_line = channel * width
qt_image = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888)
self.change_pixmap_signal.emit(qt_image)
cap.release()
def stop(self):
self._run_flag = False
self.wait()

View File

@ -1,22 +1,46 @@
import kp # import kp
# def check_available_device():
# try:
# print("checking available devices")
# device_descriptors = kp.core.scan_devices()
# return device_descriptors
# except Exception as e:
# print(f"Error scanning devices: {e}")
# # 返回一個空的設備描述符或模擬數據
# class EmptyDescriptor:
# def __init__(self):
# self.device_descriptor_number = 0
# self.device_descriptor_list = []
# return EmptyDescriptor()
def check_available_device(): def check_available_device():
try: # 模擬設備描述符
print("checking available devices") print("checking available devices")
device_descriptors = kp.core.scan_devices()
return device_descriptors
except Exception as e:
print(f"Error scanning devices: {e}")
# 返回一個空的設備描述符或模擬數據
class EmptyDescriptor: class EmptyDescriptor:
def __init__(self): def __init__(self):
self.device_descriptor_number = 0 self.device_descriptor_number = 0
self.device_descriptor_list = [] self.device_descriptor_list = [{
"usb_port_id": 4,
"vendor_id": "0x3231",
"product_id": "0x720",
"link_speed": "UsbSpeed.KP_USB_SPEED_SUPER",
"kn_number": "0xB306224C",
"is_connectable": True,
"usb_port_path": "4-1",
"firmware": "KDP2 Comp/F"
},
{
"usb_port_id": 5,
"vendor_id": "0x3231",
"product_id": "0x520",
"link_speed": "UsbSpeed.KP_USB_SPEED_SUPER",
"kn_number": "0xB306224C",
"is_connectable": True,
"usb_port_path": "4-1",
"firmware": "KDP2 Comp/F"
}]
return EmptyDescriptor() return EmptyDescriptor()
# def check_available_device():
# print("checking available devices")
# # 模擬設備描述符
# device_descriptors = [ # device_descriptors = [
# { # {
# "usb_port_id": 4, # "usb_port_id": 4,

View File

@ -0,0 +1,77 @@
import os
import shutil
from PyQt5.QtWidgets import QFileDialog, QMessageBox
class FileService:
def __init__(self, main_window, upload_dir):
self.main_window = main_window
self.upload_dir = upload_dir
self.destination = None
def upload_file(self):
"""Handle file upload process"""
try:
print("Calling QFileDialog.getOpenFileName")
options = QFileDialog.Options()
file_path, _ = QFileDialog.getOpenFileName(
self.main_window,
"Upload File",
"",
"All Files (*)",
options=options
)
print("File path obtained:", file_path)
if file_path:
print("Checking if upload directory exists")
if not os.path.exists(self.upload_dir):
os.makedirs(self.upload_dir)
print(f"Created UPLOAD_DIR: {self.upload_dir}")
print("Checking if original file exists:", file_path)
if not os.path.exists(file_path):
self.show_message(QMessageBox.Critical, "Error", "Selected file not found")
return None
file_name = os.path.basename(file_path)
self.destination = os.path.join(self.upload_dir, file_name)
print("Target path:", self.destination)
# Check if target path is writable
try:
print("Testing file write permission")
with open(self.destination, 'wb') as test_file:
pass
os.remove(self.destination)
print("Test file creation and deletion successful")
except PermissionError:
self.show_message(QMessageBox.Critical, "Error", "Cannot write to target directory")
return None
print("Starting file copy")
shutil.copy2(file_path, self.destination)
print("File copy complete")
self.show_message(QMessageBox.Information, "Success", f"File uploaded to: {self.destination}")
return self.destination
return None
except Exception as e:
import traceback
print("Exception during upload process:\n", traceback.format_exc())
self.show_message(QMessageBox.Critical, "Error", f"Upload error: {str(e)}")
return None
def show_message(self, icon, title, message):
"""Display a message box with custom styling"""
msgBox = QMessageBox(self.main_window)
msgBox.setIcon(icon)
msgBox.setWindowTitle(title)
msgBox.setText(message)
msgBox.setStyleSheet("""
QLabel { color: white; }
QPushButton { color: white; }
QMessageBox { background-color: #2b2b2b; }
""")
msgBox.exec_()

View File

@ -1,4 +1,4 @@
import kp # import kp
import cv2, os, shutil, sys import cv2, os, shutil, sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLabel, QPushButton,
QComboBox, QFileDialog, QMessageBox, QHBoxLayout, QDialog, QListWidget, QComboBox, QFileDialog, QMessageBox, QHBoxLayout, QDialog, QListWidget,

116
src/utils/config_utils.py Normal file
View File

@ -0,0 +1,116 @@
import os
import json
from src.config import UTILS_DIR, SCRIPT_CONFIG
class ConfigUtils:
@staticmethod
def generate_global_config():
"""Scan directory structure and generate global configuration file"""
try:
config = {"plugins": []}
# Ensure utils directory exists
if not os.path.exists(UTILS_DIR):
os.makedirs(UTILS_DIR, exist_ok=True)
print(f"Created UTILS_DIR: {UTILS_DIR}")
# List items in utils directory for debugging
print(f"UTILS_DIR contents: {os.listdir(UTILS_DIR) if os.path.exists(UTILS_DIR) else 'Directory does not exist'}")
# Scan mode directories (first level subdirectories)
mode_dirs = [d for d in os.listdir(UTILS_DIR)
if os.path.isdir(os.path.join(UTILS_DIR, d)) and not d.startswith('_')]
print(f"Found mode directories: {mode_dirs}")
for mode_name in mode_dirs:
mode_path = os.path.join(UTILS_DIR, mode_name)
mode_info = {
"mode": mode_name,
"display_name": mode_name.replace("_", " ").title(),
"models": []
}
# List items in mode directory for debugging
print(f"Contents of mode {mode_name}: {os.listdir(mode_path)}")
# Scan model directories (second level subdirectories)
model_dirs = [d for d in os.listdir(mode_path)
if os.path.isdir(os.path.join(mode_path, d)) and not d.startswith('_')]
print(f"Found models in mode {mode_name}: {model_dirs}")
for model_name in model_dirs:
model_path = os.path.join(mode_path, model_name)
# Check for model configuration file
model_config_path = os.path.join(model_path, "config.json")
if os.path.exists(model_config_path):
try:
with open(model_config_path, "r", encoding="utf-8") as f:
model_config = json.load(f)
print(f"Successfully read model config: {model_config_path}")
model_summary = {
"name": model_name,
"display_name": model_config.get("display_name", model_name.replace("_", " ").title()),
"description": model_config.get("description", ""),
"compatible_devices": model_config.get("compatible_devices", [])
}
mode_info["models"].append(model_summary)
except Exception as e:
print(f"Error reading model config {model_config_path}: {e}")
else:
print(f"Model config file not found: {model_config_path}")
# Optionally create template config file here
# Only add modes with models
if mode_info["models"]:
config["plugins"].append(mode_info)
# Write the configuration file
os.makedirs(os.path.dirname(SCRIPT_CONFIG), exist_ok=True)
with open(SCRIPT_CONFIG, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2, ensure_ascii=False)
print(f"Global configuration generated: {SCRIPT_CONFIG}")
return config
except Exception as e:
print(f"Error generating global configuration: {e}")
import traceback
traceback.print_exc()
return {"plugins": []}
@staticmethod
def create_model_config_template(model_path):
"""Create a template configuration file for a model"""
try:
model_name = os.path.basename(model_path)
mode_name = os.path.basename(os.path.dirname(model_path))
template_config = {
"display_name": model_name.replace("_", " ").title(),
"description": f"AI model for {model_name.replace('_', ' ')}",
"model_file": f"{model_name}.nef",
"input_info": {
"type": "video", # Default to video
"supported_formats": ["mp4", "avi"]
},
"input_parameters": {
"threshold": 0.5
},
"compatible_devices": ["KL520", "KL720"]
}
config_path = os.path.join(model_path, "config.json")
with open(config_path, "w", encoding="utf-8") as f:
json.dump(template_config, f, indent=2, ensure_ascii=False)
print(f"Created template config for {model_name}")
return True
except Exception as e:
print(f"Error creating model config template: {e}")
return False

12
src/utils/image_utils.py Normal file
View File

@ -0,0 +1,12 @@
import numpy as np
from PyQt5.QtGui import QImage
def qimage_to_numpy(qimage):
"""Convert a QImage to a numpy array"""
qimage = qimage.convertToFormat(QImage.Format_RGB888)
width = qimage.width()
height = qimage.height()
ptr = qimage.bits()
ptr.setsize(qimage.byteCount())
arr = np.array(ptr).reshape(height, width, 3)
return arr

View File

@ -0,0 +1,23 @@
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QLabel
from PyQt5.QtCore import Qt
def create_canvas_area(parent):
"""Create the canvas area for video display"""
try:
# Create frame container for canvas
canvas_frame = QFrame(parent)
canvas_frame.setStyleSheet("border: 1px solid gray; background: black; border-radius: 20px;")
canvas_frame.setFixedSize(900, 750)
canvas_layout = QVBoxLayout(canvas_frame)
canvas_layout.setContentsMargins(10, 10, 10, 10)
# Create label for video display
canvas_label = QLabel()
canvas_label.setAlignment(Qt.AlignCenter)
canvas_label.setStyleSheet("border: none; background: transparent;")
canvas_layout.addWidget(canvas_label)
return canvas_frame, canvas_label
except Exception as e:
print(f"Error in create_canvas_area: {e}")
return QFrame(parent), QLabel(parent)

View File

@ -0,0 +1,60 @@
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QHBoxLayout, QLabel, QListWidget, QWidget, QPushButton
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
import os
from src.config import SECONDARY_COLOR, UXUI_ASSETS, BUTTON_STYLE, DongleIconMap
def create_device_layout(parent, device_controller):
"""Create the device list layout"""
try:
devices_frame = QFrame(parent)
devices_frame.setStyleSheet(f"border: none; background: {SECONDARY_COLOR}; border-radius: 15px;")
# Set height based on connected devices
base_height = 250
extra_height = 100 if len(device_controller.connected_devices) > 1 else 0
devices_frame.setFixedHeight(base_height + extra_height)
devices_frame.setFixedWidth(240)
devices_layout = QVBoxLayout(devices_frame)
# Title
title_layout = QHBoxLayout()
title_container = QWidget()
container_layout = QHBoxLayout(title_container)
container_layout.setSpacing(10)
device_icon = QSvgWidget(os.path.join(UXUI_ASSETS, "Assets_svg/ic_window_device.svg"))
device_icon.setFixedSize(20, 20)
container_layout.addWidget(device_icon)
title_label = QLabel("Device")
title_label.setStyleSheet("color: white; font-size: 20px; font-weight: bold;")
container_layout.addWidget(title_label)
title_layout.addWidget(title_container)
devices_layout.addLayout(title_layout)
# Device list
device_list_widget = QListWidget(parent)
devices_layout.addWidget(device_list_widget)
# Detail button
detail_button = QPushButton("Details", parent)
detail_button.setStyleSheet(BUTTON_STYLE)
detail_button.setFixedSize(72, 30)
detail_button.clicked.connect(parent.show_device_popup)
button_container = QWidget()
button_layout = QHBoxLayout(button_container)
button_layout.addWidget(detail_button, alignment=Qt.AlignCenter)
button_layout.setContentsMargins(0, 0, 0, 0)
devices_layout.addWidget(button_container)
return devices_frame, device_list_widget
except Exception as e:
print(f"Error in create_device_layout: {e}")
return QFrame(parent), QListWidget(parent)

View File

@ -0,0 +1,76 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import Qt
import os
from src.config import SECONDARY_COLOR, BUTTON_STYLE, UXUI_ASSETS
def create_device_popup(parent, device_controller):
"""Create a popup window for device connection management"""
try:
# Device connection popup window
popup = QWidget(parent)
popup_width = int(parent.width() * 0.67)
popup_height = int(parent.height() * 0.67)
popup.setFixedSize(popup_width, popup_height)
popup.setStyleSheet(f"""
QWidget {{
background-color: {SECONDARY_COLOR};
border-radius: 20px;
padding: 20px;
}}
""")
popup_layout = QVBoxLayout(popup)
popup_layout.setContentsMargins(0, 0, 0, 0)
# Title row
title_layout = QHBoxLayout()
title_layout.setAlignment(Qt.AlignCenter)
title_container = QWidget()
container_layout = QHBoxLayout(title_container)
container_layout.setSpacing(10)
device_icon = QSvgWidget(os.path.join(UXUI_ASSETS, "Assets_svg/ic_window_device.svg"))
device_icon.setFixedSize(35, 35)
container_layout.addWidget(device_icon)
popup_label = QLabel("Device Connection")
popup_label.setStyleSheet("color: white; font-size: 32px;")
container_layout.addWidget(popup_label)
container_layout.setAlignment(Qt.AlignCenter)
title_layout.addWidget(title_container)
popup_layout.addLayout(title_layout)
# Device list
device_list_widget_popup = QListWidget(popup)
popup_layout.addWidget(device_list_widget_popup)
# Store reference to this list widget for later use
parent.device_list_widget_popup = device_list_widget_popup
# Button area
button_layout = QHBoxLayout()
refresh_button = QPushButton("Refresh")
refresh_button.clicked.connect(device_controller.refresh_devices)
refresh_button.setFixedSize(110, 45)
refresh_button.setStyleSheet(BUTTON_STYLE)
button_layout.addWidget(refresh_button)
done_button = QPushButton("Done")
done_button.setStyleSheet(BUTTON_STYLE)
done_button.setFixedSize(110, 45)
done_button.clicked.connect(parent.hide_device_popup)
button_layout.addWidget(done_button)
button_layout.setSpacing(10)
popup_layout.addSpacing(20)
popup_layout.addLayout(button_layout)
return popup
except Exception as e:
print(f"Error in create_device_popup: {e}")
return QWidget(parent)

View File

@ -0,0 +1,64 @@
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QHBoxLayout, QPushButton
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import Qt
import os
from src.config import SECONDARY_COLOR, UXUI_ASSETS
def create_media_panel(parent, media_controller, file_service):
"""Create the media control panel with buttons for media operations"""
try:
# Create a vertical layout for the buttons
media_panel = QFrame(parent)
media_panel.setStyleSheet(f"background: {SECONDARY_COLOR}; border-radius: 20px;")
media_layout = QVBoxLayout(media_panel)
media_layout.setAlignment(Qt.AlignCenter)
# Media button information
media_buttons_info = [
('screenshot', os.path.join(UXUI_ASSETS, "Assets_svg/bt_function_screencapture_normal.svg"),
media_controller.take_screenshot),
('upload file', os.path.join(UXUI_ASSETS, "Assets_svg/bt_function_upload_normal.svg"),
file_service.upload_file),
('voice', os.path.join(UXUI_ASSETS, "Assets_svg/ic_recording_voice.svg"),
lambda: media_controller.record_audio(None)),
('video', os.path.join(UXUI_ASSETS, "Assets_svg/ic_recording_camera.svg"),
lambda: media_controller.record_video(None)),
]
for button_name, icon_path, callback in media_buttons_info:
button = QPushButton()
button.setFixedSize(50, 50)
button.setStyleSheet("""
QPushButton {
background: transparent;
color: white;
border: 1px transparent;
border-radius: 10px;
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 50);
}
QPushButton:pressed {
background-color: rgba(255, 255, 255, 100);
}
""")
button_layout = QHBoxLayout(button)
button_layout.setContentsMargins(0, 0, 0, 0)
button_layout.setAlignment(Qt.AlignCenter)
icon = QSvgWidget(icon_path)
icon.setFixedSize(40, 40)
button_layout.addWidget(icon)
button.clicked.connect(callback)
media_layout.addWidget(button, alignment=Qt.AlignCenter)
media_panel.setLayout(media_layout)
media_panel.setFixedSize(90, 240)
return media_panel
except Exception as e:
print(f"Error in create_media_panel: {e}")
return QFrame(parent)

View File

@ -0,0 +1,82 @@
import json
import os
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QWidget
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import Qt
from src.config import SECONDARY_COLOR, UXUI_ASSETS, BUTTON_STYLE, SCRIPT_CONFIG
def create_ai_toolbox(parent, config_utils, inference_controller):
"""Create the AI toolbox layout"""
try:
# Read JSON configuration
print("config_path:", SCRIPT_CONFIG)
if os.path.exists(SCRIPT_CONFIG):
with open(SCRIPT_CONFIG, "r", encoding="utf-8") as f:
config = json.load(f)
plugins = config.get("plugins", [])
else:
# If no configuration file, try to generate it
plugins = config_utils.generate_global_config().get("plugins", [])
if not plugins:
print("Unable to generate configuration, using empty tool list")
# Create toolbox UI
toolbox_frame = QFrame(parent)
toolbox_frame.setStyleSheet(f"border: none; background: {SECONDARY_COLOR}; border-radius: 15px;")
toolbox_frame.setFixedHeight(450)
toolbox_frame.setFixedWidth(240)
toolbox_layout = QVBoxLayout(toolbox_frame)
# Title row
title_layout = QHBoxLayout()
title_container = QWidget()
container_layout = QHBoxLayout(title_container)
container_layout.setSpacing(10)
toolbox_icon = QSvgWidget(os.path.join(UXUI_ASSETS, "Assets_svg/ic_window_toolbox.svg"))
toolbox_icon.setFixedSize(40, 40)
container_layout.addWidget(toolbox_icon)
title_label = QLabel("AI Toolbox")
title_label.setStyleSheet("color: white; font-size: 20px; font-weight: bold;")
container_layout.addWidget(title_label)
title_layout.addWidget(title_container)
toolbox_layout.addLayout(title_layout)
# Create tool buttons (categorized)
for plugin in plugins:
mode = plugin.get("mode", "")
display_name = plugin.get("display_name", "")
# Add category title
category_label = QLabel(display_name)
category_label.setStyleSheet("color: white; font-size: 16px; font-weight: bold; margin-top: 10px;")
toolbox_layout.addWidget(category_label)
# Add all model buttons in this category
for model in plugin.get("models", []):
model_name = model.get("name", "")
display_name = model.get("display_name", "")
# Create tool configuration
tool_config = {
"mode": mode,
"model_name": model_name,
"display_name": display_name,
"description": model.get("description", ""),
"compatible_devices": model.get("compatible_devices", [])
}
# Create button
button = QPushButton(display_name)
button.clicked.connect(lambda checked, t=tool_config: inference_controller.select_tool(t))
button.setStyleSheet(BUTTON_STYLE)
button.setFixedHeight(40)
toolbox_layout.addWidget(button)
return toolbox_frame
except Exception as e:
print(f"Error in create_ai_toolbox: {e}")
return QFrame(parent)

157
src/views/login_screen.py Normal file
View File

@ -0,0 +1,157 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,
QLineEdit, QComboBox, QFrame, QMessageBox)
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QFont
import os
from src.config import UXUI_ASSETS, WINDOW_SIZE, BACKGROUND_COLOR
class LoginScreen(QWidget):
# Signals for navigation
login_success = pyqtSignal()
back_to_selection = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
def init_ui(self):
# Basic window setup
self.setGeometry(100, 100, *WINDOW_SIZE)
self.setStyleSheet(f"background-color: {BACKGROUND_COLOR};")
# Main layout
layout = QVBoxLayout(self)
# 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)
logo_label.setPixmap(logo_pixmap)
logo_label.setAlignment(Qt.AlignCenter)
layout.addWidget(logo_label)
# Title
title_label = QLabel("Login", self)
title_label.setAlignment(Qt.AlignCenter)
title_label.setFont(QFont("Arial", 24, QFont.Bold))
layout.addWidget(title_label)
# Login form container
form_container = QFrame(self)
form_container.setFrameShape(QFrame.StyledPanel)
form_container.setStyleSheet("""
QFrame {
background-color: white;
border-radius: 10px;
padding: 20px;
}
""")
form_layout = QVBoxLayout(form_container)
# Server type
server_label = QLabel("Server Authentication Type", self)
server_label.setFont(QFont("Arial", 12))
form_layout.addWidget(server_label)
self.server_combo = QComboBox(self)
self.server_combo.addItems(["Standard Password Authentication", "Other Authentication Method"])
self.server_combo.setFont(QFont("Arial", 10))
self.server_combo.setMinimumHeight(40)
form_layout.addWidget(self.server_combo)
form_layout.addSpacing(10)
# Username
username_label = QLabel("Username", self)
username_label.setFont(QFont("Arial", 12))
form_layout.addWidget(username_label)
self.username_input = QLineEdit(self)
self.username_input.setPlaceholderText("Enter your username")
self.username_input.setMinimumHeight(40)
self.username_input.setFont(QFont("Arial", 10))
form_layout.addWidget(self.username_input)
form_layout.addSpacing(10)
# Password
password_label = QLabel("Password", self)
password_label.setFont(QFont("Arial", 12))
form_layout.addWidget(password_label)
self.password_input = QLineEdit(self)
self.password_input.setPlaceholderText("Enter your password")
self.password_input.setEchoMode(QLineEdit.Password)
self.password_input.setMinimumHeight(40)
self.password_input.setFont(QFont("Arial", 10))
form_layout.addWidget(self.password_input)
form_layout.addSpacing(20)
# Error message (hidden by default)
self.error_label = QLabel("", self)
self.error_label.setStyleSheet("color: red;")
self.error_label.setFont(QFont("Arial", 10))
self.error_label.hide()
form_layout.addWidget(self.error_label)
# Buttons
button_layout = QHBoxLayout()
back_button = QPushButton("Back", self)
back_button.setMinimumHeight(40)
back_button.setFont(QFont("Arial", 12))
back_button.setStyleSheet("""
QPushButton {
background-color: #757575;
color: white;
border-radius: 5px;
padding: 5px 15px;
}
QPushButton:hover {
background-color: #616161;
}
""")
back_button.clicked.connect(self.back_to_selection.emit)
login_button = QPushButton("Login", self)
login_button.setMinimumHeight(40)
login_button.setFont(QFont("Arial", 12))
login_button.setStyleSheet("""
QPushButton {
background-color: #1E88E5;
color: white;
border-radius: 5px;
padding: 5px 15px;
}
QPushButton:hover {
background-color: #1976D2;
}
""")
login_button.clicked.connect(self.attempt_login)
button_layout.addWidget(back_button)
button_layout.addWidget(login_button)
form_layout.addLayout(button_layout)
# Add form to main layout
layout.addWidget(form_container, 1)
def attempt_login(self):
username = self.username_input.text()
password = self.password_input.text()
# For demo purposes, use a simple validation
if not username or not password:
self.show_error("Please enter both username and password")
return
# Simulate login success (in a real app, you would validate with your server)
# For demo, accept any non-empty username/password
self.login_success.emit()
def show_error(self, message):
self.error_label.setText(message)
self.error_label.show()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QFont
import os
from src.config import UXUI_ASSETS, WINDOW_SIZE, BACKGROUND_COLOR
class SelectionScreen(QWidget):
# Signals for navigation
open_utilities = pyqtSignal()
open_demo_app = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
def init_ui(self):
# Basic window setup
self.setGeometry(100, 100, *WINDOW_SIZE)
self.setStyleSheet(f"background-color: {BACKGROUND_COLOR};")
# Main layout
layout = QVBoxLayout(self)
# 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)
logo_label.setPixmap(logo_pixmap)
logo_label.setAlignment(Qt.AlignCenter)
layout.addWidget(logo_label)
# Title
title_label = QLabel("Kneron Academy", self)
title_label.setAlignment(Qt.AlignCenter)
title_label.setFont(QFont("Arial", 24, QFont.Bold))
layout.addWidget(title_label)
# Subtitle
subtitle_label = QLabel("Please select an option to continue", self)
subtitle_label.setAlignment(Qt.AlignCenter)
subtitle_label.setFont(QFont("Arial", 14))
layout.addWidget(subtitle_label)
# Add some space
layout.addSpacing(40)
# Button container
button_container = QWidget(self)
button_layout = QHBoxLayout(button_container)
button_layout.setContentsMargins(50, 0, 50, 0)
# Utilities button
utilities_button = QPushButton("Utilities", self)
utilities_button.setMinimumHeight(80)
utilities_button.setFont(QFont("Arial", 14))
utilities_button.setStyleSheet("""
QPushButton {
background-color: #1E88E5;
color: white;
border-radius: 8px;
padding: 10px;
}
QPushButton:hover {
background-color: #1976D2;
}
""")
utilities_button.clicked.connect(self.open_utilities.emit)
# Demo App button
demo_button = QPushButton("Demo App", self)
demo_button.setMinimumHeight(80)
demo_button.setFont(QFont("Arial", 14))
demo_button.setStyleSheet("""
QPushButton {
background-color: #43A047;
color: white;
border-radius: 8px;
padding: 10px;
}
QPushButton:hover {
background-color: #388E3C;
}
""")
demo_button.clicked.connect(self.open_demo_app.emit)
# Add buttons to layout
button_layout.addWidget(utilities_button)
button_layout.addWidget(demo_button)
layout.addWidget(button_container)
layout.addStretch(1) # Push everything up

View File

@ -0,0 +1,373 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,
QFrame, QMessageBox, QScrollArea, QTableWidget, QTableWidgetItem,
QHeaderView, QProgressBar)
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
from PyQt5.QtGui import QPixmap, QFont, QIcon
import os
from src.config import UXUI_ASSETS, WINDOW_SIZE, BACKGROUND_COLOR
from src.controllers.device_controller import DeviceController
class UtilitiesScreen(QWidget):
# Signals for navigation
back_to_selection = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.device_controller = DeviceController(self)
self.init_ui()
def init_ui(self):
# Basic window setup
self.setGeometry(100, 100, *WINDOW_SIZE)
self.setStyleSheet(f"background-color: {BACKGROUND_COLOR};")
# Main layout
main_layout = QVBoxLayout(self)
# Header with back button and logo
header_layout = QHBoxLayout()
# 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(200, 200, 200, 0.3);
border-radius: 20px;
}
""")
back_button.clicked.connect(self.back_to_selection.emit)
# 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(back_button)
header_layout.addStretch(1)
header_layout.addWidget(logo_label)
main_layout.addLayout(header_layout)
# Title
title_label = QLabel("Utilities", self)
title_label.setAlignment(Qt.AlignCenter)
title_label.setFont(QFont("Arial", 24, QFont.Bold))
main_layout.addWidget(title_label)
# Create main content container
content_container = QFrame(self)
content_container.setFrameShape(QFrame.StyledPanel)
content_container.setStyleSheet("""
QFrame {
background-color: white;
border-radius: 10px;
}
""")
content_layout = QVBoxLayout(content_container)
# Device connection section
device_section = QFrame(self)
device_section.setStyleSheet("""
QFrame {
background-color: #f5f5f5;
border-radius: 8px;
padding: 10px;
}
""")
device_layout = QVBoxLayout(device_section)
device_title = QLabel("Device Connection", self)
device_title.setFont(QFont("Arial", 16, QFont.Bold))
device_layout.addWidget(device_title)
device_subtitle = QLabel("Connect and manage your Kneron devices", self)
device_subtitle.setFont(QFont("Arial", 12))
device_layout.addWidget(device_subtitle)
# Device table
self.device_table = QTableWidget(0, 5, self)
self.device_table.setHorizontalHeaderLabels([
"Device ID", "Product ID", "Firmware", "KN Number", "Status"
])
self.device_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.device_table.setStyleSheet("""
QTableWidget {
border: none;
gridline-color: #e0e0e0;
}
QHeaderView::section {
background-color: #f0f0f0;
padding: 8px;
font-weight: bold;
border: none;
border-bottom: 1px solid #e0e0e0;
}
""")
device_layout.addWidget(self.device_table)
# Refresh and actions buttons
device_buttons_layout = QHBoxLayout()
refresh_button = QPushButton("Refresh Devices", self)
refresh_button.setStyleSheet("""
QPushButton {
background-color: #42a5f5;
color: white;
border-radius: 5px;
padding: 8px 15px;
}
QPushButton:hover {
background-color: #2196f3;
}
""")
refresh_button.clicked.connect(self.refresh_devices)
register_button = QPushButton("Register Device", self)
register_button.setStyleSheet("""
QPushButton {
background-color: #66bb6a;
color: white;
border-radius: 5px;
padding: 8px 15px;
}
QPushButton:hover {
background-color: #4caf50;
}
""")
register_button.clicked.connect(self.register_device)
update_fw_button = QPushButton("Update Firmware", self)
update_fw_button.setStyleSheet("""
QPushButton {
background-color: #ffa726;
color: white;
border-radius: 5px;
padding: 8px 15px;
}
QPushButton:hover {
background-color: #ff9800;
}
""")
update_fw_button.clicked.connect(self.update_firmware)
device_buttons_layout.addWidget(refresh_button)
device_buttons_layout.addWidget(register_button)
device_buttons_layout.addWidget(update_fw_button)
device_layout.addLayout(device_buttons_layout)
# Add device section to content
content_layout.addWidget(device_section)
# Status section
status_section = QFrame(self)
status_section.setStyleSheet("""
QFrame {
background-color: #f5f5f5;
border-radius: 8px;
padding: 10px;
margin-top: 15px;
}
""")
status_layout = QVBoxLayout(status_section)
status_title = QLabel("Device Status", self)
status_title.setFont(QFont("Arial", 16, QFont.Bold))
status_layout.addWidget(status_title)
# Current status
self.status_label = QLabel("No devices connected", self)
self.status_label.setFont(QFont("Arial", 12))
status_layout.addWidget(self.status_label)
# Progress bar for operations
self.progress_section = QFrame(self)
self.progress_section.setVisible(False)
progress_layout = QVBoxLayout(self.progress_section)
self.progress_title = QLabel("Operation in progress...", self)
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)
progress_layout.addWidget(self.progress_bar)
status_layout.addWidget(self.progress_section)
# Add status section to content
content_layout.addWidget(status_section)
# Add the main content to the layout
main_layout.addWidget(content_container, 1)
# Initialize with device refresh
QTimer.singleShot(500, self.refresh_devices)
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 = self.device_controller.get_devices()
# Update progress
self.update_progress(50)
# Display the devices in the table
if hasattr(device_descriptors, 'device_descriptor_list'):
devices = device_descriptors.device_descriptor_list
for i, device in enumerate(devices):
self.device_table.insertRow(i)
# Device ID
usb_id = QTableWidgetItem(str(device.get("usb_port_id", "-")))
self.device_table.setItem(i, 0, usb_id)
# Product ID
product_id = QTableWidgetItem(str(device.get("product_id", "-")))
self.device_table.setItem(i, 1, product_id)
# Firmware
firmware = QTableWidgetItem(str(device.get("firmware", "-")))
self.device_table.setItem(i, 2, firmware)
# KN Number
kn_number = QTableWidgetItem(str(device.get("kn_number", "-")))
self.device_table.setItem(i, 3, kn_number)
# Status
status = QTableWidgetItem("Connected" if device.get("is_connectable", False) else "Not Available")
self.device_table.setItem(i, 4, status)
# Hide progress
self.hide_progress()
# Update status
if self.device_table.rowCount() > 0:
self.status_label.setText(f"Found {self.device_table.rowCount()} device(s)")
else:
self.status_label.setText("No devices found")
except Exception as e:
self.hide_progress()
self.status_label.setText(f"Error refreshing devices: {str(e)}")
QMessageBox.critical(self, "Error", f"Failed to scan for devices: {str(e)}")
def register_device(self):
"""Register the selected device"""
selected_rows = self.device_table.selectedItems()
if not selected_rows:
QMessageBox.warning(self, "No Selection", "Please select a device to register")
return
row = selected_rows[0].row()
device_id = self.device_table.item(row, 0).text()
kn_number = self.device_table.item(row, 3).text()
# Show confirmation dialog
reply = QMessageBox.question(self, "Register Device",
f"Do you want to register device with KN Number: {kn_number}?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
# Show progress
self.show_progress("Registering device...", 0)
# Simulate registration process
for i in range(1, 5):
QTimer.singleShot(i * 500, lambda val=i*25: self.update_progress(val))
# Simulate completion
QTimer.singleShot(2500, lambda: self.registration_complete(True))
def update_firmware(self):
"""Update firmware for the selected device"""
selected_rows = self.device_table.selectedItems()
if not selected_rows:
QMessageBox.warning(self, "No Selection", "Please select a device to update")
return
row = selected_rows[0].row()
device_id = self.device_table.item(row, 0).text()
current_fw = self.device_table.item(row, 2).text()
# Show confirmation dialog
reply = QMessageBox.question(self, "Update Firmware",
f"Current firmware: {current_fw}\nDo you want to update the firmware?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
# Show progress
self.show_progress("Downloading firmware...", 0)
# Simulate download process
for i in range(1, 5):
QTimer.singleShot(i * 500, lambda val=i*25: self.update_progress(val))
# Simulate installation
QTimer.singleShot(2500, lambda: self.show_progress("Installing firmware...", 50))
for i in range(1, 5):
QTimer.singleShot(2500 + i * 500, lambda val=50+i*10: self.update_progress(val))
# Simulate completion
QTimer.singleShot(5000, lambda: self.firmware_update_complete(True))
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 registration_complete(self, success):
"""Handle registration completion"""
self.hide_progress()
if success:
QMessageBox.information(self, "Registration Complete", "Device registration successful!")
# Update the status in the table
selected_row = self.device_table.selectedItems()[0].row()
self.device_table.setItem(selected_row, 4, QTableWidgetItem("Registered"))
else:
QMessageBox.critical(self, "Registration Failed", "Failed to register device. Please try again.")
def firmware_update_complete(self, success):
"""Handle firmware update completion"""
self.hide_progress()
if success:
QMessageBox.information(self, "Update Complete", "Firmware update successful!")
# Update the firmware version in the table (simulate a new version)
selected_row = self.device_table.selectedItems()[0].row()
current_fw = self.device_table.item(selected_row, 2).text()
if current_fw.endswith("F"):
new_fw = current_fw + " (Updated)"
self.device_table.setItem(selected_row, 2, QTableWidgetItem(new_fw))
else:
QMessageBox.critical(self, "Update Failed", "Failed to update firmware. Please try again.")