File hesai_packet.hpp
File List > decoders > hesai_packet.hpp
Go to the documentation of this file
// Copyright 2024 TIER IV, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <nebula_common/util/crc.hpp>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <stdexcept>
#include <type_traits>
namespace nebula::drivers::hesai_packet
{
// FIXME(mojomex) This is a workaround for the compiler being pedantic about casting `enum class`s
// to their underlying type
namespace return_mode
{
enum ReturnMode {
SINGLE_FIRST = 0x33,
SINGLE_SECOND = 0x34,
SINGLE_STRONGEST = 0x37,
SINGLE_LAST = 0x38,
DUAL_LAST_STRONGEST = 0x39,
DUAL_FIRST_SECOND = 0x3a,
DUAL_FIRST_LAST = 0x3b,
DUAL_FIRST_STRONGEST = 0x3c,
TRIPLE_FIRST_LAST_STRONGEST = 0x3d,
DUAL_STRONGEST_SECONDSTRONGEST = 0x3e,
};
} // namespace return_mode
#pragma pack(push, 1)
template <int YearOffset>
struct DateTime
{
uint8_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
[[nodiscard]] uint64_t get_seconds() const
{
std::tm tm{};
tm.tm_year = year - 1900 + YearOffset;
tm.tm_mon = month - 1; // starts from 0 in C
tm.tm_mday = day;
tm.tm_hour = hour;
tm.tm_min = minute;
tm.tm_sec = second;
return timegm(&tm);
}
};
struct SecondsSinceEpoch
{
uint8_t zero;
uint8_t seconds[5];
[[nodiscard]] uint64_t get_seconds() const
{
uint64_t seconds = 0;
for (unsigned char second : this->seconds) {
seconds = (seconds << 8) | second;
}
return seconds;
}
};
struct Header12B
{
uint16_t sop;
uint8_t protocol_major;
uint8_t protocol_minor;
uint8_t reserved1[2];
uint8_t laser_num;
uint8_t block_num;
uint8_t reserved2;
uint8_t dis_unit;
uint8_t return_num;
uint8_t flags;
};
struct Header8B
{
uint16_t sop;
uint8_t laser_num;
uint8_t block_num;
uint8_t reserved1;
uint8_t dis_unit;
uint8_t reserved2[2];
};
struct Unit3B
{
uint16_t distance;
uint8_t reflectivity;
};
struct Unit4B
{
uint16_t distance;
uint8_t reflectivity;
uint8_t confidence_or_reserved;
};
template <typename UnitT, size_t UnitN>
struct Block
{
uint16_t azimuth;
UnitT units[UnitN];
using unit_t = UnitT;
[[nodiscard]] uint32_t get_azimuth() const { return azimuth; }
};
template <typename UnitT, size_t UnitN>
struct FineAzimuthBlock
{
using unit_t = UnitT;
uint16_t azimuth;
uint8_t fine_azimuth;
UnitT units[UnitN];
[[nodiscard]] uint32_t get_azimuth() const { return (azimuth << 8) + fine_azimuth; }
};
template <typename UnitT, size_t UnitN>
struct SOBBlock
{
using unit_t = UnitT;
uint16_t sob;
uint16_t azimuth;
UnitT units[UnitN];
[[nodiscard]] uint32_t get_azimuth() const { return azimuth; }
};
template <typename BlockT, size_t BlockN>
struct Body
{
using block_t = BlockT;
BlockT blocks[BlockN];
};
template <typename BlockT, size_t BlockN>
struct BodyWithCrc : public Body<BlockT, BlockN>
{
using Body<BlockT, BlockN>::blocks;
uint32_t crc_body;
[[nodiscard]] bool is_crc_valid() const
{
return crc<crc32_mpeg2_t>(&blocks, &crc_body) == crc_body;
}
};
template <size_t nBlocks, size_t nChannels, size_t maxReturns, size_t degreeSubdivisions>
struct PacketBase
{
static constexpr size_t n_blocks = nBlocks;
static constexpr size_t n_channels = nChannels;
static constexpr size_t max_returns = maxReturns;
static constexpr size_t degree_subdivisions = degreeSubdivisions;
};
#pragma pack(pop)
inline int get_n_returns(uint8_t return_mode)
{
switch (return_mode) {
case return_mode::SINGLE_FIRST:
case return_mode::SINGLE_SECOND:
case return_mode::SINGLE_STRONGEST:
case return_mode::SINGLE_LAST:
return 1;
case return_mode::DUAL_LAST_STRONGEST:
case return_mode::DUAL_FIRST_SECOND:
case return_mode::DUAL_FIRST_LAST:
case return_mode::DUAL_FIRST_STRONGEST:
case return_mode::DUAL_STRONGEST_SECONDSTRONGEST:
return 2;
case return_mode::TRIPLE_FIRST_LAST_STRONGEST:
return 3;
default:
throw std::runtime_error("Unknown return mode");
}
}
template <typename PacketT>
uint64_t get_timestamp_ns(const PacketT & packet)
{
return packet.tail.date_time.get_seconds() * 1000000000 + packet.tail.timestamp * 1000;
}
template <typename PacketT>
double get_dis_unit(const PacketT & packet)
{
// Packets define distance unit in millimeters, convert to meters here
return packet.header.dis_unit / 1000.;
}
// Helper trait to determine if a given struct has a CRC check
template <typename T, typename = void>
struct HasCrcCheck : std::false_type
{
};
template <typename T>
struct HasCrcCheck<T, std::void_t<decltype(std::declval<T>().is_crc_valid())>> : std::true_type
{
};
template <typename PayloadT>
std::enable_if_t<HasCrcCheck<PayloadT>::value, bool> is_crc_valid(const PayloadT & payload)
{
return payload.is_crc_valid();
}
template <typename PayloadT>
std::enable_if_t<!HasCrcCheck<PayloadT>::value, bool> is_crc_valid(const PayloadT & /* payload */)
{
return true;
}
// Helper trait to determine if a given struct has a functional safety part
template <typename PacketT, typename = void>
struct HasFunctionalSafety : std::false_type
{
};
template <typename PacketT>
struct HasFunctionalSafety<PacketT, std::void_t<decltype(std::declval<PacketT>().fs)>>
: std::true_type
{
};
// Helper trait to determine if a given struct has a packet loss detection part
template <typename PacketT, typename = void>
struct HasPacketLossDetection : std::false_type
{
};
template <typename PacketT>
struct HasPacketLossDetection<
PacketT, std::void_t<decltype(std::declval<PacketT>().tail.udp_sequence)>> : std::true_type
{
};
} // namespace nebula::drivers::hesai_packet