#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcloudStimulusBuffer.h" namespace smo { namespace stim_buff { // Salmanoff hooks, obtained from SMO_GET_STIM_BUFF_API_DESC_FN_NAME(). static const SmoCallbacks* smoHooksPtr = nullptr; static SmoThreadingModelDesc smoThreadingModelDesc; // Local collection of stimulus buffers static std::vector> attachedStimBuffs; // Get stimulus buffer by device attachment spec static std::shared_ptr getStimBuff(const std::shared_ptr& spec) { for (const auto& stimBuff : attachedStimBuffs) { // Compare device selectors to find matching buffer if (stimBuff->deviceAttachmentSpec.deviceSelector == spec->deviceSelector) { return stimBuff; } } return nullptr; } // LivoxProto1 library state struct LivoxProto1DllState { LivoxProto1DllState() : dlopenHandle(nullptr, DlCloser), livoxProto1_main(nullptr), livoxProto1_exit(nullptr), livoxProto1_getOrCreateDeviceReq(nullptr), livoxProto1_destroyDeviceReq(nullptr), livoxProto1_device_enablePcloudDataReq(nullptr), livoxProto1_device_disablePcloudDataReq(nullptr), livoxProto1_device_getReturnModeReq(nullptr) {} static void DlCloser(void* handle) { if (handle) { dlclose(handle); } } std::unique_ptr dlopenHandle; livoxProto1_mainFn *livoxProto1_main; livoxProto1_exitFn *livoxProto1_exit; livoxProto1_getOrCreateDeviceReqFn *livoxProto1_getOrCreateDeviceReq; livoxProto1_destroyDeviceReqFn *livoxProto1_destroyDeviceReq; livoxProto1_device_enablePcloudDataReqFn *livoxProto1_device_enablePcloudDataReq; livoxProto1_device_disablePcloudDataReqFn *livoxProto1_device_disablePcloudDataReq; livoxProto1_device_getReturnModeReqFn *livoxProto1_device_getReturnModeReq; }; static LivoxProto1DllState livoxProto1; // Continuation classes for async operations class AttachDeviceReq : public smo::NonPostedAsynchronousContinuation { public: AttachDeviceReq( const std::shared_ptr& spec, smo::Callback cb) : smo::NonPostedAsynchronousContinuation( std::move(cb)), spec(spec) {} public: const std::shared_ptr spec; std::shared_ptr stimBuff; std::shared_ptr deviceTmp; private: std::unique_ptr delayTimer; public: void attachDeviceReq1( std::shared_ptr context, bool success, std::shared_ptr dev) { if (!success || !dev) { std::cerr << __func__ << ": Failed to create/find Livox device: " << context->spec->deviceSelector << std::endl; context->callOriginalCb(false, context->spec); return; } // Stash device pointer until after getReturnMode succeeds context->deviceTmp = dev; if (1 || OptionParser::getOptions().verbose) { std::cout << __func__ << ": Successfully attached/found Livox " "device: " << context->spec->deviceSelector << " (ID: " << context->spec->deviceIdentifier << ")\n"; } /* Delay here because getOrCreate just sent HandshakeReq, so device * may not yet be ready for another command. */ context->delayedGetReturnMode(context); } void delayedGetReturnMode( std::shared_ptr context) { // Initialize timer with device's component thread delayTimer = std::make_unique( context->deviceTmp->componentThread->getIoService()); delayTimer->expires_from_now(boost::posix_time::milliseconds(5)); delayTimer->async_wait( std::bind( &AttachDeviceReq::attachDeviceReq2, context.get(), context, std::placeholders::_1)); } void attachDeviceReq2( std::shared_ptr context, const boost::system::error_code& error) { if (error) { std::cerr << __func__ << ": Timer error: " << error.message() << std::endl; context->callOriginalCb(false, context->spec); return; } (*livoxProto1.livoxProto1_device_getReturnModeReq)( context->deviceTmp, {context, std::bind( &AttachDeviceReq::attachDeviceReq3, context.get(), context, std::placeholders::_1, std::placeholders::_2)}); } void attachDeviceReq3( std::shared_ptr context, bool success, uint8_t mode) { if (!success) { std::cerr << __func__ << ": Failed to get return mode for dev " << context->spec->deviceSelector << std::endl; context->callOriginalCb(false, context->spec); return; } // Create and add PcloudStimulusBuffer to collection now that device is ready StimulusBuffer::PcloudFormatDesc formatDesc; formatDesc.format = StimulusBuffer::PcloudFormatDesc::Format::XYZI; auto pcloudStimBuff = std::make_shared( *context->spec, context->deviceTmp, formatDesc, 500); context->stimBuff = pcloudStimBuff; context->deviceTmp->nAttachedStimBuffs++; attachedStimBuffs.push_back(pcloudStimBuff); if (1 || OptionParser::getOptions().verbose) { std::cout << __func__ << ": Got return mode (" << (int)mode << ") for device: " << context->spec->deviceSelector << std::endl; } context->delayedEnablePcloudData(context); } // Helper method to delay and then call enablePcloudDataReq void delayedEnablePcloudData( std::shared_ptr context) { // Initialize timer with device's component thread delayTimer = std::make_unique( context->stimBuff->device->componentThread->getIoService()); delayTimer->expires_from_now(boost::posix_time::milliseconds(5)); delayTimer->async_wait( std::bind( &AttachDeviceReq::attachDeviceReq4, context.get(), context, std::placeholders::_1)); } void attachDeviceReq4( std::shared_ptr context, const boost::system::error_code& error) { if (error) { std::cerr << __func__ << ": Timer error: " << error.message() << std::endl; context->callOriginalCb(false, context->spec); return; } (*livoxProto1.livoxProto1_device_enablePcloudDataReq)( context->stimBuff->device, {context, std::bind( &AttachDeviceReq::attachDeviceReq5, context.get(), context, std::placeholders::_1)}); } void attachDeviceReq5( std::shared_ptr context, bool success) { if (!success) { std::cerr << __func__ << ": Failed to enable pcloud data for dev " << context->spec->deviceSelector << std::endl; context->callOriginalCb(false, context->spec); return; } if (1 || OptionParser::getOptions().verbose) { std::cout << __func__ << ": Enabled pcloud data for device: " << context->spec->deviceSelector << std::endl; } context->callOriginalCb(success, context->spec); } }; class DetachDeviceReq : public smo::NonPostedAsynchronousContinuation { public: DetachDeviceReq( const std::shared_ptr& spec, const std::shared_ptr& stimBuff, smo::Callback cb) : smo::NonPostedAsynchronousContinuation( std::move(cb)), spec(spec), stimBuff(stimBuff) {} public: const std::shared_ptr spec; std::shared_ptr stimBuff; private: std::unique_ptr delayTimer; public: void detachDeviceReq1( std::shared_ptr context, bool success) { if (!success) { std::cerr << __func__ << ": Failed to disable pcloud data for " "stimbuff " << context->spec->deviceSelector << std::endl; // Fallthrough. } // Add 5ms delay before destroying device context->delayedDestroyDevice(context); } // Helper method to delay and then call destroyDeviceReq void delayedDestroyDevice( std::shared_ptr context) { // Initialize timer with device's component thread delayTimer = std::make_unique( context->stimBuff->device->componentThread->getIoService()); delayTimer->expires_from_now(boost::posix_time::milliseconds(5)); delayTimer->async_wait( std::bind( &DetachDeviceReq::detachDeviceReq1_delayed, context.get(), context, std::placeholders::_1)); } // Callback for the delayed destroyDeviceReq void detachDeviceReq1_delayed( std::shared_ptr context, const boost::system::error_code& error) { if (error) { std::cerr << __func__ << ": Timer error: " << error.message() << std::endl; // Fallthrough. } // Remove stimulus buffer from collection before destroying device context->stimBuff->device->nAttachedStimBuffs--; auto it = std::find( attachedStimBuffs.begin(), attachedStimBuffs.end(), context->stimBuff); if (it != attachedStimBuffs.end()) { attachedStimBuffs.erase(it); } (*livoxProto1.livoxProto1_destroyDeviceReq)( context->stimBuff->device, {context, std::bind( &DetachDeviceReq::detachDeviceReq2, context.get(), context, std::placeholders::_1)}); } void detachDeviceReq2( std::shared_ptr context, bool success) { if (!success) { std::cerr << __func__ << ": Failed to destroy dev " "device " << context->spec->deviceSelector << " for stimbuff." "\n"; /** NOTE: * There's a decent argument for falling through here and still * removing the stimulus buffer from attachedStimBuffs. */ context->callOriginalCb(false, context->spec); return; } if (1 || OptionParser::getOptions().verbose) { std::cout << __func__ << ": Successfully detached pcloud stimbuff " "for device " << context->spec->deviceSelector << " and possibly also destroyed device.\n"; } context->callOriginalCb(success, context->spec); } }; // Callback function declarations extern "C" sal_mlo_initializeIndFn livoxGen1_initializeInd; extern "C" sal_mlo_finalizeIndFn livoxGen1_finalizeInd; extern "C" sal_mlo_attachDeviceReqFn livoxGen1_attachDeviceReq; extern "C" sal_mlo_detachDeviceReqFn livoxGen1_detachDeviceReq; // Stim Buff API descriptor static const StimBuffApiDesc livoxGen1ApiDesc = { .name = "livoxGen1", .exportedQualeIfaceApis = { {.name = "pcloud"}, {.name = "pcloudIntensity"}, {.name = "gyro"}, {.name = "accel"} }, .sal_mgmt_libOps = { .initializeInd = livoxGen1_initializeInd, .finalizeInd = livoxGen1_finalizeInd, .attachDeviceReq = livoxGen1_attachDeviceReq, .detachDeviceReq = livoxGen1_detachDeviceReq } }; // Callback function implementations extern "C" int livoxGen1_initializeInd(void) { if (!smoHooksPtr) { throw std::runtime_error(std::string(__func__) + ": SMO hooks " "pointers not filled in."); } // Load LivoxProto1 library auto libPath = smoHooksPtr->searchForLibInSmoSearchPaths( "liblivoxProto1.so"); livoxProto1.dlopenHandle.reset(dlopen( libPath.value_or("liblivoxProto1.so").c_str(), RTLD_LAZY)); if (!livoxProto1.dlopenHandle) { throw std::runtime_error( std::string(__func__) + ": Failed to load LivoxProto1 library: " + (dlerror() ? dlerror() : "unknown error")); } // Get LivoxProto1 library functions livoxProto1.livoxProto1_main = reinterpret_cast( dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_main")); livoxProto1.livoxProto1_exit = reinterpret_cast( dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_exit")); livoxProto1.livoxProto1_getOrCreateDeviceReq = reinterpret_cast< livoxProto1_getOrCreateDeviceReqFn *>( dlsym( livoxProto1.dlopenHandle.get(), "livoxProto1_getOrCreateDeviceReq")); livoxProto1.livoxProto1_destroyDeviceReq = reinterpret_cast< livoxProto1_destroyDeviceReqFn *>( dlsym( livoxProto1.dlopenHandle.get(), "livoxProto1_destroyDeviceReq")); livoxProto1.livoxProto1_device_enablePcloudDataReq = reinterpret_cast< livoxProto1_device_enablePcloudDataReqFn *>( dlsym( livoxProto1.dlopenHandle.get(), "livoxProto1_device_enablePcloudDataReq")); livoxProto1.livoxProto1_device_disablePcloudDataReq = reinterpret_cast< livoxProto1_device_disablePcloudDataReqFn *>( dlsym( livoxProto1.dlopenHandle.get(), "livoxProto1_device_disablePcloudDataReq")); livoxProto1.livoxProto1_device_getReturnModeReq = reinterpret_cast< livoxProto1_device_getReturnModeReqFn *>( dlsym( livoxProto1.dlopenHandle.get(), "livoxProto1_device_getReturnModeReq")); if (!livoxProto1.livoxProto1_main || !livoxProto1.livoxProto1_exit || !livoxProto1.livoxProto1_getOrCreateDeviceReq || !livoxProto1.livoxProto1_destroyDeviceReq || !livoxProto1.livoxProto1_device_enablePcloudDataReq || !livoxProto1.livoxProto1_device_disablePcloudDataReq || !livoxProto1.livoxProto1_device_getReturnModeReq) { throw std::runtime_error( std::string(__func__) + ": Failed to get LivoxProto1 library functions"); } // Call LivoxProto1 library main function (*livoxProto1.livoxProto1_main)( smoThreadingModelDesc.componentThread, *smoHooksPtr); return 0; // Success } extern "C" int livoxGen1_finalizeInd(void) { attachedStimBuffs.clear(); // Call LivoxProto1 library exit function if (livoxProto1.livoxProto1_exit) { (*livoxProto1.livoxProto1_exit)(); } if (livoxProto1.dlopenHandle) { dlclose(livoxProto1.dlopenHandle.get()); livoxProto1.dlopenHandle.reset(); } livoxProto1 = LivoxProto1DllState(); return 0; // Success } extern "C" void livoxGen1_attachDeviceReq( const std::shared_ptr& desc, const std::shared_ptr& componentThread, Callback cb ) { if (!livoxProto1.livoxProto1_getOrCreateDeviceReq) { throw std::runtime_error( std::string(__func__) + ": LivoxProto1 getOrCreateDevice function " "not available"); } auto request = std::make_shared(desc, cb); // Check if stimulus buffer already exists in the collection auto pcloudStimBuff = std::static_pointer_cast( getStimBuff(desc)); if (pcloudStimBuff) { request->stimBuff = pcloudStimBuff; // Check if device's point cloud data is already active if (pcloudStimBuff->device && pcloudStimBuff->device->pcloudDataActive) { // Point cloud data is already active, call success callback request->callOriginalCb(true, request->spec); return; } /* Enable pcloud data first. Don't need delay since no commands were * sent to device prior to us reaching here. */ (*livoxProto1.livoxProto1_device_enablePcloudDataReq)( pcloudStimBuff->device, {request, std::bind( &AttachDeviceReq::attachDeviceReq5, request.get(), request, std::placeholders::_1)}); return; } // Parse integer parameters from provider params with defaults /* The Livox Avia will generally respond to a handshake request within * 50ms. So we set the handshake timeout to 300ms to be safe. */ int handshakeTimeoutMs = 300; // Default: 50ms /* Based on testing on a Livox Avia, the device will generally resume * sending broadcast advertisement dgrams after about 5 seconds at most. * Generally, it will resume sending them within 1-2 seconds. */ int retryDelayMs = 5250; // Default: 500ms uint8_t smoSubnetNbits = 24; // Default: /24 subnet uint16_t dataPort = 56000; // Default data port uint16_t cmdPort = 56001; // Default command port uint16_t imuPort = 56002; // Default IMU port // Default: empty string (will trigger IP auto-detection) std::string smoIp = ""; // Parse optional integer parameters from provider params for (const auto& param : desc->providerParams) { if (param.first == "handshake-timeout-ms") { handshakeTimeoutMs = smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(*desc, "handshake-timeout-ms"); } else if (param.first == "retry-delay-ms") { retryDelayMs = smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(*desc, "retry-delay-ms"); } else if (param.first == "smo-subnet-nbits") { smoSubnetNbits = static_cast( smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(*desc, "smo-subnet-nbits")); } else if (param.first == "data-port") { dataPort = static_cast( smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(*desc, "data-port")); } else if (param.first == "cmd-port") { cmdPort = static_cast( smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(*desc, "cmd-port")); } else if (param.first == "imu-port") { imuPort = static_cast( smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(*desc, "imu-port")); } else if (param.first == "smo-ip") { if (param.second.empty()) { throw std::runtime_error( std::string(__func__) + ": smo-ip parameter is empty"); } if (param.second.find('.') == std::string::npos || std::count(param.second.begin(), param.second.end(), '.') != 3) { throw std::runtime_error( std::string(__func__) + ": smo-ip parameter is not an " "IPv4 address"); } smoIp = param.second; } else { throw std::runtime_error( std::string(__func__) + ": Unknown provider parameter: " + param.first); } } (*livoxProto1.livoxProto1_getOrCreateDeviceReq)( desc->deviceSelector, // deviceIdentifier (broadcast code) componentThread, handshakeTimeoutMs, retryDelayMs, smoIp, smoSubnetNbits, dataPort, cmdPort, imuPort, {request, std::bind( &AttachDeviceReq::attachDeviceReq1, request.get(), request, std::placeholders::_1, std::placeholders::_2)}); } extern "C" void livoxGen1_detachDeviceReq( const std::shared_ptr& desc, Callback cb ) { // Check if stimulus buffer exists in the collection auto stimBuff = std::static_pointer_cast( getStimBuff(desc)); if (!stimBuff) { cb.callbackFn(false, desc); return; } auto request = std::make_shared( desc, stimBuff, cb); // Disable point cloud data first (*livoxProto1.livoxProto1_device_disablePcloudDataReq)( stimBuff->device, {request, std::bind( &DetachDeviceReq::detachDeviceReq1, request.get(), request, std::placeholders::_1)}); } // Exported function extern "C" smo::stim_buff::SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF SMO_GET_STIM_BUFF_API_DESC_FN_NAME; const smo::stim_buff::StimBuffApiDesc& SMO_GET_STIM_BUFF_API_DESC_FN_NAME( const smo::stim_buff::SmoCallbacks& callbacks, const smo::stim_buff::SmoThreadingModelDesc& threadingModel) { smoHooksPtr = &callbacks; smoThreadingModelDesc = threadingModel; return livoxGen1ApiDesc; } } // namespace stim_buff } // namespace smo