LivoxGen1: Port to coros

No longer uses CPS. We also found and documented a potential bug in
the way we deal with disablePcloudData during detachDeviceReq.
This commit is contained in:
2026-05-28 15:17:50 -04:00
parent 7643cf7fed
commit fc5ebb72b9
10 changed files with 966 additions and 741 deletions
+1
View File
@@ -10,3 +10,4 @@
- UI should be responsive. Always prefer to use pre-packaged UI toolkit widgets, containers and colour sets harmoniously, instead of writing custom CSS overrides. Write custom CSS only if there's no UI toolkit mechanism available. - UI should be responsive. Always prefer to use pre-packaged UI toolkit widgets, containers and colour sets harmoniously, instead of writing custom CSS overrides. Write custom CSS only if there's no UI toolkit mechanism available.
- Aggressively isolate, split off, deduplicate and reuse code which can be made into common library code. Do the same with UI elements. Do this both when implementing new features and opportunistically while refactoring or changing old code/UI elements. - Aggressively isolate, split off, deduplicate and reuse code which can be made into common library code. Do the same with UI elements. Do this both when implementing new features and opportunistically while refactoring or changing old code/UI elements.
- Names of files, functions, classes, abstractions, database fields, etc should be aimed at disambiguating purpose and function, rather than at brevity. - Names of files, functions, classes, abstractions, database fields, etc should be aimed at disambiguating purpose and function, rather than at brevity.
- Any source or header file that includes a Boost header must include `<boostAsioLinkageFix.h>` first (at the top of the file, or immediately after the include guard in headers), before all other includes, so Boost.Asio is used as a non-header-only library correctly.
+40
View File
@@ -0,0 +1,40 @@
# Adapter Awaiters
This directory contains coroutine/awaitable adapters that wrap callback-driven
or event-driven APIs.
## Placement rules
- Put wrappers for external APIs in provider-specific subdirectories.
- Examples: `boostAsio/`, `opencl/`, `liburing/`.
- Put wrappers for SMO/internal APIs in `smo/`.
- Do not place adapter awaiters in feature folders like
`stimBuffApis/*` or `smocore/*` unless they are strictly private to one
translation unit.
## Tree layout
```text
include/adapters/
README.md
boostAsio/
<boost asio adapter awaiters>
opencl/
<OpenCL adapter awaiters>
smo/
cpsCallbackAReq.h
livoxProto1CpsAwaiters.h
<other SMO/internal callback adapters>
```
## Design guidelines
- Name adapter awaiter wrapper functions `get<fnName>AReqAwaiter()`, where
`<fnName>` is the wrapped CPS/API request symbol with its library prefix
removed and each `_`-delimited segment Pascal-cased (e.g.
`livoxProto1_getOrCreateDeviceReq``getGetOrCreateDeviceReqAReqAwaiter()`).
- Keep adapters small and single-purpose; but unify where possible to reduce
code duplication.
- Make result types explicit for multi-argument callbacks.
- Resume coroutines on a caller-specified executor/io_service.
- Avoid embedding business logic in adapters.
@@ -0,0 +1,34 @@
#ifndef ADAPTERS_BOOST_ASIO_DEADLINE_TIMER_AREQ_H
#define ADAPTERS_BOOST_ASIO_DEADLINE_TIMER_AREQ_H
#include <boostAsioLinkageFix.h>
#include <functional>
#include <boost/asio/deadline_timer.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <adapters/smo/cpsCallbackAReq.h>
namespace adapters::boostAsio {
using TimerWaitCbFn = std::function<void(bool success)>;
inline auto deadlineTimerWaitAReq(
boost::asio::io_service &ioService,
const boost::posix_time::milliseconds delay)
{
return smo::cpsBoundary::CpsCallbackAReq<bool, TimerWaitCbFn, std::function<void(sscl::cps::Callback<TimerWaitCbFn>)>>(
ioService,
[&ioService, delay](sscl::cps::Callback<TimerWaitCbFn> cb)
{
auto timer = std::make_shared<boost::asio::deadline_timer>(ioService);
timer->expires_from_now(delay);
timer->async_wait(
[timer, cb](const boost::system::error_code &error) mutable
{
cb.callbackFn(!error);
});
});
}
} // namespace adapters::boostAsio
#endif // ADAPTERS_BOOST_ASIO_DEADLINE_TIMER_AREQ_H
@@ -1,5 +1,7 @@
#ifndef CPS_CALLBACK_AREQ_H #ifndef ADAPTERS_SMO_CPS_CALLBACK_AREQ_H
#define CPS_CALLBACK_AREQ_H #define ADAPTERS_SMO_CPS_CALLBACK_AREQ_H
#include <boostAsioLinkageFix.h>
#include <atomic> #include <atomic>
#include <coroutine> #include <coroutine>
@@ -13,7 +15,7 @@
namespace smo { namespace smo {
namespace cpsBoundary { namespace cpsBoundary {
/** Eager-start CPS callback coroutine adapter (mirrors /** Eager-start CPS callback -> coroutine adapter (mirrors
* PuppetThread::ViralThreadLifetimeMgmtInvoker). * PuppetThread::ViralThreadLifetimeMgmtInvoker).
*/ */
template <typename Result, typename CallbackFn, typename StartFn> template <typename Result, typename CallbackFn, typename StartFn>
@@ -90,4 +92,4 @@ private:
} // namespace cpsBoundary } // namespace cpsBoundary
} // namespace smo } // namespace smo
#endif // CPS_CALLBACK_AREQ_H #endif // ADAPTERS_SMO_CPS_CALLBACK_AREQ_H
@@ -0,0 +1,121 @@
#ifndef ADAPTERS_SMO_LIVOX_PROTO1_CPS_AWAITERS_H
#define ADAPTERS_SMO_LIVOX_PROTO1_CPS_AWAITERS_H
#include <cstdint>
#include <memory>
#include <adapters/smo/cpsCallbackAReq.h>
#include <livoxProto1/livoxProto1.h>
#include <spinscale/componentThread.h>
namespace adapters::smo {
struct GetOrCreateDeviceResult
{
bool success = false;
std::shared_ptr<livoxProto1::Device> device;
};
struct GetReturnModeResult
{
bool success = false;
uint8_t returnMode = 0;
};
inline auto getGetOrCreateDeviceReqAReqAwaiter(
boost::asio::io_service &resumeIoService,
livoxProto1_getOrCreateDeviceReqFn *fn,
const std::string &deviceIdentifier,
const std::shared_ptr<sscl::ComponentThread> &componentThread,
int commandTimeoutMs,
int retryDelayMs,
const std::string &smoIp,
uint8_t smoSubnetNbits,
uint16_t dataPort,
uint16_t cmdPort,
uint16_t imuPort)
{
return ::smo::cpsBoundary::CpsCallbackAReq<
GetOrCreateDeviceResult,
livoxProto1_getOrCreateDeviceReqCbFn,
std::function<void(sscl::cps::Callback<livoxProto1_getOrCreateDeviceReqCbFn>)>>(
resumeIoService,
[=](sscl::cps::Callback<livoxProto1_getOrCreateDeviceReqCbFn> cb)
{
(*fn)(
deviceIdentifier,
componentThread,
commandTimeoutMs, retryDelayMs,
smoIp, smoSubnetNbits,
dataPort, cmdPort, imuPort,
std::move(cb));
});
}
inline auto getDeviceGetReturnModeReqAReqAwaiter(
boost::asio::io_service &resumeIoService,
livoxProto1_device_getReturnModeReqFn *fn,
std::shared_ptr<livoxProto1::Device> device)
{
return ::smo::cpsBoundary::CpsCallbackAReq<
GetReturnModeResult,
livoxProto1_device_getReturnModeReqCbFn,
std::function<void(sscl::cps::Callback<livoxProto1_device_getReturnModeReqCbFn>)>>(
resumeIoService,
[=](sscl::cps::Callback<livoxProto1_device_getReturnModeReqCbFn> cb)
{
(*fn)(device, std::move(cb));
});
}
inline auto getDeviceEnablePcloudDataReqAReqAwaiter(
boost::asio::io_service &resumeIoService,
livoxProto1_device_enablePcloudDataReqFn *fn,
std::shared_ptr<livoxProto1::Device> device)
{
return ::smo::cpsBoundary::CpsCallbackAReq<
bool,
livoxProto1_device_enablePcloudDataReqCbFn,
std::function<void(sscl::cps::Callback<livoxProto1_device_enablePcloudDataReqCbFn>)>>(
resumeIoService,
[=](sscl::cps::Callback<livoxProto1_device_enablePcloudDataReqCbFn> cb)
{
(*fn)(device, std::move(cb));
});
}
inline auto getDeviceDisablePcloudDataReqAReqAwaiter(
boost::asio::io_service &resumeIoService,
livoxProto1_device_disablePcloudDataReqFn *fn,
std::shared_ptr<livoxProto1::Device> device)
{
return ::smo::cpsBoundary::CpsCallbackAReq<
bool,
livoxProto1_device_disablePcloudDataReqCbFn,
std::function<void(sscl::cps::Callback<livoxProto1_device_disablePcloudDataReqCbFn>)>>(
resumeIoService,
[=](sscl::cps::Callback<livoxProto1_device_disablePcloudDataReqCbFn> cb)
{
(*fn)(device, std::move(cb));
});
}
inline auto getDestroyDeviceReqAReqAwaiter(
boost::asio::io_service &resumeIoService,
livoxProto1_destroyDeviceReqFn *fn,
std::shared_ptr<livoxProto1::Device> device)
{
return ::smo::cpsBoundary::CpsCallbackAReq<
bool,
livoxProto1_destroyDeviceReqCbFn,
std::function<void(sscl::cps::Callback<livoxProto1_destroyDeviceReqCbFn>)>>(
resumeIoService,
[=](sscl::cps::Callback<livoxProto1_destroyDeviceReqCbFn> cb)
{
(*fn)(device, std::move(cb));
});
}
} // namespace adapters::smo
#endif // ADAPTERS_SMO_LIVOX_PROTO1_CPS_AWAITERS_H
+4
View File
@@ -14,6 +14,7 @@ if(ENABLE_STIMBUFFAPI_livoxGen1)
add_library(livoxGen1 SHARED add_library(livoxGen1 SHARED
livoxGen1.cpp livoxGen1.cpp
livoxGen1Proto1CpsBridge.cpp
pcloudStimulusProducer.cpp pcloudStimulusProducer.cpp
livoxPcloudFrameDumper.cpp livoxPcloudFrameDumper.cpp
ioUringAssemblyEngine.cpp ioUringAssemblyEngine.cpp
@@ -36,6 +37,8 @@ if(ENABLE_STIMBUFFAPI_livoxGen1)
target_include_directories(livoxGen1 PUBLIC target_include_directories(livoxGen1 PUBLIC
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/smocore/include
${CMAKE_SOURCE_DIR}/commonLibs ${CMAKE_SOURCE_DIR}/commonLibs
${URING_INCLUDE_DIRS} ${URING_INCLUDE_DIRS}
${OPENCL_INCLUDE_DIRS} ${OPENCL_INCLUDE_DIRS}
@@ -46,6 +49,7 @@ if(ENABLE_STIMBUFFAPI_livoxGen1)
${URING_LIBRARIES} ${URING_LIBRARIES}
${OPENCL_LIBRARIES} ${OPENCL_LIBRARIES}
attachmentSupport attachmentSupport
spinscale
) )
target_link_directories(livoxGen1 PUBLIC target_link_directories(livoxGen1 PUBLIC
${URING_LIBRARY_DIRS} ${URING_LIBRARY_DIRS}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
#ifndef LIVOX_GEN1_INTERNAL_H
#define LIVOX_GEN1_INTERNAL_H
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <spinscale/co/invokers.h>
#include <user/deviceAttachmentSpec.h>
#include <user/senseApiDesc.h>
#include <user/stimulusProducer.h>
#include "livoxGen1.h"
#include "pcloudStimulusProducer.h"
namespace smo::stim_buff {
struct LivoxProviderParams
{
int commandTimeoutMs = 5; // Default: 5ms
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
std::string smoIp; // Default: empty string (will trigger IP auto-detection)
};
extern const SmoCallbacks *smoHooksPtr;
extern SmoThreadingModelDesc smoThreadingModelDesc;
extern std::vector<std::shared_ptr<StimulusProducer>> attachedStimulusProducers;
std::shared_ptr<StimulusProducer> getStimulusProducer(
const std::shared_ptr<device::DeviceAttachmentSpec> &spec);
size_t parseNDgramsPerFrame(
const std::shared_ptr<device::DeviceAttachmentSpec> &spec);
LivoxProviderParams parseLivoxProviderParams(
const std::shared_ptr<device::DeviceAttachmentSpec> &desc);
bool ensureStimBufferAttachedWithoutDuplicates(
const std::shared_ptr<PcloudStimulusProducer> &stimProducer,
const std::shared_ptr<device::DeviceAttachmentSpec> &spec);
sscl::co::ViralNonPostingInvoker<int> livoxGen1_initializeCInd();
sscl::co::ViralNonPostingInvoker<int> livoxGen1_finalizeCInd();
sscl::co::ViralNonPostingInvoker<StimBuffDeviceOpResult> livoxGen1_attachDeviceCReq(
const std::shared_ptr<device::DeviceAttachmentSpec> &desc,
const std::shared_ptr<sscl::ComponentThread> &componentThread);
sscl::co::ViralNonPostingInvoker<StimBuffDeviceOpResult> livoxGen1_detachDeviceCReq(
const std::shared_ptr<device::DeviceAttachmentSpec> &desc);
} // namespace smo::stim_buff
#endif // LIVOX_GEN1_INTERNAL_H
@@ -0,0 +1,89 @@
#include "livoxGen1Proto1CpsBridge.h"
#include <stdexcept>
namespace smo::stim_buff {
sscl::co::ViralNonPostingInvoker<adapters::smo::GetOrCreateDeviceResult>
coAwaitGetOrCreateDevice(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::string &deviceIdentifier,
const LivoxProviderParams &params)
{
if (!livoxProto1.livoxProto1_getOrCreateDeviceReq) {
throw std::runtime_error("coAwaitGetOrCreateDevice: proto1 function missing");
}
auto result = co_await adapters::smo::getGetOrCreateDeviceReqAReqAwaiter(
componentThread->getIoService(),
livoxProto1.livoxProto1_getOrCreateDeviceReq,
deviceIdentifier,
componentThread,
params.commandTimeoutMs,
params.retryDelayMs,
params.smoIp,
params.smoSubnetNbits,
params.dataPort,
params.cmdPort,
params.imuPort);
co_return result;
}
sscl::co::ViralNonPostingInvoker<adapters::smo::GetReturnModeResult>
coAwaitGetReturnMode(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device)
{
if (!livoxProto1.livoxProto1_device_getReturnModeReq) {
throw std::runtime_error("coAwaitGetReturnMode: proto1 function missing");
}
co_return co_await adapters::smo::getDeviceGetReturnModeReqAReqAwaiter(
componentThread->getIoService(),
livoxProto1.livoxProto1_device_getReturnModeReq,
device);
}
sscl::co::ViralNonPostingInvoker<bool> coAwaitEnablePcloudData(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device)
{
if (!livoxProto1.livoxProto1_device_enablePcloudDataReq) {
throw std::runtime_error("coAwaitEnablePcloudData: proto1 function missing");
}
co_return co_await adapters::smo::getDeviceEnablePcloudDataReqAReqAwaiter(
componentThread->getIoService(),
livoxProto1.livoxProto1_device_enablePcloudDataReq,
device);
}
sscl::co::ViralNonPostingInvoker<bool> coAwaitDisablePcloudData(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device)
{
if (!livoxProto1.livoxProto1_device_disablePcloudDataReq) {
throw std::runtime_error("coAwaitDisablePcloudData: proto1 function missing");
}
co_return co_await adapters::smo::getDeviceDisablePcloudDataReqAReqAwaiter(
componentThread->getIoService(),
livoxProto1.livoxProto1_device_disablePcloudDataReq,
device);
}
sscl::co::ViralNonPostingInvoker<bool> coAwaitDestroyDevice(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device)
{
if (!livoxProto1.livoxProto1_destroyDeviceReq) {
throw std::runtime_error("coAwaitDestroyDevice: proto1 function missing");
}
co_return co_await adapters::smo::getDestroyDeviceReqAReqAwaiter(
componentThread->getIoService(),
livoxProto1.livoxProto1_destroyDeviceReq,
device);
}
} // namespace smo::stim_buff
@@ -0,0 +1,39 @@
#ifndef LIVOX_GEN1_PROTO1_CPS_BRIDGE_H
#define LIVOX_GEN1_PROTO1_CPS_BRIDGE_H
#include <memory>
#include <adapters/smo/livoxProto1CpsAwaiters.h>
#include <spinscale/co/invokers.h>
#include <spinscale/componentThread.h>
#include "livoxGen1Internal.h"
namespace smo::stim_buff {
sscl::co::ViralNonPostingInvoker<adapters::smo::GetOrCreateDeviceResult>
coAwaitGetOrCreateDevice(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::string &deviceIdentifier,
const LivoxProviderParams &params);
sscl::co::ViralNonPostingInvoker<adapters::smo::GetReturnModeResult>
coAwaitGetReturnMode(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device);
sscl::co::ViralNonPostingInvoker<bool> coAwaitEnablePcloudData(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device);
sscl::co::ViralNonPostingInvoker<bool> coAwaitDisablePcloudData(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device);
sscl::co::ViralNonPostingInvoker<bool> coAwaitDestroyDevice(
const std::shared_ptr<sscl::ComponentThread> &componentThread,
const std::shared_ptr<livoxProto1::Device> &device);
} // namespace smo::stim_buff
#endif // LIVOX_GEN1_PROTO1_CPS_BRIDGE_H