basic bugfix with delays and disabling source switching during data collection

This commit is contained in:
2026-01-19 23:26:23 -06:00
parent 7483d3b42d
commit 2ebc0d07d4
2 changed files with 47 additions and 8 deletions

View File

@@ -29,7 +29,7 @@ from datetime import datetime
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure from matplotlib.figure import Figure
import matplotlib.animation as animation from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
# Import from the existing pipeline # Import from the existing pipeline
from learning_data_collection import ( from learning_data_collection import (
@@ -545,7 +545,12 @@ class CollectionPage(BasePage):
self.stream = RealSerialStream(port=port) self.stream = RealSerialStream(port=port)
self._update_connection_status("orange", "Connecting...") self._update_connection_status("orange", "Connecting...")
except Exception as e: except Exception as e:
messagebox.showerror("Connection Error", f"Failed to create serial stream:\n{e}") error_msg = f"Failed to create serial stream:\n{e}"
if "PermissionError" in str(type(e).__name__) or "Permission denied" in str(e):
error_msg += "\n\nThe port may still be in use. Wait a few seconds and try again."
elif "FileNotFoundError" in str(type(e).__name__):
error_msg += f"\n\nPort '{port}' not found. Try refreshing the port list."
messagebox.showerror("Connection Error", error_msg)
return return
else: else:
# Simulated stream (gesture-aware for realistic testing) # Simulated stream (gesture-aware for realistic testing)
@@ -577,6 +582,10 @@ class CollectionPage(BasePage):
self.save_button.configure(state="disabled") self.save_button.configure(state="disabled")
self.status_label.configure(text="Starting...") self.status_label.configure(text="Starting...")
# Disable source selection during collection
self.sim_radio.configure(state="disabled")
self.real_radio.configure(state="disabled")
# Start collection thread # Start collection thread
self.collection_thread = threading.Thread(target=self.collection_loop, daemon=True) self.collection_thread = threading.Thread(target=self.collection_loop, daemon=True)
self.collection_thread.start() self.collection_thread.start()
@@ -592,6 +601,9 @@ class CollectionPage(BasePage):
try: try:
if self.stream: if self.stream:
self.stream.stop() self.stream.stop()
# Give OS time to release the port (important for macOS)
if self.using_real_hardware:
time.sleep(0.5)
except Exception: except Exception:
pass # Ignore cleanup errors pass # Ignore cleanup errors
@@ -610,6 +622,10 @@ class CollectionPage(BasePage):
self.prompt_label.configure(text="DONE", text_color="green") self.prompt_label.configure(text="DONE", text_color="green")
self.countdown_label.configure(text="") self.countdown_label.configure(text="")
# Re-enable source selection
self.sim_radio.configure(state="normal")
self.real_radio.configure(state="normal")
# Update connection status # Update connection status
if self.using_real_hardware: if self.using_real_hardware:
self._update_connection_status("gray", "Disconnected") self._update_connection_status("gray", "Disconnected")
@@ -1387,6 +1403,10 @@ class PredictionPage(BasePage):
self.is_predicting = True self.is_predicting = True
self.start_button.configure(text="Stop", fg_color="red") self.start_button.configure(text="Stop", fg_color="red")
# Disable source selection during prediction
self.sim_radio.configure(state="disabled")
self.real_radio.configure(state="disabled")
# Start prediction thread # Start prediction thread
thread = threading.Thread(target=self._prediction_thread, daemon=True) thread = threading.Thread(target=self._prediction_thread, daemon=True)
thread.start() thread.start()
@@ -1402,6 +1422,9 @@ class PredictionPage(BasePage):
try: try:
if self.stream: if self.stream:
self.stream.stop() self.stream.stop()
# Give OS time to release the port (important for macOS)
if self.using_real_hardware:
time.sleep(0.5)
except Exception: except Exception:
pass # Ignore cleanup errors pass # Ignore cleanup errors
@@ -1412,6 +1435,10 @@ class PredictionPage(BasePage):
self.sim_label.configure(text="") self.sim_label.configure(text="")
self.raw_label.configure(text="", text_color="gray") self.raw_label.configure(text="", text_color="gray")
# Re-enable source selection
self.sim_radio.configure(state="normal")
self.real_radio.configure(state="normal")
# Update connection status # Update connection status
if self.using_real_hardware: if self.using_real_hardware:
self._update_connection_status("gray", "Disconnected") self._update_connection_status("gray", "Disconnected")
@@ -1453,7 +1480,10 @@ class PredictionPage(BasePage):
try: try:
self.stream = RealSerialStream(port=port) self.stream = RealSerialStream(port=port)
except Exception as e: except Exception as e:
self.data_queue.put(('error', f"Failed to create serial stream: {e}")) error_msg = f"Failed to create serial stream: {e}"
if "Permission denied" in str(e) or "Resource busy" in str(e):
error_msg += "\n\nThe port may still be in use. Wait a moment and try again."
self.data_queue.put(('error', error_msg))
return return
else: else:
self.stream = GestureAwareEMGStream(num_channels=NUM_CHANNELS, sample_rate=SAMPLING_RATE_HZ) self.stream = GestureAwareEMGStream(num_channels=NUM_CHANNELS, sample_rate=SAMPLING_RATE_HZ)
@@ -1668,7 +1698,6 @@ class VisualizationPage(BasePage):
extractor = EMGFeatureExtractor() extractor = EMGFeatureExtractor()
X_features = extractor.extract_features_batch(X) X_features = extractor.extract_features_batch(X)
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis() lda = LinearDiscriminantAnalysis()
lda.fit(X_features, y) lda.fit(X_features, y)
X_lda = lda.transform(X_features) X_lda = lda.transform(X_features)

View File

@@ -103,7 +103,11 @@ class RealSerialStream:
print(f"[SERIAL] Connected to {self.port} at {self.baud_rate} baud") print(f"[SERIAL] Connected to {self.port} at {self.baud_rate} baud")
except serial.SerialException as e: except serial.SerialException as e:
raise RuntimeError(f"Failed to open {self.port}: {e}") error_msg = f"Failed to open {self.port}: {e}"
# Add helpful context for common errors
if "Permission denied" in str(e) or "Resource busy" in str(e):
error_msg += "\n\nThe port may still be in use from a previous connection. Wait a moment and try again."
raise RuntimeError(error_msg)
def stop(self) -> None: def stop(self) -> None:
""" """
@@ -113,9 +117,15 @@ class RealSerialStream:
""" """
self.running = False self.running = False
if self.serial and self.serial.is_open: if self.serial:
try:
if self.serial.is_open:
self.serial.close() self.serial.close()
print(f"[SERIAL] Disconnected from {self.port}") print(f"[SERIAL] Disconnected from {self.port}")
except Exception as e:
print(f"[SERIAL] Warning during disconnect: {e}")
finally:
self.serial = None
def readline(self) -> Optional[str]: def readline(self) -> Optional[str]:
""" """