Files
salmanoff/stimBuffApis/livoxGen1/livoxGen1.cpp
T

852 lines
25 KiB
C++
Raw Normal View History

#include <boostAsioLinkageFix.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <map>
#include <functional>
#include <algorithm>
#include <dlfcn.h>
#include <opts.h>
#include <user/senseApiDesc.h>
#include <user/deviceAttachmentSpec.h>
#include <callback.h>
#include <livoxProto1/livoxProto1.h>
#include <livoxProto1/device.h>
#include <livoxProto1/protocol.h>
#include <asynchronousContinuation.h>
#include <boost/asio/deadline_timer.hpp>
#include "pcloudStimulusProducer.h"
#include "livoxGen1.h"
namespace smo {
2025-10-01 18:47:42 -04:00
namespace stim_buff {
2025-10-01 18:47:42 -04:00
// Salmanoff hooks, obtained from SMO_GET_STIM_BUFF_API_DESC_FN_NAME().
const SmoCallbacks* smoHooksPtr = nullptr;
static SmoThreadingModelDesc smoThreadingModelDesc;
// Local collection of stimulus producers
static std::vector<std::shared_ptr<StimulusProducer>> attachedStimulusProducers;
// Check if a StimulusProducer matches the requested stim feature
static bool isProducerForStimFeature(
const std::shared_ptr<StimulusProducer>& stimProducer,
const std::string& qualeIfaceApi)
{
// Check if the qualeIfaceApi requires a PcloudStimulusProducer
if (qualeIfaceApi == "mesh" || qualeIfaceApi == "pcloudIntensity" ||
qualeIfaceApi == "pcloudAmbience")
{
// Attempt to upcast to PcloudStimulusProducer
auto pcloudProducer = std::dynamic_pointer_cast<PcloudStimulusProducer>(
stimProducer);
return pcloudProducer != nullptr;
}
else if (qualeIfaceApi == "gyro" || qualeIfaceApi == "accel")
{
/** TODO:
* Add upcast mappings for gyro and accel later when we implement
* ImuStimulusProducer.
*/
return false;
}
return false;
}
// Get stimulus producer by device attachment spec
2025-11-14 19:50:51 -04:00
static std::shared_ptr<StimulusProducer>
getStimulusProducer(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec
)
{
for (const auto& stimProducer : attachedStimulusProducers)
{
// Compare device selectors to find matching buffer
if (livoxProto1::comms::deviceIdentifiersEqual(
stimProducer->deviceAttachmentSpec->deviceSelector,
spec->deviceSelector)
&& isProducerForStimFeature(stimProducer, spec->qualeIfaceApi))
{
return stimProducer;
}
}
return nullptr;
}
// Helper function to parse histbuffMs from device attachment spec
static int parseHistbuffMs(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec)
{
int histbuffMs = 30000; // Default: 30000ms (30 seconds)
const std::vector<std::string> histbuffParamNames = {
"history-buffer-duration-ms",
"hist-buff-duration-ms",
"histbuff-duration-ms",
"histbuff-ms"
};
// Loop through synonyms in reverse order; lattermost synonym wins.
for (auto synIt = histbuffParamNames.rbegin();
synIt != histbuffParamNames.rend(); ++synIt)
{
const auto& paramName = *synIt;
try {
histbuffMs = smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(
spec->qualeIfaceApiParams, paramName);
break; // Found and parsed successfully
} catch (const std::exception&) {
// Parameter not found or parse error, continue to next synonym
continue;
}
}
return histbuffMs;
}
// LivoxProto1DllState constructor implementation
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),
livoxProto1_getPcloudDataFdDesc(nullptr)
{}
// LivoxProto1DllState DlCloser implementation
void LivoxProto1DllState::DlCloser(void* handle)
{
if (handle) {
dlclose(handle);
}
}
LivoxProto1DllState livoxProto1;
// Continuation classes for async operations
class AttachDeviceReq
: public smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>
{
public:
AttachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
smo::Callback<sal_mlo_attachDeviceReqCbFn> cb)
: smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>(
std::move(cb)),
spec(spec)
{}
public:
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
std::shared_ptr<PcloudStimulusProducer> stimProducer;
std::shared_ptr<livoxProto1::Device> deviceTmp;
private:
std::unique_ptr<boost::asio::deadline_timer> delayTimer;
// Helper method to ensure StimBuffer is attached
// Returns true if successful, false on error
bool ensureStimBufferAttached(std::shared_ptr<AttachDeviceReq> context)
{
if (!context->stimProducer)
{
std::cerr << __func__ << ": stimProducer is null" << std::endl;
return false;
}
// Parse histbuffMs
int histbuffMs = parseHistbuffMs(context->spec);
// Call getOrCreateAttachedStimulusBuffer (may throw, catch and return failure)
try {
context->stimProducer->getOrCreateAttachedStimulusBuffer(
context->spec, histbuffMs);
} catch (const std::exception& e) {
std::cerr << __func__ << ": Failed to create StimBuffer: "
<< e.what() << ". Producer is committed, DeviceReattacher will retry."
<< std::endl;
// Return false so DeviceReattacher can retry later
return false;
}
return true;
}
public:
void attachDeviceReq1(
std::shared_ptr<AttachDeviceReq> context,
bool success, std::shared_ptr<livoxProto1::Device> 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;
2025-11-07 14:59:28 -04:00
if (1 || smoHooksPtr->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<AttachDeviceReq> context)
{
// Initialize timer with device's component thread
delayTimer = std::make_unique<boost::asio::deadline_timer>(
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<AttachDeviceReq> 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_doCreateStimProducer,
context.get(), context,
std::placeholders::_1, std::placeholders::_2)});
}
void attachDeviceReq3_doCreateStimProducer(
std::shared_ptr<AttachDeviceReq> 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;
}
/* Check if PcloudStimulusProducer already exists
* (race condition or double-add)
*/
auto existingProducer = getStimulusProducer(context->spec);
if (existingProducer)
{
throw std::runtime_error(
std::string(__func__) + ": PcloudStimulusProducer already "
"exists for device " + context->spec->deviceSelector + " "
"(race condition or double-add)");
}
// Create & add PcloudStimulusProducer to collection since dev now ready
PcloudStimulusProducer::PcloudFormatDesc formatDesc;
formatDesc.format = PcloudStimulusProducer::PcloudFormatDesc::Format
::XYZI;
auto pcloudDataProducer = std::make_shared<PcloudStimulusProducer>(
context->spec, context->deviceTmp, formatDesc, 30);
context->stimProducer = pcloudDataProducer;
context->deviceTmp->nAttachedStimulusProducers++;
attachedStimulusProducers.push_back(pcloudDataProducer);
pcloudDataProducer->start();
2025-11-07 14:59:28 -04:00
if (1 || smoHooksPtr->OptionParser_getOptions().verbose)
{
std::cout << __func__ << ": Got return mode (" << (int)mode
<< ") for device: " << context->spec->deviceSelector
<< std::endl;
}
// Ensure StimBuffer is attached
attachDeviceReq4_doCreateStimBuff_maybeDirectlyCalled(context);
}
// Ensure StimBuffer is attached
void attachDeviceReq4_doCreateStimBuff_maybeDirectlyCalled(
std::shared_ptr<AttachDeviceReq> context
)
{
// Ensure StimBuffer is attached
if (!ensureStimBufferAttached(context))
{
context->callOriginalCb(false, context->spec);
return;
}
// Continue to enable pcloud data if needed
attachDeviceReq5_doEnablePcloudData_maybeDirectlyCalled(context);
}
// Enable pcloud data if needed
void attachDeviceReq5_doEnablePcloudData_maybeDirectlyCalled(
std::shared_ptr<AttachDeviceReq> context
)
{
if (!context->stimProducer || !context->stimProducer->device)
{
std::cerr << __func__ << ": stimProducer or device is null"
<< std::endl;
context->callOriginalCb(false, context->spec);
return;
}
/* Enable pcloud data. Don't need delay since no commands were
* sent to device prior to us reaching here (or delay already handled).
*/
(*livoxProto1.livoxProto1_device_enablePcloudDataReq)(
context->stimProducer->device,
{context, std::bind(
&AttachDeviceReq::attachDeviceReq6,
context.get(), context,
std::placeholders::_1)});
}
void attachDeviceReq6(
std::shared_ptr<AttachDeviceReq> 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;
}
2025-11-07 14:59:28 -04:00
if (1 || smoHooksPtr->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<sal_mlo_detachDeviceReqCbFn>
{
public:
DetachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
const std::shared_ptr<StimulusBuffer>& stimBuffer,
smo::Callback<sal_mlo_detachDeviceReqCbFn> cb)
: smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>(
std::move(cb)),
spec(spec), stimBuffer(stimBuffer)
{}
public:
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
std::shared_ptr<StimulusBuffer> stimBuffer;
private:
std::unique_ptr<boost::asio::deadline_timer> delayTimer;
public:
void detachDeviceReq1(
std::shared_ptr<DetachDeviceReq> context,
bool success)
{
if (!success)
{
std::cerr << __func__ << ": Failed to disable pcloud data for "
"stim producer " << 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<DetachDeviceReq> context)
{
// Initialize timer with LivoxGen1 metadata io_service
delayTimer = std::make_unique<boost::asio::deadline_timer>(
smoThreadingModelDesc.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<DetachDeviceReq> context,
const boost::system::error_code& error)
{
if (error)
{
std::cerr << __func__ << ": Timer error: " << error.message()
<< std::endl;
// Fallthrough.
}
// Remove StimBuffer from collection if it exists
if (!context->stimBuffer)
{
throw std::runtime_error(std::string(__func__)
+ ": stimBuffer (API: " + context->spec->stimBuffApi + ") "
+ "is missing in detachDeviceReq1_delayed "
+ "for device " + context->spec->deviceSelector);
}
// Get the producer from the buffer's parent
auto& stimProducer = dynamic_cast<PcloudStimulusProducer&>(
context->stimBuffer->parent);
auto it = std::find(
stimProducer.attachedStimulusBuffers.begin(),
stimProducer.attachedStimulusBuffers.end(),
context->stimBuffer);
if (it != stimProducer.attachedStimulusBuffers.end())
{
stimProducer.attachedStimulusBuffers.erase(it);
}
// Clear specialized buffer members if they match
if (stimProducer.xyzStimulusBuffer == context->stimBuffer)
{ stimProducer.xyzStimulusBuffer.reset(); }
if (stimProducer.iStimulusBuffer == context->stimBuffer)
{ stimProducer.iStimulusBuffer.reset(); }
if (stimProducer.ambienceStimulusBuffer == context->stimBuffer)
{ stimProducer.ambienceStimulusBuffer.reset(); }
// Check if StimProducer has other buffers
if (!stimProducer.attachedStimulusBuffers.empty())
{
// Other buffers exist - just remove this buffer, done
context->callOriginalCb(true, context->spec);
return;
}
// No other buffers - stop and remove StimProducer
stimProducer.stop();
// Remove stimulus producer from collection before destroying device
stimProducer.device->nAttachedStimulusProducers--;
// Find and remove the producer from the collection by comparing device
auto it2 = std::find_if(
attachedStimulusProducers.begin(), attachedStimulusProducers.end(),
[&stimProducer](const std::shared_ptr<StimulusProducer>& p)
{
/** FIXME:
* When we implement the ImuStimulusProducer, we need to make
* sure we handle that properly here.
*/
auto pcloudProd = std::dynamic_pointer_cast<PcloudStimulusProducer>(p);
return pcloudProd && pcloudProd->device == stimProducer.device;
});
if (it2 != attachedStimulusProducers.end())
{ attachedStimulusProducers.erase(it2); }
(*livoxProto1.livoxProto1_destroyDeviceReq)(
stimProducer.device,
{context, std::bind(
&DetachDeviceReq::detachDeviceReq2,
context.get(), context,
std::placeholders::_1)});
}
void detachDeviceReq2(
std::shared_ptr<DetachDeviceReq> context,
bool success)
{
if (!success)
{
std::cerr << __func__ << ": Failed to destroy dev "
"device " << context->spec->deviceSelector << " for stim "
"producer.\n";
/** NOTE:
* There's a decent argument for falling through here and still
* removing the stimulus producer from attachedStimulusProducers.
*/
context->callOriginalCb(false, context->spec);
return;
}
2025-11-07 14:59:28 -04:00
if (1 || smoHooksPtr->OptionParser_getOptions().verbose)
{
std::cout << __func__ << ": Successfully detached pcloud stim "
"producer 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;
2025-10-01 18:47:42 -04:00
// Stim Buff API descriptor
static const StimBuffApiDesc livoxGen1ApiDesc = {
.name = "livoxGen1",
.exportedQualeIfaceApis = {
{.name = "mesh"},
{.name = "pcloudIntensity"},
{.name = "pcloudAmbience"},
{.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<livoxProto1_mainFn *>(
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_main"));
livoxProto1.livoxProto1_exit = reinterpret_cast<livoxProto1_exitFn *>(
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"));
livoxProto1.livoxProto1_getPcloudDataFdDesc = reinterpret_cast<
livoxProto1_getPcloudDataFdDescFn *>(
dlsym(
livoxProto1.dlopenHandle.get(),
"livoxProto1_getPcloudDataFdDesc"));
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
|| !livoxProto1.livoxProto1_getPcloudDataFdDesc)
{
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)
{
attachedStimulusProducers.clear();
// Call LivoxProto1 library exit function
if (livoxProto1.livoxProto1_exit) {
(*livoxProto1.livoxProto1_exit)();
}
2025-11-01 01:54:49 -04:00
livoxProto1.dlopenHandle.reset(nullptr);
livoxProto1 = LivoxProto1DllState();
return 0; // Success
}
extern "C" void livoxGen1_attachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<smo::ComponentThread>& componentThread,
2025-10-01 18:47:42 -04:00
Callback<smo::stim_buff::sal_mlo_attachDeviceReqCbFn> cb
)
{
if (!livoxProto1.livoxProto1_getOrCreateDeviceReq)
{
throw std::runtime_error(
std::string(__func__) + ": LivoxProto1 getOrCreateDevice function "
"not available");
}
auto request = std::make_shared<AttachDeviceReq>(desc, cb);
// Case 1: Check if StimBuffer already exists
auto stimProducerBase = getStimulusProducer(desc);
if (stimProducerBase)
{
auto stimProducer = std::static_pointer_cast<PcloudStimulusProducer>(
stimProducerBase);
auto existingBuffer = stimProducer->getAttachedStimulusBuffer(desc);
if (existingBuffer)
{
// StimBuffer exists, check if pcloud data is active
if (stimProducer->device && stimProducer->device->pcloudDataActive)
{
// Both StimBuffer and pcloud data are active, early return with success
request->callOriginalCb(true, request->spec);
return;
}
// StimBuffer exists but pcloud data is not active, enable it
request->stimProducer = stimProducer;
request->attachDeviceReq5_doEnablePcloudData_maybeDirectlyCalled(
request);
return;
}
else
{
// StimProducer exists, StimBuffer doesn't
request->stimProducer = stimProducer;
// Ensure StimBuffer is attached and enable pcloud data if needed
request->attachDeviceReq4_doCreateStimBuff_maybeDirectlyCalled(
request);
return;
}
}
// StimProducer doesn't exist - need to create device first
// Parse integer parameters from provider params with defaults
2025-10-31 08:51:17 -04:00
/** EXPLANATION:
* We may want to add a new param here called "command-delay-ms" to control
* the delay we insert between commands sent to the device. 5ms has been
* shown to be sufficient for the Livox Avia.
*/
/* The Livox Avia will generally respond to a handshake request within
* 5ms.
*/
int commandTimeoutMs = 5; // Default: 5ms
/* 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.
*/
2025-10-31 08:51:17 -04:00
int retryDelayMs = 5250; // Default: 5250ms
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 == "cmd-timeout-ms")
{
commandTimeoutMs = smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(
desc->providerParams, "cmd-timeout-ms");
} else if (param.first == "command-timeout-ms")
{
commandTimeoutMs = smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(
desc->providerParams, "command-timeout-ms");
} else if (param.first == "retry-delay-ms")
{
retryDelayMs = smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(
desc->providerParams, "retry-delay-ms");
} else if (param.first == "smo-subnet-nbits")
{
smoSubnetNbits = static_cast<uint8_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(
desc->providerParams, "smo-subnet-nbits"));
} else if (param.first == "data-port")
{
dataPort = static_cast<uint16_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(desc->providerParams, "data-port"));
} else if (param.first == "cmd-port")
{
cmdPort = static_cast<uint16_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(desc->providerParams, "cmd-port"));
} else if (param.first == "imu-port")
{
imuPort = static_cast<uint16_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(desc->providerParams, "imu-port"));
2025-09-06 21:58:20 -04:00
} else if (param.first == "smo-ip")
{
if (param.second.empty())
{
throw std::runtime_error(
std::string(__func__) + ": smo-ip parameter is empty");
}
2025-09-15 13:05:58 -04:00
if (param.second.find('.') == std::string::npos ||
std::count(param.second.begin(), param.second.end(), '.') != 3)
2025-09-06 21:58:20 -04:00
{
throw std::runtime_error(
2025-09-10 04:02:27 -04:00
std::string(__func__) + ": smo-ip parameter is not an "
"IPv4 address");
2025-09-06 21:58:20 -04:00
}
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,
commandTimeoutMs, 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<smo::device::DeviceAttachmentSpec>& desc,
2025-10-01 18:47:42 -04:00
Callback<smo::stim_buff::sal_mlo_detachDeviceReqCbFn> cb
)
{
// Case 1: Check if StimBuffer doesn't exist (early return)
auto stimProducerBase = getStimulusProducer(desc);
if (!stimProducerBase)
{
// StimProducer doesn't exist, nothing to detach - success
cb.callbackFn(true, desc);
return;
}
auto stimProducer = std::dynamic_pointer_cast<PcloudStimulusProducer>(
stimProducerBase);
if (!stimProducer)
{
throw std::runtime_error(std::string(__func__) +
": Failed to cast StimulusProducer to PcloudStimulusProducer "
"for device " + desc->deviceSelector);
}
// Check if StimBuffer exists
auto stimBuffer = stimProducer->getAttachedStimulusBuffer(desc);
if (!stimBuffer)
{
// StimBuffer doesn't exist, nothing to detach - success
cb.callbackFn(true, desc);
2025-09-16 18:36:50 -04:00
return;
}
// Case 2: StimBuffer exists - proceed with detach
auto request = std::make_shared<DetachDeviceReq>(
desc, stimBuffer, cb);
// Disable point cloud data first
(*livoxProto1.livoxProto1_device_disablePcloudDataReq)(
stimProducer->device,
{request, std::bind(
&DetachDeviceReq::detachDeviceReq1,
request.get(), request,
std::placeholders::_1)});
}
// Exported function
2025-10-01 18:47:42 -04:00
extern "C" smo::stim_buff::SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF
SMO_GET_STIM_BUFF_API_DESC_FN_NAME;
2025-10-01 18:47:42 -04:00
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;
}
2025-10-01 18:47:42 -04:00
} // namespace stim_buff
} // namespace smo