mirror of
https://github.com/esphome/esphome.git
synced 2025-10-29 14:13:51 +00:00
wip
This commit is contained in:
@@ -669,18 +669,18 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
|
|||||||
msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
|
msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION);
|
||||||
// Current feature flags and other supported parameters
|
// Current feature flags and other supported parameters
|
||||||
msg.feature_flags = traits.get_feature_flags();
|
msg.feature_flags = traits.get_feature_flags();
|
||||||
msg.supported_modes = &traits.get_supported_modes_for_api_();
|
msg.supported_modes = &traits.get_supported_modes();
|
||||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||||
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
|
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
|
||||||
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||||
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
||||||
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
||||||
msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
|
msg.supported_fan_modes = &traits.get_supported_fan_modes();
|
||||||
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
|
msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes();
|
||||||
msg.supported_presets = &traits.get_supported_presets_for_api_();
|
msg.supported_presets = &traits.get_supported_presets();
|
||||||
msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_();
|
msg.supported_custom_presets = &traits.get_supported_custom_presets();
|
||||||
msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_();
|
msg.supported_swing_modes = &traits.get_supported_swing_modes();
|
||||||
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
|
|||||||
101
esphome/components/climate/climate_mode_bitmask.h
Normal file
101
esphome/components/climate/climate_mode_bitmask.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/enum_bitmask.h"
|
||||||
|
#include "climate_mode.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace climate {
|
||||||
|
|
||||||
|
// Type aliases for climate enum bitmasks
|
||||||
|
// These replace std::set<EnumType> to eliminate red-black tree overhead
|
||||||
|
|
||||||
|
using ClimateModeMask = EnumBitmask<ClimateMode, 8>; // 7 values (OFF, HEAT_COOL, COOL, HEAT, FAN_ONLY, DRY, AUTO)
|
||||||
|
using ClimateFanModeMask =
|
||||||
|
EnumBitmask<ClimateFanMode, 16>; // 10 values (ON, OFF, AUTO, LOW, MEDIUM, HIGH, MIDDLE, FOCUS, DIFFUSE, QUIET)
|
||||||
|
using ClimateSwingModeMask = EnumBitmask<ClimateSwingMode, 8>; // 4 values (OFF, BOTH, VERTICAL, HORIZONTAL)
|
||||||
|
using ClimatePresetMask =
|
||||||
|
EnumBitmask<ClimatePreset, 8>; // 8 values (NONE, HOME, AWAY, BOOST, COMFORT, ECO, SLEEP, ACTIVITY)
|
||||||
|
|
||||||
|
} // namespace climate
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
// Template specializations for enum-to-bit conversions
|
||||||
|
// All climate enums are sequential starting from 0, so conversions are trivial
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
// ClimateMode specialization (7 values: 0-6)
|
||||||
|
template<> constexpr int EnumBitmask<climate::ClimateMode, 8>::enum_to_bit(climate::ClimateMode mode) {
|
||||||
|
return static_cast<int>(mode); // Direct mapping: enum value = bit position
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> constexpr climate::ClimateMode EnumBitmask<climate::ClimateMode, 8>::bit_to_enum(int bit) {
|
||||||
|
// Compile-time lookup array mapping bit positions to enum values
|
||||||
|
static constexpr climate::ClimateMode MODES[] = {
|
||||||
|
climate::CLIMATE_MODE_OFF, // bit 0
|
||||||
|
climate::CLIMATE_MODE_HEAT_COOL, // bit 1
|
||||||
|
climate::CLIMATE_MODE_COOL, // bit 2
|
||||||
|
climate::CLIMATE_MODE_HEAT, // bit 3
|
||||||
|
climate::CLIMATE_MODE_FAN_ONLY, // bit 4
|
||||||
|
climate::CLIMATE_MODE_DRY, // bit 5
|
||||||
|
climate::CLIMATE_MODE_AUTO, // bit 6
|
||||||
|
};
|
||||||
|
return (bit >= 0 && bit < 7) ? MODES[bit] : climate::CLIMATE_MODE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClimateFanMode specialization (10 values: 0-9)
|
||||||
|
template<> constexpr int EnumBitmask<climate::ClimateFanMode, 16>::enum_to_bit(climate::ClimateFanMode mode) {
|
||||||
|
return static_cast<int>(mode); // Direct mapping: enum value = bit position
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> constexpr climate::ClimateFanMode EnumBitmask<climate::ClimateFanMode, 16>::bit_to_enum(int bit) {
|
||||||
|
static constexpr climate::ClimateFanMode MODES[] = {
|
||||||
|
climate::CLIMATE_FAN_ON, // bit 0
|
||||||
|
climate::CLIMATE_FAN_OFF, // bit 1
|
||||||
|
climate::CLIMATE_FAN_AUTO, // bit 2
|
||||||
|
climate::CLIMATE_FAN_LOW, // bit 3
|
||||||
|
climate::CLIMATE_FAN_MEDIUM, // bit 4
|
||||||
|
climate::CLIMATE_FAN_HIGH, // bit 5
|
||||||
|
climate::CLIMATE_FAN_MIDDLE, // bit 6
|
||||||
|
climate::CLIMATE_FAN_FOCUS, // bit 7
|
||||||
|
climate::CLIMATE_FAN_DIFFUSE, // bit 8
|
||||||
|
climate::CLIMATE_FAN_QUIET, // bit 9
|
||||||
|
};
|
||||||
|
return (bit >= 0 && bit < 10) ? MODES[bit] : climate::CLIMATE_FAN_ON;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClimateSwingMode specialization (4 values: 0-3)
|
||||||
|
template<> constexpr int EnumBitmask<climate::ClimateSwingMode, 8>::enum_to_bit(climate::ClimateSwingMode mode) {
|
||||||
|
return static_cast<int>(mode); // Direct mapping: enum value = bit position
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> constexpr climate::ClimateSwingMode EnumBitmask<climate::ClimateSwingMode, 8>::bit_to_enum(int bit) {
|
||||||
|
static constexpr climate::ClimateSwingMode MODES[] = {
|
||||||
|
climate::CLIMATE_SWING_OFF, // bit 0
|
||||||
|
climate::CLIMATE_SWING_BOTH, // bit 1
|
||||||
|
climate::CLIMATE_SWING_VERTICAL, // bit 2
|
||||||
|
climate::CLIMATE_SWING_HORIZONTAL, // bit 3
|
||||||
|
};
|
||||||
|
return (bit >= 0 && bit < 4) ? MODES[bit] : climate::CLIMATE_SWING_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClimatePreset specialization (8 values: 0-7)
|
||||||
|
template<> constexpr int EnumBitmask<climate::ClimatePreset, 8>::enum_to_bit(climate::ClimatePreset preset) {
|
||||||
|
return static_cast<int>(preset); // Direct mapping: enum value = bit position
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> constexpr climate::ClimatePreset EnumBitmask<climate::ClimatePreset, 8>::bit_to_enum(int bit) {
|
||||||
|
static constexpr climate::ClimatePreset PRESETS[] = {
|
||||||
|
climate::CLIMATE_PRESET_NONE, // bit 0
|
||||||
|
climate::CLIMATE_PRESET_HOME, // bit 1
|
||||||
|
climate::CLIMATE_PRESET_AWAY, // bit 2
|
||||||
|
climate::CLIMATE_PRESET_BOOST, // bit 3
|
||||||
|
climate::CLIMATE_PRESET_COMFORT, // bit 4
|
||||||
|
climate::CLIMATE_PRESET_ECO, // bit 5
|
||||||
|
climate::CLIMATE_PRESET_SLEEP, // bit 6
|
||||||
|
climate::CLIMATE_PRESET_ACTIVITY, // bit 7
|
||||||
|
};
|
||||||
|
return (bit >= 0 && bit < 8) ? PRESETS[bit] : climate::CLIMATE_PRESET_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
@@ -1,9 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
#include <vector>
|
||||||
#include "climate_mode.h"
|
#include "climate_mode.h"
|
||||||
|
#include "climate_mode_bitmask.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace climate {
|
||||||
|
|
||||||
|
// Lightweight linear search for small vectors (1-20 items)
|
||||||
|
// Avoids std::find template overhead
|
||||||
|
template<typename T> inline bool vector_contains(const std::vector<T> &vec, const T &value) {
|
||||||
|
for (const auto &item : vec) {
|
||||||
|
if (item == value)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace climate
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
@@ -107,48 +124,68 @@ class ClimateTraits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
|
void set_supported_modes(ClimateModeMask modes) { this->supported_modes_ = modes; }
|
||||||
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
|
void set_supported_modes(std::initializer_list<ClimateMode> modes) {
|
||||||
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
|
this->supported_modes_ = ClimateModeMask(modes);
|
||||||
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
|
}
|
||||||
|
void add_supported_mode(ClimateMode mode) { this->supported_modes_.add(mode); }
|
||||||
|
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.contains(mode); }
|
||||||
|
const ClimateModeMask &get_supported_modes() const { return this->supported_modes_; }
|
||||||
|
|
||||||
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
|
void set_supported_fan_modes(ClimateFanModeMask modes) { this->supported_fan_modes_ = modes; }
|
||||||
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
|
void set_supported_fan_modes(std::initializer_list<ClimateFanMode> modes) {
|
||||||
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
|
this->supported_fan_modes_ = ClimateFanModeMask(modes);
|
||||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
|
}
|
||||||
|
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.add(mode); }
|
||||||
|
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.push_back(mode); }
|
||||||
|
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.contains(fan_mode); }
|
||||||
bool get_supports_fan_modes() const {
|
bool get_supports_fan_modes() const {
|
||||||
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
|
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
|
||||||
}
|
}
|
||||||
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
||||||
|
|
||||||
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
|
void set_supported_custom_fan_modes(std::vector<std::string> supported_custom_fan_modes) {
|
||||||
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
||||||
}
|
}
|
||||||
const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
|
void set_supported_custom_fan_modes(std::initializer_list<std::string> modes) {
|
||||||
|
this->supported_custom_fan_modes_ = modes;
|
||||||
|
}
|
||||||
|
const std::vector<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
|
||||||
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
||||||
return this->supported_custom_fan_modes_.count(custom_fan_mode);
|
return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); }
|
void set_supported_presets(ClimatePresetMask presets) { this->supported_presets_ = presets; }
|
||||||
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
|
void set_supported_presets(std::initializer_list<ClimatePreset> presets) {
|
||||||
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); }
|
this->supported_presets_ = ClimatePresetMask(presets);
|
||||||
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
|
}
|
||||||
|
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.add(preset); }
|
||||||
|
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.push_back(preset); }
|
||||||
|
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.contains(preset); }
|
||||||
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
|
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
|
||||||
const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; }
|
const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; }
|
||||||
|
|
||||||
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
|
void set_supported_custom_presets(std::vector<std::string> supported_custom_presets) {
|
||||||
this->supported_custom_presets_ = std::move(supported_custom_presets);
|
this->supported_custom_presets_ = std::move(supported_custom_presets);
|
||||||
}
|
}
|
||||||
const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
|
void set_supported_custom_presets(std::initializer_list<std::string> presets) {
|
||||||
|
this->supported_custom_presets_ = presets;
|
||||||
|
}
|
||||||
|
const std::vector<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
|
||||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||||
return this->supported_custom_presets_.count(custom_preset);
|
return vector_contains(this->supported_custom_presets_, custom_preset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); }
|
void set_supported_swing_modes(ClimateSwingModeMask modes) { this->supported_swing_modes_ = modes; }
|
||||||
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
|
void set_supported_swing_modes(std::initializer_list<ClimateSwingMode> modes) {
|
||||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
|
this->supported_swing_modes_ = ClimateSwingModeMask(modes);
|
||||||
|
}
|
||||||
|
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.add(mode); }
|
||||||
|
bool supports_swing_mode(ClimateSwingMode swing_mode) const {
|
||||||
|
return this->supported_swing_modes_.contains(swing_mode);
|
||||||
|
}
|
||||||
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
|
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
|
||||||
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; }
|
const ClimateSwingModeMask &get_supported_swing_modes() const { return this->supported_swing_modes_; }
|
||||||
|
|
||||||
float get_visual_min_temperature() const { return this->visual_min_temperature_; }
|
float get_visual_min_temperature() const { return this->visual_min_temperature_; }
|
||||||
void set_visual_min_temperature(float visual_min_temperature) {
|
void set_visual_min_temperature(float visual_min_temperature) {
|
||||||
@@ -179,42 +216,25 @@ class ClimateTraits {
|
|||||||
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
|
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
#ifdef USE_API
|
|
||||||
// The API connection is a friend class to access internal methods
|
|
||||||
friend class api::APIConnection;
|
|
||||||
// These methods return references to internal data structures.
|
|
||||||
// They are used by the API to avoid copying data when encoding messages.
|
|
||||||
// Warning: Do not use these methods outside of the API connection code.
|
|
||||||
// They return references to internal data that can be invalidated.
|
|
||||||
const std::set<ClimateMode> &get_supported_modes_for_api_() const { return this->supported_modes_; }
|
|
||||||
const std::set<ClimateFanMode> &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; }
|
|
||||||
const std::set<std::string> &get_supported_custom_fan_modes_for_api_() const {
|
|
||||||
return this->supported_custom_fan_modes_;
|
|
||||||
}
|
|
||||||
const std::set<climate::ClimatePreset> &get_supported_presets_for_api_() const { return this->supported_presets_; }
|
|
||||||
const std::set<std::string> &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; }
|
|
||||||
const std::set<ClimateSwingMode> &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
||||||
if (supported) {
|
if (supported) {
|
||||||
this->supported_modes_.insert(mode);
|
this->supported_modes_.add(mode);
|
||||||
} else {
|
} else {
|
||||||
this->supported_modes_.erase(mode);
|
this->supported_modes_.remove(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
|
void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
|
||||||
if (supported) {
|
if (supported) {
|
||||||
this->supported_fan_modes_.insert(mode);
|
this->supported_fan_modes_.add(mode);
|
||||||
} else {
|
} else {
|
||||||
this->supported_fan_modes_.erase(mode);
|
this->supported_fan_modes_.remove(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
|
void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
|
||||||
if (supported) {
|
if (supported) {
|
||||||
this->supported_swing_modes_.insert(mode);
|
this->supported_swing_modes_.add(mode);
|
||||||
} else {
|
} else {
|
||||||
this->supported_swing_modes_.erase(mode);
|
this->supported_swing_modes_.remove(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,12 +246,12 @@ class ClimateTraits {
|
|||||||
float visual_min_humidity_{30};
|
float visual_min_humidity_{30};
|
||||||
float visual_max_humidity_{99};
|
float visual_max_humidity_{99};
|
||||||
|
|
||||||
std::set<climate::ClimateMode> supported_modes_ = {climate::CLIMATE_MODE_OFF};
|
climate::ClimateModeMask supported_modes_{climate::CLIMATE_MODE_OFF};
|
||||||
std::set<climate::ClimateFanMode> supported_fan_modes_;
|
climate::ClimateFanModeMask supported_fan_modes_;
|
||||||
std::set<climate::ClimateSwingMode> supported_swing_modes_;
|
climate::ClimateSwingModeMask supported_swing_modes_;
|
||||||
std::set<climate::ClimatePreset> supported_presets_;
|
climate::ClimatePresetMask supported_presets_;
|
||||||
std::set<std::string> supported_custom_fan_modes_;
|
std::vector<std::string> supported_custom_fan_modes_;
|
||||||
std::set<std::string> supported_custom_presets_;
|
std::vector<std::string> supported_custom_presets_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
|
|||||||
155
esphome/core/enum_bitmask.h
Normal file
155
esphome/core/enum_bitmask.h
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <iterator>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
/// Generic bitmask for storing a set of enum values efficiently.
|
||||||
|
/// Replaces std::set<EnumType> to eliminate red-black tree overhead (~586 bytes per instantiation).
|
||||||
|
///
|
||||||
|
/// Template parameters:
|
||||||
|
/// EnumType: The enum type to store (must be uint8_t-based)
|
||||||
|
/// MaxBits: Maximum number of bits needed (auto-selects uint8_t/uint16_t/uint32_t)
|
||||||
|
///
|
||||||
|
/// Requirements:
|
||||||
|
/// - EnumType must be an enum with sequential values starting from 0
|
||||||
|
/// - Specialization must provide enum_to_bit() and bit_to_enum() static methods
|
||||||
|
/// - MaxBits must be sufficient to hold all enum values
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// using ClimateModeMask = EnumBitmask<ClimateMode, 8>;
|
||||||
|
/// ClimateModeMask modes({CLIMATE_MODE_HEAT, CLIMATE_MODE_COOL});
|
||||||
|
/// if (modes.contains(CLIMATE_MODE_HEAT)) { ... }
|
||||||
|
/// for (auto mode : modes) { ... } // Iterate over set bits
|
||||||
|
///
|
||||||
|
/// Design notes:
|
||||||
|
/// - Uses compile-time type selection for optimal size (uint8_t/uint16_t/uint32_t)
|
||||||
|
/// - Iterator converts bit positions to actual enum values during traversal
|
||||||
|
/// - All operations are constexpr-compatible for compile-time initialization
|
||||||
|
/// - Drop-in replacement for std::set<EnumType> with simpler API
|
||||||
|
///
|
||||||
|
template<typename EnumType, int MaxBits = 16> class EnumBitmask {
|
||||||
|
public:
|
||||||
|
// Automatic bitmask type selection based on MaxBits
|
||||||
|
// ≤8 bits: uint8_t, ≤16 bits: uint16_t, otherwise: uint32_t
|
||||||
|
using bitmask_t =
|
||||||
|
typename std::conditional<(MaxBits <= 8), uint8_t,
|
||||||
|
typename std::conditional<(MaxBits <= 16), uint16_t, uint32_t>::type>::type;
|
||||||
|
|
||||||
|
constexpr EnumBitmask() = default;
|
||||||
|
|
||||||
|
/// Construct from initializer list: {VALUE1, VALUE2, ...}
|
||||||
|
constexpr EnumBitmask(std::initializer_list<EnumType> values) {
|
||||||
|
for (auto value : values) {
|
||||||
|
this->add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a single enum value to the set
|
||||||
|
constexpr void add(EnumType value) { this->mask_ |= (static_cast<bitmask_t>(1) << enum_to_bit(value)); }
|
||||||
|
|
||||||
|
/// Add multiple enum values from initializer list
|
||||||
|
constexpr void add(std::initializer_list<EnumType> values) {
|
||||||
|
for (auto value : values) {
|
||||||
|
this->add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove an enum value from the set
|
||||||
|
constexpr void remove(EnumType value) { this->mask_ &= ~(static_cast<bitmask_t>(1) << enum_to_bit(value)); }
|
||||||
|
|
||||||
|
/// Clear all values from the set
|
||||||
|
constexpr void clear() { this->mask_ = 0; }
|
||||||
|
|
||||||
|
/// Check if the set contains a specific enum value
|
||||||
|
constexpr bool contains(EnumType value) const {
|
||||||
|
return (this->mask_ & (static_cast<bitmask_t>(1) << enum_to_bit(value))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count the number of enum values in the set
|
||||||
|
constexpr size_t size() const {
|
||||||
|
// Brian Kernighan's algorithm - efficient for sparse bitmasks
|
||||||
|
// Typical case: 2-4 modes out of 10 possible
|
||||||
|
bitmask_t n = this->mask_;
|
||||||
|
size_t count = 0;
|
||||||
|
while (n) {
|
||||||
|
n &= n - 1; // Clear the least significant set bit
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the set is empty
|
||||||
|
constexpr bool empty() const { return this->mask_ == 0; }
|
||||||
|
|
||||||
|
/// Iterator support for range-based for loops and API encoding
|
||||||
|
/// Iterates over set bits and converts bit positions to enum values
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using value_type = EnumType;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using pointer = const EnumType *;
|
||||||
|
using reference = EnumType;
|
||||||
|
|
||||||
|
constexpr Iterator(bitmask_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit_(); }
|
||||||
|
|
||||||
|
constexpr EnumType operator*() const { return bit_to_enum(bit_); }
|
||||||
|
|
||||||
|
constexpr Iterator &operator++() {
|
||||||
|
++bit_;
|
||||||
|
advance_to_next_set_bit_();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(const Iterator &other) const { return bit_ == other.bit_; }
|
||||||
|
|
||||||
|
constexpr bool operator!=(const Iterator &other) const { return !(*this == other); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
constexpr void advance_to_next_set_bit_() { bit_ = find_next_set_bit(mask_, bit_); }
|
||||||
|
|
||||||
|
bitmask_t mask_;
|
||||||
|
int bit_;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Iterator begin() const { return Iterator(mask_, 0); }
|
||||||
|
constexpr Iterator end() const { return Iterator(mask_, MaxBits); }
|
||||||
|
|
||||||
|
/// Get the raw bitmask value for optimized operations
|
||||||
|
constexpr bitmask_t get_mask() const { return this->mask_; }
|
||||||
|
|
||||||
|
/// Check if a specific enum value is present in a raw bitmask
|
||||||
|
/// Useful for checking intersection results without creating temporary objects
|
||||||
|
static constexpr bool mask_contains(bitmask_t mask, EnumType value) {
|
||||||
|
return (mask & (static_cast<bitmask_t>(1) << enum_to_bit(value))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the first enum value from a raw bitmask
|
||||||
|
/// Used for optimizing intersection logic (e.g., "pick first suitable mode")
|
||||||
|
static constexpr EnumType first_value_from_mask(bitmask_t mask) { return bit_to_enum(find_next_set_bit(mask, 0)); }
|
||||||
|
|
||||||
|
/// Find the next set bit in a bitmask starting from a given position
|
||||||
|
/// Returns the bit position, or MaxBits if no more bits are set
|
||||||
|
static constexpr int find_next_set_bit(bitmask_t mask, int start_bit) {
|
||||||
|
int bit = start_bit;
|
||||||
|
while (bit < MaxBits && !(mask & (static_cast<bitmask_t>(1) << bit))) {
|
||||||
|
++bit;
|
||||||
|
}
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Must be provided by template specialization
|
||||||
|
// These convert between enum values and bit positions (0, 1, 2, ...)
|
||||||
|
static constexpr int enum_to_bit(EnumType value);
|
||||||
|
static constexpr EnumType bit_to_enum(int bit);
|
||||||
|
|
||||||
|
bitmask_t mask_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
Reference in New Issue
Block a user