From 2ebc0d07d4c10163aa47015391d56bff4a52783d Mon Sep 17 00:00:00 2001 From: pulipakaa24 Date: Mon, 19 Jan 2026 23:26:23 -0600 Subject: [PATCH] basic bugfix with delays and disabling source switching during data collection --- emg_gui.py | 37 +++++++++++++++++++++++++++++++++---- serial_stream.py | 18 ++++++++++++++---- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/emg_gui.py b/emg_gui.py index 6b26fa5..600249a 100644 --- a/emg_gui.py +++ b/emg_gui.py @@ -29,7 +29,7 @@ from datetime import datetime import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure -import matplotlib.animation as animation +from sklearn.discriminant_analysis import LinearDiscriminantAnalysis # Import from the existing pipeline from learning_data_collection import ( @@ -545,7 +545,12 @@ class CollectionPage(BasePage): self.stream = RealSerialStream(port=port) self._update_connection_status("orange", "Connecting...") 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 else: # Simulated stream (gesture-aware for realistic testing) @@ -577,6 +582,10 @@ class CollectionPage(BasePage): self.save_button.configure(state="disabled") 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 self.collection_thread = threading.Thread(target=self.collection_loop, daemon=True) self.collection_thread.start() @@ -592,6 +601,9 @@ class CollectionPage(BasePage): try: if self.stream: self.stream.stop() + # Give OS time to release the port (important for macOS) + if self.using_real_hardware: + time.sleep(0.5) except Exception: pass # Ignore cleanup errors @@ -610,6 +622,10 @@ class CollectionPage(BasePage): self.prompt_label.configure(text="DONE", text_color="green") self.countdown_label.configure(text="") + # Re-enable source selection + self.sim_radio.configure(state="normal") + self.real_radio.configure(state="normal") + # Update connection status if self.using_real_hardware: self._update_connection_status("gray", "Disconnected") @@ -1387,6 +1403,10 @@ class PredictionPage(BasePage): self.is_predicting = True 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 thread = threading.Thread(target=self._prediction_thread, daemon=True) thread.start() @@ -1402,6 +1422,9 @@ class PredictionPage(BasePage): try: if self.stream: self.stream.stop() + # Give OS time to release the port (important for macOS) + if self.using_real_hardware: + time.sleep(0.5) except Exception: pass # Ignore cleanup errors @@ -1412,6 +1435,10 @@ class PredictionPage(BasePage): self.sim_label.configure(text="") 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 if self.using_real_hardware: self._update_connection_status("gray", "Disconnected") @@ -1453,7 +1480,10 @@ class PredictionPage(BasePage): try: self.stream = RealSerialStream(port=port) 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 else: self.stream = GestureAwareEMGStream(num_channels=NUM_CHANNELS, sample_rate=SAMPLING_RATE_HZ) @@ -1668,7 +1698,6 @@ class VisualizationPage(BasePage): extractor = EMGFeatureExtractor() X_features = extractor.extract_features_batch(X) - from sklearn.discriminant_analysis import LinearDiscriminantAnalysis lda = LinearDiscriminantAnalysis() lda.fit(X_features, y) X_lda = lda.transform(X_features) diff --git a/serial_stream.py b/serial_stream.py index 71e0ce8..713dd63 100644 --- a/serial_stream.py +++ b/serial_stream.py @@ -103,7 +103,11 @@ class RealSerialStream: print(f"[SERIAL] Connected to {self.port} at {self.baud_rate} baud") 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: """ @@ -113,9 +117,15 @@ class RealSerialStream: """ self.running = False - if self.serial and self.serial.is_open: - self.serial.close() - print(f"[SERIAL] Disconnected from {self.port}") + if self.serial: + try: + if self.serial.is_open: + self.serial.close() + 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]: """