374 lines
12 KiB
C++
374 lines
12 KiB
C++
#ifndef LIVOXPROTO1_PROTOCOL_H
|
|
#define LIVOXPROTO1_PROTOCOL_H
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <boost/asio/ip/address_v4.hpp>
|
|
#include <user/senseApiDesc.h>
|
|
|
|
namespace livoxProto1 {
|
|
namespace comms {
|
|
|
|
/** EXPLANATION:
|
|
* Undocumented Livox SDK CRC seed constants. These were found in the Livox SDK
|
|
* source code.
|
|
*/
|
|
constexpr uint16_t LIVOX_CRC16_SEED = 0x4c49;
|
|
constexpr uint32_t LIVOX_CRC32_SEED = 0x564f580a;
|
|
|
|
// Endianness detection
|
|
namespace endian {
|
|
inline bool isLittleEndian() {
|
|
union {
|
|
uint32_t i;
|
|
char c[4];
|
|
} test = {0x01020304};
|
|
return test.c[0] == 4;
|
|
}
|
|
}
|
|
|
|
// IPv4 address validation
|
|
inline bool isValidIPv4(const std::string& ipAddress) {
|
|
boost::system::error_code ec;
|
|
boost::asio::ip::address_v4::from_string(ipAddress, ec);
|
|
return !ec;
|
|
}
|
|
|
|
// CRC calculation utilities
|
|
uint16_t calculateCrc16(
|
|
const uint8_t* data, size_t length);
|
|
uint32_t calculateCrc32(
|
|
const uint8_t* data, size_t length);
|
|
|
|
// IP address parsing utility
|
|
struct IPOctets {
|
|
std::string octet1, octet2, octet3, octet4;
|
|
};
|
|
|
|
std::optional<IPOctets> parseIPv4Address(const std::string& ipAddress);
|
|
|
|
// Device identifier comparison
|
|
inline bool deviceIdentifiersEqual(
|
|
const std::string& id1, const std::string& id2
|
|
)
|
|
{
|
|
// Use pointers to avoid unnecessary string copies
|
|
const std::string* serial1_ptr;
|
|
const std::string* serial2_ptr;
|
|
|
|
// Local copies only needed for 15-character broadcast codes
|
|
std::string serial1_copy, serial2_copy;
|
|
|
|
// Determine if id1 is serial (14 chars) or broadcast code (15 chars)
|
|
if (id1.length() == 14) {
|
|
serial1_ptr = &id1; // No copy needed, use original string
|
|
} else if (id1.length() == 15) {
|
|
serial1_copy = id1.substr(0, 14); // Copy only when necessary
|
|
serial1_ptr = &serial1_copy;
|
|
} else {
|
|
return false; // Invalid length
|
|
}
|
|
|
|
// Determine if id2 is serial (14 chars) or broadcast code (15 chars)
|
|
if (id2.length() == 14) {
|
|
serial2_ptr = &id2; // No copy needed, use original string
|
|
} else if (id2.length() == 15) {
|
|
serial2_copy = id2.substr(0, 14); // Copy only when necessary
|
|
serial2_ptr = &serial2_copy;
|
|
} else {
|
|
return false; // Invalid length
|
|
}
|
|
|
|
// Compare the serial numbers using pointers
|
|
return *serial1_ptr == *serial2_ptr;
|
|
}
|
|
|
|
/** EXPLANATION:
|
|
* Device types as defined in the Livox protocol specification
|
|
*/
|
|
enum class DeviceType : uint8_t {
|
|
Hub = 0,
|
|
Mid40 = 1,
|
|
Tele15 = 2,
|
|
Horizon = 3,
|
|
Mid70 = 6,
|
|
Avia = 7
|
|
};
|
|
|
|
/** EXPLANATION:
|
|
* Protocol frame header structure.
|
|
* All multi-byte fields are in little-endian format as per protocol spec.
|
|
*/
|
|
struct Header
|
|
{
|
|
uint8_t sof; // 0: Start of Frame (0xAA)
|
|
uint8_t version; // 1: Protocol Version (1)
|
|
uint16_t length; // 2-3: Frame Length (little-endian)
|
|
uint8_t cmd_type; // 4: Command Type (0x02 = MSG for broadcast)
|
|
uint16_t seq_num; // 5-6: Sequence Number (little-endian)
|
|
uint16_t crc_16; // 7-8: Header Checksum (little-endian)
|
|
|
|
void swapToHostEndianness();
|
|
void swapToProtocolEndianness();
|
|
void swapCrc16ToHostEndianness();
|
|
void swapCrc16ToProtocolEndianness();
|
|
bool sanityCheck() const;
|
|
uint16_t calculateCrc16() const;
|
|
bool validateCrc16() const;
|
|
void setCrc16FromRawBytes();
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Protocol frame footer structure.
|
|
* All multi-byte fields are in little-endian format as per protocol spec.
|
|
*/
|
|
struct Footer
|
|
{
|
|
uint32_t crc_32; // 0-3: Whole Frame Checksum (little-endian)
|
|
|
|
void swapToHostEndianness();
|
|
void swapToProtocolEndianness();
|
|
void swapCrc32ToHostEndianness();
|
|
void swapCrc32ToProtocolEndianness();
|
|
bool validateCrc32() const;
|
|
bool sanityCheck() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Command identification structure used in all Livox protocol messages.
|
|
* Contains the command set and command ID fields.
|
|
*/
|
|
struct Command
|
|
{
|
|
uint8_t cmd_set; // 0: Command Set (0x00 = General, etc.)
|
|
uint8_t cmd_id; // 1: Command ID (0x00 = Broadcast, 0x01 = Handshake, etc.)
|
|
|
|
void swapToHostEndianness();
|
|
void swapToProtocolEndianness();
|
|
bool sanityCheck() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete wire format for Livox broadcast messages.
|
|
* All multi-byte fields are in little-endian format as per protocol spec.
|
|
*/
|
|
struct BroadcastMessage
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t broadcast_code[16]; // 11-26: Device Broadcast Code (null-terminated string)
|
|
uint8_t dev_type; // 27: Device Type
|
|
uint16_t reserved; // 28-29: Reserved (little-endian)
|
|
Footer footer; // 30-33: Protocol frame footer
|
|
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete handshake request frame for connecting to Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct HandshakeRequest
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t user_ip[4]; // 11-14: Host IP Address (little-endian)
|
|
uint16_t data_port; // 15-16: Host Point Cloud Data UDP Destination Port (little-endian)
|
|
uint16_t cmd_port; // 17-18: Host Control Command UDP Destination Port (little-endian)
|
|
uint16_t imu_port; // 19-20: Host IMU UDP Destination Port (little-endian)
|
|
Footer footer; // 21-24: Protocol frame footer
|
|
|
|
HandshakeRequest(
|
|
const std::string& hostIP,
|
|
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
|
|
|
|
// Calculate CRC32 for the entire message
|
|
uint32_t calculateCrc32() const;
|
|
void swapContentsToProtocolEndianness();
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete handshake response frame from Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct HandshakeResponse
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
|
Footer footer; // 12-15: Protocol frame footer
|
|
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete heartbeat command frame for maintaining connection with Livox devices.
|
|
* This is the complete wire format including header, command fields, and footer.
|
|
*/
|
|
struct HeartbeatMessage
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
Footer footer; // 11-14: Protocol frame footer
|
|
|
|
HeartbeatMessage();
|
|
uint32_t calculateCrc32() const;
|
|
void swapContentsToProtocolEndianness();
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete disconnect command frame for disconnecting from Livox devices.
|
|
* This is the complete wire format including header, command fields, and footer.
|
|
*/
|
|
struct DisconnectMessage
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
Footer footer; // 11-14: Protocol frame footer
|
|
|
|
DisconnectMessage();
|
|
uint32_t calculateCrc32() const;
|
|
void swapContentsToProtocolEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete start/stop sampling command frame for enabling/disabling point cloud data from Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct StartStopSamplingMessage
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t enable; // 11: Enable flag (0x01 = Start, 0x00 = Stop)
|
|
Footer footer; // 12-15: Protocol frame footer
|
|
|
|
StartStopSamplingMessage();
|
|
uint32_t calculateCrc32() const;
|
|
void swapContentsToProtocolEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete sampling response frame from Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct SamplingResponse
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
|
Footer footer; // 12-15: Protocol frame footer
|
|
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete heartbeat ACK response frame from Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct HeartbeatACK
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
|
uint8_t work_state; // 12: LiDAR/Hub State (0x00: Initializing, 0x01: Normal, 0x02: Power-Saving, 0x03: Standby, 0x04: Error)
|
|
uint8_t feature_msg; // 13: LiDAR Feature Message (Bit0: Rain/Fog Suppression Switch)
|
|
uint32_t ack_msg; // 14-17: ACK Message (Initialization Progress or Status Code)
|
|
Footer footer; // 18-21: Protocol frame footer
|
|
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete set LiDAR return mode command frame for Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct SetLiDARReturnMode
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t mode; // 11: Return Mode (0x00: Single Return First, 0x01: Single Return Strongest, 0x02: Dual Return, 0x03: Triple Return)
|
|
Footer footer; // 12-15: Protocol frame footer
|
|
|
|
SetLiDARReturnMode();
|
|
uint32_t calculateCrc32() const;
|
|
void swapContentsToProtocolEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete set LiDAR return mode response frame from Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct SetLiDARReturnModeResponse
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
|
Footer footer; // 12-15: Protocol frame footer
|
|
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete get LiDAR return mode command frame for Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct GetLiDARReturnMode
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
Footer footer; // 11-14: Protocol frame footer
|
|
|
|
GetLiDARReturnMode();
|
|
uint32_t calculateCrc32() const;
|
|
void swapContentsToProtocolEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
/** EXPLANATION:
|
|
* Complete get LiDAR return mode response frame from Livox devices.
|
|
* This is the complete wire format including header, command fields, data, and footer.
|
|
*/
|
|
struct GetLiDARReturnModeResponse
|
|
{
|
|
Header header; // 0-8: Protocol frame header
|
|
Command command; // 9-10: Command identification
|
|
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
|
uint8_t mode; // 12: Return Mode (0x00: Single Return First, 0x01: Single Return Strongest, 0x02: Dual Return, 0x03: Triple Return)
|
|
Footer footer; // 13-16: Protocol frame footer
|
|
|
|
void swapContentsToHostEndianness();
|
|
bool sanityCheck() const;
|
|
bool validateCrc32() const;
|
|
} __attribute__((packed));
|
|
|
|
} // namespace comms
|
|
} // namespace livoxProto1
|
|
|
|
#endif // LIVOXPROTO1_PROTOCOL_H
|