first commit

This commit is contained in:
stuce-bot 2025-06-30 20:47:33 +02:00
commit 5893b00dd2
1669 changed files with 1982740 additions and 0 deletions

View file

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5Utility.h
*/
#ifndef M5_UTILITY_H
#define M5_UTILITY_H
#ifdef __cplusplus
#include "M5Utility.hpp"
#else
#error M5Utility requires a C++ compiler, please change file extension to .cc or .cpp
#endif
#endif

View file

@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5Utility.hpp
@brief Main header of M5Utility
@mainpage M5Utility
This library is a collection of common functionality for other M5Libraries and products.
*/
#ifndef M5_UTILITY_HPP
#define M5_UTILITY_HPP
#include "m5_utility/stl/expected.hpp"
#include "m5_utility/stl/extension.hpp"
#include "m5_utility/stl/optional.hpp"
#include "m5_utility/stl/endianness.hpp"
#include "m5_utility/log/library_log.hpp"
#include "m5_utility/container/circular_buffer.hpp"
#include "m5_utility/bit_segment.hpp"
#include "m5_utility/compatibility_feature.hpp"
#include "m5_utility/murmurhash3.hpp"
#include "m5_utility/types.hpp"
#include "m5_utility/crc.hpp"
#include "m5_utility/string.hpp"
#include "m5_utility/conversion.hpp"
#include "m5_utility/math.hpp"
#include "m5_utility/misc.hpp"
/*!
@namespace m5
@brief Top level namespace of M5
*/
namespace m5 {
/*!
@namespace utility
@brief For utilities
*/
namespace utility {
}
/*!
@namespace stl
@brief STL compatibility functions and classes
*/
namespace stl {
}
/*!
@namespace container
@brief Container classes
*/
namespace container {
}
} // namespace m5
#endif

View file

@ -0,0 +1,237 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file bit_segment.hpp
@brief A class for separating the bits of an integer variable and giving
meaning to each
*/
#ifndef M5_UTILITY_BIT_SEGMENT_HPP
#define M5_UTILITY_BIT_SEGMENT_HPP
#include <cstddef>
#include <type_traits>
namespace m5 {
namespace utility {
/*!
@class BitSegment
@tparam LowerBits How many lower bits are used as a lower segment.
@tparam T Integer type used as base class
@note If the base class is signed, the upper bits excluding the sign bit are
used
*/
template <size_t LowerBits, typename T>
class BitSegment {
public:
///@cond
using base_type = typename std::remove_const<typename std::remove_reference<T>::type>::type;
constexpr static bool SIGNED = std::is_signed<base_type>::value;
static_assert(std::is_integral<base_type>::value, "Base type must be integral");
static_assert(LowerBits > 0, "LowerBits must be not zero");
static_assert(LowerBits <= (sizeof(base_type) * 8 - (SIGNED ? 1 : 0)), "LowerBits too large");
using unsigned_type = typename std::make_unsigned<base_type>::type;
constexpr static unsigned_type UPPER_BITS = sizeof(unsigned_type) * 8U - LowerBits - (SIGNED ? 1 : 0);
constexpr static unsigned_type LOWER_BITS = static_cast<unsigned_type>(LowerBits);
constexpr static unsigned_type UPPER_SHIFT = LOWER_BITS;
constexpr static unsigned_type UPPER_MASK = ((unsigned_type)1 << UPPER_BITS) - 1;
constexpr static unsigned_type LOWER_MASK = ((unsigned_type)1 << LOWER_BITS) - 1;
///@endcond
///@name Constructor
///@{
inline constexpr BitSegment() = default; //!< @brief default
//! @brief Copy
inline constexpr BitSegment(const BitSegment& o) : _v(o._v)
{
}
//! @brief Implicit conversion
inline constexpr BitSegment(const base_type v) : _v(v)
{
}
///@}
///@name Assignment
///@{
BitSegment& operator=(const BitSegment& o)
{
if (this != &o) {
_v = o._v;
}
return *this;
}
BitSegment& operator=(const base_type v)
{
_v = v;
return *this;
}
///@}
///@name Cast
///@{
/*! @brief Cast to boolean */
inline constexpr explicit operator bool() const
{
return _v;
}
//! @brief Cast to base_type (Implicit conversion)
inline constexpr operator base_type() const
{
return _v;
}
///@}
///@name Getter
///@{
/*! @brief Gets the value of upper segment */
inline constexpr unsigned_type upper() const
{
return (_v >> UPPER_SHIFT) & UPPER_MASK;
}
//! @brief Gets the value of lower segment
inline constexpr unsigned_type lower() const
{
return _v & LOWER_MASK;
}
//! @brief Gets the raw value
inline constexpr base_type raw() const
{
return _v;
}
///@}
///@name Setter
///@{
/*! @brief Set the value of upper segment */
inline void upper(const unsigned_type v)
{
_v = (_v & ~(UPPER_MASK << UPPER_SHIFT)) | ((v & UPPER_MASK) << UPPER_SHIFT);
}
//! @brief Set the value of lower segment
inline void lower(const unsigned_type v)
{
_v = (_v & ~LOWER_MASK) | (v & LOWER_MASK);
}
//! @brief Set the raw value
inline void raw(const base_type v)
{
_v = v;
}
///@}
private:
base_type _v{};
};
///@name Compare between same types.
/// @related m5::utility::BitSegment
///@{
template <size_t LowerBits, typename T>
bool operator==(const BitSegment<LowerBits, T>& a, const BitSegment<LowerBits, T>& b)
{
return a.raw() == b.raw();
}
template <size_t LowerBits, typename T>
bool operator!=(const BitSegment<LowerBits, T>& a, const BitSegment<LowerBits, T>& b)
{
return !(a == b);
}
template <size_t LowerBits, typename T>
bool operator<(const BitSegment<LowerBits, T>& a, const BitSegment<LowerBits, T>& b)
{
return a.raw() < b.raw();
}
template <size_t LowerBits, typename T>
bool operator>(const BitSegment<LowerBits, T>& a, const BitSegment<LowerBits, T>& b)
{
return b < a;
}
template <size_t LowerBits, typename T>
bool operator<=(const BitSegment<LowerBits, T>& a, const BitSegment<LowerBits, T>& b)
{
return !(a > b);
}
template <size_t LowerBits, typename T>
bool operator>=(const BitSegment<LowerBits, T>& a, const BitSegment<LowerBits, T>& b)
{
return !(a < b);
}
///@}
///@name Compare between BitSegment and integer
/// @related m5::utility::BitSegment
///@{
template <size_t LowerBits, typename T>
bool operator==(const BitSegment<LowerBits, T>& a, const int b)
{
return a.raw() == b;
}
template <size_t LowerBits, typename T>
bool operator!=(const BitSegment<LowerBits, T>& a, const int b)
{
return !(a == b);
}
template <size_t LowerBits, typename T>
bool operator<(const BitSegment<LowerBits, T>& a, const int b)
{
return a.raw() < b;
}
template <size_t LowerBits, typename T>
bool operator>(const BitSegment<LowerBits, T>& a, const int b)
{
return b < a;
}
template <size_t LowerBits, typename T>
bool operator<=(const BitSegment<LowerBits, T>& a, const int b)
{
return !(a > b);
}
template <size_t LowerBits, typename T>
bool operator>=(const BitSegment<LowerBits, T>& a, const int b)
{
return !(a < b);
}
///@}
///@name Compare between integer and BitSegment
/// @related m5::utility::BitSegment
///@{
template <size_t LowerBits, typename T>
bool operator==(const int a, const BitSegment<LowerBits, T>& b)
{
return a == b.raw();
}
template <size_t LowerBits, typename T>
bool operator!=(const int a, const BitSegment<LowerBits, T>& b)
{
return !(a == b);
}
template <size_t LowerBits, typename T>
bool operator<(const int a, const BitSegment<LowerBits, T>& b)
{
return a < b.raw();
}
template <size_t LowerBits, typename T>
bool operator>(const int a, const BitSegment<LowerBits, T>& b)
{
return b < a;
}
template <size_t LowerBits, typename T>
bool operator<=(const int a, const BitSegment<LowerBits, T>& b)
{
return !(a > b);
}
template <size_t LowerBits, typename T>
bool operator>=(const int a, const BitSegment<LowerBits, T>& b)
{
return !(a < b);
}
///@}
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file compatibility_feature.cpp
@brief Maintain compatibility with Arduino API, etc.
*/
#include "compatibility_feature.hpp"
#include <ctime>
#include <chrono>
#include <thread>
namespace {
using clock = std::chrono::high_resolution_clock;
const clock::time_point start_at = clock::now();
} // namespace
namespace m5 {
namespace utility {
unsigned long millis()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - ::start_at).count();
}
unsigned long micros()
{
return std::chrono::duration_cast<std::chrono::microseconds>(clock::now() - ::start_at).count();
}
void delay(const unsigned long ms)
{
#if 0
auto abst = clock::now() + std::chrono::milliseconds(ms);
std::this_thread::sleep_until(abst);
#else
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
#endif
}
void delayMicroseconds(const unsigned int us)
{
#if 0
auto abst = clock::now() + std::chrono::microseconds(us);
std::this_thread::sleep_until(abst);
#else
std::this_thread::sleep_for(std::chrono::microseconds(us));
#endif
}
} // namespace utility
} // namespace m5

View file

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file compatibility_feature.hpp
@brief Maintain compatibility with Arduino API, etc.
*/
#ifndef M5_UTILITY_COMPATIBILITY_FEATURE_HPP
#define M5_UTILITY_COMPATIBILITY_FEATURE_HPP
namespace m5 {
namespace utility {
///@name Arduino API
///@{
/*!
@brief Returns the number of milliseconds passed since the Arduino board began
running the current program
*/
unsigned long millis();
/*!
@brief Returns the number of microseconds since the Arduino board began
running the current program
*/
unsigned long micros();
/*!
@brief Pauses the program for the amount of time (in milliseconds) specified
as parameter.
@warning Accuracy varies depending on the environment.
*/
void delay(const unsigned long ms);
/*!
@brief Pauses the program for the amount of time (in microseconds) specified
by the parameter.
@warning Accuracy varies depending on the environment.
*/
void delayMicroseconds(const unsigned int us);
///@}
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,537 @@
/*
* Spdx-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file circular_buffer.hpp
@brief Circular buffer with STL-like interface
*/
#ifndef M5_UTILITY_CONTAINER_CIRCULAR_BUFFER_HPP
#define M5_UTILITY_CONTAINER_CIRCULAR_BUFFER_HPP
#include <cstddef>
#include <vector>
#include <iterator>
#include <cassert>
#if __cplusplus >= 201703L
#pragma message "Using std::optional"
#include <optional>
#else
#pragma message "Using m5::stl::optional"
#include "../stl/optional.hpp"
#endif
namespace m5 {
namespace container {
/*!
@class CircularBuffer
@brief Type CircularBuffer giving size in constructor
@tparam T Type of the element
*/
template <typename T>
class CircularBuffer {
public:
using value_type = T;
using size_type = size_t;
using reference = T&;
using const_reference = const T&;
#if __cplusplus >= 201703L
using return_type = std::optional<value_type>;
#else
using return_type = m5::stl::optional<value_type>;
#endif
class iterator;
class const_iterator;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
///@name Constructor
///@{
CircularBuffer() = delete;
explicit CircularBuffer(const size_t n)
{
assert(n != 0 && "Illegal size");
_cap = n;
_buf.resize(n);
}
CircularBuffer(const size_type n, const_reference value) : CircularBuffer(n)
{
assign(n, value);
}
template <class InputIter>
CircularBuffer(const size_type n, InputIter first, InputIter last) : CircularBuffer(n)
{
assign(first, last);
}
CircularBuffer(const size_type n, std::initializer_list<T> il) : CircularBuffer(n, il.begin(), il.end())
{
}
CircularBuffer(const CircularBuffer&) = default;
CircularBuffer(CircularBuffer&&) noexcept = default;
///@}
/// @name Assignment
/// @{
/*! @brief Copy */
CircularBuffer& operator=(const CircularBuffer&) = default;
//! @brief Move
CircularBuffer& operator=(CircularBuffer&&) = default;
/*!
@brief Replaces the contents with copies of those in the range [first,
last)
@param first,last The Range to copy the elements from
*/
template <class InputIterator>
void assign(InputIterator first, InputIterator last)
{
clear();
size_type sz = last - first;
if (sz > _cap) {
first += (sz - _cap);
}
auto n = std::min(_cap, sz);
while (n--) {
push_back(*first++);
}
}
/*!
@brief assigns values to the container
@param n Number of elements
@param v Value to assign to the elements
@note Fill with the value as many times as n or the capacity
*/
void assign(size_type n, const_reference v)
{
clear();
n = std::min(_cap, n);
while (n--) {
push_back(v);
}
}
/*!
@brief assigns values to the container
@param il Initializer list from which the copy is made
*/
inline void assign(std::initializer_list<T> il)
{
assign(il.begin(), il.end());
}
/// @}
///@name Element access
///@{
/*!
@brief Access the first element
@return m5::stl::optional<value_type>
*/
inline return_type front() const
{
#if __cplusplus >= 201703L
return !empty() ? std::make_optional(_buf[_tail]) : std::nullopt;
#else
return !empty() ? m5::stl::make_optional(_buf[_tail]) : m5::stl::nullopt;
#endif
}
/*!
@brief Access the last element
@return m5::stl::optional<value_type>
*/
inline return_type back() const
{
#if __cplusplus >= 201703L
return !empty() ? std::make_optional(_buf[(_head - 1 + _cap) % _cap]) : std::nullopt;
#else
return !empty() ? m5::stl::make_optional(_buf[(_head - 1 + _cap) % _cap]) : m5::stl::nullopt;
#endif
}
/*!
@brief Access specified element
@return Reference to the requested element
*/
inline const_reference operator[](size_type i) const&
{
assert(size() > 0 && "container empty");
assert(i < size() && "index overflow");
return _buf[(_tail + i) % _cap];
}
/*!
@brief Access specified element with bounds checking
@return m5::stl::optional<value_type>
*/
inline return_type at(size_type i) const
{
#if __cplusplus >= 201703L
return (!empty() && i < size()) ? std::make_optional(_buf[(_tail + i) % _cap]) : std::nullopt;
#else
return (!empty() && i < size()) ? m5::stl::make_optional(_buf[(_tail + i) % _cap]) : m5::stl::nullopt;
#endif
}
/*!
@brief Read from buffer
@param[out] outbuf Output buffer
@param num Max elements of output buffer
@return Number of elements read
*/
size_t read(value_type* outbuf, const size_t num)
{
size_t sz = std::min(num, size());
if (sz == 0) {
return sz;
}
auto tail = _tail;
auto src = &_buf[tail];
size_t elms = std::min(_cap - tail, sz);
std::copy(src, src + elms, outbuf);
tail = (tail + elms) % _cap;
size_t ret = elms;
if (elms < sz) {
outbuf += elms;
src = &_buf[tail];
elms = sz - elms;
std::copy(src, src + elms, outbuf);
ret += elms;
}
return ret;
}
/// @}
///@name Capacity
///@{
/*!
@brief checks whether the container is empty
@return True if empty
*/
inline bool empty() const
{
return !full() && (_head == _tail);
}
/*!
@brief checks whether the container is full
@return True if full
*/
inline bool full() const
{
return _full;
}
/*!
@brief returns the number of elements
*/
inline size_type size() const
{
return full() ? _cap : (_head >= _tail ? _head - _tail : _cap + _head - _tail);
}
/*!
@brief Returns the number of elements that can be held in currently
storage
*/
inline size_type capacity() const
{
return _cap;
}
///@}
///@name Modifiers
///@{
/*! @brief Clears the contents */
void clear()
{
_full = false;
_head = _tail = 0U;
}
//! @brief Adds an element to the top
void push_front(const value_type& v)
{
_tail = (_tail - 1 + _cap) % _cap;
_buf[_tail] = v;
if (_full) {
_head = (_head - 1 + _cap) % _cap;
}
_full = (_head == _tail);
}
//! @brief Adds an element to the end
void push_back(const value_type& v)
{
_buf[_head] = v;
_head = (_head + 1) % _cap;
if (_full) {
_tail = (_tail + 1) % _cap;
}
_full = (_head == _tail);
}
//! @brief removes the top element
inline void pop_front()
{
if (!empty()) {
_tail = (_tail + 1) % _cap;
_full = false;
}
}
//! @brief removes the end element
inline void pop_back()
{
if (!empty()) {
_head = (_head - 1 + _cap) % _cap;
_full = false;
}
}
///@}
///@name Operations
///@{
/*!
@brief Assigns the value to all elements in the container
@param v Value to assign to the elements
*/
void fill(const value_type& v)
{
clear();
std::fill(_buf.begin(), _buf.end(), v);
_full = true;
}
/*!
@brief Swaps the contents
@param o Ccontainer to exchange the contents with
*/
void swap(CircularBuffer& o)
{
if (this != &o) {
std::swap(_buf, o._buf);
std::swap(_cap, o._cap);
std::swap(_head, o._head);
std::swap(_tail, o._tail);
std::swap(_full, o._full);
}
}
///@}
///@cond
class iterator {
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = CircularBuffer::value_type;
using pointer = CircularBuffer::value_type*;
using reference = CircularBuffer::reference;
iterator() : _buffer(nullptr), _pos(0)
{
}
iterator(CircularBuffer* buf, size_t pos) : _buffer(buf), _pos(pos)
{
}
inline reference operator*() const
{
return _buffer->_buf[_pos % _buffer->capacity()];
}
inline pointer operator->() const
{
return &(_buffer->_buf[_pos % _buffer->capacity()]);
}
inline iterator& operator++()
{
++_pos;
return *this;
}
inline iterator& operator--()
{
--_pos;
return *this;
}
inline iterator operator++(int)
{
iterator tmp = *this;
++(*this);
return tmp;
}
inline iterator operator--(int)
{
iterator tmp = *this;
--(*this);
return tmp;
}
friend inline bool operator==(const iterator& a, const iterator& b)
{
return a._buffer == b._buffer && a._pos == b._pos;
}
friend inline bool operator!=(const iterator& a, const iterator& b)
{
return !(a == b);
}
private:
CircularBuffer* _buffer;
size_t _pos;
};
class const_iterator {
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = CircularBuffer::value_type;
using pointer = const CircularBuffer::value_type*;
using reference = CircularBuffer::const_reference;
const_iterator() : _buffer(nullptr), _pos(0)
{
}
const_iterator(const CircularBuffer* buf, size_t pos) : _buffer(buf), _pos(pos)
{
}
inline reference operator*() const
{
return _buffer->_buf[_pos % _buffer->capacity()];
}
inline pointer operator->() const
{
return &(_buffer->_buf[_pos % _buffer->capacity()]);
}
inline const_iterator& operator++()
{
++_pos;
return *this;
}
inline const_iterator& operator--()
{
--_pos;
return *this;
}
inline const_iterator operator++(int)
{
const_iterator tmp = *this;
++(*this);
return tmp;
}
inline const_iterator operator--(int)
{
const_iterator tmp = *this;
--(*this);
return tmp;
}
friend inline bool operator==(const const_iterator& a, const const_iterator& b)
{
return a._buffer == b._buffer && a._pos == b._pos;
}
friend inline bool operator!=(const const_iterator& a, const const_iterator& b)
{
return !(a == b);
}
private:
const CircularBuffer* _buffer;
size_t _pos;
};
///@endcond
///@note Iterator is bidirectional
/// @name Iterator
/// @{
inline iterator begin() noexcept
{
return iterator(this, _tail);
}
inline iterator end() noexcept
{
return iterator(this, _tail + size());
}
inline const_iterator cbegin() const noexcept
{
return const_iterator(this, _tail);
}
inline const_iterator cend() const noexcept
{
return const_iterator(this, _tail + size());
}
inline reverse_iterator rbegin() noexcept
{
return std::reverse_iterator<iterator>(end());
}
inline reverse_iterator rend() noexcept
{
return std::reverse_iterator<iterator>(begin());
}
inline const_reverse_iterator crbegin() const noexcept
{
return std::reverse_iterator<const_iterator>(cend());
}
inline const_reverse_iterator crend() const noexcept
{
return std::reverse_iterator<const_iterator>(cbegin());
}
/// @}
private:
std::vector<T> _buf{};
size_t _cap{}, _head{}, _tail{};
bool _full{};
};
/*!
@class FixedCircularBuffer
@brief Type CircularBuffer giving size in template parameter
@tparam T Type of the element
@tpatam N Capacity of the buffer
*/
template <typename T, size_t N>
class FixedCircularBuffer : public CircularBuffer<T> {
public:
using value_type = T;
using size_type = size_t;
using reference = T&;
using const_reference = const T&;
#if __cplusplus >= 201703L
using return_type = std::optional<value_type>;
#else
using return_type = m5::stl::optional<value_type>;
#endif
FixedCircularBuffer() : CircularBuffer<T>(N)
{
}
FixedCircularBuffer(const size_type n, const_reference value) : CircularBuffer<T>(N)
{
CircularBuffer<T>::assign(n, value);
}
template <class InputIter>
FixedCircularBuffer(InputIter first, InputIter last) : CircularBuffer<T>(N, first, last)
{
}
FixedCircularBuffer(std::initializer_list<T> il) : CircularBuffer<T>(N, il)
{
}
FixedCircularBuffer(const FixedCircularBuffer&) = default;
FixedCircularBuffer(FixedCircularBuffer&&) = default;
FixedCircularBuffer& operator=(const FixedCircularBuffer&) = default;
FixedCircularBuffer& operator=(FixedCircularBuffer&&) noexcept = default;
};
} // namespace container
} // namespace m5
namespace std {
/*!
@brief Specializes the std::swap algorithm
@related m5::container::CircularBuffer
@param a,b Containers whose contents to swap
*/
template <typename T>
inline void swap(m5::container::CircularBuffer<T>& a, m5::container::CircularBuffer<T>& b)
{
a.swap(b);
}
} // namespace std
#endif

View file

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file conversion.hpp
@brief Numeric conversion
*/
#ifndef M5_UTILITY_CONVERSION_HPP
#define M5_UTILITY_CONVERSION_HPP
#include <type_traits>
#include <cstdint>
#include <cstddef>
namespace m5 {
namespace utility {
/*!
@brief Convert an unsigned integer of any maximum number of bits to a signed
integer
@tparam Bits Number of bits assumed by value
@code {.cpp}
uint32_t u24{0x00FFFFFF};
// 24 bit unsigned int to int32_t
uint32_t s32 = unsigned_to_signed<24>(u24);
// s32 is -1 (Not 16777215)
@endcode
*/
template <size_t Bits, typename T>
constexpr auto unsigned_to_signed(const T v) -> typename std::make_signed<T>::type
{
static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, "T must be an unsigned integer");
static_assert(Bits <= sizeof(T) * 8, "Bits must be less than or equal to the number of bits in T");
using S = typename std::make_signed<T>::type;
return static_cast<S>((v & (1ULL << (Bits - 1))) ? (v & ((1ULL << Bits) - 1)) - (1ULL << Bits)
: (v & ((1ULL << Bits) - 1)));
}
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,220 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file crc.hpp
@brief Calculate CRC
*/
#ifndef M5_UTILITY_CRC_HPP
#define M5_UTILITY_CRC_HPP
#include <cstdint>
#include <cstddef>
#include "misc.hpp"
namespace m5 {
namespace utility {
/*!
@class CRC8
@brief Base class of the CRC8 calculator
*/
class CRC8 {
public:
CRC8() = delete;
/*!
@param init Initial value
@param polynormal Generated polynomial
@param refIn Inverted input?
@param refOut Inverted output?
@param xorout Exclusive OR output
*/
CRC8(const uint8_t init, const uint8_t polynomial, const bool refIn, const bool refOut, const uint8_t xorout)
: _crc{init}, _init(init), _polynomial{polynomial}, _xorout{xorout}, _refIn{refIn}, _refOut{refOut}
{
}
/*!
@brief Calculate the CRC of the specified range
@param data Pointer of the array
@param len Length of the array
@return CRC value
*/
inline uint8_t range(const uint8_t* data, size_t len)
{
auto crc = calculate(data, len, _init, _polynomial, _refIn, _refOut, _xorout, false);
return finalize(crc, _refOut, _xorout);
}
/*!
@brief Stores the CRC of the specified array using the current internal
information
@param data Pointer of the array
@param len Length of the array
@return CRC value
@note Used when you want to calculate the value of the entire divided
continuous data, such as streaming data
*/
inline uint8_t update(const uint8_t* data, size_t len)
{
_crc = calculate(data, len, _crc, _polynomial, _refIn, _refOut, _xorout, false);
return finalize(_crc, _refOut, _xorout);
}
/*!
@brief CRC value at the time of the call
@return CRC value
*/
inline uint8_t value() const
{
return finalize(_crc, _refOut, _xorout);
}
/*!
@brief Calculate CRC8
@param data Pointer of the array
@param len Length of the array
@param polynormal Generated polynomial
@param refIn Inverted input?
@param refOut Inverted output?
@param xorout Exclusive OR output
@param do_finalize Apply processing to output values?(true as defaut)
@return CRC value
*/
static uint8_t calculate(const uint8_t* data, size_t len, const uint8_t init, const uint8_t polynomial,
const bool refIn, const bool refOut, const uint8_t xorout, bool do_finalize = true)
{
uint8_t crc{init};
while (len--) {
uint8_t e = refIn ? reverseBitOrder(*data) : *data;
++data;
crc ^= e;
uint_fast8_t cnt{8};
while (cnt--) {
if (crc & 0x80) {
crc = (crc << 1) ^ polynomial;
} else {
crc <<= 1;
}
}
}
return do_finalize ? finalize(crc, refOut, xorout) : crc;
}
protected:
static inline uint8_t finalize(const uint8_t value, const bool refOut, const uint8_t xorout)
{
return (refOut ? reverseBitOrder(value) : value) ^ xorout;
}
private:
uint8_t _crc{}, _init{}, _polynomial{}, _xorout{};
bool _refIn{}, _refOut{};
};
/*!
@class CRC16
@brief Base class of the CRC16 calculator
*/
class CRC16 {
public:
CRC16() = delete;
/*!
@param init Initial value
@param polynormal Generated polynomial
@param refIn Inverted input?
@param refOut Inverted output?
@param xorout Exclusive OR output
*/
CRC16(const uint16_t init, const uint16_t polynomial, const bool refIn, const bool refOut, const uint16_t xorout)
: _crc{init}, _init{init}, _polynomial{polynomial}, _xorout{xorout}, _refIn{refIn}, _refOut{refOut}
{
}
/*!
@brief Calculate the CRC of the specified range
@param data Pointer of the array
@param len Length of the array
@return CRC value
*/
inline uint16_t range(const uint8_t* data, size_t len)
{
auto crc = calculate(data, len, _init, _polynomial, _refIn, _refOut, _xorout, false);
return finalize(crc, _refOut, _xorout);
}
/*!
@brief Stores the CRC of the specified array using the current internal
information
@param data Pointer of the array
@param len Length of the array
@return CRC value
@note Used when you want to calculate the value of the entire divided
continuous data, such as streaming data
*/
inline uint16_t update(const uint8_t* data, size_t len)
{
_crc = calculate(data, len, _crc, _polynomial, _refIn, _refOut, _xorout, false);
return finalize(_crc, _refOut, _xorout);
}
/*!
@brief CRC value at the time of the call
@return CRC value
*/
inline uint16_t value() const
{
return finalize(_crc, _refOut, _xorout);
}
/*!
@brief Calculate CRC16
@param data Pointer of the array
@param len Length of the array
@param polynormal Generated polynomial
@param refIn Inverted input?
@param refOut Inverted output?
@param xorout Exclusive OR output
@param do_finalize Apply processing to output values?(true as defaut)
@return CRC value
*/
static uint16_t calculate(const uint8_t* data, size_t len, const uint16_t init, const uint16_t polynomial,
const bool refIn, const bool refOut, const uint16_t xorout, bool do_finalize = true)
{
uint16_t crc{init};
while (len--) {
uint8_t e{refIn ? reverseBitOrder(*data) : *data};
++data;
crc ^= (e << 8);
uint_fast8_t cnt{8};
while (cnt--) {
if (crc & 0x8000) {
crc = (crc << 1) ^ polynomial;
} else {
crc <<= 1;
}
}
}
return do_finalize ? finalize(crc, refOut, xorout) : crc;
}
protected:
static inline uint16_t finalize(const uint16_t value, const bool refOut, const uint16_t xorout)
{
return (refOut ? reverseBitOrder(value) : value) ^ xorout;
}
private:
uint16_t _crc{}, _init{}, _polynomial{}, _xorout{};
bool _refIn{}, _refOut{};
};
/*!
@class CRC8_CheckSum
@brief Typical CRC8 calculator used for read and write data with the chip
*/
class CRC8_Checksum : public CRC8 {
public:
CRC8_Checksum() : CRC8(0xFF, 0x31, false, false, 0x00)
{
}
};
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file library_log.cpp
@brief Logging for libraries
*/
#include "library_log.hpp"
#include <cstdio>
#include <cstdarg>
#include <inttypes.h>
#include <algorithm>
#include <cctype>
namespace {
using clock = std::chrono::steady_clock;
// using clock = std::chrono::high_resolution_clock;
const clock::time_point start_at = clock::now();
} // namespace
namespace m5 {
namespace utility {
namespace log {
void logPrintf(const char* format, ...)
{
va_list ap;
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
#if !defined(ARDUINO)
fflush(stdout);
#endif
}
void dump(const void* iaddr, const size_t len, const bool align)
{
constexpr static char hc[] = "0123456789ABCDEF";
uint8_t* addr = (uint8_t*)iaddr;
uint8_t abyte = align ? 0x0F : 0x00;
uint_fast8_t skip_left{(uint_fast8_t)((uintptr_t)addr & abyte)};
char hex[128]{};
printf("DUMP:0x%08" PRIxPTR " %zu bytes\n", (uintptr_t)addr, len);
// First line
size_t i{}, ia{};
uint_fast8_t cols = std::min(len - i, (size_t)16 - skip_left);
uint8_t left = snprintf(hex, sizeof(hex), "0x%08" PRIxPTR "| ", (uintptr_t)addr & ~abyte);
for (uint_fast8_t s = 0; s < skip_left; ++s) {
hex[left++] = ' ';
hex[left++] = ' ';
hex[left++] = ' ';
}
for (uint_fast8_t c = 0; c < cols; ++c) {
left += snprintf(hex + left, 4, "%c%c ", hc[(addr[i] >> 4) & 0x0F], hc[addr[i] & 0x0F]);
++i;
}
for (uint_fast8_t s = skip_left; s < 16U - cols; ++s) {
hex[left++] = ' ';
hex[left++] = ' ';
hex[left++] = ' ';
}
hex[left++] = '|';
for (uint_fast8_t s = 0; s < skip_left; ++s) {
hex[left++] = ' ';
}
for (uint_fast8_t c = 0; c < cols; ++c) {
left += snprintf(hex + left, 2, "%c", std::isprint(addr[ia]) ? (char)addr[ia] : '.');
++ia;
}
puts(hex);
// Second line~
while (i < len) {
cols = std::min(len - i, (size_t)16U);
left = snprintf(hex, sizeof(hex), "0x%08" PRIxPTR "| ", (uintptr_t)(addr + i) & ~abyte);
for (uint_fast8_t c = 0; c < cols; ++c) {
left += snprintf(hex + left, 4, "%c%c ", hc[(addr[i] >> 4) & 0x0F], hc[addr[i] & 0x0F]);
++i;
}
for (uint_fast8_t s = 0; s < 16U - cols; ++s) {
hex[left++] = ' ';
hex[left++] = ' ';
hex[left++] = ' ';
}
hex[left++] = '|';
for (uint_fast8_t c = 0; c < cols; ++c) {
left += snprintf(hex + left, 2, "%c", std::isprint(addr[ia]) ? (char)addr[ia] : '.');
++ia;
}
puts(hex);
}
}
elapsed_time_t elapsedTime()
{
return std::chrono::duration_cast<elapsed_time_t>(clock::now() - start_at);
}
} // namespace log
} // namespace utility
} // namespace m5

View file

@ -0,0 +1,206 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file library_log.hpp
@brief Logging for libraries
*/
#ifndef M5_UTILITY_LOG_LIBRARY_LOG_HPP
#define M5_UTILITY_LOG_LIBRARY_LOG_HPP
#include <cstdint>
#include <chrono>
namespace m5 {
namespace utility {
namespace log {
/*!
@enum log_level_t
@brief Log output control level
@details
*/
enum class LogLevel : uint8_t {
None, //!< No output
Error, //!< Error
Warn, //!< Warning
Info, //!< Information
Debug, //!< Debug
Verbose, //!< Verbose
};
using log_level_t = LogLevel;
#if defined(NDEBUG)
constexpr log_level_t logOutputLevel = log_level_t::None;
#elif defined(M5_LOG_LEVEL)
constexpr log_level_t logOutputLevel = static_cast<log_level_t>(M5_LOG_LEVEL);
#elif defined(CORE_DEBUG_LEVEL)
constexpr log_level_t logOutputLevel = static_cast<log_level_t>(CORE_DEBUG_LEVEL);
#else
/*!
@var logOutputLevel
@brief Base value of log level to be output
@details The value can be specified in the compile options.
-DM5_LOG_LEVEL=[0..5] or -DCORE_LOG_LEVEL=[0...5]
default as NONE
@warning No output if NDEBUG defined
*/
constexpr log_level_t logOutputLevel = log_level_t::None;
#endif
/// @cond
// Does the string contain slash?
constexpr bool containss_slash(const char* s)
{
return *s ? (*s == '/' ? true : containss_slash(s + 1)) : false;
}
// Returns the next position after the right-most slash
constexpr const char* after_right_slash(const char* s)
{
return (*s == '/') ? (s + 1) : after_right_slash(s - 1);
}
// Gets the tail of string
constexpr const char* tail(const char* s)
{
return *s ? tail(s + 1) : s;
}
/// @endcond
/*!
@brief Gets the filename from full pathname
@warning If the string is too long, the recursion depth may be too deep to
fail. (If compile time calculation)
*/
constexpr const char* pathToFilename(const char* path)
{
return (path && path[0]) ? (containss_slash(path) ? after_right_slash(tail(path)) : path) : "";
}
//! @brief Output formatted strings
void logPrintf(const char* format, ...);
//! @brief Output dump
void dump(const void* addr, const size_t len, const bool align = true);
using elapsed_time_t = std::chrono::milliseconds;
// using elapsed_time_t = std::chrono::microseconds;
//! @brief Gets the elapsed time for log
elapsed_time_t elapsedTime();
///@cond
#ifndef M5_UTILITY_LOG_FORMAT
#define M5_UTILITY_LOG_FORMAT(letter, format) \
"[%6lld][" #letter "][%s:%u] %s(): " format "\n", (int64_t)m5::utility::log::elapsedTime().count(), \
m5::utility::log::pathToFilename(__FILE__), __LINE__, __func__
#endif
///@endcond
/*!
@def M5_LIB_LOGE
@brief Output log (level ERROR)
*/
#define M5_LIB_LOGE(format, ...) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Error) { \
m5::utility::log::logPrintf(M5_UTILITY_LOG_FORMAT(E, format), ##__VA_ARGS__); \
} \
} while (0)
/*!
@def M5_LIB_LOGW
@brief Output log (level WARN)
*/
#define M5_LIB_LOGW(format, ...) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Warn) { \
m5::utility::log::logPrintf(M5_UTILITY_LOG_FORMAT(W, format), ##__VA_ARGS__); \
} \
} while (0)
/*!
@def M5_LIB_LOGI
@brief Output log (level INFO)
*/
#define M5_LIB_LOGI(format, ...) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Info) { \
m5::utility::log::logPrintf(M5_UTILITY_LOG_FORMAT(I, format), ##__VA_ARGS__); \
} \
} while (0)
/*!
@def M5_LIB_LOGD
@brief Output log (level DEBUG)
*/
#define M5_LIB_LOGD(format, ...) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Debug) { \
m5::utility::log::logPrintf(M5_UTILITY_LOG_FORMAT(D, format), ##__VA_ARGS__); \
} \
} while (0)
/*!
@def M5_LIB_LOGV
@brief Output log (level VERBOSE)
*/
#define M5_LIB_LOGV(format, ...) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Verbose) { \
m5::utility::log::logPrintf(M5_UTILITY_LOG_FORMAT(V, format), ##__VA_ARGS__); \
} \
} while (0)
/*!
@def M5_DUMPE
@brief Output log (level ERROR)
*/
#define M5_DUMPE(addr, len) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Error) { \
m5::utility::log::dump((addr), (len)); \
} \
} while (0)
/*!
@def M5_DUMPW
@brief Output log (level WARN)
*/
#define M5_DUMPW(addr, len) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Warn) { \
m5::utility::log::dump((addr), (len)); \
} \
} while (0)
/*!
@def M5_DUMPI
@brief Output log (level INFO)
*/
#define M5_DUMPI(addr, len) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Info) { \
m5::utility::log::dump((addr), (len)); \
} \
} while (0)
/*!
@def M5_DUMPD
@brief Output log (level DEBUG)
*/
#define M5_DUMPD(addr, len) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Debug) { \
m5::utility::log::dump((addr), (len)); \
} \
} while (0)
/*!
@def M5_DUMPV
@brief Output log (level VERBOSE)
*/
#define M5_DUMPV(addr, len) \
do { \
if (m5::utility::log::logOutputLevel >= m5::utility::log::log_level_t::Verbose) { \
m5::utility::log::dump((addr), (len)); \
} \
} while (0)
} // namespace log
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file math.hpp
@brief Maths-related
*/
#ifndef M5_UTILITY_MATH_HPP
#define M5_UTILITY_MATH_HPP
#include <cstdint>
#include <type_traits>
namespace m5 {
namespace math {
template <typename T>
/*!
@brief Is value power of 2?
@tparam Type of the value
@param v Value
@return True if value is power of 2
*/
inline constexpr bool is_powerof2(const T v)
{
static_assert(std::is_integral<T>::value, "The argument v is only an integer value.");
return v > 0 && ((v & (v - 1)) == 0);
}
} // namespace math
} // namespace m5
#endif

View file

@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file misc.hpp
@brief Miscellaneous features
*/
#ifndef M5_UTILITY_MISC_HPP
#define M5_UTILITY_MISC_HPP
#include <cstdint>
namespace m5 {
namespace utility {
//! @brief Valid I2C address?
inline bool isValidI2CAddress(const uint16_t addr)
{
if (addr <= 0x7F) { // 7 bit
return (addr >= 0x08 && addr <= 0x77);
}
return addr <= 0x3FF; // 10 bit
}
//! @brief Reversing the bit order
inline uint8_t reverseBitOrder(const uint8_t u8)
{
#if defined(__clang__) && 0
#pragma message "Using clang builtin"
return __builtin_bitreverse8(u8);
#else
uint8_t v{u8};
v = ((v & 0xF0) >> 4) | ((v & 0x0F) << 4);
v = ((v & 0xCC) >> 2) | ((v & 0x33) << 2);
v = ((v & 0xAA) >> 1) | ((v & 0x55) << 1);
return v;
#endif
}
//! @brief Reversing the bit order
inline uint16_t reverseBitOrder(const uint16_t u16)
{
#if defined(__clang__) && 0
#pragma message "Using clang builtin"
return __builtin_bitreverse16(u16);
#else
uint16_t v{u16};
v = ((v & 0xFF00) >> 8) | ((v & 0x00FF) << 8);
v = ((v & 0xF0F0) >> 4) | ((v & 0x0F0F) << 4);
v = ((v & 0xCCCC) >> 2) | ((v & 0x3333) << 2);
v = ((v & 0xAAAA) >> 1) | ((v & 0x5555) << 1);
return v;
#endif
}
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file murmurhash3.cpp
@brief MurmurHash3
*/
#include "murmurhash3.hpp"
namespace m5 {
namespace utility {
namespace mmh3 {
uint32_t calculate(const char* str)
{
auto len = strlen(str);
return finalize(rest(str + ((len >> 2) * sizeof(uint32_t)), (len & 3), group_of_4(str, len >> 2)), len);
}
} // namespace mmh3
} // namespace utility
} // namespace m5

View file

@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file murmurhash3.hpp
@brief MurmurHash3
MurmurHash (public domain) by Austin Appleby in 2008
@sa https://en.wikipedia.org/wiki/MurmurHash
*/
#ifndef M5_UTILITY_MURMURHASH3_HPP
#define M5_UTILITY_MURMURHASH3_HPP
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <type_traits>
#include "./stl/endianness.hpp"
namespace m5 {
namespace utility {
/*!
@namespace mmh3
@brief For Murmurhash3
*/
namespace mmh3 {
/// @cond
constexpr uint32_t mul(const uint32_t v, const uint32_t vv)
{
return v * vv;
}
constexpr uint32_t xor_value(const uint32_t v, const uint32_t x)
{
return v ^ x;
}
constexpr uint32_t shift_right(const uint32_t v, const uint8_t s)
{
return v >> s;
}
constexpr uint32_t xor_by_shift_right(const uint32_t v, const uint32_t s)
{
return xor_value(v, shift_right(v, s));
}
constexpr uint32_t str2uint32_little(const char* str, const size_t getc = sizeof(uint32_t), uint32_t v = 0)
{
return getc ? str2uint32_little(str, getc - 1, (v << 8) | str[getc - 1]) : v;
}
constexpr uint32_t str2uint32_big(const char* str, const size_t getc = sizeof(uint32_t), uint32_t v = 0)
{
return getc ? str2uint32_big(str + 1, getc - 1, (v << 8) | *str) : v;
}
// Switch between little and big enfian.
template <uint32_t Endian>
constexpr typename std::enable_if<Endian, uint32_t>::type str2uint32(const char* str,
const size_t getc = sizeof(uint32_t),
uint32_t v = 0)
{
return str2uint32_little(str, getc, v);
}
template <uint32_t Endian>
constexpr typename std::enable_if<!Endian, uint32_t>::type str2uint32(const char* str,
const size_t getc = sizeof(uint32_t),
uint32_t v = 0)
{
return str2uint32_big(str, getc, v);
}
constexpr uint32_t scramble_sub(const uint32_t k)
{
return (k << 15) | (k >> 17);
}
constexpr uint32_t scramble(const uint32_t k, const uint32_t h)
{
return h ^ mul(scramble_sub(mul(k, 0xcc9e2d51)), 0x1b873593);
}
constexpr uint32_t group_of_4_sub_3(const uint32_t h)
{
return h * 5 + 0xe6546b64;
}
constexpr uint32_t group_of_4_sub_2(const uint32_t h)
{
return (h << 13) | (h >> 19);
}
constexpr uint32_t group_of_4_sub_1(const uint32_t k, const uint32_t h)
{
return group_of_4_sub_3(group_of_4_sub_2(scramble(k, h)));
}
constexpr uint32_t group_of_4(const char* str, const size_t len, const uint32_t h = 0)
{
return len ? group_of_4(str + sizeof(uint32_t), len - 1, group_of_4_sub_1(str2uint32<m5::endian::little>(str), h))
: h;
}
constexpr uint32_t rest(const char* str, const size_t len, const uint32_t h = 0)
{
return len ? scramble(str2uint32<m5::endian::little>(str, len), h) : h;
}
constexpr uint32_t finalize(uint32_t h, size_t len)
{
return xor_by_shift_right(
mul(xor_by_shift_right(mul(xor_by_shift_right(xor_value(h, len), 16), 0x85ebca6b), 13), 0xc2b2ae35), 16);
}
/// @endcond
/*!
@brief MurmurHash3 by compile-time calculation
@param str String
@param len Length of string
@return 32bit MurmurHash3 from input string
*/
constexpr uint32_t calculate(const char* str, const size_t len)
{
return finalize(rest(str + ((len >> 2) * sizeof(uint32_t)), (len & 3), group_of_4(str, len >> 2)), len);
}
/*!
@brief User-defined literals "_mmh3"
@return 32bit MurmurHash3 from input string
@code
using namespace m5::unit::mmh3;
uint32_t h = "M5 Stack"_mmh3;
@endcode
*/
constexpr uint32_t operator"" _mmh3(const char* str, const size_t len)
{
return calculate(str, len);
};
/*!
@brief Calculate MurmurHash3 from string
@param str String
@return 32bit MurmurHash3 from input string
*/
uint32_t calculate(const char* str);
} // namespace mmh3
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file endianness.hpp
@brief Compile-time endian identification
*/
#ifndef M5_UTILITY_STL_ENDIANESS_HPP
#define M5_UTILITY_STL_ENDIANESS_HPP
#if __cplusplus >= 202002L
// #pragma message "Using std::endian"
#include <bit>
#elif __has_include(<endian.h>)
// #pragma message "Using endian.h"
#include <endian.h>
#elif __has_include(<machine/endian.h>)
// #pragma message "Using machine/endian.h"
#include <machine/endian.h>
#else
// #pragma message "Using hacked"
#include <cstdint>
#endif
namespace m5 {
namespace stl {
// C++20 or later
#if __cplusplus >= 202002L || DOXYGEN_PROCESS
using endian = std::endian;
// endian header
#elif __has_include(<endian.h>) || __has_include(<machine/endian.h>)
enum class endian {
#if defined(__BYTE_ORDER)
little = __LITTLE_ENDIAN,
big = __BIG_ENDIAN,
native = __BYTE_ORDER
#elif defined(_BYTE_ORDER)
little = _LITTLE_ENDIAN,
big = _BIG_ENDIAN,
native = _BYTE_ORDER
#elif defined(BYTE_ORDER)
little = LITTLE_ENDIAN,
big = BIG_ENDIAN,
native = BYTE_ORDER
#else
little = 0,
big = 0,
native = 0,
#endif
};
#else
/// @cond
constexpr uint32_t val32 = 0x11223344;
constexpr uint8_t ref8 = static_cast<const uint8_t&>(val32);
/// @endcond
enum class endian { little = 0x44, big = 0x11, native = ref8 };
#endif
} // namespace stl
/*!
@namespace enidan
@brief endianness detection
*/
namespace endian {
///@name endian type
///@{
constexpr bool little = m5::stl::endian::native == m5::stl::endian::little; //!< @brief true if little endian.
constexpr bool big = m5::stl::endian::native == m5::stl::endian::big; //!< @brief true if big endian.
constexpr bool other = !little && !big; //!< @brief true if other endian.
///@}
static_assert(little || big || other, "Unable to determine endianness");
static_assert(((int)little + (int)big + (int)other) == 1, "Endian matches more than one");
} // namespace endian
} // namespace m5
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
/*!
@file stl_extension.hpp
@brief STL extensions
Add features that cannot be used depending on the C++ version, etc.
SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
#ifndef M5_UTILITY_STL_EXTENSION_HPP
#define M5_UTILITY_STL_EXTENSION_HPP
#include <cstddef>
#include <type_traits>
namespace m5 {
namespace stl {
/*! @brief Like std::size C++17 or later.(for container) */
template <class C>
constexpr auto size(const C& c) -> decltype(c.size())
{
return c.size();
}
/*! @brief Like std::size C++17 or later.(for raw array) */
template <typename T, size_t N>
constexpr auto size(const T (&)[N]) noexcept -> size_t
{
return N;
}
/*!
@brief Converts an enumeration to its underlying type.(Like std::to_underlying
C++23 or later)
@tparam E Type of enum
@param e Enumeration value to convert
@return The integer value of the underlying type of Enum, converted from e.
*/
template <typename E>
constexpr inline typename std::underlying_type<E>::type to_underlying(const E e) noexcept
{
return static_cast<typename std::underlying_type<E>::type>(e);
}
} // namespace stl
} // namespace m5
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file string.cpp
@brief Utilities for string
*/
#include "string.hpp"
#include <cstdarg>
#include <algorithm>
namespace m5 {
namespace utility {
std::string formatString(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
size_t sz = vsnprintf(nullptr, 0U, fmt, args); // calculate length
va_end(args);
char buf[sz + 1];
va_start(args, fmt); // Reinitiaize args (args cannot reuse because
// indefinite value after vsnprintf)
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
// String don't has constructor(const char*, const size_t);
buf[sz] = '\0';
return std::string(buf);
}
std::string& trimRight(std::string& s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](std::string::value_type& ch) { return !std::isspace(ch); }));
return s;
}
std::string& trimLeft(std::string& s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](std::string::value_type& ch) { return !std::isspace(ch); }).base(),
s.end());
return s;
}
std::string& trim(std::string& s)
{
return trimRight(trimLeft(s));
}
} // namespace utility
} // namespace m5

View file

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file string.hpp
@brief Utilities for string
*/
#ifndef M5_UTILITY_STRING_HPP
#define M5_UTILITY_STRING_HPP
#include <string>
#include <cstdint>
namespace m5 {
namespace utility {
//! @ brief Create a string in a format similar to printf
std::string formatString(const char* fmt, ...);
///@name Trim
///@warning The string entered will be changed
///@{
/*! @brief Trim right */
std::string& trimRight(std::string& s);
//! @brief Trim left
std::string& trimLeft(std::string& s);
//! @brief Trim both ends
std::string& trim(std::string& s);
///@}
///@name Convert
///@{
/*!
@brief Convert from 0~15 to hexadecimal character
@tparam Case Capitalise if true
*/
template <bool Case = true>
constexpr char uintToHexChar(const uint8_t v)
{
return (v & 0x0F) < 10 ? '0' + (v & 0x0F) : (Case ? 'A' : 'a') + ((v & 0x0F) - 10);
}
/*!
@brief Convert any one unsigned integer to a hexadecimal string
@tparam T Value type (Must be unsigned integer)
*/
template <typename T, bool Case = true>
std::string unsignedToHexString(const T& v)
{
static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, "T must be unsigned integer");
std::string s;
for (size_t i = sizeof(T); i > 0; --i) {
uint8_t u8 = (v >> ((i - 1) * 8)) & 0xFF;
s += uintToHexChar<Case>((u8 >> 4) & 0x0F);
s += uintToHexChar<Case>(u8 & 0x0F);
}
return s;
}
///@}
} // namespace utility
} // namespace m5
#endif

View file

@ -0,0 +1,276 @@
/*
* Spdx-Filecopyrighttext: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file types.hpp
@brief Type and enumerator definitions
*/
#ifndef M5_UTILITY_TYPES_HPP
#define M5_UTILITY_TYPES_HPP
#include <cstdint>
#include <cstddef>
#include <utility>
#include <tuple>
#include "stl/endianness.hpp"
namespace m5 {
namespace types {
/*!
@struct U16
@tparam DELittle Endian type specification<br> true: Little false: Big
@brief Endian-compliant uint16
*/
template <bool DELittle>
union U16 {
/// @name Constrcutor
///@{
/*! @brief default constructor */
constexpr U16() : u16{0}
{
}
//!@brief from uint16_t
#if 0
template <bool PELittle = m5::endian::little>
constexpr explicit U16(const uint16_t v) : u8{
static_cast<uint8_t>(
DELittle == PELittle ? (v & 0XFF) : (v >> 8)),
u8[1] = static_cast<uint8_t>(
DELittle == PELittle ? (v >> 8) : (v & 0xFF)) } {}
#else
template <bool PELittle = m5::endian::little>
explicit U16(const uint16_t v)
{
set<PELittle>(v);
}
#endif
//! @brief Stored in order of high and low
constexpr U16(const uint8_t high, const uint8_t low) : u8{high, low}
{
}
constexpr U16(const U16&) = default;
constexpr U16(U16&& o) noexcept = default;
///@}
///@name Assignment
///@{
U16& operator=(const U16&) = default;
U16& operator=(U16&&) noexcept = default;
template <bool PELittle = m5::endian::little>
U16& operator=(const uint16_t v)
{
set<PELittle>(v);
return *this;
}
template <typename H, typename L>
U16& operator=(const std::pair<H, L>& o)
{
static_assert(std::is_integral<H>::value && std::is_integral<L>::value, "HIGH & LOW Must be integral");
u8[0] = static_cast<uint8_t>(o.first);
u8[1] = static_cast<uint8_t>(o.second);
return *this;
}
///@}
///@name Cast
///@{
/*! @brief To boolean */
inline explicit operator bool() const
{
return u16;
}
/*! @brief To const uint8_t* */
inline explicit operator const uint8_t*() const
{
return data();
}
/*! @brief To uint8_t* */
inline explicit operator uint8_t*() const
{
return data();
}
//! @brief To uint16_t on processor endianness
inline explicit operator uint16_t() const
{
return get();
}
///@}
/*!
@brief Set value with specified endianness
@tparam PELittle Endianness (default as processor endianness)
*/
template <bool PELittle = m5::endian::little>
inline void set(const uint16_t v)
{
if (DELittle == PELittle) {
u16 = v;
} else {
u8[0] = static_cast<uint8_t>(v >> 8);
u8[1] = static_cast<uint8_t>(v & 0xFF);
}
}
/*!
@brief Gets value with specified endianness
@tparam PELittle Endianness (default as processor endianness)
*/
template <bool PELittle = m5::endian::little>
inline uint16_t get() const
{
uint16_t r{u16};
if (DELittle != PELittle) {
r = U16<DELittle>{u8[1], u8[0]}.u16;
}
return r;
};
//! @brief Gets the high byte
inline uint8_t high() const
{
return u8[0];
}
//! @brief Gets the low byte
inline uint8_t low() const
{
return u8[1];
}
//! @brief Gets the const pointer
inline const uint8_t* data() const
{
return u8;
}
//! @brief Gets the pointer
inline uint8_t* data()
{
return u8;
}
//! @brief Gets size in uint8_t units.
inline size_t size() const
{
return 2;
}
uint16_t u16{}; //!< @brief Raw value
uint8_t u8[2]; //!< @brief Raw value according to uint8_t
};
using big_uint16_t = U16<false>;
using little_uint16_t = U16<true>;
///@name Compare
/// @related m5::types::U16
///@{
// ==
inline bool operator==(const big_uint16_t& a, const big_uint16_t& b)
{
return a.u16 == b.u16;
}
inline bool operator==(const big_uint16_t& a, const little_uint16_t& b)
{
return std::tie(a.u8[0], a.u8[1]) == std::tie(b.u8[1], b.u8[0]);
}
inline bool operator==(const little_uint16_t& a, const big_uint16_t& b)
{
return std::tie(a.u8[1], a.u8[0]) == std::tie(b.u8[0], b.u8[1]);
}
inline bool operator==(const little_uint16_t& a, const little_uint16_t& b)
{
return a.u16 == b.u16;
}
// !=
inline bool operator!=(const big_uint16_t& a, const big_uint16_t& b)
{
return !(a == b);
}
inline bool operator!=(const big_uint16_t& a, const little_uint16_t& b)
{
return !(a == b);
}
inline bool operator!=(const little_uint16_t& a, const big_uint16_t& b)
{
return !(a == b);
}
inline bool operator!=(const little_uint16_t& a, const little_uint16_t& b)
{
return !(a == b);
}
// <
inline bool operator<(const big_uint16_t& a, const big_uint16_t& b)
{
return a.u16 < b.u16;
}
inline bool operator<(const big_uint16_t& a, const little_uint16_t& b)
{
return std::tie(a.u8[0], a.u8[1]) < std::tie(b.u8[1], b.u8[0]);
}
inline bool operator<(const little_uint16_t& a, const big_uint16_t& b)
{
return std::tie(a.u8[1], a.u8[0]) < std::tie(b.u8[0], b.u8[1]);
}
inline bool operator<(const little_uint16_t& a, const little_uint16_t& b)
{
return a.u16 < b.u16;
}
// >
inline bool operator>(const big_uint16_t& a, const big_uint16_t& b)
{
return b < a;
}
inline bool operator>(const big_uint16_t& a, const little_uint16_t& b)
{
return b < a;
}
inline bool operator>(const little_uint16_t& a, const big_uint16_t& b)
{
return b < a;
}
inline bool operator>(const little_uint16_t& a, const little_uint16_t& b)
{
return b < a;
}
// <=
inline bool operator<=(const big_uint16_t& a, const big_uint16_t& b)
{
return !(a > b);
}
inline bool operator<=(const big_uint16_t& a, const little_uint16_t& b)
{
return !(a > b);
}
inline bool operator<=(const little_uint16_t& a, const big_uint16_t& b)
{
return !(a > b);
}
inline bool operator<=(const little_uint16_t& a, const little_uint16_t& b)
{
return !(a > b);
}
// >=
inline bool operator>=(const big_uint16_t& a, const big_uint16_t& b)
{
return !(a < b);
}
inline bool operator>=(const big_uint16_t& a, const little_uint16_t& b)
{
return !(a < b);
}
inline bool operator>=(const little_uint16_t& a, const big_uint16_t& b)
{
return !(a < b);
}
inline bool operator>=(const little_uint16_t& a, const little_uint16_t& b)
{
return !(a < b);
}
///@}
} // namespace types
} // namespace m5
#endif