#include #include #include #include #include #include #include #include #include "protocol.h" #include "core.h" #include "device.h" #include "broadcastListener.h" #include "livoxProto1.h" namespace livoxProto1 { static ProtoState protoState = { .isInitialized = false, .componentThread = nullptr, .deviceManager = nullptr, .smoCallbacks = {} }; ProtoState& getProtoState() { return protoState; } DeviceManager::DeviceManager() : broadcastListener(protoState.componentThread), udpCommandDemuxer(protoState.componentThread, *this) { broadcastListener.setDeviceGoneAwayCb(deviceGoneAwayInd); } void DeviceManager::deviceGoneAwayInd(const comms::DiscoveredDevice &device) { std::cout << "Device gone away: " << device.stringify() << std::endl; // Check if device exists in our collection if (!protoState.deviceManager->getDevice(device)) { return; } // Find and remove the device from the collection auto it = std::find_if( protoState.deviceManager->devices.begin(), protoState.deviceManager->devices.end(), [&device](const std::shared_ptr &d) { return d->discoveredDevice == device; } ); if (it != protoState.deviceManager->devices.end()) { protoState.deviceManager->devices.erase(it); } } std::optional> DeviceManager::getDevice( const std::string &deviceIdentifier ) { for (auto& device : devices) { if (comms::deviceIdentifiersEqual( device->discoveredDevice.deviceIdentifier, deviceIdentifier)) { return device; } } return std::nullopt; } // GetOrCreateDeviceReq nested class implementation class DeviceManager::GetOrCreateDeviceReq : public smo::NonPostedAsynchronousContinuation< livoxProto1_getOrCreateDeviceReqCbFn> { public: DeviceManager& deviceManager; // The device we're trying to connect (holds all connection parameters) std::shared_ptr pendingDevice; public: GetOrCreateDeviceReq( DeviceManager& mgr, std::shared_ptr device, smo::Callback cb) : smo::NonPostedAsynchronousContinuation< livoxProto1_getOrCreateDeviceReqCbFn>(std::move(cb)), deviceManager(mgr), pendingDevice(device) {} // Public accessor for the original callback void callOriginalCallback(bool success, std::shared_ptr device) { callOriginalCb(success, device); } void callOriginalCallbackWithFailure() { callOriginalCallback(false, nullptr); } void getOrCreateDeviceReq1( std::shared_ptr context, bool connectSuccess ) { if (!connectSuccess) { std::cerr << __func__ << ": Connection failed for device " << context->pendingDevice->discoveredDevice.deviceIdentifier << std::endl; context->callOriginalCallbackWithFailure(); return; } // Connection successful, add device to collection context->deviceManager.devices.push_back(context->pendingDevice); if (getProtoState().smoCallbacks.OptionParser_getOptions().verbose) { std::cout << __func__ << ": Successfully connected and added device " << context->pendingDevice->discoveredDevice.deviceIdentifier << std::endl; } // Return success with the connected device context->callOriginalCallback(true, context->pendingDevice); } }; void DeviceManager::getOrCreateDeviceReq( const std::string &deviceIdentifier, const std::shared_ptr& componentThread, int commandTimeoutMs, int retryDelayMs, const std::string& smoIp, uint8_t smoSubnetNbits, uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort, smo::Callback callback) { // Validate smoIp format using Boost.Asio IPv4 validation if (!smoIp.empty() && !comms::isValidIPv4(smoIp)) { throw std::invalid_argument( std::string(__func__) + ": Invalid IPv4 smoIp format: " + smoIp); } // Validate subnet nbits if (smoSubnetNbits > 32) { throw std::invalid_argument( std::string(__func__) + ": smoSubnetNbits must be between 0 and 32, got: " + std::to_string(smoSubnetNbits)); } // First try to get existing device auto existingDevice = getDevice(deviceIdentifier); if (existingDevice) { // Device already exists and is connected, return it callback.callbackFn(true, existingDevice.value()); return; } // Device doesn't exist, create a new one but don't add it to collection yet auto newDevice = std::make_shared( deviceIdentifier, componentThread, commandTimeoutMs, retryDelayMs, smoIp, smoSubnetNbits, dataPort, cmdPort, imuPort); // Create the continuation request object to hold state and callbacks auto request = std::make_shared( *this, newDevice, std::move(callback)); // Start the connection process - only add to collection on success request->pendingDevice->connectReq( {request, std::bind( &DeviceManager::GetOrCreateDeviceReq::getOrCreateDeviceReq1, request.get(), request, std::placeholders::_1)}); } class DeviceManager::DestroyDeviceReq : public smo::NonPostedAsynchronousContinuation< livoxProto1_destroyDeviceReqCbFn> { public: DeviceManager& deviceManager; std::shared_ptr pendingDevice; public: DestroyDeviceReq( DeviceManager& mgr, std::shared_ptr device, smo::Callback cb) : smo::NonPostedAsynchronousContinuation< livoxProto1_destroyDeviceReqCbFn>(std::move(cb)), deviceManager(mgr), pendingDevice(device) {} // Public accessor for the original callback void callOriginalCallback(bool success) { callOriginalCb(success); } void callOriginalCallbackWithFailure() { callOriginalCallback(false); } void destroyDeviceReq1( std::shared_ptr context, bool success ) { context->deviceManager.devices.erase( std::remove( context->deviceManager.devices.begin(), context->deviceManager.devices.end(), context->pendingDevice), context->deviceManager.devices.end()); context->callOriginalCallback(success); } }; void DeviceManager::destroyDeviceReq( std::shared_ptr dev, smo::Callback callback ) { /** EXPLANATION: * Check to see if the device is in our collection. If so, call * disconnectReq and then remove it. */ std::shared_ptr device = getDevice(dev->discoveredDevice). value_or(nullptr); if (!device || device->nAttachedStimulusProducers > 0) { callback.callbackFn(false); return; } auto request = std::make_shared( *this, device, std::move(callback)); device->disconnectReq( {request, std::bind( &DeviceManager::DestroyDeviceReq::destroyDeviceReq1, request.get(), request, std::placeholders::_1)}); } void main(const std::shared_ptr &componentThread, const smo::stim_buff::SmoCallbacks& smoCallbacks) { if (protoState.isInitialized) { return; } protoState.isInitialized = true; protoState.componentThread = componentThread; protoState.smoCallbacks = smoCallbacks; protoState.deviceManager = std::make_unique(); protoState.deviceManager->broadcastListener.start(); protoState.deviceManager->udpCommandDemuxer.start(); } void exit(void) { if (!protoState.isInitialized) { return; } protoState.deviceManager->udpCommandDemuxer.stop(); protoState.deviceManager->broadcastListener.stop(); protoState.deviceManager.reset(); protoState.componentThread.reset(); protoState.isInitialized = false; } } // namespace livoxProto1