diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 32e2e21..901d41b 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -441,6 +441,15 @@ class MainWindow(ctk.CTk): ) read_btn.grid(row=2, column=0, padx=PADDING, pady=PADDING_SMALL) + # Test version button + self._test_version_btn = ctk.CTkButton( + panel, + text="Test Version", + font=FONT_BODY, + command=self._test_serial_version, + ) + self._test_version_btn.grid(row=3, column=0, padx=PADDING, pady=PADDING_SMALL) + def _build_status_panel(self, parent: ctk.CTkFrame) -> None: """Build the status display panel.""" panel = ctk.CTkFrame(parent, corner_radius=CORNER_RADIUS) @@ -972,6 +981,46 @@ class MainWindow(ctk.CTk): except Exception as e: self._log_message(f" Error: {e}") + def _test_serial_version(self) -> None: + """Test serial port by sending 'ver()' command and parsing response.""" + port = self._port_var.get() + if not port: + self._log_message("No serial port selected") + return + + self._log_message(f"Testing version on {port}...") + self._test_version_btn.configure(state="disabled", text="Testing...") + + # Run in background thread + def test_version_thread(): + try: + from serial_monitor import test_serial_version + + success, message, version = test_serial_version( + port, + self._config.serial_baudrate if self._config else 115200, + ) + + if success: + if version: + self._log_message(f" ✓ {message}") + self._log_message(f" ✓ Version detected: {version}") + self._status.set_status(f"Version: {version}", "success") + else: + self._log_message(f" ✓ {message}") + self._status.set_status("Port responded", "info") + else: + self._log_message(f" ✗ {message}") + self._status.set_status(message, "error") + except Exception as e: + self._log_message(f" ✗ Error: {e}") + self._status.set_status(f"Error: {e}", "error") + finally: + # Re-enable button + self.after(0, lambda: self._test_version_btn.configure(state="normal", text="Test Version")) + + threading.Thread(target=test_version_thread, daemon=True).start() + # ── Vitis Check ──────────────────────────────────────────── def _check_vitis(self) -> None: diff --git a/src/serial_monitor.py b/src/serial_monitor.py index a553ae0..b27db01 100644 --- a/src/serial_monitor.py +++ b/src/serial_monitor.py @@ -192,3 +192,96 @@ def read_serial_stream( if line_callback: line_callback(line) return lines + + +def test_serial_version( + port: str, + baudrate: int = 115200, + timeout: float = 5.0, +) -> tuple[bool, str, str]: + """Test serial port by sending 'ver()' command and parsing response. + + Sends 'ver\\r\\n' to the serial port and reads the response. + Attempts to parse version string from the response. + + Args: + port: Serial port device path (e.g., '/dev/ttyUSB0'). + baudrate: Baud rate for serial communication. + timeout: Read timeout in seconds. + + Returns: + Tuple of (success, message, version_string). + - (True, "Version: x.y.z", "x.y.z") — version detected + - (True, "Port responded", "") — port responded but no version + - (False, "Error message", "") — error occurred + """ + import serial + + try: + with serial.Serial(port, baudrate, timeout=timeout) as ser: + # Clear buffers + ser.reset_input_buffer() + ser.reset_output_buffer() + + # Send ver() command + ser.write(b"ver()\r\n") + + # Read response + response = b"" + start_time = __import__("time").time() + while __import__("time").time() - start_time < timeout: + if ser.in_waiting: + data = ser.read(ser.in_waiting) + response += data + # Check if we have a complete response + if b"\n" in data or b"\r" in data: + break + __import__("time").sleep(0.1) + + # Decode response + response_str = response.decode("utf-8", errors="replace").strip() + + if not response_str: + return False, "Port responded but no data received", "" + + # Parse version from response + version = _parse_version_from_response(response_str) + + if version: + return True, f"Version: {version}", version + else: + return True, f"Port responded: {response_str[:100]}", "" + + except serial.SerialException as e: + return False, f"Serial error: {e}", "" + except Exception as e: + return False, f"Error: {e}", "" + + +def _parse_version_from_response(response: str) -> str: + """Parse version string from serial response. + + Args: + response: Response string from serial port. + + Returns: + Version string if found, empty string otherwise. + """ + # Common version patterns + version_patterns = [ + re.compile(r"([Vv]ersion[:\s]+)?([^\s;,\n]+v\d+\.\d+\.\d+[^\s;,\n]*)", re.IGNORECASE), + re.compile(r"(?:^|[\s:=])(v\d+\.\d+\.\d+)(?:\s|$)", re.IGNORECASE), + re.compile(r"(\d+\.\d+\.\d+)", re.IGNORECASE), + ] + + for pattern in version_patterns: + match = pattern.search(response) + if match: + # Return the version part (group 1 or group 2) + version = match.group(1) if match.lastindex and match.lastindex >= 1 else match.group(0) + # Clean up version string + version = version.strip().strip("';\"") + if version and len(version) > 2: # Basic validation + return version + + return ""