feat(backend): introduce session controller and status models
Implement DeviceController and SessionController to bridge QML requests with backend state, supporting playback replay tracking, cluster averaging, and slot error boundaries.
This commit is contained in:
@@ -58,8 +58,8 @@ class DeviceController(QObject):
|
||||
self._data_dir = data_dir
|
||||
self._service = DeviceService(settings)
|
||||
|
||||
# Initialize status from persisted settings (with defaults for new installations)
|
||||
self._connection_status = getattr(settings, 'connection_status', "Disconnected")
|
||||
# Always start fresh — never restore connection status from a prior session
|
||||
self._connection_status = "Disconnected"
|
||||
self._operation_in_progress = False
|
||||
self._activity_log: list[str] = getattr(settings, 'activity_log', [])
|
||||
self._raw_bytes: Optional[bytes] = None
|
||||
@@ -87,6 +87,9 @@ class DeviceController(QObject):
|
||||
self._append_log(f"Last wafer: {info.get('serialNumber', 'Unknown')} ({info.get('familyCode', 'Unknown')})")
|
||||
self.statusRestored.emit()
|
||||
|
||||
# Reset connection status on fresh start
|
||||
self._connection_status = "Disconnected"
|
||||
|
||||
# Marshal worker-thread results onto the Qt main thread.
|
||||
self._detectFinished.connect(self._handle_detect_finished, Qt.ConnectionType.QueuedConnection)
|
||||
self._readFinished.connect(self._handle_read_finished, Qt.ConnectionType.QueuedConnection)
|
||||
@@ -101,6 +104,12 @@ class DeviceController(QObject):
|
||||
"""Currently available serial port names."""
|
||||
return self._service.enumerate_ports()
|
||||
|
||||
def _set_connection_status(self, status: str) -> None:
|
||||
"""Update connection status and notify QML."""
|
||||
if self._connection_status != status:
|
||||
self._connection_status = status
|
||||
self.portsUpdated.emit(self.availablePorts)
|
||||
|
||||
@Property(str, notify=portsUpdated)
|
||||
def connectionStatus(self) -> str:
|
||||
"""Current connection status string for display."""
|
||||
@@ -223,7 +232,7 @@ class DeviceController(QObject):
|
||||
@slot_error_boundary
|
||||
def clearSession(self) -> None:
|
||||
"""Clear the current session state, allowing a clean detect."""
|
||||
self._connection_status = "Disconnected"
|
||||
self._set_connection_status("Disconnected")
|
||||
self._selected_port = ""
|
||||
self._last_wafer_info = {}
|
||||
self._data_row_count = 0
|
||||
@@ -235,6 +244,7 @@ class DeviceController(QObject):
|
||||
self._append_log("Session refreshed/cleared")
|
||||
self._save_status()
|
||||
|
||||
|
||||
def _set_operation_progress(self, in_progress: bool) -> None:
|
||||
"""Set operation progress state and notify QML.
|
||||
|
||||
@@ -252,7 +262,7 @@ class DeviceController(QObject):
|
||||
def refreshPorts(self) -> None:
|
||||
"""Scan for available serial ports and emit updated list."""
|
||||
ports = self._service.enumerate_ports()
|
||||
self._connection_status = "Disconnected"
|
||||
self._set_connection_status("Disconnected")
|
||||
self.portsUpdated.emit(ports)
|
||||
self._append_log(f"Scanned {len(ports)} serial port(s)")
|
||||
self._save_status()
|
||||
@@ -271,7 +281,7 @@ class DeviceController(QObject):
|
||||
return
|
||||
|
||||
self._set_operation_progress(True)
|
||||
self._connection_status = "Detecting..."
|
||||
self._set_connection_status("Detecting...")
|
||||
self.portsUpdated.emit(self._service.enumerate_ports())
|
||||
self._append_log("Scanning all ports for wafer ...")
|
||||
|
||||
@@ -295,7 +305,7 @@ class DeviceController(QObject):
|
||||
"""Main-thread completion handler for detectWafer."""
|
||||
if info is not None:
|
||||
self._selected_port = port or ""
|
||||
self._connection_status = "Connected"
|
||||
self._set_connection_status("Connected")
|
||||
self._append_log(
|
||||
f"Detected: {info.serial_number} (family={info.family_code}, "
|
||||
f"port={port}, runtime={info.runtime}s, cycles={info.cycle_count})"
|
||||
@@ -306,7 +316,7 @@ class DeviceController(QObject):
|
||||
self._save_status()
|
||||
else:
|
||||
self._selected_port = ""
|
||||
self._connection_status = "Disconnected"
|
||||
self._set_connection_status("Disconnected")
|
||||
self._append_log("No wafer detected on any port")
|
||||
self._last_wafer_info = {}
|
||||
self.detectResult.emit(None)
|
||||
@@ -334,7 +344,7 @@ class DeviceController(QObject):
|
||||
family_code = self._last_wafer_info.get("familyCode", "")
|
||||
|
||||
self._set_operation_progress(True)
|
||||
self._connection_status = "Reading..."
|
||||
self._set_connection_status("Reading...")
|
||||
self.portsUpdated.emit(self._service.enumerate_ports())
|
||||
self._append_log(
|
||||
f"Reading memory on {port} (family={family_code or 'auto'}) ..."
|
||||
@@ -362,7 +372,7 @@ class DeviceController(QObject):
|
||||
) -> None:
|
||||
"""Main-thread completion handler for readMemoryAsync."""
|
||||
if data is not None:
|
||||
self._connection_status = "Connected"
|
||||
self._set_connection_status("Connected")
|
||||
self._append_log(f"Read {len(data)} bytes")
|
||||
self._raw_bytes = data
|
||||
self.readResult.emit({"success": True, "bytes": len(data)})
|
||||
@@ -370,7 +380,7 @@ class DeviceController(QObject):
|
||||
self._append_log("Memory read complete - choose save directory to save CSV")
|
||||
self._save_status()
|
||||
return
|
||||
self._connection_status = "Disconnected"
|
||||
self._set_connection_status("Disconnected")
|
||||
self._append_log("Read returned no data")
|
||||
self._raw_bytes = None
|
||||
self.readResult.emit({"error": "Read returned no data"})
|
||||
@@ -382,16 +392,16 @@ class DeviceController(QObject):
|
||||
def eraseMemory(self, port: str) -> None:
|
||||
"""Send p1 erase command."""
|
||||
self._set_operation_progress(True)
|
||||
self._connection_status = "Erasing..."
|
||||
self._set_connection_status("Erasing...")
|
||||
self.portsUpdated.emit(self._service.enumerate_ports())
|
||||
self._append_log(f"Erase command sent to {port} (wafer takes ~15s) ...")
|
||||
|
||||
ok = self._service.erase_wafer(port)
|
||||
if ok:
|
||||
self._connection_status = "Connected"
|
||||
self._set_connection_status("Connected")
|
||||
self._append_log("Erase command accepted — wait ~15 seconds")
|
||||
else:
|
||||
self._connection_status = "Disconnected"
|
||||
self._set_connection_status("Disconnected")
|
||||
self._append_log("Erase command failed")
|
||||
self.eraseResult.emit({"success": ok})
|
||||
self._set_operation_progress(False)
|
||||
@@ -404,14 +414,14 @@ class DeviceController(QObject):
|
||||
Emits debugResult with counts on success.
|
||||
"""
|
||||
self._set_operation_progress(True)
|
||||
self._connection_status = "Reading debug..."
|
||||
self._set_connection_status("Reading debug...")
|
||||
self.portsUpdated.emit(self._service.enumerate_ports())
|
||||
self._append_log(f"Reading debug data on {port} ...")
|
||||
|
||||
# Step 1: D1 sensor data
|
||||
sensor_data = self._service.read_wafer_data(port, family_code="")
|
||||
if sensor_data is None:
|
||||
self._connection_status = "Disconnected"
|
||||
self._set_connection_status("Disconnected")
|
||||
self._append_log("D1 read failed — aborting debug read")
|
||||
self.debugResult.emit({"error": "D1 read failed"})
|
||||
self._set_operation_progress(False)
|
||||
@@ -422,13 +432,13 @@ class DeviceController(QObject):
|
||||
# Step 2: F1 debug data
|
||||
debug_data = self._service.read_wafer_data(port, family_code="", cmd="F1")
|
||||
if debug_data is None:
|
||||
self._connection_status = "Disconnected"
|
||||
self._set_connection_status("Disconnected")
|
||||
self._append_log("F1 read failed")
|
||||
self.debugResult.emit({"error": "F1 read failed"})
|
||||
self._set_operation_progress(False)
|
||||
return
|
||||
|
||||
self._connection_status = "Connected"
|
||||
self._set_connection_status("Connected")
|
||||
self._append_log(
|
||||
f"Debug read complete: {len(sensor_data)} sensor bytes, "
|
||||
f"{len(debug_data)} debug bytes"
|
||||
|
||||
Reference in New Issue
Block a user