mirror of
https://github.com/esphome/esphome.git
synced 2024-11-24 12:06:26 +01:00
[bytebuffer] Rework ByteBuffer using templates (#7638)
This commit is contained in:
parent
22f30d42a6
commit
858d97ccef
@ -85,6 +85,7 @@ esphome/components/bmp581/* @kahrendt
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/bytebuffer/* @clydebarrow
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @mreditor97
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
|
5
esphome/components/bytebuffer/__init__.py
Normal file
5
esphome/components/bytebuffer/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
# Allows bytebuffer to be configured in yaml, to allow use of the C++ api.
|
||||
|
||||
CONFIG_SCHEMA = {}
|
421
esphome/components/bytebuffer/bytebuffer.h
Normal file
421
esphome/components/bytebuffer/bytebuffer.h
Normal file
@ -0,0 +1,421 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bytebuffer {
|
||||
|
||||
enum Endian { LITTLE, BIG };
|
||||
|
||||
/**
|
||||
* A class modelled on the Java ByteBuffer class. It wraps a vector of bytes and permits putting and getting
|
||||
* items of various sizes, with an automatically incremented position.
|
||||
*
|
||||
* There are three variables maintained pointing into the buffer:
|
||||
*
|
||||
* capacity: the maximum amount of data that can be stored - set on construction and cannot be changed
|
||||
* limit: the limit of the data currently available to get or put
|
||||
* position: the current insert or extract position
|
||||
*
|
||||
* 0 <= position <= limit <= capacity
|
||||
*
|
||||
* In addition a mark can be set to the current position with mark(). A subsequent call to reset() will restore
|
||||
* the position to the mark.
|
||||
*
|
||||
* The buffer can be marked to be little-endian (default) or big-endian. All subsequent operations will use that order.
|
||||
*
|
||||
* The flip() operation will reset the position to 0 and limit to the current position. This is useful for reading
|
||||
* data from a buffer after it has been written.
|
||||
*
|
||||
* The code is defined here in the header file rather than in a .cpp file, so that it does not get compiled if not used.
|
||||
* The templated functions ensure that only those typed functions actually used are compiled. The functions
|
||||
* are implicitly inline-able which will aid performance.
|
||||
*/
|
||||
class ByteBuffer {
|
||||
public:
|
||||
// Default constructor (compatibility with TEMPLATABLE_VALUE)
|
||||
// Creates a zero-length ByteBuffer which is little use to anybody.
|
||||
ByteBuffer() : ByteBuffer(std::vector<uint8_t>()) {}
|
||||
|
||||
/**
|
||||
* Create a new Bytebuffer with the given capacity
|
||||
*/
|
||||
ByteBuffer(size_t capacity, Endian endianness = LITTLE)
|
||||
: data_(std::vector<uint8_t>(capacity)), endianness_(endianness), limit_(capacity){};
|
||||
|
||||
// templated functions to implement putting and getting data of various types. There are two flavours of all
|
||||
// functions - one that uses the position as the offset, and updates the position accordingly, and one that
|
||||
// takes an explicit offset and does not update the position.
|
||||
// Separate temnplates are provided for types that fit into 32 bits and those that are bigger. These delegate
|
||||
// the actual put/get to common code based around those sizes.
|
||||
// This reduces the code size and execution time for smaller types. A similar structure for e.g. 16 bits is unlikely
|
||||
// to provide any further benefit given that all target platforms are native 32 bit.
|
||||
|
||||
template<typename T>
|
||||
T get(typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
|
||||
// integral types that fit into 32 bit
|
||||
return static_cast<T>(this->get_uint32_(sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get(size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
|
||||
return static_cast<T>(this->get_uint32_(offset, sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void put(const T &value, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
|
||||
this->put_uint32_(static_cast<uint32_t>(value), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void put(const T &value, size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
|
||||
this->put_uint32_(static_cast<uint32_t>(value), offset, sizeof(T));
|
||||
}
|
||||
|
||||
// integral types that do not fit into 32 bit (basically only 64 bit types)
|
||||
template<typename T>
|
||||
T get(typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
return static_cast<T>(this->get_uint64_(sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get(size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
return static_cast<T>(this->get_uint64_(offset, sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void put(const T &value, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
this->put_uint64_(value, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void put(const T &value, size_t offset, typename std::enable_if<std::is_integral<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
this->put_uint64_(static_cast<uint64_t>(value), offset, sizeof(T));
|
||||
}
|
||||
|
||||
// floating point types. Caters for 32 and 64 bit floating point.
|
||||
template<typename T>
|
||||
T get(typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) {
|
||||
return bit_cast<T>(this->get_uint32_(sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get(typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
return bit_cast<T>(this->get_uint64_(sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get(size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) {
|
||||
return bit_cast<T>(this->get_uint32_(offset, sizeof(T)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get(size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
return bit_cast<T>(this->get_uint64_(offset, sizeof(T)));
|
||||
}
|
||||
template<typename T>
|
||||
void put(const T &value, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
|
||||
this->put_uint32_(bit_cast<uint32_t>(value), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void put(const T &value, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
this->put_uint64_(bit_cast<uint64_t>(value), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void put(const T &value, size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) {
|
||||
this->put_uint32_(bit_cast<uint32_t>(value), offset, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void put(const T &value, size_t offset, typename std::enable_if<std::is_floating_point<T>::value, T>::type * = 0,
|
||||
typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) {
|
||||
this->put_uint64_(bit_cast<uint64_t>(value), offset, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> static ByteBuffer wrap(T value, Endian endianness = LITTLE) {
|
||||
ByteBuffer buffer = ByteBuffer(sizeof(T), endianness);
|
||||
buffer.put(value);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static ByteBuffer wrap(std::vector<uint8_t> const &data, Endian endianness = LITTLE) {
|
||||
ByteBuffer buffer = {data};
|
||||
buffer.endianness_ = endianness;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static ByteBuffer wrap(const uint8_t *ptr, size_t len, Endian endianness = LITTLE) {
|
||||
return wrap(std::vector<uint8_t>(ptr, ptr + len), endianness);
|
||||
}
|
||||
|
||||
// convenience functions with explicit types named..
|
||||
void put_float(float value) { this->put(value); }
|
||||
void put_double(double value) { this->put(value); }
|
||||
|
||||
uint8_t get_uint8() { return this->data_[this->position_++]; }
|
||||
// Get a 16 bit unsigned value, increment by 2
|
||||
uint16_t get_uint16() { return this->get<uint16_t>(); }
|
||||
// Get a 24 bit unsigned value, increment by 3
|
||||
uint32_t get_uint24() { return this->get_uint32_(3); };
|
||||
// Get a 32 bit unsigned value, increment by 4
|
||||
uint32_t get_uint32() { return this->get<uint32_t>(); };
|
||||
// Get a 64 bit unsigned value, increment by 8
|
||||
uint64_t get_uint64() { return this->get<uint64_t>(); };
|
||||
// Signed versions of the get functions
|
||||
uint8_t get_int8() { return static_cast<int8_t>(this->get_uint8()); };
|
||||
int16_t get_int16() { return this->get<uint16_t>(); }
|
||||
int32_t get_int32() { return this->get<int32_t>(); }
|
||||
int64_t get_int64() { return this->get<int64_t>(); }
|
||||
// Get a float value, increment by 4
|
||||
float get_float() { return this->get<float>(); }
|
||||
// Get a double value, increment by 8
|
||||
double get_double() { return this->get<double>(); }
|
||||
|
||||
// Get a bool value, increment by 1
|
||||
bool get_bool() { return static_cast<bool>(this->get_uint8()); }
|
||||
|
||||
uint32_t get_int24(size_t offset) {
|
||||
auto value = this->get_uint24(offset);
|
||||
uint32_t mask = (~static_cast<uint32_t>(0)) << 23;
|
||||
if ((value & mask) != 0)
|
||||
value |= mask;
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t get_int24() {
|
||||
auto value = this->get_uint24();
|
||||
uint32_t mask = (~static_cast<uint32_t>(0)) << 23;
|
||||
if ((value & mask) != 0)
|
||||
value |= mask;
|
||||
return value;
|
||||
}
|
||||
std::vector<uint8_t> get_vector(size_t length, size_t offset) {
|
||||
auto start = this->data_.begin() + offset;
|
||||
return {start, start + length};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> get_vector(size_t length) {
|
||||
auto result = this->get_vector(length, this->position_);
|
||||
this->position_ += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Convenience named functions
|
||||
void put_uint8(uint8_t value) { this->data_[this->position_++] = value; }
|
||||
void put_uint16(uint16_t value) { this->put(value); }
|
||||
void put_uint24(uint32_t value) { this->put_uint32_(value, 3); }
|
||||
void put_uint32(uint32_t value) { this->put(value); }
|
||||
void put_uint64(uint64_t value) { this->put(value); }
|
||||
// Signed versions of the put functions
|
||||
void put_int8(int8_t value) { this->put_uint8(static_cast<uint8_t>(value)); }
|
||||
void put_int16(int16_t value) { this->put(value); }
|
||||
void put_int24(int32_t value) { this->put_uint32_(value, 3); }
|
||||
void put_int32(int32_t value) { this->put(value); }
|
||||
void put_int64(int64_t value) { this->put(value); }
|
||||
// Extra put functions
|
||||
void put_bool(bool value) { this->put_uint8(value); }
|
||||
|
||||
// versions of the above with an offset, these do not update the position
|
||||
|
||||
uint64_t get_uint64(size_t offset) { return this->get<uint64_t>(offset); }
|
||||
uint32_t get_uint24(size_t offset) { return this->get_uint32_(offset, 3); };
|
||||
double get_double(size_t offset) { return get<double>(offset); }
|
||||
|
||||
// Get one byte from the buffer, increment position by 1
|
||||
uint8_t get_uint8(size_t offset) { return this->data_[offset]; }
|
||||
// Get a 16 bit unsigned value, increment by 2
|
||||
uint16_t get_uint16(size_t offset) { return get<uint16_t>(offset); }
|
||||
// Get a 24 bit unsigned value, increment by 3
|
||||
uint32_t get_uint32(size_t offset) { return this->get<uint32_t>(offset); };
|
||||
// Get a 64 bit unsigned value, increment by 8
|
||||
uint8_t get_int8(size_t offset) { return get<int8_t>(offset); }
|
||||
int16_t get_int16(size_t offset) { return get<int16_t>(offset); }
|
||||
int32_t get_int32(size_t offset) { return get<int32_t>(offset); }
|
||||
int64_t get_int64(size_t offset) { return get<int64_t>(offset); }
|
||||
// Get a float value, increment by 4
|
||||
float get_float(size_t offset) { return get<float>(offset); }
|
||||
// Get a double value, increment by 8
|
||||
|
||||
// Get a bool value, increment by 1
|
||||
bool get_bool(size_t offset) { return this->get_uint8(offset); }
|
||||
|
||||
void put_uint8(uint8_t value, size_t offset) { this->data_[offset] = value; }
|
||||
void put_uint16(uint16_t value, size_t offset) { this->put(value, offset); }
|
||||
void put_uint24(uint32_t value, size_t offset) { this->put(value, offset); }
|
||||
void put_uint32(uint32_t value, size_t offset) { this->put(value, offset); }
|
||||
void put_uint64(uint64_t value, size_t offset) { this->put(value, offset); }
|
||||
// Signed versions of the put functions
|
||||
void put_int8(int8_t value, size_t offset) { this->put_uint8(static_cast<uint8_t>(value), offset); }
|
||||
void put_int16(int16_t value, size_t offset) { this->put(value, offset); }
|
||||
void put_int24(int32_t value, size_t offset) { this->put_uint32_(value, offset, 3); }
|
||||
void put_int32(int32_t value, size_t offset) { this->put(value, offset); }
|
||||
void put_int64(int64_t value, size_t offset) { this->put(value, offset); }
|
||||
// Extra put functions
|
||||
void put_float(float value, size_t offset) { this->put(value, offset); }
|
||||
void put_double(double value, size_t offset) { this->put(value, offset); }
|
||||
void put_bool(bool value, size_t offset) { this->put_uint8(value, offset); }
|
||||
void put(const std::vector<uint8_t> &value, size_t offset) {
|
||||
std::copy(value.begin(), value.end(), this->data_.begin() + offset);
|
||||
}
|
||||
void put_vector(const std::vector<uint8_t> &value, size_t offset) { this->put(value, offset); }
|
||||
void put(const std::vector<uint8_t> &value) {
|
||||
this->put_vector(value, this->position_);
|
||||
this->position_ += value.size();
|
||||
}
|
||||
void put_vector(const std::vector<uint8_t> &value) { this->put(value); }
|
||||
|
||||
// Getters
|
||||
|
||||
inline size_t get_capacity() const { return this->data_.size(); }
|
||||
inline size_t get_position() const { return this->position_; }
|
||||
inline size_t get_limit() const { return this->limit_; }
|
||||
inline size_t get_remaining() const { return this->get_limit() - this->get_position(); }
|
||||
inline Endian get_endianness() const { return this->endianness_; }
|
||||
inline void mark() { this->mark_ = this->position_; }
|
||||
inline void big_endian() { this->endianness_ = BIG; }
|
||||
inline void little_endian() { this->endianness_ = LITTLE; }
|
||||
// retrieve a pointer to the underlying data.
|
||||
std::vector<uint8_t> get_data() { return this->data_; };
|
||||
|
||||
void get_bytes(void *dest, size_t length) {
|
||||
std::copy(this->data_.begin() + this->position_, this->data_.begin() + this->position_ + length, (uint8_t *) dest);
|
||||
this->position_ += length;
|
||||
}
|
||||
|
||||
void get_bytes(void *dest, size_t length, size_t offset) {
|
||||
std::copy(this->data_.begin() + offset, this->data_.begin() + offset + length, (uint8_t *) dest);
|
||||
}
|
||||
|
||||
void rewind() { this->position_ = 0; }
|
||||
void reset() { this->position_ = this->mark_; }
|
||||
|
||||
void set_limit(size_t limit) { this->limit_ = limit; }
|
||||
void set_position(size_t position) { this->position_ = position; }
|
||||
void clear() {
|
||||
this->limit_ = this->get_capacity();
|
||||
this->position_ = 0;
|
||||
}
|
||||
void flip() {
|
||||
this->limit_ = this->position_;
|
||||
this->position_ = 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t get_uint64_(size_t offset, size_t length) const {
|
||||
uint64_t value = 0;
|
||||
if (this->endianness_ == LITTLE) {
|
||||
offset += length;
|
||||
while (length-- != 0) {
|
||||
value <<= 8;
|
||||
value |= this->data_[--offset];
|
||||
}
|
||||
} else {
|
||||
while (length-- != 0) {
|
||||
value <<= 8;
|
||||
value |= this->data_[offset++];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint64_t get_uint64_(size_t length) {
|
||||
auto result = this->get_uint64_(this->position_, length);
|
||||
this->position_ += length;
|
||||
return result;
|
||||
}
|
||||
uint32_t get_uint32_(size_t offset, size_t length) const {
|
||||
uint32_t value = 0;
|
||||
if (this->endianness_ == LITTLE) {
|
||||
offset += length;
|
||||
while (length-- != 0) {
|
||||
value <<= 8;
|
||||
value |= this->data_[--offset];
|
||||
}
|
||||
} else {
|
||||
while (length-- != 0) {
|
||||
value <<= 8;
|
||||
value |= this->data_[offset++];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t get_uint32_(size_t length) {
|
||||
auto result = this->get_uint32_(this->position_, length);
|
||||
this->position_ += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Putters
|
||||
|
||||
void put_uint64_(uint64_t value, size_t length) {
|
||||
this->put_uint64_(value, this->position_, length);
|
||||
this->position_ += length;
|
||||
}
|
||||
void put_uint32_(uint32_t value, size_t length) {
|
||||
this->put_uint32_(value, this->position_, length);
|
||||
this->position_ += length;
|
||||
}
|
||||
|
||||
void put_uint64_(uint64_t value, size_t offset, size_t length) {
|
||||
if (this->endianness_ == LITTLE) {
|
||||
while (length-- != 0) {
|
||||
this->data_[offset++] = static_cast<uint8_t>(value);
|
||||
value >>= 8;
|
||||
}
|
||||
} else {
|
||||
offset += length;
|
||||
while (length-- != 0) {
|
||||
this->data_[--offset] = static_cast<uint8_t>(value);
|
||||
value >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void put_uint32_(uint32_t value, size_t offset, size_t length) {
|
||||
if (this->endianness_ == LITTLE) {
|
||||
while (length-- != 0) {
|
||||
this->data_[offset++] = static_cast<uint8_t>(value);
|
||||
value >>= 8;
|
||||
}
|
||||
} else {
|
||||
offset += length;
|
||||
while (length-- != 0) {
|
||||
this->data_[--offset] = static_cast<uint8_t>(value);
|
||||
value >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
ByteBuffer(std::vector<uint8_t> const &data) : data_(data), limit_(data.size()) {}
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
Endian endianness_{LITTLE};
|
||||
size_t position_{0};
|
||||
size_t mark_{0};
|
||||
size_t limit_{0};
|
||||
};
|
||||
|
||||
} // namespace bytebuffer
|
||||
} // namespace esphome
|
@ -1,167 +0,0 @@
|
||||
#include "bytebuffer.h"
|
||||
#include <cassert>
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(const uint8_t *ptr, size_t len, Endian endianness) {
|
||||
// there is a double copy happening here, could be optimized but at cost of clarity.
|
||||
std::vector<uint8_t> data(ptr, ptr + len);
|
||||
ByteBuffer buffer = {data};
|
||||
buffer.endianness_ = endianness;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(std::vector<uint8_t> const &data, Endian endianness) {
|
||||
ByteBuffer buffer = {data};
|
||||
buffer.endianness_ = endianness;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(uint8_t value) {
|
||||
ByteBuffer buffer = ByteBuffer(1);
|
||||
buffer.put_uint8(value);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(uint16_t value, Endian endianness) {
|
||||
ByteBuffer buffer = ByteBuffer(2, endianness);
|
||||
buffer.put_uint16(value);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(uint32_t value, Endian endianness) {
|
||||
ByteBuffer buffer = ByteBuffer(4, endianness);
|
||||
buffer.put_uint32(value);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(uint64_t value, Endian endianness) {
|
||||
ByteBuffer buffer = ByteBuffer(8, endianness);
|
||||
buffer.put_uint64(value);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(float value, Endian endianness) {
|
||||
ByteBuffer buffer = ByteBuffer(sizeof(float), endianness);
|
||||
buffer.put_float(value);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer ByteBuffer::wrap(double value, Endian endianness) {
|
||||
ByteBuffer buffer = ByteBuffer(sizeof(double), endianness);
|
||||
buffer.put_double(value);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ByteBuffer::set_limit(size_t limit) {
|
||||
assert(limit <= this->get_capacity());
|
||||
this->limit_ = limit;
|
||||
}
|
||||
void ByteBuffer::set_position(size_t position) {
|
||||
assert(position <= this->get_limit());
|
||||
this->position_ = position;
|
||||
}
|
||||
void ByteBuffer::clear() {
|
||||
this->limit_ = this->get_capacity();
|
||||
this->position_ = 0;
|
||||
}
|
||||
void ByteBuffer::flip() {
|
||||
this->limit_ = this->position_;
|
||||
this->position_ = 0;
|
||||
}
|
||||
|
||||
/// Getters
|
||||
uint8_t ByteBuffer::get_uint8() {
|
||||
assert(this->get_remaining() >= 1);
|
||||
return this->data_[this->position_++];
|
||||
}
|
||||
uint64_t ByteBuffer::get_uint(size_t length) {
|
||||
assert(this->get_remaining() >= length);
|
||||
uint64_t value = 0;
|
||||
if (this->endianness_ == LITTLE) {
|
||||
this->position_ += length;
|
||||
auto index = this->position_;
|
||||
while (length-- != 0) {
|
||||
value <<= 8;
|
||||
value |= this->data_[--index];
|
||||
}
|
||||
} else {
|
||||
while (length-- != 0) {
|
||||
value <<= 8;
|
||||
value |= this->data_[this->position_++];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t ByteBuffer::get_int24() {
|
||||
auto value = this->get_uint24();
|
||||
uint32_t mask = (~static_cast<uint32_t>(0)) << 23;
|
||||
if ((value & mask) != 0)
|
||||
value |= mask;
|
||||
return value;
|
||||
}
|
||||
float ByteBuffer::get_float() {
|
||||
assert(this->get_remaining() >= sizeof(float));
|
||||
return bit_cast<float>(this->get_uint32());
|
||||
}
|
||||
double ByteBuffer::get_double() {
|
||||
assert(this->get_remaining() >= sizeof(double));
|
||||
return bit_cast<double>(this->get_uint64());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ByteBuffer::get_vector(size_t length) {
|
||||
assert(this->get_remaining() >= length);
|
||||
auto start = this->data_.begin() + this->position_;
|
||||
this->position_ += length;
|
||||
return {start, start + length};
|
||||
}
|
||||
|
||||
/// Putters
|
||||
void ByteBuffer::put_uint8(uint8_t value) {
|
||||
assert(this->get_remaining() >= 1);
|
||||
this->data_[this->position_++] = value;
|
||||
}
|
||||
|
||||
void ByteBuffer::put_uint(uint64_t value, size_t length) {
|
||||
assert(this->get_remaining() >= length);
|
||||
if (this->endianness_ == LITTLE) {
|
||||
while (length-- != 0) {
|
||||
this->data_[this->position_++] = static_cast<uint8_t>(value);
|
||||
value >>= 8;
|
||||
}
|
||||
} else {
|
||||
this->position_ += length;
|
||||
auto index = this->position_;
|
||||
while (length-- != 0) {
|
||||
this->data_[--index] = static_cast<uint8_t>(value);
|
||||
value >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ByteBuffer::put_float(float value) {
|
||||
static_assert(sizeof(float) == sizeof(uint32_t), "Float sizes other than 32 bit not supported");
|
||||
assert(this->get_remaining() >= sizeof(float));
|
||||
this->put_uint32(bit_cast<uint32_t>(value));
|
||||
}
|
||||
void ByteBuffer::put_double(double value) {
|
||||
static_assert(sizeof(double) == sizeof(uint64_t), "Double sizes other than 64 bit not supported");
|
||||
assert(this->get_remaining() >= sizeof(double));
|
||||
this->put_uint64(bit_cast<uint64_t>(value));
|
||||
}
|
||||
void ByteBuffer::put_vector(const std::vector<uint8_t> &value) {
|
||||
assert(this->get_remaining() >= value.size());
|
||||
std::copy(value.begin(), value.end(), this->data_.begin() + this->position_);
|
||||
this->position_ += value.size();
|
||||
}
|
||||
} // namespace esphome
|
@ -1,144 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
enum Endian { LITTLE, BIG };
|
||||
|
||||
/**
|
||||
* A class modelled on the Java ByteBuffer class. It wraps a vector of bytes and permits putting and getting
|
||||
* items of various sizes, with an automatically incremented position.
|
||||
*
|
||||
* There are three variables maintained pointing into the buffer:
|
||||
*
|
||||
* capacity: the maximum amount of data that can be stored - set on construction and cannot be changed
|
||||
* limit: the limit of the data currently available to get or put
|
||||
* position: the current insert or extract position
|
||||
*
|
||||
* 0 <= position <= limit <= capacity
|
||||
*
|
||||
* In addition a mark can be set to the current position with mark(). A subsequent call to reset() will restore
|
||||
* the position to the mark.
|
||||
*
|
||||
* The buffer can be marked to be little-endian (default) or big-endian. All subsequent operations will use that order.
|
||||
*
|
||||
* The flip() operation will reset the position to 0 and limit to the current position. This is useful for reading
|
||||
* data from a buffer after it has been written.
|
||||
*
|
||||
*/
|
||||
class ByteBuffer {
|
||||
public:
|
||||
// Default constructor (compatibility with TEMPLATABLE_VALUE)
|
||||
ByteBuffer() : ByteBuffer(std::vector<uint8_t>()) {}
|
||||
/**
|
||||
* Create a new Bytebuffer with the given capacity
|
||||
*/
|
||||
ByteBuffer(size_t capacity, Endian endianness = LITTLE)
|
||||
: data_(std::vector<uint8_t>(capacity)), endianness_(endianness), limit_(capacity){};
|
||||
/**
|
||||
* Wrap an existing vector in a ByteBufffer
|
||||
*/
|
||||
static ByteBuffer wrap(std::vector<uint8_t> const &data, Endian endianness = LITTLE);
|
||||
/**
|
||||
* Wrap an existing array in a ByteBuffer. Note that this will create a copy of the data.
|
||||
*/
|
||||
static ByteBuffer wrap(const uint8_t *ptr, size_t len, Endian endianness = LITTLE);
|
||||
// Convenience functions to create a ByteBuffer from a value
|
||||
static ByteBuffer wrap(uint8_t value);
|
||||
static ByteBuffer wrap(uint16_t value, Endian endianness = LITTLE);
|
||||
static ByteBuffer wrap(uint32_t value, Endian endianness = LITTLE);
|
||||
static ByteBuffer wrap(uint64_t value, Endian endianness = LITTLE);
|
||||
static ByteBuffer wrap(int8_t value) { return wrap(static_cast<uint8_t>(value)); }
|
||||
static ByteBuffer wrap(int16_t value, Endian endianness = LITTLE) {
|
||||
return wrap(static_cast<uint16_t>(value), endianness);
|
||||
}
|
||||
static ByteBuffer wrap(int32_t value, Endian endianness = LITTLE) {
|
||||
return wrap(static_cast<uint32_t>(value), endianness);
|
||||
}
|
||||
static ByteBuffer wrap(int64_t value, Endian endianness = LITTLE) {
|
||||
return wrap(static_cast<uint64_t>(value), endianness);
|
||||
}
|
||||
static ByteBuffer wrap(float value, Endian endianness = LITTLE);
|
||||
static ByteBuffer wrap(double value, Endian endianness = LITTLE);
|
||||
static ByteBuffer wrap(bool value) { return wrap(static_cast<uint8_t>(value)); }
|
||||
|
||||
// Get an integral value from the buffer, increment position by length
|
||||
uint64_t get_uint(size_t length);
|
||||
// Get one byte from the buffer, increment position by 1
|
||||
uint8_t get_uint8();
|
||||
// Get a 16 bit unsigned value, increment by 2
|
||||
uint16_t get_uint16() { return static_cast<uint16_t>(this->get_uint(sizeof(uint16_t))); };
|
||||
// Get a 24 bit unsigned value, increment by 3
|
||||
uint32_t get_uint24() { return static_cast<uint32_t>(this->get_uint(3)); };
|
||||
// Get a 32 bit unsigned value, increment by 4
|
||||
uint32_t get_uint32() { return static_cast<uint32_t>(this->get_uint(sizeof(uint32_t))); };
|
||||
// Get a 64 bit unsigned value, increment by 8
|
||||
uint64_t get_uint64() { return this->get_uint(sizeof(uint64_t)); };
|
||||
// Signed versions of the get functions
|
||||
uint8_t get_int8() { return static_cast<int8_t>(this->get_uint8()); };
|
||||
int16_t get_int16() { return static_cast<int16_t>(this->get_uint(sizeof(int16_t))); }
|
||||
uint32_t get_int24();
|
||||
int32_t get_int32() { return static_cast<int32_t>(this->get_uint(sizeof(int32_t))); }
|
||||
int64_t get_int64() { return static_cast<int64_t>(this->get_uint(sizeof(int64_t))); }
|
||||
// Get a float value, increment by 4
|
||||
float get_float();
|
||||
// Get a double value, increment by 8
|
||||
double get_double();
|
||||
// Get a bool value, increment by 1
|
||||
bool get_bool() { return this->get_uint8(); }
|
||||
// Get vector of bytes, increment by length
|
||||
std::vector<uint8_t> get_vector(size_t length);
|
||||
|
||||
// Put values into the buffer, increment the position accordingly
|
||||
// put any integral value, length represents the number of bytes
|
||||
void put_uint(uint64_t value, size_t length);
|
||||
void put_uint8(uint8_t value);
|
||||
void put_uint16(uint16_t value) { this->put_uint(value, sizeof(uint16_t)); }
|
||||
void put_uint24(uint32_t value) { this->put_uint(value, 3); }
|
||||
void put_uint32(uint32_t value) { this->put_uint(value, sizeof(uint32_t)); }
|
||||
void put_uint64(uint64_t value) { this->put_uint(value, sizeof(uint64_t)); }
|
||||
// Signed versions of the put functions
|
||||
void put_int8(int8_t value) { this->put_uint8(static_cast<uint8_t>(value)); }
|
||||
void put_int16(int32_t value) { this->put_uint(static_cast<uint16_t>(value), sizeof(uint16_t)); }
|
||||
void put_int24(int32_t value) { this->put_uint(static_cast<uint32_t>(value), 3); }
|
||||
void put_int32(int32_t value) { this->put_uint(static_cast<uint32_t>(value), sizeof(uint32_t)); }
|
||||
void put_int64(int64_t value) { this->put_uint(static_cast<uint64_t>(value), sizeof(uint64_t)); }
|
||||
// Extra put functions
|
||||
void put_float(float value);
|
||||
void put_double(double value);
|
||||
void put_bool(bool value) { this->put_uint8(value); }
|
||||
void put_vector(const std::vector<uint8_t> &value);
|
||||
|
||||
inline size_t get_capacity() const { return this->data_.size(); }
|
||||
inline size_t get_position() const { return this->position_; }
|
||||
inline size_t get_limit() const { return this->limit_; }
|
||||
inline size_t get_remaining() const { return this->get_limit() - this->get_position(); }
|
||||
inline Endian get_endianness() const { return this->endianness_; }
|
||||
inline void mark() { this->mark_ = this->position_; }
|
||||
inline void big_endian() { this->endianness_ = BIG; }
|
||||
inline void little_endian() { this->endianness_ = LITTLE; }
|
||||
void set_limit(size_t limit);
|
||||
void set_position(size_t position);
|
||||
// set position to 0, limit to capacity.
|
||||
void clear();
|
||||
// set limit to current position, postition to zero. Used when swapping from write to read operations.
|
||||
void flip();
|
||||
// retrieve a pointer to the underlying data.
|
||||
std::vector<uint8_t> get_data() { return this->data_; };
|
||||
void rewind() { this->position_ = 0; }
|
||||
void reset() { this->position_ = this->mark_; }
|
||||
|
||||
protected:
|
||||
ByteBuffer(std::vector<uint8_t> const &data) : data_(data), limit_(data.size()) {}
|
||||
std::vector<uint8_t> data_;
|
||||
Endian endianness_{LITTLE};
|
||||
size_t position_{0};
|
||||
size_t mark_{0};
|
||||
size_t limit_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome
|
161
tests/components/bytebuffer/common.yaml
Normal file
161
tests/components/bytebuffer/common.yaml
Normal file
@ -0,0 +1,161 @@
|
||||
bytebuffer:
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- lambda: |-
|
||||
using namespace bytebuffer;
|
||||
auto buf = ByteBuffer(16);
|
||||
assert(buf.get_endianness() == LITTLE);
|
||||
assert(buf.get_remaining() == 16);
|
||||
buf.set_limit(10);
|
||||
assert(buf.get_capacity() == 16);
|
||||
buf.put_uint8(1);
|
||||
assert(buf.get_remaining() == 9);
|
||||
buf.put_uint16(0xABCD);
|
||||
auto da = buf.get_data();
|
||||
assert(buf.get_uint8(0) == 1);
|
||||
auto x = buf.get_uint16(1);
|
||||
assert(buf.get_uint16(1) == 0xABCD);
|
||||
assert(buf.get_remaining() == 7);
|
||||
buf.put_uint32(0x12345678UL);
|
||||
assert(buf.get_uint32(3) == 0x12345678UL);
|
||||
assert(buf.get_remaining() == 3);
|
||||
assert(buf.get_data()[1] == 0xCD);
|
||||
assert(buf.get_data()[2] == 0xAB);
|
||||
assert(buf.get_data()[3] == 0x78);
|
||||
assert(buf.get_data()[4] == 0x56);
|
||||
assert(buf.get_data()[5] == 0x34);
|
||||
assert(buf.get_data()[6] == 0x12);
|
||||
buf.flip();
|
||||
assert(buf.get_capacity() == 16);
|
||||
assert(buf.get_uint32(3) == 0x12345678UL);
|
||||
assert(buf.get_uint8(0) == 1);
|
||||
assert(buf.get_uint16(1) == 0xABCD);
|
||||
buf.put_uint16(0x1234, 1);
|
||||
assert(buf.get_uint16(1) == 0x1234);
|
||||
assert(buf.get_remaining() == 7);
|
||||
assert(buf.get_uint8() == 1);
|
||||
assert(buf.get_uint16() == 0x1234);
|
||||
assert(buf.get_uint32() == 0x12345678ul);
|
||||
assert(buf.get_remaining() == 0);
|
||||
assert(buf.get_remaining() == 0);
|
||||
buf.rewind();
|
||||
buf.big_endian();
|
||||
assert(buf.get_remaining() == 7);
|
||||
assert(buf.get_uint8() == 1);
|
||||
assert(buf.get_uint16() == 0x3412);
|
||||
buf.mark();
|
||||
assert(buf.get_uint32() == 0x78563412ul);
|
||||
assert(buf.get_remaining() == 0);
|
||||
buf.reset();
|
||||
assert(buf.get_remaining() == 4);
|
||||
assert(buf.get_uint32() == 0x78563412ul);
|
||||
auto buf1 = ByteBuffer::wrap(buf.get_data().data(), buf.get_limit());
|
||||
buf.clear();
|
||||
assert(buf.get_position() == 0);
|
||||
assert(buf.get_capacity() == 16);
|
||||
assert(buf.get_limit() == 16);
|
||||
assert(buf1.get_remaining() == 7);
|
||||
assert(buf1.get_capacity() == 7);
|
||||
buf1.set_position(3);
|
||||
assert(buf1.get_uint32() == 0x12345678ul);
|
||||
buf1.clear();
|
||||
assert(buf1.get_limit() == 7);
|
||||
assert(buf1.get_capacity() == 7);
|
||||
assert(buf1.get_position() == 0);
|
||||
float f = 1.2345;
|
||||
buf1.put_float(f);
|
||||
buf1.flip();
|
||||
assert(buf1.get_remaining() == 4);
|
||||
assert(buf1.get_float() == f);
|
||||
buf1.clear();
|
||||
buf1.put_uint16(-32760);
|
||||
buf1.put_uint24(-302760);
|
||||
buf1.flip();
|
||||
assert(buf1.get_int16() == -32760);
|
||||
assert(buf1.get_int24() == -302760);
|
||||
uint8_t arr[4] = {0x10, 0x20, 0x30, 0x40};
|
||||
buf1 = ByteBuffer::wrap(arr, 4);
|
||||
assert(buf1.get_capacity() == 4);
|
||||
assert(buf1.get_limit() == 4);
|
||||
assert(buf1.get_position() == 0);
|
||||
assert(buf1.get_uint32() == 0x40302010UL);
|
||||
assert(buf1.get_position() == 4);
|
||||
assert(buf1.get_remaining() == 0);
|
||||
std::vector<uint8_t> vec{};
|
||||
vec.push_back(0x10);
|
||||
vec.push_back(0x20);
|
||||
vec.push_back(0x30);
|
||||
vec.push_back(0x40);
|
||||
buf1 = ByteBuffer::wrap(vec);
|
||||
assert(buf1.get_capacity() == 4);
|
||||
assert(buf1.get_limit() == 4);
|
||||
assert(buf1.get_position() == 0);
|
||||
buf1.mark();
|
||||
buf1.reset();
|
||||
assert(buf1.get_uint32() == 0x40302010UL);
|
||||
buf = ByteBuffer::wrap(true);
|
||||
assert(buf.get_bool() == true);
|
||||
buf = ByteBuffer::wrap((uint8_t)0xFE);
|
||||
assert(buf.get_uint8() == 0xFE);
|
||||
buf = ByteBuffer::wrap((uint16_t)0xA5A6, BIG);
|
||||
assert(buf.get_remaining() == 2);
|
||||
assert(buf.get_position() == 0);
|
||||
assert(buf.get_capacity() == 2);
|
||||
assert(buf.get_endianness() == BIG);
|
||||
assert(buf.get_data()[0] == 0xA5);
|
||||
assert(buf.get_uint16() == 0xA5A6);
|
||||
buf.flip();
|
||||
buf.little_endian();
|
||||
assert(buf.get_uint16() == 0xA6A5);
|
||||
buf = ByteBuffer::wrap(f, BIG);
|
||||
assert(buf.get_float() == f);
|
||||
double d = 1.2345678E7;
|
||||
buf = ByteBuffer::wrap(d, BIG);
|
||||
assert(buf.get_double() == d);
|
||||
buf = ByteBuffer::wrap({1, 2, 3, 4}, BIG);
|
||||
assert(buf.get_endianness() == BIG);
|
||||
assert(buf.get_remaining() == 4);
|
||||
assert(buf.get_data()[2] == 3);
|
||||
buf.little_endian();
|
||||
assert(buf.get_data()[2] == 3);
|
||||
assert(buf.get_uint16() == 0x0201);
|
||||
buf.big_endian();
|
||||
assert(buf.get_uint16() == 0x0304);
|
||||
buf.rewind();
|
||||
vec = buf.get_vector(3);
|
||||
assert(buf.get_remaining() == 1);
|
||||
assert(vec[0] == 1);
|
||||
assert(vec.size() == 3);
|
||||
buf = ByteBuffer(10);
|
||||
buf.put_vector(vec);
|
||||
assert(buf.get_remaining() == 7);
|
||||
buf.flip();
|
||||
assert(buf.get_remaining() == 3);
|
||||
assert(buf.get_uint24() == 0x030201);
|
||||
buf = ByteBuffer(64);
|
||||
buf.put_uint8(1, 1);
|
||||
buf.put_uint16(16, 2);
|
||||
buf.put_uint32(1232, 4);
|
||||
buf.put_uint64(123432ul, 8);
|
||||
buf.put_float(1.2f, 16);
|
||||
buf.put_int24(0x678, 20);
|
||||
|
||||
assert(buf.get_uint8(1) == 1);
|
||||
assert(buf.get<uint8_t>(1) == 1);
|
||||
assert(buf.get_uint16(2) == 16);
|
||||
assert(buf.get<uint16_t>(2) == 16);
|
||||
assert(buf.get_uint32(4) == 1232);
|
||||
assert(buf.get<uint32_t>(4) == 1232);
|
||||
assert(buf.get_uint64(8) == 123432ul);
|
||||
assert(buf.get<uint64_t>(8) == 123432ul);
|
||||
assert(buf.get_float(16) == 1.2f);
|
||||
assert(buf.get<float>(16) == 1.2f);
|
||||
assert(buf.get_int24(20) == 0x678);
|
||||
buf.clear();
|
||||
buf.put(1.234, 10);
|
||||
double dx = buf.get<double>(10);
|
||||
assert(dx == 1.234);
|
||||
buf.put((uint16_t)1, 10);
|
||||
assert(buf.get_uint16(10) == 1);
|
||||
ESP_LOGD("bytebuffer", "******************** All tests succeeded");
|
1
tests/components/bytebuffer/test.esp32-ard.yaml
Normal file
1
tests/components/bytebuffer/test.esp32-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
1
tests/components/bytebuffer/test.esp32-c3-ard.yaml
Normal file
1
tests/components/bytebuffer/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
1
tests/components/bytebuffer/test.esp32-c3-idf.yaml
Normal file
1
tests/components/bytebuffer/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
1
tests/components/bytebuffer/test.esp32-idf.yaml
Normal file
1
tests/components/bytebuffer/test.esp32-idf.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
1
tests/components/bytebuffer/test.esp8266-ard.yaml
Normal file
1
tests/components/bytebuffer/test.esp8266-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
1
tests/components/bytebuffer/test.host.yaml
Normal file
1
tests/components/bytebuffer/test.host.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
1
tests/components/bytebuffer/test.rp2040-ard.yaml
Normal file
1
tests/components/bytebuffer/test.rp2040-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
!include common.yaml
|
Loading…
Reference in New Issue
Block a user