livoxProto1: Keep protocol headers pure;

Split the BroadcastListener, DiscoveredDevice and other concerns
out of the protocol header and implementation files.
This commit is contained in:
2025-09-06 08:50:07 -04:00
parent 5e4597b8fd
commit 3e9eecc279
8 changed files with 350 additions and 284 deletions
+1
View File
@@ -6,6 +6,7 @@ if(ENABLE_LIB_livoxProto1)
livoxProto1Core.cpp
livoxProto1Device.cpp
livoxProto1Protocol.cpp
broadcastListener.cpp
)
# Set config define for header generation
@@ -0,0 +1,157 @@
#include <algorithm>
#include <iostream>
#include "broadcastListener.h"
namespace livoxProto1 {
namespace comms {
BroadcastListener::BroadcastListener(
const std::shared_ptr<smo::ComponentThread>& componentThread,
uint16_t listeningPort, uint16_t connectPort
)
: componentThread(componentThread),
listeningPort(listeningPort),
connectPort(connectPort),
deviceGoneAwayCb(nullptr),
socket(componentThread->getIoService()),
listeningEndpoint(boost::asio::ip::udp::v4(), listeningPort),
isListening(false)
{
}
std::shared_ptr<DiscoveredDevice>
BroadcastListener::getDevice(const std::string &deviceIdentifier) const
{
auto it = std::find_if(discoveredDevices.begin(), discoveredDevices.end(),
[&deviceIdentifier](const std::shared_ptr<DiscoveredDevice>& device) {
return device->deviceIdentifier == deviceIdentifier;
}
);
return it != discoveredDevices.end() ? *it : nullptr;
}
void BroadcastListener::broadcastMsgInd(
const boost::system::error_code& ec, std::size_t bytes_received)
{
if (ec)
{
std::cerr << __func__ << ": Error receiving broadcast message: "
<< ec.message() << std::endl;
return;
}
if (bytes_received < sizeof(BroadcastMessage))
{
std::cerr << "Received packet too small: " << bytes_received
<< " bytes (expected at least " << sizeof(BroadcastMessage) << ")"
<< std::endl;
return;
}
// Use placement new to construct BroadcastMessage in the buffer
BroadcastMessage* msg = new (bcastMsgRecvBuffer) BroadcastMessage;
// Validate the message using sanity check methods
if (!msg->sanityCheck())
{
std::cerr << "Broadcast message failed sanity check" << std::endl;
return;
}
// Convert from little-endian to host endianness
msg->swapToHostEndianness();
// Extract device information
std::string senderIP = senderEndpoint.address().to_string();
std::string broadcastCode(reinterpret_cast<const char*>(msg->broadcast_code));
// Early return if device already exists
if (deviceExists(broadcastCode))
{
// Device already exists, just log the update
std::cout << "Received broadcast from known device: " << broadcastCode
<< " at " << senderIP << std::endl;
return;
}
// Create new DiscoveredDevice using conversion constructor
auto device = std::make_shared<DiscoveredDevice>(*msg, senderIP);
discoveredDevices.push_back(device);
// Output device information using stringify
std::cout << "Discovered new Livox device: " << device->stringify()
<< std::endl;
}
void BroadcastListener::start(void)
{
if (isListening.load()) { return; }
try
{
/** EXPLANATION:
* Set up a boost::asio udp listening socket on the broadcast listening
* port.
*
* FIXME:
* We should also set up a timer to check for devices that have gone
* away.
*/
socket.open(boost::asio::ip::udp::v4());
socket.bind(listeningEndpoint);
isListening.store(true);
// Start the first async receive operation
startReceive();
std::cout << "BroadcastListener started on port " << listeningPort
<< std::endl;
}
catch (const boost::system::system_error& e)
{
isListening.store(false);
std::cerr << "Failed to start BroadcastListener: " << e.what()
<< std::endl;
throw;
}
}
void BroadcastListener::startReceive(void)
{
if (!isListening.load()) { return; }
socket.async_receive_from(
boost::asio::buffer(bcastMsgRecvBuffer, sizeof(bcastMsgRecvBuffer)),
senderEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytes_received)
{
broadcastMsgInd(ec, bytes_received);
// Continue listening for the next packet
if (isListening.load())
{ startReceive(); }
}
);
}
void BroadcastListener::stop(void)
{
if (!isListening.load()) { return; }
isListening.store(false);
try
{
socket.close();
std::cout << "BroadcastListener stopped" << std::endl;
}
catch (const boost::system::system_error& e)
{
std::cerr << "Error stopping BroadcastListener: " << e.what()
<< std::endl;
throw;
}
}
} // namespace comms
} // namespace livoxProto1
@@ -0,0 +1,76 @@
#ifndef BROADCAST_LISTENER_H
#define BROADCAST_LISTENER_H
#include <vector>
#include <string>
#include <memory>
#include <atomic>
#include <user/senseApiDesc.h>
#include "livoxProto1Device.h"
namespace livoxProto1 {
namespace comms {
/** EXPLANATION:
* This class merely listens for UDP bcast dgrams on the designated listening
* port. It then builds a list of client device IP addrs that it has heard from.
* It doesn't connect to them or signal any events to the rest of the lib,
* except in the case that a device which the lib is using has gone away.
*
* Other than that, its role is to tell the lib which devices are available
* on the network.
*/
#define UDP_BCAST_MSG_BUFFER_NBYTES (1024)
class BroadcastListener
{
public:
BroadcastListener(
const std::shared_ptr<smo::ComponentThread>& componentThread,
uint16_t listeningPort=55000, uint16_t connectPort=65000);
~BroadcastListener() = default;
typedef void (DeviceGoneAwayCbFn)(const DiscoveredDevice &device);
void setDeviceGoneAwayCb(DeviceGoneAwayCbFn *cb)
{ deviceGoneAwayCb = cb; }
bool deviceExists(const std::string &deviceIdentifier) const
{ return getDevice(deviceIdentifier) != nullptr; }
std::shared_ptr<DiscoveredDevice>
getDevice(const std::string &deviceIdentifier) const;
void start(void);
void stop(void);
void broadcastMsgInd(
const boost::system::error_code& ec, std::size_t bytes_received);
private:
void startReceive(void);
private:
std::shared_ptr<smo::ComponentThread> componentThread;
/** EXPLANATION:
* The Livox proto says that client devices will spam broadcast UDP
* dgrams to us on the listening port. We can then use the source IP from
* the bcast dgram to figure out the client device's IP addr. Then we
* should send a connect dgram to the connect port. This will tell the
* client device our IP addr.
*/
uint16_t listeningPort, connectPort;
DeviceGoneAwayCbFn *deviceGoneAwayCb;
std::vector<std::shared_ptr<DiscoveredDevice>> discoveredDevices;
boost::asio::ip::udp::socket socket;
boost::asio::ip::udp::endpoint listeningEndpoint, senderEndpoint;
std::atomic<bool> isListening;
uint8_t bcastMsgRecvBuffer[UDP_BCAST_MSG_BUFFER_NBYTES];
};
} // namespace comms
} // namespace livoxProto1
#endif // BROADCAST_LISTENER_H
+2 -11
View File
@@ -4,20 +4,11 @@
#include <vector>
#include <string>
#include <memory>
#include "livoxProto1Protocol.h"
#include "livoxProto1Device.h"
#include "broadcastListener.h"
namespace livoxProto1 {
class Device
{
public:
Device(const comms::DiscoveredDevice &discoveredDevice);
~Device() = default;
public:
comms::DiscoveredDevice discoveredDevice;
};
class DeviceManager
{
public:
@@ -0,0 +1,61 @@
#include <sstream>
#include "livoxProto1Device.h"
namespace livoxProto1 {
namespace comms {
// DiscoveredDevice constructors
DiscoveredDevice::DiscoveredDevice(
const std::string &deviceIdentifier,
DeviceType deviceType,
const std::string &ipAddr)
: deviceIdentifier(deviceIdentifier),
deviceType(deviceType),
ipAddr(ipAddr)
{
}
DiscoveredDevice::DiscoveredDevice(
const BroadcastMessage &msg, const std::string &ipAddr
)
: DiscoveredDevice(
reinterpret_cast<const char*>(msg.broadcast_code),
static_cast<DeviceType>(msg.dev_type),
ipAddr)
{
}
std::string DiscoveredDevice::stringify(void) const
{
std::ostringstream oss;
oss << "DiscoveredDevice{"
<< "identifier='" << deviceIdentifier << "', "
<< "ipAddr='" << ipAddr << "', "
<< "deviceType=" << (int)deviceType << " (" << getDeviceTypeName() << ")"
<< "}";
return oss.str();
}
std::string DiscoveredDevice::getDeviceTypeName(void) const
{
switch (deviceType)
{
case DeviceType::Hub: return "Hub";
case DeviceType::Mid40: return "Mid-40";
case DeviceType::Tele15: return "Tele-15";
case DeviceType::Horizon: return "Horizon";
case DeviceType::Mid70: return "Mid-70";
case DeviceType::Avia: return "Avia";
default: return "Unknown";
}
}
} // namespace comms
// Device implementation
Device::Device(const comms::DiscoveredDevice &discoveredDevice)
: discoveredDevice(discoveredDevice)
{
}
} // namespace livoxProto1
@@ -0,0 +1,53 @@
#ifndef LIVOX_PROTO1_DEVICE_H
#define LIVOX_PROTO1_DEVICE_H
#include <string>
#include "livoxProto1Protocol.h"
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 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 comms::DiscoveredDevice &discoveredDevice);
~Device() = default;
public:
comms::DiscoveredDevice discoveredDevice;
};
} // namespace livoxProto1
#endif // LIVOX_PROTO1_DEVICE_H
@@ -54,190 +54,5 @@ bool BroadcastMessage::sanityCheck() const
footer.sanityCheck();
}
// DiscoveredDevice constructors
DiscoveredDevice::DiscoveredDevice(
const std::string &deviceIdentifier,
DeviceType deviceType,
const std::string &ipAddr)
: deviceIdentifier(deviceIdentifier),
deviceType(deviceType),
ipAddr(ipAddr)
{
}
DiscoveredDevice::DiscoveredDevice(
const BroadcastMessage &msg, const std::string &ipAddr
)
: DiscoveredDevice(
reinterpret_cast<const char*>(msg.broadcast_code),
static_cast<DeviceType>(msg.dev_type),
ipAddr)
{
}
std::string DiscoveredDevice::stringify(void) const
{
std::ostringstream oss;
oss << "DiscoveredDevice{"
<< "identifier='" << deviceIdentifier << "', "
<< "ipAddr='" << ipAddr << "', "
<< "deviceType=" << (int)deviceType << " (" << getDeviceTypeName() << ")"
<< "}";
return oss.str();
}
std::string DiscoveredDevice::getDeviceTypeName(void) const
{
switch (deviceType)
{
case DeviceType::Hub: return "Hub";
case DeviceType::Mid40: return "Mid-40";
case DeviceType::Tele15: return "Tele-15";
case DeviceType::Horizon: return "Horizon";
case DeviceType::Mid70: return "Mid-70";
case DeviceType::Avia: return "Avia";
default: return "Unknown";
}
}
BroadcastListener::BroadcastListener(
const std::shared_ptr<smo::ComponentThread>& componentThread,
uint16_t listeningPort, uint16_t connectPort
)
: componentThread(componentThread),
listeningPort(listeningPort),
connectPort(connectPort),
deviceGoneAwayCb(nullptr),
socket(componentThread->getIoService()),
listeningEndpoint(boost::asio::ip::udp::v4(), listeningPort),
isListening(false)
{
}
std::shared_ptr<DiscoveredDevice>
BroadcastListener::getDevice(const std::string &deviceIdentifier) const
{
auto it = std::find_if(discoveredDevices.begin(), discoveredDevices.end(),
[&deviceIdentifier](const std::shared_ptr<DiscoveredDevice>& device) {
return device->deviceIdentifier == deviceIdentifier;
}
);
return it != discoveredDevices.end() ? *it : nullptr;
}
void BroadcastListener::broadcastMsgInd(
const boost::system::error_code& ec, std::size_t bytes_received)
{
if (ec)
{
std::cerr << __func__ << ": Error receiving broadcast message: "
<< ec.message() << std::endl;
return;
}
if (bytes_received < sizeof(BroadcastMessage))
{
std::cerr << "Received packet too small: " << bytes_received
<< " bytes (expected at least " << sizeof(BroadcastMessage) << ")"
<< std::endl;
return;
}
// Use placement new to construct BroadcastMessage in the buffer
BroadcastMessage* msg = new (bcastMsgRecvBuffer) BroadcastMessage;
if (!msg->sanityCheck())
{
std::cerr << "Broadcast message failed sanity check" << std::endl;
return;
}
// Convert from little-endian to host endianness
msg->swapToHostEndianness();
// Extract device information
std::string senderIP = senderEndpoint.address().to_string();
std::string broadcastCode(reinterpret_cast<const char*>(msg->broadcast_code));
// Early return if device already exists
if (deviceExists(broadcastCode)) { return; }
// Create new DiscoveredDevice using conversion constructor
auto device = std::make_shared<DiscoveredDevice>(*msg, senderIP);
discoveredDevices.push_back(device);
std::cout << "Discovered new Livox device: " << device->stringify()
<< std::endl;
}
void BroadcastListener::start(void)
{
if (isListening.load()) { return; }
try
{
/** EXPLANATION:
* Set up a boost::asio udp listening socket on the broadcast listening
* port.
*
* FIXME:
* We should also set up a timer to check for devices that have gone
* away.
*/
socket.open(boost::asio::ip::udp::v4());
socket.bind(listeningEndpoint);
isListening.store(true);
// Start the first async receive operation
startReceive();
std::cout << "BroadcastListener started on port " << listeningPort
<< std::endl;
}
catch (const boost::system::system_error& e)
{
isListening.store(false);
std::cerr << "Failed to start BroadcastListener: " << e.what()
<< std::endl;
throw;
}
}
void BroadcastListener::startReceive(void)
{
if (!isListening.load()) { return; }
socket.async_receive_from(
boost::asio::buffer(bcastMsgRecvBuffer, sizeof(bcastMsgRecvBuffer)),
senderEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytes_received)
{
broadcastMsgInd(ec, bytes_received);
// Continue listening for the next packet
if (isListening.load())
{ startReceive(); }
}
);
}
void BroadcastListener::stop(void)
{
if (!isListening.load()) { return; }
isListening.store(false);
try
{
socket.close();
std::cout << "BroadcastListener stopped" << std::endl;
}
catch (const boost::system::system_error& e)
{
std::cerr << "Error stopping BroadcastListener: " << e.what()
<< std::endl;
throw;
}
}
} // namespace comms
} // namespace livoxProto1
@@ -81,94 +81,6 @@ struct BroadcastMessage
bool sanityCheck() const;
} __attribute__((packed));
/** 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 deviceIdentifier == other.deviceIdentifier; }
std::string stringify(void) const;
std::string getDeviceTypeName(void) const;
public:
std::string deviceIdentifier;
DeviceType deviceType;
std::string ipAddr;
};
/** EXPLANATION:
* This class merely listens for UDP bcast dgrams on the designated listening
* port. It then builds a list of client device IP addrs that it has heard from.
* It doesn't connect to them or signal any events to the rest of the lib,
* except in the case that a device which the lib is using has gone away.
*
* Other than that, its role is to tell the lib which devices are available
* on the network.
*/
#define UDP_BCAST_MSG_BUFFER_NBYTES (1024)
class BroadcastListener
{
public:
BroadcastListener(
const std::shared_ptr<smo::ComponentThread>& componentThread,
uint16_t listeningPort=55000, uint16_t connectPort=65000);
~BroadcastListener() = default;
typedef void (DeviceGoneAwayCbFn)(const DiscoveredDevice &device);
void setDeviceGoneAwayCb(DeviceGoneAwayCbFn *cb)
{ deviceGoneAwayCb = cb; }
bool deviceExists(const std::string &deviceIdentifier) const
{ return getDevice(deviceIdentifier) != nullptr; }
std::shared_ptr<DiscoveredDevice>
getDevice(const std::string &deviceIdentifier) const;
void start(void);
void stop(void);
void broadcastMsgInd(
const boost::system::error_code& ec, std::size_t bytes_received);
private:
void startReceive(void);
private:
std::shared_ptr<smo::ComponentThread> componentThread;
/** EXPLANATION:
* The Livox proto says that client devices will spam broadcast UDP
* dgrams to us on the listening port. We can then use the source IP from
* the bcast dgram to figure out the client device's IP addr. Then we
* should send a connect dgram to the connect port. This will tell the
* client device our IP addr.
*/
uint16_t listeningPort, connectPort;
DeviceGoneAwayCbFn *deviceGoneAwayCb;
std::vector<std::shared_ptr<DiscoveredDevice>> discoveredDevices;
boost::asio::ip::udp::socket socket;
boost::asio::ip::udp::endpoint listeningEndpoint, senderEndpoint;
std::atomic<bool> isListening;
uint8_t bcastMsgRecvBuffer[UDP_BCAST_MSG_BUFFER_NBYTES];
};
} // namespace comms
} // namespace livoxProto1