Merged from the three drifted hand-copies in TrackBackFwd, healthy-band-nrf and zephyr_gateway_fwd: - as_u8s(T&) overloads and deferrer (band/hub side) - hexdump, add_overflow, constrain_value_in_range_static, to_mac_string (TrackBackFwd side) - add_overflow now returns std::expected directly instead of the app_result alias; to_mac_string guarded by __cpp_lib_format for GCC 12 toolchains - app_clock.hpp now() selects k_uptime_ticks / esp_timer_get_time via __ZEPHYR__ / ESP_PLATFORM; fixed doc comment (microseconds, not ms) - dropped vestigial esp_err.h/esp_system.h/FreeRTOS.h includes Dual build glue: ESP-IDF component (root CMakeLists) + Zephyr module (zephyr/). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
252 lines
6.9 KiB
C++
252 lines
6.9 KiB
C++
#pragma once
|
||
|
||
#include <cstdint>
|
||
#include <cstdio>
|
||
#include <expected>
|
||
#include <functional>
|
||
#include <limits>
|
||
#include <optional>
|
||
#include <ranges>
|
||
#include <span>
|
||
#include <tuple>
|
||
#include <variant>
|
||
#include <version>
|
||
#ifdef __cpp_lib_format
|
||
#include <format>
|
||
#include <string>
|
||
#endif
|
||
|
||
namespace app::utils {
|
||
/// @brief reinterpret_cast a trivially copyable value to a span of bytes
|
||
template <typename T>
|
||
requires std::is_trivially_copyable_v<T>
|
||
inline std::span<const std::byte> as_bytes(const T &value) {
|
||
return {reinterpret_cast<const std::byte *>(&value), sizeof(value)};
|
||
}
|
||
|
||
/// @brief reinterpret_cast a trivially copyable value to a span of bytes (mutable)
|
||
template <typename T>
|
||
requires std::is_trivially_copyable_v<T>
|
||
inline std::span<std::byte> as_bytes(T &value) {
|
||
return {reinterpret_cast<std::byte *>(&value), sizeof(value)};
|
||
}
|
||
|
||
/// @brief convert a uint8_t span to a std::byte span
|
||
inline std::span<const std::byte> as_bytes(std::span<const uint8_t> span) {
|
||
return {reinterpret_cast<const std::byte *>(span.data()), span.size()};
|
||
}
|
||
|
||
/// @brief convert a uint8_t span to a std::byte span (mutable)
|
||
inline std::span<std::byte> as_bytes(std::span<uint8_t> span) {
|
||
return {reinterpret_cast<std::byte *>(span.data()), span.size()};
|
||
}
|
||
|
||
/// @brief construct a std::byte span from a raw pointer and size
|
||
inline std::span<std::byte> as_bytes(std::byte *data, std::size_t size) {
|
||
return {data, size};
|
||
}
|
||
|
||
/// @brief construct an immutable std::byte span from a raw pointer and size
|
||
inline std::span<const std::byte> as_bytes(const std::byte *data, std::size_t size) {
|
||
return {data, size};
|
||
}
|
||
|
||
/// @brief construct a std::byte span from a uint8_t raw pointer and size
|
||
inline std::span<std::byte> as_bytes(std::uint8_t *data, std::size_t size) {
|
||
return {reinterpret_cast<std::byte *>(data), size};
|
||
}
|
||
|
||
/// @brief construct an immutable std::byte span from a uint8_t raw pointer and size
|
||
inline std::span<const std::byte> as_bytes(const std::uint8_t *data, std::size_t size) {
|
||
return {reinterpret_cast<const std::byte *>(data), size};
|
||
}
|
||
|
||
|
||
/// @brief reinterpret_cast a trivially copyable value to a span of uint8_t
|
||
template <typename T>
|
||
requires std::is_trivially_copyable_v<T>
|
||
inline std::span<const std::uint8_t> as_u8s(const T &value) {
|
||
return {reinterpret_cast<const std::uint8_t *>(&value), sizeof(value)};
|
||
}
|
||
|
||
/// @brief reinterpret_cast a trivially copyable value to a span of uint8_t span
|
||
template <typename T>
|
||
requires std::is_trivially_copyable_v<T>
|
||
inline std::span<std::uint8_t> as_u8s(T &value) {
|
||
return {reinterpret_cast<std::uint8_t *>(&value), sizeof(value)};
|
||
}
|
||
|
||
/// @brief convert a std::byte span to a uint8_t span
|
||
inline std::span<const std::uint8_t> as_u8s(std::span<const std::byte> span) {
|
||
return {reinterpret_cast<const std::uint8_t *>(span.data()), span.size()};
|
||
}
|
||
|
||
/// @brief convert a std::byte span to a uint8_t span (mutable)
|
||
inline std::span<std::uint8_t> as_u8s(std::span<std::byte> span) {
|
||
return {reinterpret_cast<std::uint8_t *>(span.data()), span.size()};
|
||
}
|
||
|
||
/// @brief helper type for the visitor
|
||
template <class... Ts>
|
||
struct overloads : Ts... {
|
||
using Ts::operator()...;
|
||
};
|
||
|
||
inline void hexdump(std::span<const std::byte> data) {
|
||
const auto enumerate = [](const auto &data) {
|
||
return data | std::views::transform([i = 0](const auto &value) mutable {
|
||
return std::make_tuple(i++, value);
|
||
});
|
||
};
|
||
for (const auto [i, byte] : enumerate(data)) {
|
||
bool is_end = i == data.size() - 1;
|
||
if (is_end) {
|
||
printf("%02x\n", static_cast<uint8_t>(byte));
|
||
} else {
|
||
if (i % 16 == 15) {
|
||
printf("%02x\n", static_cast<uint8_t>(byte));
|
||
} else {
|
||
printf("%02x ", static_cast<uint8_t>(byte));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief try to get an element from a span
|
||
* @tparam T, should be trivially copyable
|
||
* @param s span
|
||
* @param idx index, can be negative, will be shifted into [0, s.size())
|
||
* @return std::optional<T> element, or std::nullopt if index is out of range
|
||
*/
|
||
template <typename T>
|
||
std::optional<T> try_get(std::span<T> s, int idx) {
|
||
const int n = static_cast<int>(s.size());
|
||
// 1) if negative, shift into [−n, 0) → [0, n)
|
||
if (idx < 0) {
|
||
idx += n;
|
||
}
|
||
// 2) now both negative-too-big and positive-too-big land outside [0,n)
|
||
if (idx < 0 || idx >= n) {
|
||
return std::nullopt;
|
||
}
|
||
return s[idx];
|
||
}
|
||
|
||
/**
|
||
* @brief try to get an element from a const span
|
||
* @tparam T
|
||
* @param s const span
|
||
* @param idx index, can be negative, will be shifted into [0, s.size())
|
||
* @return std::optional<std::reference_wrapper<const T>> element reference, or std::nullopt if index is out of range
|
||
* @see try_get
|
||
*/
|
||
template <typename T>
|
||
std::optional<std::reference_wrapper<const T>>
|
||
try_get_ref(std::span<const T> s, int idx) {
|
||
const int n = static_cast<int>(s.size());
|
||
if (idx < 0) {
|
||
idx += n;
|
||
}
|
||
if (idx < 0 || idx >= n) {
|
||
return std::nullopt;
|
||
}
|
||
return std::cref(s[idx]);
|
||
}
|
||
|
||
/**
|
||
* @brief a simple RAII helper for `defer` like behavior
|
||
*/
|
||
struct deferrer {
|
||
#ifdef __cpp_lib_move_only_function
|
||
using func_t = std::move_only_function<void()>;
|
||
#else
|
||
using func_t = std::function<void()>;
|
||
#endif
|
||
|
||
deferrer(func_t &&f) : _f(std::move(f)) {}
|
||
~deferrer() {
|
||
if (_f) {
|
||
_f();
|
||
}
|
||
}
|
||
deferrer(const deferrer &) = delete;
|
||
deferrer &operator=(const deferrer &) = delete;
|
||
deferrer(deferrer &&other) noexcept : _f(std::move(other._f)) {
|
||
other._f = {};
|
||
}
|
||
deferrer &operator=(deferrer &&other) noexcept {
|
||
if (this != &other) {
|
||
// call current function before overwriting
|
||
if (_f) {
|
||
_f();
|
||
}
|
||
_f = std::move(other._f);
|
||
other._f = {};
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
private:
|
||
func_t _f;
|
||
};
|
||
|
||
using overflow_error = std::monostate;
|
||
/**
|
||
* @brief a safe addition that returns an overflow error if the result is out of range
|
||
*/
|
||
template <typename T>
|
||
requires std::is_integral_v<T>
|
||
std::expected<T, overflow_error> add_overflow(T a, T b) {
|
||
using ue = std::unexpected<overflow_error>;
|
||
|
||
if constexpr (std::is_unsigned_v<T>) {
|
||
// For unsigned: check if a > max - b (rearranged to avoid overflow in the check itself)
|
||
if (a > std::numeric_limits<T>::max() - b) {
|
||
return ue{overflow_error{}};
|
||
}
|
||
} else {
|
||
// For signed integers: need to check both positive and negative overflow
|
||
if (b > 0) {
|
||
if (a > std::numeric_limits<T>::max() - b) {
|
||
return ue{overflow_error{}};
|
||
}
|
||
} else {
|
||
if (a < std::numeric_limits<T>::min() - b) {
|
||
return ue{overflow_error{}};
|
||
}
|
||
}
|
||
}
|
||
|
||
return a + b;
|
||
}
|
||
|
||
|
||
/**
|
||
* @see `std::clamp`
|
||
*/
|
||
template <typename T, T min, T max>
|
||
T constrain_value_in_range_static(T value) {
|
||
static_assert(min < max, "min must be less than max");
|
||
if (value < min) {
|
||
return min;
|
||
}
|
||
if (value > max) {
|
||
return max;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
#ifdef __cpp_lib_format
|
||
inline std::string to_mac_string(std::span<const std::byte, 6> mac) {
|
||
return std::format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
|
||
static_cast<uint8_t>(mac[0]),
|
||
static_cast<uint8_t>(mac[1]),
|
||
static_cast<uint8_t>(mac[2]),
|
||
static_cast<uint8_t>(mac[3]),
|
||
static_cast<uint8_t>(mac[4]),
|
||
static_cast<uint8_t>(mac[5]));
|
||
}
|
||
#endif
|
||
}
|