diff --git a/commonLibs/livoxProto1/device.cpp b/commonLibs/livoxProto1/device.cpp index b7fb57b..3a4f0f5 100644 --- a/commonLibs/livoxProto1/device.cpp +++ b/commonLibs/livoxProto1/device.cpp @@ -81,7 +81,7 @@ Device::Device(const std::string &deviceIdentifier, ""), componentThread(componentThread), handshakeTimeoutMs(handshakeTimeoutMs), retryDelayMs(retryDelayMs), -smoIp(smoIp), smoSubnetNbits(smoSubnetNbits), +smoIp(smoIp), detectedSmoListeningIp(""), smoSubnetNbits(smoSubnetNbits), dataPort(dataPort), cmdPort(cmdPort), imuPort(imuPort), heartbeatFd(-1), heartbeatActive(false) @@ -136,8 +136,8 @@ public: context->device.heartbeatFd = fd; context->device.startHeartbeat(); context->originalCbFn(true); - return; - } + return; + } if (OptionParser::getOptions().verbose) { @@ -145,7 +145,7 @@ public: << "identifier" << "\n"; } - // Try direct connect by device identifier + // Try direct connect by device identifier context->device.connectByDeviceIdentifierReq( std::bind(&ConnectReq::connectReq2, context.get(), context, std::placeholders::_1, std::placeholders::_2, @@ -286,7 +286,8 @@ void Device::connectToKnownDeviceReq( ) { // Create the connection request object to hold state and callbacks - auto request = std::make_shared(*this, std::move(callback)); + auto request = std::make_shared( + *this, std::move(callback)); auto& protoState = livoxProto1::getProtoState(); if (!protoState.deviceManager) @@ -314,6 +315,30 @@ void Device::connectToKnownDeviceReq( // Use the IP address from the broadcast message request->deviceIP = request->deviceInfo->ipAddr; + // Determine the final listening IP address + auto smoIpResult = request->device.getSmoIp(request->deviceIP); + if (!smoIpResult.has_value()) + { + // Auto-detection failed, fail early + std::cerr << __func__ << ": Failed to detect SMO listening IP for " + << "known device (" + << request->device.discoveredDevice.deviceIdentifier << ")" + << " @(" << request->deviceIP << ").\n"; + + request->callOriginalCallbackWithFailure(); + return; + } + + if (OptionParser::getOptions().verbose) + { + std::cout << __func__ << ": Detected SMO listening IP for known device " + << request->device.discoveredDevice.deviceIdentifier + << " @(" << request->deviceIP << ") is " + << smoIpResult.value() << ". About to try to handshake.\n"; + } + + request->device.detectedSmoListeningIp = smoIpResult.value(); + // Execute handshake with the known device using async method request->device.executeHandshakeReq( request->deviceIP, @@ -358,6 +383,24 @@ void Device::connectByDeviceIdentifierReq( Device::connectByDeviceIdentifierReqCbFn callback ) { + /** EXPLANATION: + * This method uses heuristic device IP construction from the serial number. + * This requires smoIp to be provided because: + * 1. We need the network prefix to generate a valid device IP address + * 2. Without a target device IP, we cannot detect which interface faces the device + * 3. Therefore, if smoIp is omitted, heuristic construction is impossible + * + * If smoIp is not provided, the driver must rely only on broadcast advertisements + * from the device (handled by connectToKnownDeviceReq). + */ + + // Check if smoIp is provided - required for heuristic construction + if (smoIp.empty()) + { + callback(false, "", -1); + return; + } + // Create the connection request object to hold state and callbacks auto request = std::make_shared( *this, std::move(callback)); @@ -366,6 +409,16 @@ void Device::connectByDeviceIdentifierReq( request->deviceIP = generateClientDeviceIpFromSerialNumber( request->device.discoveredDevice.deviceIdentifier); + // For heuristic construction, always use the provided smoIp. + request->device.detectedSmoListeningIp = request->device.smoIp; + + if (OptionParser::getOptions().verbose) + { + std::cout << __func__ << ": About to try to connect to device by " + << "identifier (" << discoveredDevice.deviceIdentifier << ")" + << " at IP (" << smoIp << ").\n"; + } + // Execute handshake using async method request->device.executeHandshakeReq( request->deviceIP, @@ -508,9 +561,9 @@ private: /** EXPLANATION: * Prepare handshake request. */ - std::string smoIp = device.getSmoIp(deviceIP); comms::HandshakeRequest handshakeReq( - smoIp, device.dataPort, device.cmdPort, device.imuPort); + device.detectedSmoListeningIp, + device.dataPort, device.cmdPort, device.imuPort); handshakeReq.swapContentsToProtocolEndianness(); handshakeReq.header.setCrc16FromRawBytes(); handshakeReq.header.swapCrc16ToProtocolEndianness(); @@ -764,6 +817,14 @@ void Device::executeHandshakeReq( auto request = std::make_shared( *this, deviceIP, std::move(callback)); + // Check if detectedSmoListeningIp is empty - this should not happen + if (detectedSmoListeningIp.empty()) + { + // This should not happen as it should be set by the calling method + request->callOriginalCallbackWithFailure(); + return; + } + try { if (!request->setupSocket()) { @@ -1143,25 +1204,42 @@ std::optional Device::detectSmoIp(const std::string& deviceIP) } } -std::string Device::getSmoIp(const std::string& deviceIP) +std::optional Device::getSmoIp(const std::string& deviceIP) { /** EXPLANATION: - * If smo-ip was provided, return it. - * Otherwise, try to detect it based on the client device's IP address. - * If detection failed, throw an exception. + * This is only used when connecting to a device that's already known to + * the broadcastListener. + * It is NOT and SHOULD not be used when connecting by heuristic IP + * construction using the client device's serial number. + * + * Determines the SMO listening IP address for this device. + * If smoIp is provided, validate it against detected IP and return it. + * If smoIp is empty, attempt auto-detection based on device IP. + * Returns std::optional - empty if detection fails. */ - if (!smoIp.empty()) { + auto detectedIp = detectSmoIp(deviceIP); + + if (!smoIp.empty()) + { + // smoIp was provided, validate it against detected IP + if (detectedIp.has_value() && detectedIp.value() != smoIp) + { + // Print warning if provided smoIp doesn't match detected IP + std::cerr << "Warning: Provided smo-ip (" << smoIp + << ") doesn't match detected IP (" << detectedIp.value() + << ") for device " << discoveredDevice.deviceIdentifier + << " @(" << deviceIP << ")" + << ". Using provided smo-ip anyway." << std::endl; + } return smoIp; } - auto detectedIp = detectSmoIp(deviceIP); if (detectedIp.has_value()) { return detectedIp.value(); } - throw std::runtime_error( - std::string(__func__) + ": Failed to detect SMO IP address for device " - + deviceIP + " with subnet mask /" + std::to_string(smoSubnetNbits)); + // Auto-detection failed + return std::nullopt; } } // namespace livoxProto1 diff --git a/commonLibs/livoxProto1/device.h b/commonLibs/livoxProto1/device.h index dc5dca9..9f7a494 100644 --- a/commonLibs/livoxProto1/device.h +++ b/commonLibs/livoxProto1/device.h @@ -73,6 +73,7 @@ public: std::shared_ptr componentThread; int handshakeTimeoutMs, retryDelayMs; std::string smoIp; + std::string detectedSmoListeningIp; uint8_t smoSubnetNbits; uint16_t dataPort, cmdPort, imuPort; @@ -96,7 +97,7 @@ private: public: // Utility methods - std::string getSmoIp(const std::string& deviceIP); + std::optional getSmoIp(const std::string& deviceIP); // Callback function type definitions for async methods typedef std::function connectReqCbFn; diff --git a/docs/livox-gen1-lidar-dap-spec.md b/docs/livox-gen1-lidar-dap-spec.md index 4f18000..2fd7047 100644 --- a/docs/livox-gen1-lidar-dap-spec.md +++ b/docs/livox-gen1-lidar-dap-spec.md @@ -145,7 +145,23 @@ The `livoxProto1` provider accepts the following parameters: ## Device Discovery and Connection -The specification uses a retry-based connection strategy: +The specification uses a retry-based connection strategy with two different approaches: + +### Connection Methods + +**1. Broadcast-Based Connection (connectToKnownDeviceReq)** +- Uses device IP addresses discovered from broadcast advertisements +- **smo-ip parameter**: Optional - if omitted, driver auto-detects the appropriate interface +- **smo-subnet-nbits parameter**: Optional - used for validation if smo-ip is provided +- **When to use**: When devices are actively broadcasting their presence + +**2. Heuristic Connection (connectByDeviceIdentifierReq)** +- Generates device IP addresses from serial numbers using network prefix +- **smo-ip parameter**: **Required** - needed to determine network prefix for IP generation +- **smo-subnet-nbits parameter**: **Required** - needed to calculate valid device IP addresses +- **When to use**: When devices are not broadcasting or for initial setup + +### Connection Strategy 1. **Initial Check**: Check if device is already known from broadcasts 2. **Direct Connect**: Attempt direct connection based on calculated IP address diff --git a/senseApis/livoxGen1/livoxGen1.cpp b/senseApis/livoxGen1/livoxGen1.cpp index 6410ea9..03d9b15 100644 --- a/senseApis/livoxGen1/livoxGen1.cpp +++ b/senseApis/livoxGen1/livoxGen1.cpp @@ -176,8 +176,8 @@ extern "C" int livoxGen1_attachDeviceReq( uint16_t dataPort = 56000; // Default data port uint16_t cmdPort = 56001; // Default command port uint16_t imuPort = 56002; // Default IMU port - // Default: 10.42.0.1 - std::string smoIp = "10.42.0.1"; + // Default: empty string (will trigger IP auto-detection) + std::string smoIp = ""; // Parse optional integer parameters from provider params for (const auto& param : desc->providerParams)