2025-09-06 20:06:38 -04:00
|
|
|
#ifndef LIVOX_PROTO1_DEVICE_H
|
|
|
|
|
#define LIVOX_PROTO1_DEVICE_H
|
|
|
|
|
|
2025-11-03 22:18:45 -04:00
|
|
|
#include <boostAsioLinkageFix.h>
|
2025-09-06 20:06:38 -04:00
|
|
|
#include <string>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <atomic>
|
|
|
|
|
#include <optional>
|
2025-09-09 12:07:49 -04:00
|
|
|
#include <functional>
|
2025-10-22 07:28:00 -04:00
|
|
|
#include <unordered_map>
|
2025-09-07 07:27:14 -04:00
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <netinet/in.h>
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
#include <unistd.h>
|
2025-10-16 01:00:48 -04:00
|
|
|
#include <boost/asio/deadline_timer.hpp>
|
2025-10-22 00:54:28 -04:00
|
|
|
#include <boost/asio/posix/stream_descriptor.hpp>
|
2025-09-06 20:06:38 -04:00
|
|
|
#include "protocol.h"
|
2025-09-27 18:30:09 -04:00
|
|
|
#include <callback.h>
|
2025-09-06 20:06:38 -04:00
|
|
|
|
2025-10-22 07:28:00 -04:00
|
|
|
// Custom hash function for std::pair<uint8_t, uint8_t>
|
|
|
|
|
namespace std {
|
|
|
|
|
template<>
|
|
|
|
|
struct hash<std::pair<uint8_t, uint8_t>> {
|
|
|
|
|
size_t operator()(const std::pair<uint8_t, uint8_t>& p) const noexcept {
|
|
|
|
|
return (static_cast<size_t>(p.first) << 8) | static_cast<size_t>(p.second);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 20:06:38 -04:00
|
|
|
// Forward declaration
|
|
|
|
|
namespace smo {
|
|
|
|
|
class ComponentThread;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace livoxProto1 {
|
|
|
|
|
namespace comms {
|
|
|
|
|
|
|
|
|
|
/** EXPLANATION:
|
|
|
|
|
* This class represents a discovered device. It is used to store the
|
|
|
|
|
* device identifier and IP address of a discovered device.
|
|
|
|
|
*/
|
|
|
|
|
class DiscoveredDevice
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
DiscoveredDevice(
|
|
|
|
|
const std::string &deviceIdentifier,
|
|
|
|
|
DeviceType deviceType,
|
|
|
|
|
const std::string &ipAddr);
|
|
|
|
|
|
|
|
|
|
// "Conversion" constructor from BroadcastMessage
|
|
|
|
|
DiscoveredDevice(const BroadcastMessage &msg, const std::string &ipAddr);
|
|
|
|
|
|
|
|
|
|
~DiscoveredDevice() = default;
|
|
|
|
|
|
|
|
|
|
bool operator==(const DiscoveredDevice &other) const
|
|
|
|
|
{
|
|
|
|
|
return comms::deviceIdentifiersEqual(
|
|
|
|
|
deviceIdentifier, other.deviceIdentifier);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string stringify(void) const;
|
|
|
|
|
std::string getDeviceTypeName(void) const;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
std::string deviceIdentifier;
|
|
|
|
|
DeviceType deviceType;
|
|
|
|
|
std::string ipAddr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace comms
|
|
|
|
|
|
|
|
|
|
class Device
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
Device(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,
|
|
|
|
|
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
|
|
|
|
|
~Device();
|
|
|
|
|
|
|
|
|
|
private:
|
2025-09-09 12:07:49 -04:00
|
|
|
// Heartbeat mechanism
|
|
|
|
|
void startHeartbeat();
|
2025-10-23 00:24:23 -04:00
|
|
|
void stopHeartbeat();
|
2025-09-09 12:07:49 -04:00
|
|
|
void sendHeartbeat();
|
|
|
|
|
void onHeartbeatTimer(const boost::system::error_code& error);
|
2025-09-06 20:06:38 -04:00
|
|
|
std::string generateClientDeviceIpFromSerialNumber(
|
|
|
|
|
const std::string& broadcastCode);
|
|
|
|
|
|
|
|
|
|
// IP detection methods
|
2025-09-06 20:44:28 -04:00
|
|
|
std::optional<std::string> detectSmoIp(const std::string& deviceIP);
|
2025-09-06 20:06:38 -04:00
|
|
|
uint32_t getSubnetMaskFor(uint8_t nbits);
|
|
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
class ConnectReq;
|
|
|
|
|
class ConnectToKnownDeviceReq;
|
|
|
|
|
class ConnectByDeviceIdentifierReq;
|
|
|
|
|
class ExecuteHandshakeReq;
|
|
|
|
|
class DisconnectReq;
|
2025-10-22 00:54:28 -04:00
|
|
|
class EnablePcloudDataReq;
|
|
|
|
|
class DisablePcloudDataReq;
|
2025-10-25 00:19:06 -04:00
|
|
|
class SetReturnModeReq;
|
|
|
|
|
class GetReturnModeReq;
|
2025-09-09 12:07:49 -04:00
|
|
|
|
|
|
|
|
public:
|
2025-10-30 11:49:54 -04:00
|
|
|
enum class ReturnMode : uint8_t
|
|
|
|
|
{
|
|
|
|
|
SingleFirst = 0x00,
|
|
|
|
|
SingleStrongest = 0x01,
|
|
|
|
|
Dual = 0x02,
|
|
|
|
|
Triple = 0x03
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-09 12:07:49 -04:00
|
|
|
// Utility methods
|
2025-09-09 19:54:14 -04:00
|
|
|
std::optional<std::string> getSmoIp(const std::string& deviceIP);
|
2025-09-09 12:07:49 -04:00
|
|
|
|
|
|
|
|
// Callback function type definitions for async methods
|
|
|
|
|
typedef std::function<void(bool success)> connectReqCbFn;
|
|
|
|
|
typedef std::function<
|
2025-10-23 00:24:23 -04:00
|
|
|
void(bool success, const std::string& ipAddr)>
|
2025-09-09 12:07:49 -04:00
|
|
|
connectToKnownDeviceReqCbFn;
|
|
|
|
|
typedef std::function<
|
2025-10-23 00:24:23 -04:00
|
|
|
void(bool success, const std::string& ipAddr)>
|
2025-09-09 12:07:49 -04:00
|
|
|
connectByDeviceIdentifierReqCbFn;
|
2025-10-23 00:24:23 -04:00
|
|
|
typedef std::function<void(bool success)> executeHandshakeReqCbFn;
|
2025-09-09 12:07:49 -04:00
|
|
|
typedef std::function<void(bool success)> disconnectReqCbFn;
|
2025-10-22 00:54:28 -04:00
|
|
|
typedef std::function<void(bool success)> enablePcloudDataReqCbFn;
|
|
|
|
|
typedef std::function<void(bool success)> disablePcloudDataReqCbFn;
|
2025-10-25 00:19:06 -04:00
|
|
|
typedef std::function<void(bool success)> setReturnModeReqCbFn;
|
|
|
|
|
typedef std::function<void(bool success, uint8_t returnMode)>
|
|
|
|
|
getReturnModeReqCbFn;
|
2025-09-09 12:07:49 -04:00
|
|
|
|
|
|
|
|
// Async connection methods
|
2025-09-27 18:30:09 -04:00
|
|
|
void connectReq(smo::Callback<connectReqCbFn> callback);
|
|
|
|
|
void connectToKnownDeviceReq(
|
|
|
|
|
smo::Callback<connectToKnownDeviceReqCbFn> callback);
|
2025-09-09 12:07:49 -04:00
|
|
|
void connectByDeviceIdentifierReq(
|
2025-09-27 18:30:09 -04:00
|
|
|
smo::Callback<connectByDeviceIdentifierReqCbFn> callback);
|
2025-09-09 12:07:49 -04:00
|
|
|
void executeHandshakeReq(
|
2025-09-27 18:30:09 -04:00
|
|
|
const std::string& deviceIP,
|
|
|
|
|
smo::Callback<executeHandshakeReqCbFn> callback);
|
|
|
|
|
void disconnectReq(smo::Callback<disconnectReqCbFn> callback);
|
2025-10-22 00:54:28 -04:00
|
|
|
void enablePcloudDataReq(smo::Callback<enablePcloudDataReqCbFn> callback);
|
|
|
|
|
void disablePcloudDataReq(smo::Callback<disablePcloudDataReqCbFn> callback);
|
2025-10-25 00:19:06 -04:00
|
|
|
void setReturnModeReq(
|
|
|
|
|
uint8_t returnMode, smo::Callback<setReturnModeReqCbFn> callback);
|
|
|
|
|
void getReturnModeReq(smo::Callback<getReturnModeReqCbFn> callback);
|
2025-09-06 20:06:38 -04:00
|
|
|
|
2025-10-25 14:43:51 -04:00
|
|
|
public:
|
|
|
|
|
comms::DiscoveredDevice discoveredDevice;
|
2025-10-25 23:03:00 -04:00
|
|
|
std::atomic<size_t> nAttachedStimBuffs;
|
2025-10-25 14:43:51 -04:00
|
|
|
|
|
|
|
|
// Configuration
|
|
|
|
|
std::shared_ptr<smo::ComponentThread> componentThread;
|
2025-11-01 02:45:24 -04:00
|
|
|
int commandTimeoutMs, retryDelayMs;
|
2025-10-25 14:43:51 -04:00
|
|
|
std::string smoIp;
|
|
|
|
|
std::string detectedSmoListeningIp;
|
|
|
|
|
uint8_t smoSubnetNbits;
|
|
|
|
|
uint16_t dataPort, cmdPort, imuPort;
|
|
|
|
|
|
2025-09-06 20:06:38 -04:00
|
|
|
// Heartbeat state
|
|
|
|
|
std::unique_ptr<boost::asio::deadline_timer> heartbeatTimer;
|
|
|
|
|
std::atomic<bool> heartbeatActive;
|
2025-10-22 00:54:28 -04:00
|
|
|
|
|
|
|
|
// Point cloud data state
|
|
|
|
|
std::atomic<bool> pcloudDataActive;
|
|
|
|
|
|
2025-10-30 11:49:54 -04:00
|
|
|
// Cached last-known return mode for this device
|
|
|
|
|
ReturnMode currentReturnMode = ReturnMode::SingleFirst;
|
|
|
|
|
|
2025-10-22 06:17:42 -04:00
|
|
|
public:
|
|
|
|
|
// UDP datagram handling
|
|
|
|
|
void handleUdpDgram(
|
|
|
|
|
const uint8_t* data, ssize_t bytesReceived,
|
|
|
|
|
const struct sockaddr_in& senderAddr);
|
|
|
|
|
|
2025-10-22 07:28:00 -04:00
|
|
|
// Command handler registration
|
|
|
|
|
void registerUdpCommandHandler(
|
|
|
|
|
uint8_t cmd_set, uint8_t cmd_id,
|
|
|
|
|
std::function<void(
|
|
|
|
|
const uint8_t* data, ssize_t bytesReceived,
|
2025-10-22 22:13:38 -04:00
|
|
|
const struct sockaddr_in& senderAddr)> handler,
|
|
|
|
|
const std::string& deviceIP = "");
|
|
|
|
|
|
|
|
|
|
void unregisterUdpCommandHandler(
|
|
|
|
|
uint8_t cmd_set, uint8_t cmd_id, const std::string& deviceIP = "");
|
2025-10-22 07:28:00 -04:00
|
|
|
|
2025-10-22 00:54:28 -04:00
|
|
|
private:
|
|
|
|
|
// Point cloud data setup
|
|
|
|
|
void cleanupPcloudDataSocket();
|
2025-10-22 07:28:00 -04:00
|
|
|
|
2025-10-24 03:09:17 -04:00
|
|
|
/** EXPLANATION:
|
|
|
|
|
* This is the "straightforward" map of command set and command id to
|
|
|
|
|
* handlers. This is useful for any commands which are guaranteed to be
|
|
|
|
|
* issued to the device *AFTER* the device has successfully been added
|
|
|
|
|
* to the DeviceManager's list of devices.
|
|
|
|
|
*
|
|
|
|
|
* I.e: it cannot be used for commands which are issued to the device before
|
|
|
|
|
* getOrCreateDevice() has added the device to the DeviceManager's list of
|
|
|
|
|
* devices.
|
|
|
|
|
*/
|
2025-10-22 07:28:00 -04:00
|
|
|
// Command handler map
|
|
|
|
|
std::unordered_map<
|
|
|
|
|
std::pair<uint8_t, uint8_t>,
|
|
|
|
|
std::function<void(
|
|
|
|
|
const uint8_t* data, ssize_t bytesReceived,
|
|
|
|
|
const struct sockaddr_in& senderAddr)>> udpCommandHandlers;
|
2025-10-24 03:09:17 -04:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
/** EXPLANATION:
|
|
|
|
|
* This is the "temporary" map of command set and command id to
|
|
|
|
|
* handlers. This is useful for any commands which are issued to the device
|
|
|
|
|
* while it is being constructed.
|
|
|
|
|
*
|
|
|
|
|
* I.e: it shouldn't be used for cmds which are issued to the device after
|
|
|
|
|
* getOrCreateDevice() has added the device to the DeviceManager's list of
|
|
|
|
|
* devices. It will work for such commands, but we'd kind of prefer to use
|
|
|
|
|
* the "straightforward" map above for such commands.
|
|
|
|
|
*
|
|
|
|
|
* NOTE:
|
|
|
|
|
* There's a strong argument to be made for just getting rid of the
|
|
|
|
|
* "straightforward" map above and just using this one, tho.
|
|
|
|
|
*/
|
|
|
|
|
struct CommandHandler {
|
|
|
|
|
uint8_t cmd_set;
|
|
|
|
|
uint8_t cmd_id;
|
|
|
|
|
std::function<void(
|
|
|
|
|
const uint8_t* data, ssize_t bytesReceived,
|
|
|
|
|
const struct sockaddr_in& senderAddr)> handler;
|
|
|
|
|
};
|
|
|
|
|
static std::unordered_map<std::string, std::vector<CommandHandler>>
|
|
|
|
|
devicesUnderConstruction;
|
2025-09-06 20:06:38 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace livoxProto1
|
|
|
|
|
|
|
|
|
|
#endif // LIVOX_PROTO1_DEVICE_H
|