#ifndef LIVOX_PROTO1_DEVICE_H #define LIVOX_PROTO1_DEVICE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "protocol.h" #include #include // Custom hash function for std::pair namespace std { template<> struct hash> { size_t operator()(const std::pair& p) const noexcept { return (static_cast(p.first) << 8) | static_cast(p.second); } }; } // 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& componentThread, int commandTimeoutMs, int retryDelayMs, const std::string& smoIp, uint8_t smoSubnetNbits, uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort); ~Device(); private: // Heartbeat mechanism void startHeartbeat(); void stopHeartbeat(); void sendHeartbeat(); void onHeartbeatTimer(const boost::system::error_code& error); std::string generateClientDeviceIpFromSerialNumber( const std::string& broadcastCode); // IP detection methods std::optional 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 { SingleFirst = 0x00, SingleStrongest = 0x01, Dual = 0x02, Triple = 0x03 }; /** * Get the number of points per datagram based on return mode * @param returnMode The return mode (0=SingleFirst, 1=SingleStrongest, 2=Dual, 3=Triple) * @return Number of points per datagram */ static inline size_t getNPointsPerDgram(int returnMode) { /* * Map modes to points per datagram based on Livox docs * 1: first, 2: strongest -> 96 samples => 96 points * 3: dual -> 48 samples * 2 points = 96 * 4: triple -> 30 samples * 3 points = 90 */ switch (returnMode) { case static_cast(ReturnMode::SingleFirst): case static_cast(ReturnMode::SingleStrongest): case static_cast(ReturnMode::Dual): return 96u; case static_cast(ReturnMode::Triple): return 90u; default: throw std::runtime_error( std::string(__func__) + ": Unknown returnMode " + std::to_string(returnMode)); } } // Utility methods std::optional getSmoIp(const std::string& deviceIP); // Callback function type definitions for async methods typedef std::function 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 executeHandshakeReqCbFn; typedef std::function disconnectReqCbFn; typedef std::function enablePcloudDataReqCbFn; typedef std::function disablePcloudDataReqCbFn; typedef std::function setReturnModeReqCbFn; typedef std::function getReturnModeReqCbFn; // Async connection methods void connectReq(smo::Callback callback); void connectToKnownDeviceReq( smo::Callback callback); void connectByDeviceIdentifierReq( smo::Callback callback); void executeHandshakeReq( const std::string& deviceIP, smo::Callback callback); void disconnectReq(smo::Callback callback); void enablePcloudDataReq(smo::Callback callback); void disablePcloudDataReq(smo::Callback callback); void setReturnModeReq( uint8_t returnMode, smo::Callback callback); void getReturnModeReq(smo::Callback callback); public: comms::DiscoveredDevice discoveredDevice; std::atomic nAttachedStimulusProducers; // Configuration std::shared_ptr componentThread; int commandTimeoutMs, retryDelayMs; std::string smoIp; std::string detectedSmoListeningIp; uint8_t smoSubnetNbits; uint16_t dataPort, cmdPort, imuPort; // Heartbeat state std::unique_ptr heartbeatTimer; std::atomic heartbeatActive; smo::SpinLock heartbeatActiveLock; // Point cloud data state std::atomic pcloudDataActive; // Cached last-known return mode for this device ReturnMode currentReturnMode = ReturnMode::SingleFirst; public: // UDP datagram handling void handleUdpDgram( const uint8_t* data, ssize_t bytesReceived, const struct sockaddr_in& senderAddr); // Command handler registration void registerUdpCommandHandler( uint8_t cmd_set, uint8_t cmd_id, std::function handler, const std::string& deviceIP = ""); void unregisterUdpCommandHandler( uint8_t cmd_set, uint8_t cmd_id, const std::string& deviceIP = ""); private: // Point cloud data setup void cleanupPcloudDataSocket(); /** 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. */ // Command handler map std::unordered_map< std::pair, std::function> udpCommandHandlers; 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 handler; }; static std::unordered_map> devicesUnderConstruction; }; } // namespace livoxProto1 #endif // LIVOX_PROTO1_DEVICE_H