Files
fleet_sports_app_common/inc/app_utils.hpp
T
crosstyanandClaude Fable 5 c948c7565c feat: unify app_utils/app_clock headers shared by the fleet
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>
2026-07-15 15:30:28 +08:00

252 lines
6.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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
}