2025-09-06 20:06:38 -04:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <iostream>
|
2025-09-09 12:07:49 -04:00
|
|
|
#include <functional>
|
|
|
|
|
#include <optional>
|
|
|
|
|
#include <opts.h>
|
2025-09-10 15:10:10 -04:00
|
|
|
#include <asynchronousContinuation.h>
|
2025-09-27 18:30:09 -04:00
|
|
|
#include <callback.h>
|
2025-09-06 20:06:38 -04:00
|
|
|
#include <user/senseApiDesc.h>
|
|
|
|
|
#include "protocol.h"
|
|
|
|
|
#include "core.h"
|
2025-09-09 12:07:49 -04:00
|
|
|
#include "device.h"
|
|
|
|
|
#include "broadcastListener.h"
|
|
|
|
|
#include "livoxProto1.h"
|
|
|
|
|
|
2025-09-06 20:06:38 -04:00
|
|
|
|
|
|
|
|
namespace livoxProto1 {
|
|
|
|
|
|
|
|
|
|
static ProtoState protoState =
|
|
|
|
|
{
|
|
|
|
|
.isInitialized = false,
|
|
|
|
|
.componentThread = nullptr,
|
2025-09-09 12:07:49 -04:00
|
|
|
.deviceManager = nullptr,
|
|
|
|
|
.smoCallbacks = {}
|
2025-09-06 20:06:38 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ProtoState& getProtoState()
|
|
|
|
|
{
|
|
|
|
|
return protoState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeviceManager::DeviceManager()
|
2025-10-22 06:17:42 -04:00
|
|
|
: broadcastListener(protoState.componentThread),
|
|
|
|
|
udpCommandDemuxer(protoState.componentThread, *this)
|
2025-09-06 20:06:38 -04:00
|
|
|
{
|
|
|
|
|
broadcastListener.setDeviceGoneAwayCb(deviceGoneAwayInd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DeviceManager::deviceGoneAwayInd(const comms::DiscoveredDevice &device)
|
|
|
|
|
{
|
|
|
|
|
std::cout << "Device gone away: " << device.stringify() << std::endl;
|
|
|
|
|
|
|
|
|
|
// Check if device exists in our collection
|
|
|
|
|
if (!protoState.deviceManager->getDevice(device)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find and remove the device from the collection
|
|
|
|
|
auto it = std::find_if(
|
|
|
|
|
protoState.deviceManager->devices.begin(),
|
|
|
|
|
protoState.deviceManager->devices.end(),
|
|
|
|
|
[&device](const std::shared_ptr<Device> &d) {
|
|
|
|
|
return d->discoveredDevice == device;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
if (it != protoState.deviceManager->devices.end()) {
|
|
|
|
|
protoState.deviceManager->devices.erase(it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
std::optional<std::shared_ptr<Device>> DeviceManager::getDevice(
|
|
|
|
|
const std::string &deviceIdentifier
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
for (auto& device : devices)
|
|
|
|
|
{
|
|
|
|
|
if (comms::deviceIdentifiersEqual(
|
|
|
|
|
device->discoveredDevice.deviceIdentifier, deviceIdentifier))
|
|
|
|
|
{
|
|
|
|
|
return device;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetOrCreateDeviceReq nested class implementation
|
|
|
|
|
class DeviceManager::GetOrCreateDeviceReq
|
2025-09-17 16:32:20 -04:00
|
|
|
: public smo::NonPostedAsynchronousContinuation<
|
|
|
|
|
livoxProto1_getOrCreateDeviceReqCbFn>
|
2025-09-09 12:07:49 -04:00
|
|
|
{
|
|
|
|
|
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,
|
2025-09-27 18:30:09 -04:00
|
|
|
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> cb)
|
2025-09-17 16:32:20 -04:00
|
|
|
: smo::NonPostedAsynchronousContinuation<
|
2025-09-11 18:37:48 -04:00
|
|
|
livoxProto1_getOrCreateDeviceReqCbFn>(std::move(cb)),
|
2025-09-09 12:07:49 -04:00
|
|
|
deviceManager(mgr), pendingDevice(device)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
// Public accessor for the original callback
|
|
|
|
|
void callOriginalCallback(bool success, std::shared_ptr<Device> device)
|
2025-09-17 16:32:20 -04:00
|
|
|
{ callOriginalCb(success, device); }
|
2025-09-09 12:07:49 -04:00
|
|
|
|
|
|
|
|
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);
|
2025-11-07 14:59:28 -04:00
|
|
|
if (getProtoState().smoCallbacks.OptionParser_getOptions().verbose)
|
2025-09-09 12:07:49 -04:00
|
|
|
{
|
|
|
|
|
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(
|
2025-09-06 20:06:38 -04:00
|
|
|
const std::string &deviceIdentifier,
|
|
|
|
|
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
2025-11-01 02:45:24 -04:00
|
|
|
int commandTimeoutMs, int retryDelayMs,
|
2025-09-06 20:06:38 -04:00
|
|
|
const std::string& smoIp, uint8_t smoSubnetNbits,
|
2025-09-09 12:07:49 -04:00
|
|
|
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
2025-09-27 18:30:09 -04:00
|
|
|
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback)
|
2025-09-06 20:06:38 -04:00
|
|
|
{
|
|
|
|
|
// Validate smoIp format using Boost.Asio IPv4 validation
|
|
|
|
|
if (!smoIp.empty() && !comms::isValidIPv4(smoIp))
|
|
|
|
|
{
|
|
|
|
|
throw std::invalid_argument(
|
|
|
|
|
std::string(__func__) +
|
|
|
|
|
": Invalid IPv4 smoIp format: " + smoIp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate subnet nbits
|
|
|
|
|
if (smoSubnetNbits > 32)
|
|
|
|
|
{
|
|
|
|
|
throw std::invalid_argument(
|
|
|
|
|
std::string(__func__) +
|
|
|
|
|
": smoSubnetNbits must be between 0 and 32, got: " +
|
|
|
|
|
std::to_string(smoSubnetNbits));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First try to get existing device
|
|
|
|
|
auto existingDevice = getDevice(deviceIdentifier);
|
2025-09-09 12:07:49 -04:00
|
|
|
if (existingDevice)
|
|
|
|
|
{
|
|
|
|
|
// Device already exists and is connected, return it
|
2025-09-27 18:30:09 -04:00
|
|
|
callback.callbackFn(true, existingDevice.value());
|
2025-09-09 12:07:49 -04:00
|
|
|
return;
|
2025-09-06 20:06:38 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
// Device doesn't exist, create a new one but don't add it to collection yet
|
2025-09-06 20:06:38 -04:00
|
|
|
auto newDevice = std::make_shared<Device>(
|
|
|
|
|
deviceIdentifier, componentThread,
|
2025-11-01 02:45:24 -04:00
|
|
|
commandTimeoutMs, retryDelayMs,
|
2025-09-06 20:06:38 -04:00
|
|
|
smoIp, smoSubnetNbits,
|
|
|
|
|
dataPort, cmdPort, imuPort);
|
|
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
// 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(
|
2025-09-27 18:30:09 -04:00
|
|
|
{request, std::bind(
|
2025-09-09 12:07:49 -04:00
|
|
|
&DeviceManager::GetOrCreateDeviceReq::getOrCreateDeviceReq1,
|
2025-09-27 18:30:09 -04:00
|
|
|
request.get(), request, std::placeholders::_1)});
|
2025-09-06 20:06:38 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
class DeviceManager::DestroyDeviceReq
|
2025-09-17 16:32:20 -04:00
|
|
|
: public smo::NonPostedAsynchronousContinuation<
|
|
|
|
|
livoxProto1_destroyDeviceReqCbFn>
|
2025-09-06 20:06:38 -04:00
|
|
|
{
|
2025-09-11 18:37:48 -04:00
|
|
|
public:
|
2025-09-09 12:07:49 -04:00
|
|
|
DeviceManager& deviceManager;
|
|
|
|
|
std::shared_ptr<Device> pendingDevice;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
DestroyDeviceReq(
|
|
|
|
|
DeviceManager& mgr,
|
|
|
|
|
std::shared_ptr<Device> device,
|
2025-09-27 18:30:09 -04:00
|
|
|
smo::Callback<livoxProto1_destroyDeviceReqCbFn> cb)
|
2025-09-17 16:32:20 -04:00
|
|
|
: smo::NonPostedAsynchronousContinuation<
|
2025-09-11 18:37:48 -04:00
|
|
|
livoxProto1_destroyDeviceReqCbFn>(std::move(cb)),
|
2025-09-09 12:07:49 -04:00
|
|
|
deviceManager(mgr), pendingDevice(device)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
// Public accessor for the original callback
|
|
|
|
|
void callOriginalCallback(bool success)
|
2025-09-17 16:32:20 -04:00
|
|
|
{ callOriginalCb(success); }
|
2025-09-09 12:07:49 -04:00
|
|
|
|
|
|
|
|
void callOriginalCallbackWithFailure()
|
|
|
|
|
{ callOriginalCallback(false); }
|
|
|
|
|
|
|
|
|
|
void destroyDeviceReq1(
|
|
|
|
|
std::shared_ptr<DestroyDeviceReq> context, bool success
|
|
|
|
|
)
|
2025-09-06 20:06:38 -04:00
|
|
|
{
|
2025-09-09 12:07:49 -04:00
|
|
|
context->deviceManager.devices.erase(
|
|
|
|
|
std::remove(
|
|
|
|
|
context->deviceManager.devices.begin(),
|
|
|
|
|
context->deviceManager.devices.end(),
|
|
|
|
|
context->pendingDevice),
|
|
|
|
|
context->deviceManager.devices.end());
|
|
|
|
|
|
|
|
|
|
context->callOriginalCallback(success);
|
2025-09-06 20:06:38 -04:00
|
|
|
}
|
2025-09-09 12:07:49 -04:00
|
|
|
};
|
2025-09-06 20:06:38 -04:00
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
void DeviceManager::destroyDeviceReq(
|
|
|
|
|
std::shared_ptr<Device> dev,
|
2025-09-27 18:30:09 -04:00
|
|
|
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback
|
2025-09-09 12:07:49 -04:00
|
|
|
)
|
2025-09-06 20:06:38 -04:00
|
|
|
{
|
2025-09-09 12:07:49 -04:00
|
|
|
/** EXPLANATION:
|
|
|
|
|
* Check to see if the device is in our collection. If so, call
|
|
|
|
|
* disconnectReq and then remove it.
|
|
|
|
|
*/
|
|
|
|
|
std::shared_ptr<Device> device = getDevice(dev->discoveredDevice).
|
|
|
|
|
value_or(nullptr);
|
|
|
|
|
|
2025-11-15 00:56:20 -04:00
|
|
|
if (!device || device->nAttachedStimulusProducers > 0)
|
2025-09-09 12:07:49 -04:00
|
|
|
{
|
2025-09-27 18:30:09 -04:00
|
|
|
callback.callbackFn(false);
|
2025-09-09 12:07:49 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto request = std::make_shared<DestroyDeviceReq>(
|
|
|
|
|
*this, device, std::move(callback));
|
|
|
|
|
|
|
|
|
|
device->disconnectReq(
|
2025-09-27 18:30:09 -04:00
|
|
|
{request, std::bind(
|
2025-09-09 12:07:49 -04:00
|
|
|
&DeviceManager::DestroyDeviceReq::destroyDeviceReq1,
|
2025-09-27 18:30:09 -04:00
|
|
|
request.get(), request, std::placeholders::_1)});
|
2025-09-06 20:06:38 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
void main(const std::shared_ptr<smo::ComponentThread> &componentThread,
|
2025-10-01 18:47:42 -04:00
|
|
|
const smo::stim_buff::SmoCallbacks& smoCallbacks)
|
2025-09-06 20:06:38 -04:00
|
|
|
{
|
|
|
|
|
if (protoState.isInitialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protoState.isInitialized = true;
|
|
|
|
|
protoState.componentThread = componentThread;
|
2025-09-09 12:07:49 -04:00
|
|
|
protoState.smoCallbacks = smoCallbacks;
|
2025-09-06 20:06:38 -04:00
|
|
|
protoState.deviceManager = std::make_unique<DeviceManager>();
|
|
|
|
|
protoState.deviceManager->broadcastListener.start();
|
2025-10-22 07:28:00 -04:00
|
|
|
protoState.deviceManager->udpCommandDemuxer.start();
|
2025-09-06 20:06:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void exit(void)
|
|
|
|
|
{
|
|
|
|
|
if (!protoState.isInitialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 07:28:00 -04:00
|
|
|
protoState.deviceManager->udpCommandDemuxer.stop();
|
2025-09-06 20:06:38 -04:00
|
|
|
protoState.deviceManager->broadcastListener.stop();
|
|
|
|
|
protoState.deviceManager.reset();
|
|
|
|
|
protoState.componentThread.reset();
|
|
|
|
|
protoState.isInitialized = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 07:28:00 -04:00
|
|
|
} // namespace livoxProto1
|