LivoxProto1: port to sscl::co framework

Code now actually looks a lot cleaner, tbh.
This commit is contained in:
2026-05-28 20:13:12 -04:00
parent bbc16dc4c4
commit 25efccf6c5
20 changed files with 1275 additions and 2145 deletions
+9 -2
View File
@@ -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
View File
@@ -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,
+6 -11
View File
@@ -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(
File diff suppressed because it is too large Load Diff
+23 -40
View File
@@ -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;
+24 -30
View File
@@ -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>
+35 -34
View File
@@ -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
+89 -6
View File
@@ -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;