feat(ui): improve device session management, update data directory paths, and add session clearing functionality.

This commit is contained in:
jack
2026-06-15 11:28:28 -07:00
parent 7e584e08e8
commit 2e4763510d
7 changed files with 173 additions and 42 deletions
+27 -3
View File
@@ -24,6 +24,19 @@ Rectangle {
property int selectedTabIndex: 0 property int selectedTabIndex: 0
property int selectedSideActionIndex: -1 // nothing active on startup property int selectedSideActionIndex: -1 // nothing active on startup
Connections {
target: deviceController
function onParsedDataReady(result) {
if (result.success && result.csv_path) {
// Load the freshly read CSV into the player
streamController.loadFile(result.csv_path)
// Automatically switch to the "Wafer Map Tab" (index 3)
root.selectedTabIndex = 3
}
}
}
// ===== Main Two-Column Layout ===== // ===== Main Two-Column Layout =====
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
@@ -60,9 +73,19 @@ Rectangle {
onClicked: { onClicked: {
root.selectedSideActionIndex = index root.selectedSideActionIndex = index
root.selectedTabIndex = 0 // always jump to Status tab root.selectedTabIndex = 0 // always jump to Status tab
if (index === 0) deviceController.detectWafer() if (index === 0) {
else if (index === 1) deviceController.readMemoryAsync() streamController.setMode("review")
else if (index === 2) deviceController.openCsvFile() streamController.stopStream()
deviceController.detectWafer()
}
else if (index === 1) {
streamController.setMode("review")
streamController.stopStream()
deviceController.readMemoryAsync()
}
else if (index === 2) {
deviceController.openCsvFile()
}
} }
background: Rectangle { background: Rectangle {
@@ -182,6 +205,7 @@ Rectangle {
color: Theme.tabBarBackground color: Theme.tabBarBackground
border.color: Theme.workspaceBorder border.color: Theme.workspaceBorder
border.width: Theme.borderThin border.width: Theme.borderThin
radius: Theme.radiusMd
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
+95 -21
View File
@@ -47,11 +47,7 @@ ColumnLayout {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 86 Layout.preferredHeight: 86
color: { color: Theme.cardBackground
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor + "22"
if (deviceController.connectionStatus === "Disconnected") return Theme.statusErrorColor + "22"
return Theme.cardBackground
}
border.color: { border.color: {
if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor if (deviceController.connectionStatus === "Connected") return Theme.statusSuccessColor
if (deviceController.connectionStatus === "Disconnected") return Theme.statusErrorColor if (deviceController.connectionStatus === "Disconnected") return Theme.statusErrorColor
@@ -187,27 +183,105 @@ ColumnLayout {
} }
// --- Activity Log --- // --- Activity Log ---
GroupBox { Rectangle {
title: "Activity Log"
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
color: Theme.cardBackground
border.color: Theme.cardBorder
radius: Theme.radiusMd
clip: true
ScrollView { ColumnLayout {
anchors.fill: parent anchors.fill: parent
clip: true spacing: 0
TextArea { // Header
id: activityLog Rectangle {
width: parent.width Layout.fillWidth: true
text: "" Layout.preferredHeight: 40
readOnly: true color: Theme.subtleSectionBackground
font.family: "monospace"
font.pixelSize: 12 RowLayout {
wrapMode: TextArea.WordWrap anchors.fill: parent
leftPadding: 4 anchors.leftMargin: Theme.panelPadding
rightPadding: 4 anchors.rightMargin: Theme.panelPadding
color: Theme.bodyColor
} Label {
text: "ACTIVITY LOG"
color: Theme.bodyColor
font.pixelSize: 10
font.letterSpacing: 1.8
font.weight: Font.Medium
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
Button {
text: "REFRESH SESSION"
font.pixelSize: 10
implicitHeight: 24
hoverEnabled: true
background: Rectangle {
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
color: Theme.bodyColor
font: parent.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: deviceController.clearSession()
}
Button {
text: "CLEAR LOG"
font.pixelSize: 10
implicitHeight: 24
hoverEnabled: true
background: Rectangle {
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
color: Theme.bodyColor
font: parent.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: deviceController.clearActivityLog()
}
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: Theme.cardBorder
}
}
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: Theme.panelPadding
clip: true
TextArea {
id: activityLog
width: parent.width
text: ""
readOnly: true
font.family: "monospace"
font.pixelSize: 12
wrapMode: TextArea.WordWrap
color: Theme.bodyColor
background: null
}
}
} }
Connections { Connections {
+14 -1
View File
@@ -81,7 +81,20 @@ Item {
} }
} }
onCurrentIndexChanged: onCurrentIndexChanged:
streamController.setMode(currentIndex === 0 ? "live" : "review") if (currentIndex === 0){
// Entering Live mode, only work when wafer detected/connected
streamController.setMode("live")
if (deviceController.connectionStatus === "Connected") {
var fc = ""
if (deviceController.lastWaferInfo && deviceController.lastWaferInfo.length > 0) {
fc = deviceController.lastWaferInfo[0]
}
streamController.startStream(deviceController.selectedPort, fc)
}
} else {
// Entering Review Mode (automatically calls stopStream in backend)
streamController.setMode("review")
}
} }
// Live source info: WiFi icon + port · serial // Live source info: WiFi icon + port · serial
@@ -15,6 +15,8 @@ Item {
sensors: streamController.sensorLayout // [{label,x,y}] sensors: streamController.sensorLayout // [{label,x,y}]
values: streamController.sensorValues // [float] values: streamController.sensorValues // [float]
bands: streamController.sensorBands // ["in_range"|"high"|"low"] bands: streamController.sensorBands // ["in_range"|"high"|"low"]
shape: streamController.waferShape
size: streamController.waferSize
target: streamController.target target: streamController.target
margin: streamController.margin margin: streamController.margin
blend: root.blend blend: root.blend
+5
View File
@@ -1,5 +1,6 @@
import sys import sys
from pathlib import Path from pathlib import Path
import logging
from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuickControls2 import QQuickStyle from PySide6.QtQuickControls2 import QQuickStyle
@@ -15,6 +16,10 @@ from pygui.backend.visualization.wafer_map_item import WaferMapItem
# ===== Application Entry Point ===== # ===== Application Entry Point =====
def main() -> int: def main() -> int:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
# ===== UI Style Setup ===== # ===== UI Style Setup =====
# Use a non-native controls style so our custom QML button backgrounds are supported. # Use a non-native controls style so our custom QML button backgrounds are supported.
QQuickStyle.setStyle("Basic") QQuickStyle.setStyle("Basic")
@@ -1,10 +1,4 @@
"""QML-exposed controller for wafer device communication. """QML-exposed controller for wafer device communication."""
Bridges DeviceService (serialcomm) to QML via @Slot methods and signals.
All hardware operations run synchronously on the main thread (matching
C# Form1.cs per-operation open/close pattern). For long operations
(erase ~15s, read up to 120s) the caller should show a loading state.
"""
from __future__ import annotations from __future__ import annotations
@@ -67,9 +61,8 @@ class DeviceController(QObject):
self._operation_in_progress = False self._operation_in_progress = False
self._activity_log: list[str] = getattr(settings, 'activity_log', []) self._activity_log: list[str] = getattr(settings, 'activity_log', [])
self._raw_bytes: Optional[bytes] = None self._raw_bytes: Optional[bytes] = None
# Default save dir lives next to the settings file (~/Documents/isc_data/csv)
from pathlib import Path from pathlib import Path
self._save_data_dir: str = getattr(settings, 'save_data_dir', str(Path.home() / "Documents" / "isc_data" / "csv")) self._save_data_dir: str = getattr(settings, 'save_data_dir', "") or str(Path(self._data_dir) / "csv")
self._last_wafer_info: dict[str, Any] = getattr(settings, 'last_wafer_info', {}) self._last_wafer_info: dict[str, Any] = getattr(settings, 'last_wafer_info', {})
self._last_csv_path: str = getattr(settings, 'last_csv_path', "") self._last_csv_path: str = getattr(settings, 'last_csv_path', "")
self._selected_port: str = getattr(settings, 'selected_port', "") self._selected_port: str = getattr(settings, 'selected_port', "")
@@ -209,6 +202,28 @@ class DeviceController(QObject):
self._activity_log = self._activity_log[-200:] self._activity_log = self._activity_log[-200:]
self.activityLogUpdated.emit("\n".join(self._activity_log)) self.activityLogUpdated.emit("\n".join(self._activity_log))
@Slot()
def clearActivityLog(self) -> None:
"""Clear the activity log."""
self._activity_log.clear()
self.activityLogUpdated.emit("")
self._save_status()
@Slot()
def clearSession(self) -> None:
"""Clear the current session state, allowing a clean detect."""
self._connection_status = "Disconnected"
self._selected_port = ""
self._last_wafer_info = {}
self._data_row_count = 0
self._data_col_count = 0
self._raw_bytes = None
self._operation_in_progress = False
self.detectResult.emit(None)
self.portsUpdated.emit(self.availablePorts)
self._append_log("Session refreshed/cleared")
self._save_status()
def _set_operation_progress(self, in_progress: bool) -> None: def _set_operation_progress(self, in_progress: bool) -> None:
"""Set operation progress state and notify QML. """Set operation progress state and notify QML.
@@ -424,12 +439,10 @@ class DeviceController(QObject):
return return
if not self._save_data_dir: if not self._save_data_dir:
self._append_log("No save data directory set") from pathlib import Path
self.parsedDataReady.emit({ self._save_data_dir = str(Path(self._data_dir) / "csv")
"success": False, self._append_log(f"Auto-set save directory to: {self._save_data_dir}")
"error": "No save data directory set", self._save_status()
})
return
fc = family_code or ( fc = family_code or (
self._last_wafer_info.get("familyCode", "") self._last_wafer_info.get("familyCode", "")
@@ -509,7 +522,7 @@ class DeviceController(QObject):
self._settings.last_csv_path = self._last_csv_path self._settings.last_csv_path = self._last_csv_path
# Save to disk # Save to disk
LocalSettings.save_settings(str(LocalSettings._settings_path(self._data_dir)), self._settings) LocalSettings.save_settings(self._data_dir, self._settings)
# ---- Helpers ---- # ---- Helpers ----
@@ -52,7 +52,7 @@ class LocalSettingsModel(QObject):
QStandardPaths.DocumentsLocation QStandardPaths.DocumentsLocation
) )
base_dir = Path(documents_dir) if documents_dir else (Path.home() / "Documents") base_dir = Path(documents_dir) if documents_dir else (Path.home() / "Documents")
return base_dir / "isc_data" return base_dir / "ISC_DATA"
def _new_defaults(self) -> dict[str, Any]: def _new_defaults(self) -> dict[str, Any]:
return { return {