LivoxProto1: port to sscl::co framework
Code now actually looks a lot cleaner, tbh.
This commit is contained in:
@@ -17,10 +17,17 @@ if(ENABLE_LIB_livoxProto1)
|
||||
|
||||
# Set config define for header generation
|
||||
add_compile_definitions(CONFIG_LIB_LIVOXPROTO1_ENABLED)
|
||||
target_include_directories(livoxProto1 PUBLIC ${Boost_INCLUDE_DIRS})
|
||||
target_include_directories(livoxProto1 PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/smocore/include
|
||||
${CMAKE_SOURCE_DIR}/commonLibs
|
||||
)
|
||||
target_link_libraries(livoxProto1 PUBLIC
|
||||
Boost::system Boost::log
|
||||
attachmentSupport)
|
||||
attachmentSupport
|
||||
spinscale
|
||||
)
|
||||
|
||||
# Verify Boost dynamic dependencies after build
|
||||
add_custom_command(TARGET livoxProto1 POST_BUILD
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
|
||||
+41
-125
@@ -1,10 +1,10 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <opts.h>
|
||||
#include <spinscale/cps/asynchronousContinuation.h>
|
||||
#include <spinscale/cps/callback.h>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include "protocol.h"
|
||||
#include "core.h"
|
||||
@@ -72,68 +72,16 @@ std::optional<std::shared_ptr<Device>> DeviceManager::getDevice(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// GetOrCreateDeviceReq nested class implementation
|
||||
class DeviceManager::GetOrCreateDeviceReq
|
||||
: public sscl::cps::NonPostedAsynchronousContinuation<
|
||||
livoxProto1_getOrCreateDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
DeviceManager& deviceManager;
|
||||
// The device we're trying to connect (holds all connection parameters)
|
||||
std::shared_ptr<Device> pendingDevice;
|
||||
|
||||
public:
|
||||
GetOrCreateDeviceReq(
|
||||
DeviceManager& mgr,
|
||||
std::shared_ptr<Device> device,
|
||||
sscl::cps::Callback<livoxProto1_getOrCreateDeviceReqCbFn> cb)
|
||||
: sscl::cps::NonPostedAsynchronousContinuation<
|
||||
livoxProto1_getOrCreateDeviceReqCbFn>(std::move(cb)),
|
||||
deviceManager(mgr), pendingDevice(device)
|
||||
{}
|
||||
|
||||
// Public accessor for the original callback
|
||||
void callOriginalCallback(bool success, std::shared_ptr<Device> device)
|
||||
{ callOriginalCb(success, device); }
|
||||
|
||||
void callOriginalCallbackWithFailure()
|
||||
{ callOriginalCallback(false, nullptr); }
|
||||
|
||||
void getOrCreateDeviceReq1(
|
||||
std::shared_ptr<GetOrCreateDeviceReq> 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(
|
||||
sscl::co::ViralNonPostingInvoker<LivoxProto1GetOrCreateDeviceResult>
|
||||
DeviceManager::getOrCreateDeviceCReq(
|
||||
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,
|
||||
sscl::cps::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback)
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort)
|
||||
{
|
||||
LivoxProto1GetOrCreateDeviceResult result;
|
||||
|
||||
// Validate smoIp format using Boost.Asio IPv4 validation
|
||||
if (!smoIp.empty() && !comms::isValidIPv4(smoIp))
|
||||
{
|
||||
@@ -155,9 +103,9 @@ void DeviceManager::getOrCreateDeviceReq(
|
||||
auto existingDevice = getDevice(deviceIdentifier);
|
||||
if (existingDevice)
|
||||
{
|
||||
// Device already exists and is connected, return it
|
||||
callback.callbackFn(true, existingDevice.value());
|
||||
return;
|
||||
result.success = true;
|
||||
result.device = existingDevice.value();
|
||||
co_return result;
|
||||
}
|
||||
|
||||
// Device doesn't exist, create a new one but don't add it to collection yet
|
||||
@@ -167,82 +115,50 @@ void DeviceManager::getOrCreateDeviceReq(
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort);
|
||||
|
||||
// Create the continuation request object to hold state and callbacks
|
||||
auto request = std::make_shared<GetOrCreateDeviceReq>(
|
||||
*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)});
|
||||
const bool connectSuccess = co_await newDevice->connectCReq();
|
||||
if (!connectSuccess)
|
||||
{
|
||||
std::cerr << __func__ << ": Connection failed for device "
|
||||
<< newDevice->discoveredDevice.deviceIdentifier
|
||||
<< std::endl;
|
||||
co_return result;
|
||||
}
|
||||
|
||||
devices.push_back(newDevice);
|
||||
if (getProtoState().smoCallbacks.OptionParser_getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": Successfully connected and added device "
|
||||
<< newDevice->discoveredDevice.deviceIdentifier
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
result.device = newDevice;
|
||||
co_return result;
|
||||
}
|
||||
|
||||
class DeviceManager::DestroyDeviceReq
|
||||
: public sscl::cps::NonPostedAsynchronousContinuation<
|
||||
livoxProto1_destroyDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
DeviceManager& deviceManager;
|
||||
std::shared_ptr<Device> pendingDevice;
|
||||
|
||||
public:
|
||||
DestroyDeviceReq(
|
||||
DeviceManager& mgr,
|
||||
std::shared_ptr<Device> device,
|
||||
sscl::cps::Callback<livoxProto1_destroyDeviceReqCbFn> cb)
|
||||
: sscl::cps::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<DestroyDeviceReq> 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<Device> dev,
|
||||
sscl::cps::Callback<livoxProto1_destroyDeviceReqCbFn> callback
|
||||
)
|
||||
sscl::co::ViralNonPostingInvoker<bool> DeviceManager::destroyDeviceCReq(
|
||||
std::shared_ptr<Device> dev)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Check to see if the device is in our collection. If so, call
|
||||
* disconnectReq and then remove it.
|
||||
* disconnectCReq and then remove it.
|
||||
*/
|
||||
std::shared_ptr<Device> device = getDevice(dev->discoveredDevice).
|
||||
value_or(nullptr);
|
||||
|
||||
if (!device || device->nAttachedStimulusProducers > 0)
|
||||
{
|
||||
callback.callbackFn(false);
|
||||
return;
|
||||
if (!device || device->nAttachedStimulusProducers > 0) {
|
||||
co_return false;
|
||||
}
|
||||
|
||||
auto request = std::make_shared<DestroyDeviceReq>(
|
||||
*this, device, std::move(callback));
|
||||
const bool success = co_await device->disconnectCReq();
|
||||
|
||||
device->disconnectReq(
|
||||
{request, std::bind(
|
||||
&DeviceManager::DestroyDeviceReq::destroyDeviceReq1,
|
||||
request.get(), request, std::placeholders::_1)});
|
||||
devices.erase(
|
||||
std::remove(devices.begin(), devices.end(), device),
|
||||
devices.end());
|
||||
|
||||
co_return success;
|
||||
}
|
||||
|
||||
void main(const std::shared_ptr<sscl::ComponentThread> &componentThread,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "broadcastListener.h"
|
||||
#include "udpCommandDemuxer.h"
|
||||
#include "livoxProto1.h"
|
||||
#include <spinscale/cps/callback.h>
|
||||
#include <spinscale/co/invokers.h>
|
||||
|
||||
namespace livoxProto1 {
|
||||
|
||||
@@ -23,17 +23,16 @@ public:
|
||||
|
||||
static void deviceGoneAwayInd(const comms::DiscoveredDevice &device);
|
||||
|
||||
void getOrCreateDeviceReq(
|
||||
sscl::co::ViralNonPostingInvoker<LivoxProto1GetOrCreateDeviceResult>
|
||||
getOrCreateDeviceCReq(
|
||||
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,
|
||||
sscl::cps::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
|
||||
|
||||
void destroyDeviceReq(
|
||||
std::shared_ptr<Device> device,
|
||||
sscl::cps::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
|
||||
sscl::co::ViralNonPostingInvoker<bool> destroyDeviceCReq(
|
||||
std::shared_ptr<Device> device);
|
||||
|
||||
std::optional<std::shared_ptr<Device>> getDevice(
|
||||
const std::string &deviceIdentifier);
|
||||
@@ -52,10 +51,6 @@ public:
|
||||
std::vector<std::shared_ptr<Device>> devices;
|
||||
comms::BroadcastListener broadcastListener;
|
||||
comms::UdpCommandDemuxer udpCommandDemuxer;
|
||||
|
||||
// Nested continuation class for async device creation
|
||||
class GetOrCreateDeviceReq;
|
||||
class DestroyDeviceReq;
|
||||
};
|
||||
|
||||
void main(
|
||||
|
||||
+670
-1575
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include "protocol.h"
|
||||
#include <spinscale/cps/callback.h>
|
||||
#include <spinscale/co/invokers.h>
|
||||
#include <spinscale/spinLock.h>
|
||||
|
||||
// Custom hash function for std::pair<uint8_t, uint8_t>
|
||||
@@ -96,16 +96,6 @@ private:
|
||||
std::optional<std::string> detectSmoIp(const std::string& deviceIP);
|
||||
uint32_t getSubnetMaskFor(uint8_t nbits);
|
||||
|
||||
class ConnectReq;
|
||||
class ConnectToKnownDeviceReq;
|
||||
class ConnectByDeviceIdentifierReq;
|
||||
class ExecuteHandshakeReq;
|
||||
class DisconnectReq;
|
||||
class EnablePcloudDataReq;
|
||||
class DisablePcloudDataReq;
|
||||
class SetReturnModeReq;
|
||||
class GetReturnModeReq;
|
||||
|
||||
public:
|
||||
enum class ReturnMode : uint8_t
|
||||
{
|
||||
@@ -146,37 +136,30 @@ public:
|
||||
// Utility methods
|
||||
std::optional<std::string> getSmoIp(const std::string& deviceIP);
|
||||
|
||||
// Callback function type definitions for async methods
|
||||
typedef std::function<void(bool success)> connectReqCbFn;
|
||||
typedef std::function<
|
||||
void(bool success, const std::string& ipAddr)>
|
||||
connectToKnownDeviceReqCbFn;
|
||||
typedef std::function<
|
||||
void(bool success, const std::string& ipAddr)>
|
||||
connectByDeviceIdentifierReqCbFn;
|
||||
typedef std::function<void(bool success)> executeHandshakeReqCbFn;
|
||||
typedef std::function<void(bool success)> disconnectReqCbFn;
|
||||
typedef std::function<void(bool success)> enablePcloudDataReqCbFn;
|
||||
typedef std::function<void(bool success)> disablePcloudDataReqCbFn;
|
||||
typedef std::function<void(bool success)> setReturnModeReqCbFn;
|
||||
typedef std::function<void(bool success, uint8_t returnMode)>
|
||||
getReturnModeReqCbFn;
|
||||
struct ConnectIpResult
|
||||
{
|
||||
bool success = false;
|
||||
std::string ipAddr;
|
||||
};
|
||||
|
||||
// Async connection methods
|
||||
void connectReq(sscl::cps::Callback<connectReqCbFn> callback);
|
||||
void connectToKnownDeviceReq(
|
||||
sscl::cps::Callback<connectToKnownDeviceReqCbFn> callback);
|
||||
void connectByDeviceIdentifierReq(
|
||||
sscl::cps::Callback<connectByDeviceIdentifierReqCbFn> callback);
|
||||
void executeHandshakeReq(
|
||||
const std::string& deviceIP,
|
||||
sscl::cps::Callback<executeHandshakeReqCbFn> callback);
|
||||
void disconnectReq(sscl::cps::Callback<disconnectReqCbFn> callback);
|
||||
void enablePcloudDataReq(sscl::cps::Callback<enablePcloudDataReqCbFn> callback);
|
||||
void disablePcloudDataReq(sscl::cps::Callback<disablePcloudDataReqCbFn> callback);
|
||||
void setReturnModeReq(
|
||||
uint8_t returnMode, sscl::cps::Callback<setReturnModeReqCbFn> callback);
|
||||
void getReturnModeReq(sscl::cps::Callback<getReturnModeReqCbFn> callback);
|
||||
sscl::co::ViralNonPostingInvoker<bool> connectCReq();
|
||||
sscl::co::ViralNonPostingInvoker<ConnectIpResult> connectToKnownDeviceCReq();
|
||||
sscl::co::ViralNonPostingInvoker<ConnectIpResult> connectByDeviceIdentifierCReq();
|
||||
sscl::co::ViralNonPostingInvoker<bool> executeHandshakeCReq(
|
||||
const std::string& deviceIP);
|
||||
sscl::co::ViralNonPostingInvoker<bool> disconnectCReq();
|
||||
sscl::co::ViralNonPostingInvoker<bool> enablePcloudDataCReq();
|
||||
sscl::co::ViralNonPostingInvoker<bool> disablePcloudDataCReq();
|
||||
|
||||
struct GetReturnModeResult
|
||||
{
|
||||
bool success = false;
|
||||
uint8_t returnMode = 0;
|
||||
};
|
||||
|
||||
sscl::co::ViralNonPostingInvoker<bool> setReturnModeCReq(uint8_t returnMode);
|
||||
sscl::co::ViralNonPostingInvoker<GetReturnModeResult> getReturnModeCReq();
|
||||
|
||||
public:
|
||||
comms::DiscoveredDevice discoveredDevice;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <stdexcept>
|
||||
#include <spinscale/cps/callback.h>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include "livoxProto1.h"
|
||||
#include "device.h"
|
||||
@@ -10,14 +9,13 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
void livoxProto1_getOrCreateDeviceReq(
|
||||
sscl::co::ViralNonPostingInvoker<LivoxProto1GetOrCreateDeviceResult>
|
||||
livoxProto1_getOrCreateDeviceCReq(
|
||||
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,
|
||||
sscl::cps::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback
|
||||
)
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort)
|
||||
{
|
||||
// Get the global DeviceManager instance
|
||||
auto& protoState = livoxProto1::getProtoState();
|
||||
@@ -28,19 +26,16 @@ void livoxProto1_getOrCreateDeviceReq(
|
||||
"livoxProto1_main first");
|
||||
}
|
||||
|
||||
// Delegate to DeviceManager
|
||||
protoState.deviceManager->getOrCreateDeviceReq(
|
||||
// Delegate to the DeviceManager to create the device
|
||||
co_return co_await protoState.deviceManager->getOrCreateDeviceCReq(
|
||||
deviceIdentifier, componentThread,
|
||||
commandTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort,
|
||||
callback);
|
||||
dataPort, cmdPort, imuPort);
|
||||
}
|
||||
|
||||
void livoxProto1_destroyDeviceReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_destroyDeviceReqCbFn> callback
|
||||
)
|
||||
sscl::co::ViralNonPostingInvoker<bool> livoxProto1_destroyDeviceCReq(
|
||||
std::shared_ptr<livoxProto1::Device> device)
|
||||
{
|
||||
auto& protoState = livoxProto1::getProtoState();
|
||||
if (!protoState.deviceManager)
|
||||
@@ -49,8 +44,7 @@ void livoxProto1_destroyDeviceReq(
|
||||
+ ": DeviceManager not initialized");
|
||||
}
|
||||
|
||||
protoState.deviceManager->destroyDeviceReq(
|
||||
device, callback);
|
||||
co_return co_await protoState.deviceManager->destroyDeviceCReq(device);
|
||||
}
|
||||
|
||||
void livoxProto1_main(
|
||||
@@ -65,10 +59,8 @@ void livoxProto1_exit(void)
|
||||
livoxProto1::exit();
|
||||
}
|
||||
|
||||
void livoxProto1_device_enablePcloudDataReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_device_enablePcloudDataReqCbFn> callback
|
||||
)
|
||||
sscl::co::ViralNonPostingInvoker<bool> livoxProto1_device_enablePcloudDataCReq(
|
||||
std::shared_ptr<livoxProto1::Device> device)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
@@ -76,13 +68,11 @@ void livoxProto1_device_enablePcloudDataReq(
|
||||
+ ": Device pointer is null");
|
||||
}
|
||||
|
||||
device->enablePcloudDataReq(callback);
|
||||
co_return co_await device->enablePcloudDataCReq();
|
||||
}
|
||||
|
||||
void livoxProto1_device_disablePcloudDataReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_device_disablePcloudDataReqCbFn> callback
|
||||
)
|
||||
sscl::co::ViralNonPostingInvoker<bool> livoxProto1_device_disablePcloudDataCReq(
|
||||
std::shared_ptr<livoxProto1::Device> device)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
@@ -90,13 +80,12 @@ void livoxProto1_device_disablePcloudDataReq(
|
||||
+ ": Device pointer is null");
|
||||
}
|
||||
|
||||
device->disablePcloudDataReq(callback);
|
||||
co_return co_await device->disablePcloudDataCReq();
|
||||
}
|
||||
|
||||
void livoxProto1_device_getReturnModeReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_device_getReturnModeReqCbFn> callback
|
||||
)
|
||||
sscl::co::ViralNonPostingInvoker<LivoxProto1GetReturnModeResult>
|
||||
livoxProto1_device_getReturnModeCReq(
|
||||
std::shared_ptr<livoxProto1::Device> device)
|
||||
{
|
||||
if (!device)
|
||||
{
|
||||
@@ -104,7 +93,12 @@ void livoxProto1_device_getReturnModeReq(
|
||||
+ ": Device pointer is null");
|
||||
}
|
||||
|
||||
device->getReturnModeReq(callback);
|
||||
livoxProto1::Device::GetReturnModeResult deviceResult =
|
||||
co_await device->getReturnModeCReq();
|
||||
LivoxProto1GetReturnModeResult result;
|
||||
result.success = deviceResult.success;
|
||||
result.returnMode = deviceResult.returnMode;
|
||||
co_return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <spinscale/cps/callback.h>
|
||||
#include <spinscale/co/invokers.h>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
|
||||
// Forward declarations
|
||||
@@ -23,6 +23,18 @@ namespace livoxProto1 {
|
||||
class Device;
|
||||
}
|
||||
|
||||
struct LivoxProto1GetOrCreateDeviceResult
|
||||
{
|
||||
bool success = false;
|
||||
std::shared_ptr<livoxProto1::Device> device;
|
||||
};
|
||||
|
||||
struct LivoxProto1GetReturnModeResult
|
||||
{
|
||||
bool success = false;
|
||||
uint8_t returnMode = 0;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -52,54 +64,43 @@ typedef void livoxProto1_exitFn(void);
|
||||
* @param dataPort Data port for point cloud (default: 56000)
|
||||
* @param cmdPort Command port (default: 56001)
|
||||
* @param imuPort IMU port (default: 56002)
|
||||
* @return Device pointer on success, nullptr on failure
|
||||
* @return LivoxProto1GetOrCreateDeviceResult (success + device on success,
|
||||
* null device on failure)
|
||||
*/
|
||||
typedef std::function<
|
||||
void(bool success, std::shared_ptr<livoxProto1::Device> device)>
|
||||
livoxProto1_getOrCreateDeviceReqCbFn;
|
||||
|
||||
typedef void livoxProto1_getOrCreateDeviceReqFn(
|
||||
typedef sscl::co::ViralNonPostingInvoker<LivoxProto1GetOrCreateDeviceResult>
|
||||
livoxProto1_getOrCreateDeviceCReqFn(
|
||||
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,
|
||||
sscl::cps::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
|
||||
|
||||
typedef std::function<void(bool success)> livoxProto1_destroyDeviceReqCbFn;
|
||||
typedef void livoxProto1_destroyDeviceReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
|
||||
typedef sscl::co::ViralNonPostingInvoker<bool> livoxProto1_destroyDeviceCReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device);
|
||||
|
||||
typedef std::function<void(bool success)>
|
||||
livoxProto1_device_enablePcloudDataReqCbFn;
|
||||
typedef void livoxProto1_device_enablePcloudDataReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_device_enablePcloudDataReqCbFn> callback);
|
||||
typedef sscl::co::ViralNonPostingInvoker<bool>
|
||||
livoxProto1_device_enablePcloudDataCReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device);
|
||||
|
||||
typedef std::function<void(bool success)>
|
||||
livoxProto1_device_disablePcloudDataReqCbFn;
|
||||
typedef void livoxProto1_device_disablePcloudDataReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_device_disablePcloudDataReqCbFn> callback);
|
||||
typedef sscl::co::ViralNonPostingInvoker<bool>
|
||||
livoxProto1_device_disablePcloudDataCReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device);
|
||||
|
||||
typedef std::function<void(bool success, uint8_t returnMode)>
|
||||
livoxProto1_device_getReturnModeReqCbFn;
|
||||
typedef void livoxProto1_device_getReturnModeReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
sscl::cps::Callback<livoxProto1_device_getReturnModeReqCbFn> callback);
|
||||
typedef sscl::co::ViralNonPostingInvoker<LivoxProto1GetReturnModeResult>
|
||||
livoxProto1_device_getReturnModeCReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device);
|
||||
|
||||
typedef std::shared_ptr<boost::asio::posix::stream_descriptor>
|
||||
livoxProto1_getPcloudDataFdDescFn(void);
|
||||
|
||||
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;
|
||||
livoxProto1_getOrCreateDeviceCReqFn livoxProto1_getOrCreateDeviceCReq;
|
||||
livoxProto1_destroyDeviceCReqFn livoxProto1_destroyDeviceCReq;
|
||||
livoxProto1_device_enablePcloudDataCReqFn livoxProto1_device_enablePcloudDataCReq;
|
||||
livoxProto1_device_disablePcloudDataCReqFn
|
||||
livoxProto1_device_disablePcloudDataCReq;
|
||||
livoxProto1_device_getReturnModeCReqFn livoxProto1_device_getReturnModeCReq;
|
||||
livoxProto1_getPcloudDataFdDescFn livoxProto1_getPcloudDataFdDesc;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include <boostAsioLinkageFix.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <coroutine>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
@@ -8,7 +12,11 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <adapters/boostAsio/deadlineTimerAReq.h>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <spinscale/co/group.h>
|
||||
#include "udpCommandDemuxer.h"
|
||||
#include "protocol.h"
|
||||
#include "core.h"
|
||||
#include "device.h"
|
||||
|
||||
@@ -330,6 +338,19 @@ void UdpCommandDemuxer::processIncomingData()
|
||||
char sourceIP[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &senderAddr.sin_addr, sourceIP, INET_ADDRSTRLEN);
|
||||
|
||||
if (bytesReceived >= static_cast<ssize_t>(
|
||||
sizeof(Header) + sizeof(Command)))
|
||||
{
|
||||
const uint8_t cmdSet = receiveBuffer[sizeof(Header)];
|
||||
const uint8_t cmdId = receiveBuffer[sizeof(Header) + 1];
|
||||
|
||||
if (tryCompletePendingCommandWait(
|
||||
sourceIP, cmdSet, cmdId, receiveBuffer, bytesReceived))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// First, find device with matching IP address in DeviceManager collection
|
||||
for (const auto &device : deviceManager.devices)
|
||||
{
|
||||
@@ -395,5 +416,208 @@ void UdpCommandDemuxer::processIncomingData()
|
||||
<< sourceIP << ", discarding datagram" << std::endl;
|
||||
}
|
||||
|
||||
struct UdpCommandDemuxer::PendingCommandWaitDesc
|
||||
{
|
||||
CommandWaitKey key;
|
||||
boost::asio::io_service &resumeIoService;
|
||||
std::atomic<bool> settled{false};
|
||||
UdpCommandResponseResult result{};
|
||||
std::coroutine_handle<> callerSchedHandle;
|
||||
|
||||
PendingCommandWaitDesc(
|
||||
CommandWaitKey keyIn,
|
||||
boost::asio::io_service &resumeIoServiceIn)
|
||||
: key(std::move(keyIn)),
|
||||
resumeIoService(resumeIoServiceIn)
|
||||
{}
|
||||
};
|
||||
|
||||
void UdpCommandDemuxer::settlePendingCommandWait(
|
||||
const std::shared_ptr<PendingCommandWaitDesc> &wait,
|
||||
UdpCommandResponseResult::Outcome outcome,
|
||||
const uint8_t *data, ssize_t bytesReceived)
|
||||
{
|
||||
if (wait->settled.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
wait->result.outcome = outcome;
|
||||
wait->result.bytesReceived = bytesReceived;
|
||||
|
||||
if (outcome == UdpCommandResponseResult::Outcome::Response
|
||||
&& data != nullptr
|
||||
&& bytesReceived > 0
|
||||
&& bytesReceived
|
||||
<= static_cast<ssize_t>(sizeof(wait->result.buffer)))
|
||||
{
|
||||
memcpy(wait->result.buffer, data, bytesReceived);
|
||||
}
|
||||
|
||||
std::coroutine_handle<> handle = wait->callerSchedHandle;
|
||||
if (!handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
boost::asio::post(wait->resumeIoService, handle);
|
||||
}
|
||||
|
||||
std::shared_ptr<UdpCommandDemuxer::PendingCommandWaitDesc>
|
||||
UdpCommandDemuxer::findAndRemovePendingCommandWait(const CommandWaitKey &key)
|
||||
{
|
||||
sscl::SpinLock::Guard guard(pendingWaits.lock);
|
||||
const auto iterator = pendingWaits.rsrc.pendingWaits.find(key);
|
||||
if (iterator == pendingWaits.rsrc.pendingWaits.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<PendingCommandWaitDesc> wait = iterator->second;
|
||||
pendingWaits.rsrc.pendingWaits.erase(iterator);
|
||||
return wait;
|
||||
}
|
||||
|
||||
void UdpCommandDemuxer::cancelPendingCommandWait(
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const std::string &deviceIp)
|
||||
{
|
||||
std::shared_ptr<PendingCommandWaitDesc> wait = findAndRemovePendingCommandWait(
|
||||
{deviceIp, cmdSet, cmdId});
|
||||
|
||||
if (!wait) { return; }
|
||||
|
||||
settlePendingCommandWait(
|
||||
wait,
|
||||
UdpCommandResponseResult::Outcome::Timeout,
|
||||
nullptr, -1);
|
||||
}
|
||||
|
||||
bool UdpCommandDemuxer::tryCompletePendingCommandWait(
|
||||
const char *sourceIp,
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const uint8_t *data, ssize_t bytesReceived)
|
||||
{
|
||||
std::shared_ptr<PendingCommandWaitDesc> wait = findAndRemovePendingCommandWait(
|
||||
{sourceIp, cmdSet, cmdId});
|
||||
|
||||
if (!wait) { return false; }
|
||||
|
||||
const UdpCommandResponseResult::Outcome outcome =
|
||||
(bytesReceived > 0
|
||||
&& bytesReceived
|
||||
<= static_cast<ssize_t>(sizeof(wait->result.buffer)))
|
||||
? UdpCommandResponseResult::Outcome::Response
|
||||
: UdpCommandResponseResult::Outcome::RecvError;
|
||||
|
||||
settlePendingCommandWait(wait, outcome, data, bytesReceived);
|
||||
return true;
|
||||
}
|
||||
|
||||
sscl::co::ViralNonPostingInvoker<UdpCommandResponseResult>
|
||||
UdpCommandDemuxer::waitForCommandResponseCReq(
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const std::string &deviceIp)
|
||||
{
|
||||
const CommandWaitKey key{deviceIp, cmdSet, cmdId};
|
||||
auto wait = std::make_shared<PendingCommandWaitDesc>(
|
||||
key, componentThread->getIoService());
|
||||
|
||||
{
|
||||
sscl::SpinLock::Guard guard(pendingWaits.lock);
|
||||
pendingWaits.rsrc.pendingWaits[key] = wait;
|
||||
}
|
||||
|
||||
struct PendingCommandWaitDescAwaiter
|
||||
{
|
||||
std::shared_ptr<PendingCommandWaitDesc> wait;
|
||||
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return wait->settled.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool await_suspend(std::coroutine_handle<> caller) noexcept
|
||||
{
|
||||
if (wait->settled.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wait->callerSchedHandle = caller;
|
||||
return true;
|
||||
}
|
||||
|
||||
UdpCommandResponseResult await_resume() const noexcept
|
||||
{
|
||||
return wait->result;
|
||||
}
|
||||
};
|
||||
|
||||
const UdpCommandResponseResult result =
|
||||
co_await PendingCommandWaitDescAwaiter{wait};
|
||||
|
||||
if (findAndRemovePendingCommandWait(key))
|
||||
{
|
||||
std::cerr << __func__ << ": pending wait still registered after "
|
||||
"settle for device " << deviceIp << " (cmd_set="
|
||||
<< static_cast<int>(cmdSet) << ", cmd_id="
|
||||
<< static_cast<int>(cmdId) << "); program error"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
co_return result;
|
||||
}
|
||||
|
||||
sscl::co::ViralNonPostingInvoker<UdpCommandResponseResult>
|
||||
UdpCommandDemuxer::waitForCommandResponseCReq(
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const std::string &deviceIp,
|
||||
int timeoutMs)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* We setup an async timer event to detect timeout, and register a UDP
|
||||
* command handler to wait for the device to respond to the incoming command
|
||||
* request. If the device does not respond within the timeout period,
|
||||
* we will consider the command to have failed.
|
||||
*/
|
||||
boost::asio::io_service &ioService = componentThread->getIoService();
|
||||
std::optional<std::shared_ptr<boost::asio::deadline_timer>> raceTimer;
|
||||
auto timerAwaiter = adapters::boostAsio::getDeadlineTimerAReqAwaiter(
|
||||
ioService,
|
||||
boost::posix_time::milliseconds(timeoutMs),
|
||||
raceTimer);
|
||||
auto responseInvoker = waitForCommandResponseCReq(cmdSet, cmdId, deviceIp);
|
||||
|
||||
static constexpr int timerMemberSettlementIndex = 0;
|
||||
|
||||
sscl::co::Group group;
|
||||
group.add(timerAwaiter);
|
||||
group.add(responseInvoker);
|
||||
|
||||
co_await group.getAwaitFirstSettlementInvoker();
|
||||
group.checkForAndReThrowGroupExceptions();
|
||||
|
||||
const bool timerWonFirst =
|
||||
group.s.rsrc.firstSettledInvokerIdx == timerMemberSettlementIndex;
|
||||
|
||||
if (timerWonFirst) {
|
||||
cancelPendingCommandWait(cmdSet, cmdId, deviceIp);
|
||||
} else if (raceTimer) {
|
||||
(*raceTimer)->cancel();
|
||||
}
|
||||
|
||||
/** Group member adapter coros are fire-and-forget; keep group alive until
|
||||
* both members settle so the loser adapter does not touch freed state.
|
||||
*/
|
||||
co_await group.getAwaitAllSettlementsInvoker();
|
||||
group.checkForAndReThrowGroupExceptions();
|
||||
|
||||
if (timerWonFirst)
|
||||
{
|
||||
UdpCommandResponseResult timeoutResult;
|
||||
timeoutResult.outcome = UdpCommandResponseResult::Outcome::Timeout;
|
||||
co_return timeoutResult;
|
||||
}
|
||||
|
||||
co_return responseInvoker.completedReturnValues().myReturnValue;
|
||||
}
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
|
||||
@@ -3,10 +3,16 @@
|
||||
|
||||
#include <boostAsioLinkageFix.h>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include <componentThread.h>
|
||||
#include <spinscale/spinLock.h>
|
||||
#include <spinscale/sharedResourceGroup.h>
|
||||
#include <spinscale/co/invokers.h>
|
||||
|
||||
namespace livoxProto1 {
|
||||
|
||||
@@ -15,6 +21,45 @@ class DeviceManager;
|
||||
|
||||
namespace comms {
|
||||
|
||||
struct UdpCommandResponseResult
|
||||
{
|
||||
enum class Outcome
|
||||
{
|
||||
Timeout,
|
||||
Response,
|
||||
RecvError
|
||||
};
|
||||
|
||||
Outcome outcome = Outcome::Timeout;
|
||||
uint8_t buffer[1024]{};
|
||||
ssize_t bytesReceived = -1;
|
||||
};
|
||||
|
||||
struct CommandWaitKey
|
||||
{
|
||||
std::string deviceIp;
|
||||
uint8_t cmdSet;
|
||||
uint8_t cmdId;
|
||||
|
||||
bool operator==(const CommandWaitKey &other) const
|
||||
{
|
||||
return deviceIp == other.deviceIp
|
||||
&& cmdSet == other.cmdSet
|
||||
&& cmdId == other.cmdId;
|
||||
}
|
||||
};
|
||||
|
||||
struct CommandWaitKeyHash
|
||||
{
|
||||
std::size_t operator()(const CommandWaitKey &key) const
|
||||
{
|
||||
std::size_t hash = std::hash<std::string>{}(key.deviceIp);
|
||||
hash ^= (static_cast<std::size_t>(key.cmdSet) << 8)
|
||||
| static_cast<std::size_t>(key.cmdId);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* UdpCommandDemuxer - Routes UDP command datagrams to appropriate devices
|
||||
*
|
||||
@@ -62,13 +107,20 @@ public:
|
||||
return pcloudDataFdDesc;
|
||||
}
|
||||
|
||||
private:
|
||||
// Socket and async objects
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor> pcloudDataFdDesc;
|
||||
// Socket and async objects
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor> cmdEndpointFdDesc;
|
||||
sscl::co::ViralNonPostingInvoker<UdpCommandResponseResult>
|
||||
waitForCommandResponseCReq(
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const std::string &deviceIp,
|
||||
int timeoutMs);
|
||||
|
||||
private:
|
||||
struct PendingCommandWaitDesc;
|
||||
|
||||
sscl::co::ViralNonPostingInvoker<UdpCommandResponseResult>
|
||||
waitForCommandResponseCReq(
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const std::string &deviceIp);
|
||||
|
||||
void setupSockets();
|
||||
void setupCommandSocket();
|
||||
void setupPcloudDataSocket();
|
||||
@@ -76,6 +128,23 @@ private:
|
||||
void onDataReady(const boost::system::error_code& error);
|
||||
void processIncomingData();
|
||||
|
||||
bool tryCompletePendingCommandWait(
|
||||
const char *sourceIp,
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const uint8_t *data, ssize_t bytesReceived);
|
||||
|
||||
void cancelPendingCommandWait(
|
||||
uint8_t cmdSet, uint8_t cmdId,
|
||||
const std::string &deviceIp);
|
||||
|
||||
std::shared_ptr<PendingCommandWaitDesc> findAndRemovePendingCommandWait(
|
||||
const CommandWaitKey &key);
|
||||
|
||||
void settlePendingCommandWait(
|
||||
const std::shared_ptr<PendingCommandWaitDesc> &wait,
|
||||
UdpCommandResponseResult::Outcome outcome,
|
||||
const uint8_t *data, ssize_t bytesReceived);
|
||||
|
||||
std::shared_ptr<sscl::ComponentThread> componentThread;
|
||||
DeviceManager& deviceManager;
|
||||
uint16_t commandPort;
|
||||
@@ -86,7 +155,21 @@ private:
|
||||
std::atomic<bool> isActive{false};
|
||||
std::atomic<bool> shouldStop{false};
|
||||
|
||||
// Receive buffer
|
||||
struct PendingWaitsResources
|
||||
{
|
||||
std::unordered_map<
|
||||
CommandWaitKey,
|
||||
std::shared_ptr<PendingCommandWaitDesc>,
|
||||
CommandWaitKeyHash>
|
||||
pendingWaits;
|
||||
};
|
||||
|
||||
sscl::SharedResourceGroup<sscl::SpinLock, PendingWaitsResources>
|
||||
pendingWaits;
|
||||
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor> pcloudDataFdDesc;
|
||||
std::shared_ptr<boost::asio::posix::stream_descriptor> cmdEndpointFdDesc;
|
||||
|
||||
uint8_t receiveBuffer[1024];
|
||||
struct sockaddr_in senderAddr;
|
||||
socklen_t senderAddrLen;
|
||||
|
||||
Reference in New Issue
Block a user