From cfeac1dc110f41fe142edbe71d7d2263b81f8cef Mon Sep 17 00:00:00 2001 From: Robin Getz Date: Sat, 4 Jul 2026 16:41:16 -0400 Subject: [PATCH] plutosdr: refactor device identity and backend-based grouping Improve PlutoSDR device identity handling to correctly distinguish multiple libiio backends (IP, USB, etc.) and provide stable grouping of physically identical hardware in the UI. - Append backend prefix to scanned device serials to ensure uniqueness across multiple discovery paths (e.g. "_ip", "_usb"). This prevents collisions when the same device is visible through different transports. - Add physical serial extraction and grouping logic so that devices discovered via multiple backends are treated as a single physical unit for display purposes. This stabilizes UI numbering and avoids duplicate entries for the same hardware. - Update enumOriginDevices() to: - group devices by physical serial rather than scan index - assign stable per-device indices independent of backend enumeration - include backend label in the display name to distinguish connection paths when multiple backends are active - Introduce helper utilities: - createBackendSuffix(): extract backend identifier from libiio URI - getPhysicalSerial(): strip backend suffix for grouping logic - getBackendLabel(): format backend suffix for user-facing display These changes ensure: - unique internal identifiers per backend connection - consistent grouping per physical device across discovery mechanisms - improved UI clarity when multiple access paths exist for the same SDR No change to RF/data path behavior; this is purely a discovery and presentation-layer refactor. Signed-off-by: Robin Getz --- devices/plutosdr/deviceplutosdrscan.cpp | 122 +++++++++++++++++++++++- devices/plutosdr/deviceplutosdrscan.h | 6 ++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/devices/plutosdr/deviceplutosdrscan.cpp b/devices/plutosdr/deviceplutosdrscan.cpp index 54043b94d..794b98c40 100644 --- a/devices/plutosdr/deviceplutosdrscan.cpp +++ b/devices/plutosdr/deviceplutosdrscan.cpp @@ -90,7 +90,9 @@ void DevicePlutoSDRScan::scan() if (desc_match.size() == 2) { - m_scans.back()->m_serial = desc_match[1]; + // A device can be discovered through multiple backends (e.g. USB and IP). + // Keep the backend in the serial key so each scanned entry remains unique. + m_scans.back()->m_serial = desc_match[1].str() + createBackendSuffix(uri); m_serialMap[m_scans.back()->m_serial] = m_scans.back(); } } @@ -145,13 +147,42 @@ void DevicePlutoSDRScan::enumOriginDevices(const QString& hardwareId, PluginInte std::vector serials; getSerials(serials); + // Multiple backends can refer to the same physical device. + // Keep a separate list of physical serials for stable display numbering. + // Example: Pluto[0] (ip) and Pluto[0] (usb) refer to the same hardware. + std::map physicalSerialIndexes; std::vector::const_iterator it = serials.begin(); int i; for (i = 0; it != serials.end(); ++it, ++i) { + // Keep the backend-qualified serial as the device identifier. QString serial_str = QString::fromLocal8Bit(it->c_str()); - QString displayableName(QString("PlutoSDR[%1] %2").arg(i).arg(serial_str)); + // Remove backend suffix for display and grouping. + std::string physicalSerial = getPhysicalSerial(*it); + + // Find the existing physical device index so USB/IP instances share a label. + auto pos = physicalSerialIndexes.find(physicalSerial); + + // Display index is based on physical devices, not backend instances. + int physical_idx; + + if (pos == physicalSerialIndexes.end()) + { + physical_idx = physicalSerialIndexes.size(); + physicalSerialIndexes[physicalSerial] = physical_idx; + } + else + { + physical_idx = pos->second; + } + + // Show backend information to distinguish multiple connections to the same device. + QString displayableName( + QString("PlutoSDR%1%2 %3") + .arg(physical_idx) + .arg(getBackendLabel(*it)) + .arg(QString::fromLocal8Bit(physicalSerial.c_str()))); originDevices.append(PluginInterface::OriginDevice( displayableName, @@ -166,3 +197,90 @@ void DevicePlutoSDRScan::enumOriginDevices(const QString& hardwareId, PluginInte } } + +/** + * @brief Creates a backend prefix from a libiio URI. + * + * The backend is converted into a serial suffix so that devices + * discovered through different backends remain unique. + * + * Examples: + * ip:192.168.2.1 -> "_ip" + * usb:3.32.5 -> "_usb" + * serial:/dev/ttyUSB0 -> "_serial" + * local: -> "_local" + * + * @param uri Device URI returned by libiio. + * + * @return iio backend suffix including the separator, or an empty string + * if no backend prefix is present. + */ +std::string DevicePlutoSDRScan::createBackendSuffix(const char *uri) const +{ + if (!uri) { + return {}; + } + + const char *sep = std::strchr(uri, ':'); + + if (!sep) { + return {}; + } + + return std::string(1, BACKEND_SEPARATOR) + std::string(uri, sep - uri); +} + +/** + * @brief Creates a display string for the backend portion of a serial. + * + * The scan process appends the backend to make serial numbers unique. + * This function recovers the backend suffix into a user-facing label for + * display purposes. + * + * Examples: + * ABC123_ip -> " (ip)" + * ABC123_usb -> " (usb)" + * + * @param serial backend-qualified serial number. + * + * @return Display label suffix. + */ +QString DevicePlutoSDRScan::getBackendLabel(const std::string& serial) const +{ + size_t sep = serial.rfind(BACKEND_SEPARATOR); + + if (sep == std::string::npos) + { + return {}; + } + + return QString(" (%1)").arg( + QString::fromStdString(serial.substr(sep + 1))); +} + +/** + * @brief Removes the backend suffix from a serial number. + * + * The scan process appends the backend to make serial numbers unique. + * This function recovers the physical device serial for grouping and + * display purposes. + * + * Examples: + * ABC123_ip -> ABC123 + * ABC123_usb -> ABC123 + * + * @param serial backend-qualified serial number. + * + * @return Physical device serial number. + */ +std::string DevicePlutoSDRScan::getPhysicalSerial(const std::string& serial) const +{ + size_t sep = serial.rfind(BACKEND_SEPARATOR); + + if (sep == std::string::npos) + { + return serial; + } + + return serial.substr(0, sep); +} diff --git a/devices/plutosdr/deviceplutosdrscan.h b/devices/plutosdr/deviceplutosdrscan.h index 60bac73a4..dc0c4a790 100644 --- a/devices/plutosdr/deviceplutosdrscan.h +++ b/devices/plutosdr/deviceplutosdrscan.h @@ -50,6 +50,12 @@ public: void enumOriginDevices(const QString& hardwareId, PluginInterface::OriginDevices& originDevices); private: + static constexpr char BACKEND_SEPARATOR = '_'; + + std::string createBackendSuffix(const char *uri) const; + QString getBackendLabel(const std::string& serial) const; + std::string getPhysicalSerial(const std::string& serial) const; + std::vector> m_scans; std::map> m_serialMap; std::map> m_urilMap;