diff --git a/9_Firmware/9_3_GUI/GUI_V65_Tk.py b/9_Firmware/9_3_GUI/GUI_V65_Tk.py index 6ac8007..0ecae7b 100644 --- a/9_Firmware/9_3_GUI/GUI_V65_Tk.py +++ b/9_Firmware/9_3_GUI/GUI_V65_Tk.py @@ -59,7 +59,7 @@ except (ModuleNotFoundError, ImportError): # Import protocol layer (no GUI deps) from radar_protocol import ( - RadarProtocol, FT2232HConnection, + RadarProtocol, FT2232HConnection, FT601Connection, DataRecorder, RadarAcquisition, RadarFrame, StatusResponse, NUM_RANGE_BINS, NUM_DOPPLER_BINS, WATERFALL_DEPTH, @@ -388,10 +388,11 @@ class RadarDashboard: BANDWIDTH = 500e6 # Hz — chirp bandwidth C = 3e8 # m/s — speed of light - def __init__(self, root: tk.Tk, connection: FT2232HConnection, + def __init__(self, root: tk.Tk, mock: bool, recorder: DataRecorder, device_index: int = 0): self.root = root - self.conn = connection + self._mock = mock + self.conn: FT2232HConnection | FT601Connection | None = None self.recorder = recorder self.device_index = device_index @@ -485,6 +486,16 @@ class RadarDashboard: style="Accent.TButton") self.btn_connect.pack(side="right", padx=4) + # USB Interface selector (production FT2232H / premium FT601) + self._usb_iface_var = tk.StringVar(value="FT2232H (Production)") + self.cmb_usb_iface = ttk.Combobox( + top, textvariable=self._usb_iface_var, + values=["FT2232H (Production)", "FT601 (Premium)"], + state="readonly", width=20, + ) + self.cmb_usb_iface.pack(side="right", padx=4) + ttk.Label(top, text="USB:", font=("Menlo", 10)).pack(side="right") + self.btn_record = ttk.Button(top, text="Record", command=self._on_record) self.btn_record.pack(side="right", padx=4) @@ -1018,15 +1029,17 @@ class RadarDashboard: # ------------------------------------------------------------ Actions def _on_connect(self): - if self.conn.is_open: + if self.conn is not None and self.conn.is_open: # Disconnect if self._acq_thread is not None: self._acq_thread.stop() self._acq_thread.join(timeout=2) self._acq_thread = None self.conn.close() + self.conn = None self.lbl_status.config(text="DISCONNECTED", foreground=RED) self.btn_connect.config(text="Connect") + self.cmb_usb_iface.config(state="readonly") log.info("Disconnected") return @@ -1036,6 +1049,16 @@ class RadarDashboard: if self._replay_active: self._replay_stop() + # Create connection based on USB Interface selector + iface = self._usb_iface_var.get() + if "FT601" in iface: + self.conn = FT601Connection(mock=self._mock) + else: + self.conn = FT2232HConnection(mock=self._mock) + + # Disable interface selector while connecting/connected + self.cmb_usb_iface.config(state="disabled") + # Open connection in a background thread to avoid blocking the GUI self.lbl_status.config(text="CONNECTING...", foreground=YELLOW) self.btn_connect.config(state="disabled") @@ -1062,6 +1085,8 @@ class RadarDashboard: else: self.lbl_status.config(text="CONNECT FAILED", foreground=RED) self.btn_connect.config(text="Connect") + self.cmb_usb_iface.config(state="readonly") + self.conn = None def _on_record(self): if self.recorder.recording: @@ -1110,6 +1135,9 @@ class RadarDashboard: f"Opcode 0x{opcode:02X} is hardware-only (ignored in replay)")) return cmd = RadarProtocol.build_command(opcode, value) + if self.conn is None: + log.warning("No connection — command not sent") + return ok = self.conn.write(cmd) log.info(f"CMD 0x{opcode:02X} val={value} ({'OK' if ok else 'FAIL'})") @@ -1148,7 +1176,7 @@ class RadarDashboard: if self._replay_active or self._replay_ctrl is not None: self._replay_stop() if self._acq_thread is not None: - if self.conn.is_open: + if self.conn is not None and self.conn.is_open: self._on_connect() # disconnect else: # Connection dropped unexpectedly — just clean up the thread @@ -1547,17 +1575,17 @@ def main(): args = parser.parse_args() if args.live: - conn = FT2232HConnection(mock=False) + mock = False mode_str = "LIVE" else: - conn = FT2232HConnection(mock=True) + mock = True mode_str = "MOCK" recorder = DataRecorder() root = tk.Tk() - dashboard = RadarDashboard(root, conn, recorder, device_index=args.device) + dashboard = RadarDashboard(root, mock, recorder, device_index=args.device) if args.record: filepath = os.path.join( @@ -1582,8 +1610,8 @@ def main(): if dashboard._acq_thread is not None: dashboard._acq_thread.stop() dashboard._acq_thread.join(timeout=2) - if conn.is_open: - conn.close() + if dashboard.conn is not None and dashboard.conn.is_open: + dashboard.conn.close() if recorder.recording: recorder.stop() root.destroy() diff --git a/9_Firmware/9_3_GUI/radar_protocol.py b/9_Firmware/9_3_GUI/radar_protocol.py index e04d768..52176d2 100644 --- a/9_Firmware/9_3_GUI/radar_protocol.py +++ b/9_Firmware/9_3_GUI/radar_protocol.py @@ -6,6 +6,7 @@ Pure-logic module for USB packet parsing and command building. No GUI dependencies — safe to import from tests and headless scripts. USB Interface: FT2232H USB 2.0 (8-bit, 50T production board) via pyftdi + FT601 USB 3.0 (32-bit, 200T premium board) via ftd3xx USB Packet Protocol (11-byte): TX (FPGA→Host): @@ -22,7 +23,7 @@ import queue import logging import contextlib from dataclasses import dataclass, field -from typing import Any +from typing import Any, ClassVar from enum import IntEnum @@ -200,7 +201,9 @@ class RadarProtocol: range_i = _to_signed16(struct.unpack_from(">H", raw, 3)[0]) doppler_i = _to_signed16(struct.unpack_from(">H", raw, 5)[0]) doppler_q = _to_signed16(struct.unpack_from(">H", raw, 7)[0]) - detection = raw[9] & 0x01 + det_byte = raw[9] + detection = det_byte & 0x01 + frame_start = (det_byte >> 7) & 0x01 return { "range_i": range_i, @@ -208,6 +211,7 @@ class RadarProtocol: "doppler_i": doppler_i, "doppler_q": doppler_q, "detection": detection, + "frame_start": frame_start, } @staticmethod @@ -433,7 +437,191 @@ class FT2232HConnection: pkt += struct.pack(">h", np.clip(range_i, -32768, 32767)) pkt += struct.pack(">h", np.clip(dop_i, -32768, 32767)) pkt += struct.pack(">h", np.clip(dop_q, -32768, 32767)) - pkt.append(detection & 0x01) + # Bit 7 = frame_start (sample_counter == 0), bit 0 = detection + det_byte = (detection & 0x01) | (0x80 if idx == 0 else 0x00) + pkt.append(det_byte) + pkt.append(FOOTER_BYTE) + + buf += pkt + + self._mock_seq_idx = (start_idx + num_packets) % NUM_CELLS + return bytes(buf) + + +# ============================================================================ +# FT601 USB 3.0 Connection (premium board only) +# ============================================================================ + +# Optional ftd3xx import (FTDI's proprietary driver for FT60x USB 3.0 chips). +# pyftdi does NOT support FT601 — it only handles USB 2.0 chips (FT232H, etc.) +try: + import ftd3xx # type: ignore[import-untyped] + FTD3XX_AVAILABLE = True + _Ftd3xxError: type = ftd3xx.FTD3XXError # type: ignore[attr-defined] +except ImportError: + FTD3XX_AVAILABLE = False + _Ftd3xxError = OSError # fallback for type-checking; never raised + + +class FT601Connection: + """ + FT601 USB 3.0 SuperSpeed FIFO bridge — premium board only. + + The FT601 has a 32-bit data bus and runs at 100 MHz. + VID:PID = 0x0403:0x6030 or 0x6031 (FTDI FT60x). + + Requires the ``ftd3xx`` library (``pip install ftd3xx`` on Windows, + or ``libft60x`` on Linux). This is FTDI's proprietary USB 3.0 driver; + ``pyftdi`` only supports USB 2.0 and will NOT work with FT601. + + Public contract matches FT2232HConnection so callers can swap freely. + """ + + VID = 0x0403 + PID_LIST: ClassVar[list[int]] = [0x6030, 0x6031] + + def __init__(self, mock: bool = True): + self._mock = mock + self._dev = None + self._lock = threading.Lock() + self.is_open = False + # Mock state (reuses same synthetic data pattern) + self._mock_frame_num = 0 + self._mock_rng = np.random.RandomState(42) + + def open(self, device_index: int = 0) -> bool: + if self._mock: + self.is_open = True + log.info("FT601 mock device opened (no hardware)") + return True + + if not FTD3XX_AVAILABLE: + log.error( + "ftd3xx library required for FT601 hardware — " + "install with: pip install ftd3xx" + ) + return False + + try: + self._dev = ftd3xx.create(device_index, ftd3xx.OPEN_BY_INDEX) + if self._dev is None: + log.error("No FT601 device found at index %d", device_index) + return False + # Verify chip configuration — only reconfigure if needed. + # setChipConfiguration triggers USB re-enumeration, which + # invalidates the device handle and requires a re-open cycle. + cfg = self._dev.getChipConfiguration() + needs_reconfig = ( + cfg.FIFOMode != 0 # 245 FIFO mode + or cfg.ChannelConfig != 0 # 1 channel, 32-bit + or cfg.OptionalFeatureSupport != 0 + ) + if needs_reconfig: + cfg.FIFOMode = 0 + cfg.ChannelConfig = 0 + cfg.OptionalFeatureSupport = 0 + self._dev.setChipConfiguration(cfg) + # Device re-enumerates — close stale handle, wait, re-open + self._dev.close() + self._dev = None + import time + time.sleep(2.0) # wait for USB re-enumeration + self._dev = ftd3xx.create(device_index, ftd3xx.OPEN_BY_INDEX) + if self._dev is None: + log.error("FT601 not found after reconfiguration") + return False + log.info("FT601 reconfigured and re-opened (index %d)", device_index) + self.is_open = True + log.info("FT601 device opened (index %d)", device_index) + return True + except (OSError, _Ftd3xxError) as e: + log.error("FT601 open failed: %s", e) + self._dev = None + return False + + def close(self): + if self._dev is not None: + with contextlib.suppress(Exception): + self._dev.close() + self._dev = None + self.is_open = False + + def read(self, size: int = 4096) -> bytes | None: + """Read raw bytes from FT601. Returns None on error/timeout.""" + if not self.is_open: + return None + + if self._mock: + return self._mock_read(size) + + with self._lock: + try: + data = self._dev.readPipe(0x82, size, raw=True) + return bytes(data) if data else None + except (OSError, _Ftd3xxError) as e: + log.error("FT601 read error: %s", e) + return None + + def write(self, data: bytes) -> bool: + """Write raw bytes to FT601. Data must be 4-byte aligned for 32-bit bus.""" + if not self.is_open: + return False + + if self._mock: + log.info(f"FT601 mock write: {data.hex()}") + return True + + # Pad to 4-byte alignment (FT601 32-bit bus requirement). + # NOTE: Radar commands are already 4 bytes, so this should be a no-op. + remainder = len(data) % 4 + if remainder: + data = data + b"\x00" * (4 - remainder) + + with self._lock: + try: + written = self._dev.writePipe(0x02, data, raw=True) + return written == len(data) + except (OSError, _Ftd3xxError) as e: + log.error("FT601 write error: %s", e) + return False + + def _mock_read(self, size: int) -> bytes: + """Generate synthetic radar packets (same pattern as FT2232H mock).""" + time.sleep(0.05) + self._mock_frame_num += 1 + + buf = bytearray() + num_packets = min(NUM_CELLS, size // DATA_PACKET_SIZE) + start_idx = getattr(self, "_mock_seq_idx", 0) + + for n in range(num_packets): + idx = (start_idx + n) % NUM_CELLS + rbin = idx // NUM_DOPPLER_BINS + dbin = idx % NUM_DOPPLER_BINS + + range_i = int(self._mock_rng.normal(0, 100)) + range_q = int(self._mock_rng.normal(0, 100)) + if abs(rbin - 20) < 3: + range_i += 5000 + range_q += 3000 + + dop_i = int(self._mock_rng.normal(0, 50)) + dop_q = int(self._mock_rng.normal(0, 50)) + if abs(rbin - 20) < 3 and abs(dbin - 8) < 2: + dop_i += 8000 + dop_q += 4000 + + detection = 1 if (abs(rbin - 20) < 2 and abs(dbin - 8) < 2) else 0 + + pkt = bytearray() + pkt.append(HEADER_BYTE) + pkt += struct.pack(">h", np.clip(range_q, -32768, 32767)) + pkt += struct.pack(">h", np.clip(range_i, -32768, 32767)) + pkt += struct.pack(">h", np.clip(dop_i, -32768, 32767)) + pkt += struct.pack(">h", np.clip(dop_q, -32768, 32767)) + # Bit 7 = frame_start (sample_counter == 0), bit 0 = detection + det_byte = (detection & 0x01) | (0x80 if idx == 0 else 0x00) + pkt.append(det_byte) pkt.append(FOOTER_BYTE) buf += pkt @@ -600,6 +788,12 @@ class RadarAcquisition(threading.Thread): if sample.get("detection", 0): self._frame.detections[rbin, dbin] = 1 self._frame.detection_count += 1 + # Accumulate FPGA range profile data (matched-filter output) + # Each sample carries the range_i/range_q for this range bin. + # Accumulate magnitude across Doppler bins for the range profile. + ri = int(sample.get("range_i", 0)) + rq = int(sample.get("range_q", 0)) + self._frame.range_profile[rbin] += abs(ri) + abs(rq) self._sample_idx += 1 @@ -607,11 +801,11 @@ class RadarAcquisition(threading.Thread): self._finalize_frame() def _finalize_frame(self): - """Complete frame: compute range profile, push to queue, record.""" + """Complete frame: push to queue, record.""" self._frame.timestamp = time.time() self._frame.frame_number = self._frame_num - # Range profile = sum of magnitude across Doppler bins - self._frame.range_profile = np.sum(self._frame.magnitude, axis=1) + # range_profile is already accumulated from FPGA range_i/range_q + # data in _ingest_sample(). No need to synthesize from doppler magnitude. # Push to display queue (drop old if backed up) try: diff --git a/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py b/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py index de5f18f..1cd32ad 100644 --- a/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py +++ b/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py @@ -16,7 +16,7 @@ import unittest import numpy as np from radar_protocol import ( - RadarProtocol, FT2232HConnection, DataRecorder, RadarAcquisition, + RadarProtocol, FT2232HConnection, FT601Connection, DataRecorder, RadarAcquisition, RadarFrame, StatusResponse, Opcode, HEADER_BYTE, FOOTER_BYTE, STATUS_HEADER_BYTE, NUM_RANGE_BINS, NUM_DOPPLER_BINS, @@ -312,6 +312,61 @@ class TestFT2232HConnection(unittest.TestCase): self.assertFalse(conn.write(b"\x00\x00\x00\x00")) +class TestFT601Connection(unittest.TestCase): + """Test mock FT601 connection (mirrors FT2232H tests).""" + + def test_mock_open_close(self): + conn = FT601Connection(mock=True) + self.assertTrue(conn.open()) + self.assertTrue(conn.is_open) + conn.close() + self.assertFalse(conn.is_open) + + def test_mock_read_returns_data(self): + conn = FT601Connection(mock=True) + conn.open() + data = conn.read(4096) + self.assertIsNotNone(data) + self.assertGreater(len(data), 0) + conn.close() + + def test_mock_read_contains_valid_packets(self): + """Mock data should contain parseable data packets.""" + conn = FT601Connection(mock=True) + conn.open() + raw = conn.read(4096) + packets = RadarProtocol.find_packet_boundaries(raw) + self.assertGreater(len(packets), 0) + for start, end, ptype in packets: + if ptype == "data": + result = RadarProtocol.parse_data_packet(raw[start:end]) + self.assertIsNotNone(result) + conn.close() + + def test_mock_write(self): + conn = FT601Connection(mock=True) + conn.open() + cmd = RadarProtocol.build_command(0x01, 1) + self.assertTrue(conn.write(cmd)) + conn.close() + + def test_write_pads_to_4_bytes(self): + """FT601 write() should pad data to 4-byte alignment.""" + conn = FT601Connection(mock=True) + conn.open() + # 3-byte payload should be padded internally (no error) + self.assertTrue(conn.write(b"\x01\x02\x03")) + conn.close() + + def test_read_when_closed(self): + conn = FT601Connection(mock=True) + self.assertIsNone(conn.read()) + + def test_write_when_closed(self): + conn = FT601Connection(mock=True) + self.assertFalse(conn.write(b"\x00\x00\x00\x00")) + + class TestDataRecorder(unittest.TestCase): """Test HDF5 recording (skipped if h5py not available).""" diff --git a/9_Firmware/9_3_GUI/v7/__init__.py b/9_Firmware/9_3_GUI/v7/__init__.py index 1da6cdb..3789667 100644 --- a/9_Firmware/9_3_GUI/v7/__init__.py +++ b/9_Firmware/9_3_GUI/v7/__init__.py @@ -26,6 +26,7 @@ from .models import ( # Hardware interfaces — production protocol via radar_protocol.py from .hardware import ( FT2232HConnection, + FT601Connection, RadarProtocol, Opcode, RadarAcquisition, @@ -89,7 +90,7 @@ __all__ = [ # noqa: RUF022 "USB_AVAILABLE", "FTDI_AVAILABLE", "SCIPY_AVAILABLE", "SKLEARN_AVAILABLE", "FILTERPY_AVAILABLE", # hardware — production FPGA protocol - "FT2232HConnection", "RadarProtocol", "Opcode", + "FT2232HConnection", "FT601Connection", "RadarProtocol", "Opcode", "RadarAcquisition", "RadarFrame", "StatusResponse", "DataRecorder", "STM32USBInterface", # processing diff --git a/9_Firmware/9_3_GUI/v7/dashboard.py b/9_Firmware/9_3_GUI/v7/dashboard.py index c7e2c64..8c7233f 100644 --- a/9_Firmware/9_3_GUI/v7/dashboard.py +++ b/9_Firmware/9_3_GUI/v7/dashboard.py @@ -13,13 +13,14 @@ RadarDashboard is a QMainWindow with six tabs: 6. Settings — Host-side DSP parameters + About section Uses production radar_protocol.py for all FPGA communication: - - FT2232HConnection for real hardware + - FT2232HConnection for production board (FT2232H USB 2.0) + - FT601Connection for premium board (FT601 USB 3.0) — selectable from GUI - Unified replay via SoftwareFPGA + ReplayEngine + ReplayWorker - Mock mode (FT2232HConnection(mock=True)) for development The old STM32 magic-packet start flow has been removed. FPGA registers are controlled directly via 4-byte {opcode, addr, value_hi, value_lo} -commands sent over FT2232H. +commands sent over FT2232H or FT601. """ from __future__ import annotations @@ -55,6 +56,7 @@ from .models import ( ) from .hardware import ( FT2232HConnection, + FT601Connection, RadarProtocol, RadarFrame, StatusResponse, @@ -142,7 +144,7 @@ class RadarDashboard(QMainWindow): ) # Hardware interfaces — production protocol - self._connection: FT2232HConnection | None = None + self._connection: FT2232HConnection | FT601Connection | None = None self._stm32 = STM32USBInterface() self._recorder = DataRecorder() @@ -364,7 +366,7 @@ class RadarDashboard(QMainWindow): # Row 0: connection mode + device combos + buttons ctrl_layout.addWidget(QLabel("Mode:"), 0, 0) self._mode_combo = QComboBox() - self._mode_combo.addItems(["Mock", "Live FT2232H", "Replay"]) + self._mode_combo.addItems(["Mock", "Live", "Replay"]) self._mode_combo.setCurrentIndex(0) ctrl_layout.addWidget(self._mode_combo, 0, 1) @@ -377,6 +379,13 @@ class RadarDashboard(QMainWindow): refresh_btn.clicked.connect(self._refresh_devices) ctrl_layout.addWidget(refresh_btn, 0, 4) + # USB Interface selector (production FT2232H / premium FT601) + ctrl_layout.addWidget(QLabel("USB Interface:"), 0, 5) + self._usb_iface_combo = QComboBox() + self._usb_iface_combo.addItems(["FT2232H (Production)", "FT601 (Premium)"]) + self._usb_iface_combo.setCurrentIndex(0) + ctrl_layout.addWidget(self._usb_iface_combo, 0, 6) + self._start_btn = QPushButton("Start Radar") self._start_btn.setStyleSheet( f"QPushButton {{ background-color: {DARK_SUCCESS}; color: white; font-weight: bold; }}" @@ -1001,7 +1010,8 @@ class RadarDashboard(QMainWindow): self._conn_ft2232h = self._make_status_label("FT2232H") self._conn_stm32 = self._make_status_label("STM32 USB") - conn_layout.addWidget(QLabel("FT2232H:"), 0, 0) + self._conn_usb_label = QLabel("USB Data:") + conn_layout.addWidget(self._conn_usb_label, 0, 0) conn_layout.addWidget(self._conn_ft2232h, 0, 1) conn_layout.addWidget(QLabel("STM32 USB:"), 1, 0) conn_layout.addWidget(self._conn_stm32, 1, 1) @@ -1167,7 +1177,7 @@ class RadarDashboard(QMainWindow): about_lbl = QLabel( "AERIS-10 Radar System V7
" "PyQt6 Edition with Embedded Leaflet Map

" - "Data Interface: FT2232H USB 2.0 (production protocol)
" + "Data Interface: FT2232H USB 2.0 (production) / FT601 USB 3.0 (premium)
" "FPGA Protocol: 4-byte register commands, 0xAA/0xBB packets
" "Map: OpenStreetMap + Leaflet.js
" "Framework: PyQt6 + QWebEngine
" @@ -1224,7 +1234,7 @@ class RadarDashboard(QMainWindow): # ===================================================================== def _send_fpga_cmd(self, opcode: int, value: int): - """Send a 4-byte register command to the FPGA via FT2232H.""" + """Send a 4-byte register command to the FPGA via USB (FT2232H or FT601).""" if self._connection is None or not self._connection.is_open: logger.warning(f"Cannot send 0x{opcode:02X}={value}: no connection") return @@ -1287,16 +1297,26 @@ class RadarDashboard(QMainWindow): if "Mock" in mode: self._replay_mode = False - self._connection = FT2232HConnection(mock=True) + iface = self._usb_iface_combo.currentText() + if "FT601" in iface: + self._connection = FT601Connection(mock=True) + else: + self._connection = FT2232HConnection(mock=True) if not self._connection.open(): QMessageBox.critical(self, "Error", "Failed to open mock connection.") return elif "Live" in mode: self._replay_mode = False - self._connection = FT2232HConnection(mock=False) + iface = self._usb_iface_combo.currentText() + if "FT601" in iface: + self._connection = FT601Connection(mock=False) + iface_name = "FT601" + else: + self._connection = FT2232HConnection(mock=False) + iface_name = "FT2232H" if not self._connection.open(): QMessageBox.critical(self, "Error", - "Failed to open FT2232H. Check USB connection.") + f"Failed to open {iface_name}. Check USB connection.") return elif "Replay" in mode: self._replay_mode = True @@ -1368,6 +1388,7 @@ class RadarDashboard(QMainWindow): self._start_btn.setEnabled(False) self._stop_btn.setEnabled(True) self._mode_combo.setEnabled(False) + self._usb_iface_combo.setEnabled(False) self._demo_btn_main.setEnabled(False) self._demo_btn_map.setEnabled(False) n_frames = self._replay_engine.total_frames @@ -1417,6 +1438,7 @@ class RadarDashboard(QMainWindow): self._start_btn.setEnabled(False) self._stop_btn.setEnabled(True) self._mode_combo.setEnabled(False) + self._usb_iface_combo.setEnabled(False) self._demo_btn_main.setEnabled(False) self._demo_btn_map.setEnabled(False) self._status_label_main.setText(f"Status: Running ({mode})") @@ -1462,6 +1484,7 @@ class RadarDashboard(QMainWindow): self._start_btn.setEnabled(True) self._stop_btn.setEnabled(False) self._mode_combo.setEnabled(True) + self._usb_iface_combo.setEnabled(True) self._demo_btn_main.setEnabled(True) self._demo_btn_map.setEnabled(True) self._status_label_main.setText("Status: Radar stopped") @@ -1954,6 +1977,12 @@ class RadarDashboard(QMainWindow): self._set_conn_indicator(self._conn_ft2232h, conn_open) self._set_conn_indicator(self._conn_stm32, self._stm32.is_open) + # Update USB label to reflect which interface is active + if isinstance(self._connection, FT601Connection): + self._conn_usb_label.setText("FT601:") + else: + self._conn_usb_label.setText("FT2232H:") + gps_count = self._gps_packet_count if self._gps_worker: gps_count = self._gps_worker.gps_count diff --git a/9_Firmware/9_3_GUI/v7/hardware.py b/9_Firmware/9_3_GUI/v7/hardware.py index 84bbb9a..2d85dc7 100644 --- a/9_Firmware/9_3_GUI/v7/hardware.py +++ b/9_Firmware/9_3_GUI/v7/hardware.py @@ -25,6 +25,7 @@ if USB_AVAILABLE: sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from radar_protocol import ( # noqa: F401 — re-exported for v7 package FT2232HConnection, + FT601Connection, RadarProtocol, Opcode, RadarAcquisition,