mirror of
https://github.com/esphome/esphome.git
synced 2025-10-29 14:13:51 +00:00
Merge branch 'enum_mask_helper' into climate_overhead
This commit is contained in:
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -92,8 +92,8 @@ class DoubleClickTrigger : public Trigger<> {
|
|||||||
|
|
||||||
class MultiClickTrigger : public Trigger<>, public Component {
|
class MultiClickTrigger : public Trigger<>, public Component {
|
||||||
public:
|
public:
|
||||||
explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing)
|
explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
||||||
: parent_(parent), timing_(std::move(timing)) {}
|
: parent_(parent), timing_(timing) {}
|
||||||
|
|
||||||
void setup() override {
|
void setup() override {
|
||||||
this->last_state_ = this->parent_->get_state_default(false);
|
this->last_state_ = this->parent_->get_state_default(false);
|
||||||
@@ -115,7 +115,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
|||||||
void trigger_();
|
void trigger_();
|
||||||
|
|
||||||
BinarySensor *parent_;
|
BinarySensor *parent_;
|
||||||
std::vector<MultiClickTriggerEvent> timing_;
|
FixedVector<MultiClickTriggerEvent> timing_;
|
||||||
uint32_t invalid_cooldown_{1000};
|
uint32_t invalid_cooldown_{1000};
|
||||||
optional<size_t> at_index_{};
|
optional<size_t> at_index_{};
|
||||||
bool last_state_{false};
|
bool last_state_{false};
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ namespace event {
|
|||||||
static const char *const TAG = "event";
|
static const char *const TAG = "event";
|
||||||
|
|
||||||
void Event::trigger(const std::string &event_type) {
|
void Event::trigger(const std::string &event_type) {
|
||||||
auto found = types_.find(event_type);
|
// Linear search - faster than std::set for small datasets (1-5 items typical)
|
||||||
if (found == types_.end()) {
|
const std::string *found = nullptr;
|
||||||
|
for (const auto &type : this->types_) {
|
||||||
|
if (type == event_type) {
|
||||||
|
found = &type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found == nullptr) {
|
||||||
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
|
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
last_event_type = &(*found);
|
last_event_type = found;
|
||||||
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
|
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
|
||||||
this->event_callback_.call(event_type);
|
this->event_callback_.call(event_type);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
@@ -26,13 +25,13 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
|||||||
const std::string *last_event_type;
|
const std::string *last_event_type;
|
||||||
|
|
||||||
void trigger(const std::string &event_type);
|
void trigger(const std::string &event_type);
|
||||||
void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; }
|
void set_event_types(const std::initializer_list<std::string> &event_types) { this->types_ = event_types; }
|
||||||
std::set<std::string> get_event_types() const { return this->types_; }
|
const FixedVector<std::string> &get_event_types() const { return this->types_; }
|
||||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CallbackManager<void(const std::string &event_type)> event_callback_;
|
CallbackManager<void(const std::string &event_type)> event_callback_;
|
||||||
std::set<std::string> types_;
|
FixedVector<std::string> types_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace event
|
} // namespace event
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include "esphome/core/enum_bitmask.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace light {
|
namespace light {
|
||||||
@@ -104,16 +105,16 @@ constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) {
|
|||||||
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type alias for raw color mode bitmask values
|
// Type alias for raw color mode bitmask values (retained for compatibility)
|
||||||
using color_mode_bitmask_t = uint16_t;
|
using color_mode_bitmask_t = uint16_t;
|
||||||
|
|
||||||
// Constants for ColorMode count and bit range
|
// Number of ColorMode enum values
|
||||||
static constexpr int COLOR_MODE_COUNT = 10; // UNKNOWN through RGB_COLD_WARM_WHITE
|
constexpr int COLOR_MODE_BITMASK_SIZE = 10;
|
||||||
static constexpr int MAX_BIT_INDEX = sizeof(color_mode_bitmask_t) * 8; // Number of bits in bitmask type
|
|
||||||
|
|
||||||
// Compile-time array of all ColorMode values in declaration order
|
// Shared lookup table for ColorMode bit mapping
|
||||||
// Bit positions (0-9) map directly to enum declaration order
|
// This array defines the canonical order of color modes (bit 0-9)
|
||||||
static constexpr ColorMode COLOR_MODES[COLOR_MODE_COUNT] = {
|
// Declared early so it can be used by constexpr functions
|
||||||
|
constexpr ColorMode COLOR_MODE_LOOKUP[COLOR_MODE_BITMASK_SIZE] = {
|
||||||
ColorMode::UNKNOWN, // bit 0
|
ColorMode::UNKNOWN, // bit 0
|
||||||
ColorMode::ON_OFF, // bit 1
|
ColorMode::ON_OFF, // bit 1
|
||||||
ColorMode::BRIGHTNESS, // bit 2
|
ColorMode::BRIGHTNESS, // bit 2
|
||||||
@@ -126,33 +127,20 @@ static constexpr ColorMode COLOR_MODES[COLOR_MODE_COUNT] = {
|
|||||||
ColorMode::RGB_COLD_WARM_WHITE, // bit 9
|
ColorMode::RGB_COLD_WARM_WHITE, // bit 9
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Map ColorMode enum values to bit positions (0-9)
|
// Type alias for ColorMode bitmask using generic EnumBitmask template
|
||||||
/// Bit positions follow the enum declaration order
|
using ColorModeMask = EnumBitmask<ColorMode, COLOR_MODE_BITMASK_SIZE>;
|
||||||
static constexpr int mode_to_bit(ColorMode mode) {
|
|
||||||
// Linear search through COLOR_MODES array
|
|
||||||
// Compiler optimizes this to efficient code since array is constexpr
|
|
||||||
for (int i = 0; i < COLOR_MODE_COUNT; ++i) {
|
|
||||||
if (COLOR_MODES[i] == mode)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map bit positions (0-9) to ColorMode enum values
|
// Number of ColorCapability enum values
|
||||||
/// Bit positions follow the enum declaration order
|
constexpr int COLOR_CAPABILITY_COUNT = 6;
|
||||||
static constexpr ColorMode bit_to_mode(int bit) {
|
|
||||||
// Direct lookup in COLOR_MODES array
|
|
||||||
return (bit >= 0 && bit < COLOR_MODE_COUNT) ? COLOR_MODES[bit] : ColorMode::UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper to compute capability bitmask at compile time
|
/// Helper to compute capability bitmask at compile time
|
||||||
static constexpr color_mode_bitmask_t compute_capability_bitmask(ColorCapability capability) {
|
constexpr uint16_t compute_capability_bitmask(ColorCapability capability) {
|
||||||
color_mode_bitmask_t mask = 0;
|
uint16_t mask = 0;
|
||||||
uint8_t cap_bit = static_cast<uint8_t>(capability);
|
uint8_t cap_bit = static_cast<uint8_t>(capability);
|
||||||
|
|
||||||
// Check each ColorMode to see if it has this capability
|
// Check each ColorMode to see if it has this capability
|
||||||
for (int bit = 0; bit < COLOR_MODE_COUNT; ++bit) {
|
for (int bit = 0; bit < COLOR_MODE_BITMASK_SIZE; ++bit) {
|
||||||
uint8_t mode_val = static_cast<uint8_t>(bit_to_mode(bit));
|
uint8_t mode_val = static_cast<uint8_t>(COLOR_MODE_LOOKUP[bit]);
|
||||||
if ((mode_val & cap_bit) != 0) {
|
if ((mode_val & cap_bit) != 0) {
|
||||||
mask |= (1 << bit);
|
mask |= (1 << bit);
|
||||||
}
|
}
|
||||||
@@ -160,12 +148,9 @@ static constexpr color_mode_bitmask_t compute_capability_bitmask(ColorCapability
|
|||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of ColorCapability enum values
|
|
||||||
static constexpr int COLOR_CAPABILITY_COUNT = 6;
|
|
||||||
|
|
||||||
/// Compile-time lookup table mapping ColorCapability to bitmask
|
/// Compile-time lookup table mapping ColorCapability to bitmask
|
||||||
/// This array is computed at compile time using constexpr
|
/// This array is computed at compile time using constexpr
|
||||||
static constexpr color_mode_bitmask_t CAPABILITY_BITMASKS[] = {
|
constexpr uint16_t CAPABILITY_BITMASKS[] = {
|
||||||
compute_capability_bitmask(ColorCapability::ON_OFF), // 1 << 0
|
compute_capability_bitmask(ColorCapability::ON_OFF), // 1 << 0
|
||||||
compute_capability_bitmask(ColorCapability::BRIGHTNESS), // 1 << 1
|
compute_capability_bitmask(ColorCapability::BRIGHTNESS), // 1 << 1
|
||||||
compute_capability_bitmask(ColorCapability::WHITE), // 1 << 2
|
compute_capability_bitmask(ColorCapability::WHITE), // 1 << 2
|
||||||
@@ -174,130 +159,51 @@ static constexpr color_mode_bitmask_t CAPABILITY_BITMASKS[] = {
|
|||||||
compute_capability_bitmask(ColorCapability::RGB), // 1 << 5
|
compute_capability_bitmask(ColorCapability::RGB), // 1 << 5
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Bitmask for storing a set of ColorMode values efficiently.
|
/// Check if any mode in the bitmask has a specific capability
|
||||||
/// Replaces std::set<ColorMode> to eliminate red-black tree overhead (~586 bytes).
|
/// Used for checking if a light supports a capability (e.g., BRIGHTNESS, RGB)
|
||||||
class ColorModeMask {
|
inline bool has_capability(const ColorModeMask &mask, ColorCapability capability) {
|
||||||
public:
|
// Lookup the pre-computed bitmask for this capability and check intersection with our mask
|
||||||
constexpr ColorModeMask() = default;
|
// ColorCapability values: 1, 2, 4, 8, 16, 32 -> array indices: 0, 1, 2, 3, 4, 5
|
||||||
|
// We need to convert the power-of-2 value to an index
|
||||||
/// Support initializer list syntax: {ColorMode::RGB, ColorMode::WHITE}
|
uint8_t cap_val = static_cast<uint8_t>(capability);
|
||||||
constexpr ColorModeMask(std::initializer_list<ColorMode> modes) {
|
|
||||||
for (auto mode : modes) {
|
|
||||||
this->add(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void add(ColorMode mode) { this->mask_ |= (1 << mode_to_bit(mode)); }
|
|
||||||
|
|
||||||
/// Add multiple modes at once using initializer list
|
|
||||||
constexpr void add(std::initializer_list<ColorMode> modes) {
|
|
||||||
for (auto mode : modes) {
|
|
||||||
this->add(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool contains(ColorMode mode) const { return (this->mask_ & (1 << mode_to_bit(mode))) != 0; }
|
|
||||||
|
|
||||||
constexpr size_t size() const {
|
|
||||||
// Count set bits using Brian Kernighan's algorithm
|
|
||||||
// More efficient for sparse bitmasks (typical case: 2-4 modes out of 10)
|
|
||||||
uint16_t n = this->mask_;
|
|
||||||
size_t count = 0;
|
|
||||||
while (n) {
|
|
||||||
n &= n - 1; // Clear the least significant set bit
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool empty() const { return this->mask_ == 0; }
|
|
||||||
|
|
||||||
/// Iterator support for API encoding
|
|
||||||
class Iterator {
|
|
||||||
public:
|
|
||||||
using iterator_category = std::forward_iterator_tag;
|
|
||||||
using value_type = ColorMode;
|
|
||||||
using difference_type = std::ptrdiff_t;
|
|
||||||
using pointer = const ColorMode *;
|
|
||||||
using reference = ColorMode;
|
|
||||||
|
|
||||||
constexpr Iterator(color_mode_bitmask_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit_(); }
|
|
||||||
|
|
||||||
constexpr ColorMode operator*() const { return bit_to_mode(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_ = ColorModeMask::find_next_set_bit(mask_, bit_); }
|
|
||||||
|
|
||||||
color_mode_bitmask_t mask_;
|
|
||||||
int bit_;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr Iterator begin() const { return Iterator(mask_, 0); }
|
|
||||||
constexpr Iterator end() const { return Iterator(mask_, MAX_BIT_INDEX); }
|
|
||||||
|
|
||||||
/// Get the raw bitmask value for API encoding
|
|
||||||
constexpr color_mode_bitmask_t get_mask() const { return this->mask_; }
|
|
||||||
|
|
||||||
/// Find the next set bit in a bitmask starting from a given position
|
|
||||||
/// Returns the bit position, or MAX_BIT_INDEX if no more bits are set
|
|
||||||
static constexpr int find_next_set_bit(color_mode_bitmask_t mask, int start_bit) {
|
|
||||||
int bit = start_bit;
|
|
||||||
while (bit < MAX_BIT_INDEX && !(mask & (1 << bit))) {
|
|
||||||
++bit;
|
|
||||||
}
|
|
||||||
return bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the first set bit in a bitmask and return the corresponding ColorMode
|
|
||||||
/// Used for optimizing compute_color_mode_() intersection logic
|
|
||||||
static constexpr ColorMode first_mode_from_mask(color_mode_bitmask_t mask) {
|
|
||||||
return bit_to_mode(find_next_set_bit(mask, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a ColorMode is present in a raw bitmask value
|
|
||||||
/// Useful for checking intersection results without creating a temporary ColorModeMask
|
|
||||||
static constexpr bool mask_contains(color_mode_bitmask_t mask, ColorMode mode) {
|
|
||||||
return (mask & (1 << mode_to_bit(mode))) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if any mode in the bitmask has a specific capability
|
|
||||||
/// Used for checking if a light supports a capability (e.g., BRIGHTNESS, RGB)
|
|
||||||
bool has_capability(ColorCapability capability) const {
|
|
||||||
// Lookup the pre-computed bitmask for this capability and check intersection with our mask
|
|
||||||
// ColorCapability values: 1, 2, 4, 8, 16, 32 -> array indices: 0, 1, 2, 3, 4, 5
|
|
||||||
// We need to convert the power-of-2 value to an index
|
|
||||||
uint8_t cap_val = static_cast<uint8_t>(capability);
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
// Use compiler intrinsic for efficient bit position lookup (O(1) vs O(log n))
|
// Use compiler intrinsic for efficient bit position lookup (O(1) vs O(log n))
|
||||||
int index = __builtin_ctz(cap_val);
|
int index = __builtin_ctz(cap_val);
|
||||||
#else
|
#else
|
||||||
// Fallback for compilers without __builtin_ctz
|
// Fallback for compilers without __builtin_ctz
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (cap_val > 1) {
|
while (cap_val > 1) {
|
||||||
cap_val >>= 1;
|
cap_val >>= 1;
|
||||||
++index;
|
++index;
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return (this->mask_ & CAPABILITY_BITMASKS[index]) != 0;
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
private:
|
return (mask.get_mask() & CAPABILITY_BITMASKS[index]) != 0;
|
||||||
// Using uint16_t instead of uint32_t for more efficient iteration (fewer bits to scan).
|
}
|
||||||
// Currently only 10 ColorMode values exist, so 16 bits is sufficient.
|
|
||||||
// Can be changed to uint32_t if more than 16 color modes are needed in the future.
|
|
||||||
// Note: Due to struct padding, uint16_t and uint32_t result in same LightTraits size (12 bytes).
|
|
||||||
color_mode_bitmask_t mask_{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
|
// Template specializations for ColorMode must be in global namespace
|
||||||
|
|
||||||
|
/// Map ColorMode enum values to bit positions (0-9)
|
||||||
|
/// Bit positions follow the enum declaration order
|
||||||
|
template<>
|
||||||
|
constexpr int esphome::EnumBitmask<esphome::light::ColorMode, esphome::light::COLOR_MODE_BITMASK_SIZE>::enum_to_bit(
|
||||||
|
esphome::light::ColorMode mode) {
|
||||||
|
// Linear search through COLOR_MODE_LOOKUP array
|
||||||
|
// Compiler optimizes this to efficient code since array is constexpr
|
||||||
|
for (int i = 0; i < esphome::light::COLOR_MODE_BITMASK_SIZE; ++i) {
|
||||||
|
if (esphome::light::COLOR_MODE_LOOKUP[i] == mode)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map bit positions (0-9) to ColorMode enum values
|
||||||
|
/// Bit positions follow the enum declaration order
|
||||||
|
template<>
|
||||||
|
inline esphome::light::ColorMode esphome::EnumBitmask<esphome::light::ColorMode,
|
||||||
|
esphome::light::COLOR_MODE_BITMASK_SIZE>::bit_to_enum(int bit) {
|
||||||
|
return (bit >= 0 && bit < esphome::light::COLOR_MODE_BITMASK_SIZE) ? esphome::light::COLOR_MODE_LOOKUP[bit]
|
||||||
|
: esphome::light::ColorMode::UNKNOWN;
|
||||||
|
}
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ ColorMode LightCall::compute_color_mode_() {
|
|||||||
|
|
||||||
// Use the preferred suitable mode.
|
// Use the preferred suitable mode.
|
||||||
if (intersection != 0) {
|
if (intersection != 0) {
|
||||||
ColorMode mode = ColorModeMask::first_mode_from_mask(intersection);
|
ColorMode mode = ColorModeMask::first_value_from_mask(intersection);
|
||||||
ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
|
ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
|
||||||
LOG_STR_ARG(color_mode_to_human(mode)));
|
LOG_STR_ARG(color_mode_to_human(mode)));
|
||||||
return mode;
|
return mode;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class LightTraits {
|
|||||||
|
|
||||||
bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.contains(color_mode); }
|
bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.contains(color_mode); }
|
||||||
bool supports_color_capability(ColorCapability color_capability) const {
|
bool supports_color_capability(ColorCapability color_capability) const {
|
||||||
return this->supported_color_modes_.has_capability(color_capability);
|
return has_capability(this->supported_color_modes_, color_capability);
|
||||||
}
|
}
|
||||||
|
|
||||||
float get_min_mireds() const { return this->min_mireds_; }
|
float get_min_mireds() const { return this->min_mireds_; }
|
||||||
|
|||||||
@@ -378,14 +378,19 @@ async def to_code(config):
|
|||||||
# Track if any network uses Enterprise authentication
|
# Track if any network uses Enterprise authentication
|
||||||
has_eap = False
|
has_eap = False
|
||||||
|
|
||||||
def add_sta(ap, network):
|
# Initialize FixedVector with the count of networks
|
||||||
ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
|
networks = config.get(CONF_NETWORKS, [])
|
||||||
cg.add(var.add_sta(wifi_network(network, ap, ip_config)))
|
if networks:
|
||||||
|
cg.add(var.init_sta(len(networks)))
|
||||||
|
|
||||||
for network in config.get(CONF_NETWORKS, []):
|
def add_sta(ap: cg.MockObj, network: dict) -> None:
|
||||||
if CONF_EAP in network:
|
ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
|
||||||
has_eap = True
|
cg.add(var.add_sta(wifi_network(network, ap, ip_config)))
|
||||||
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
|
|
||||||
|
for network in networks:
|
||||||
|
if CONF_EAP in network:
|
||||||
|
has_eap = True
|
||||||
|
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
|
||||||
|
|
||||||
if CONF_AP in config:
|
if CONF_AP in config:
|
||||||
conf = config[CONF_AP]
|
conf = config[CONF_AP]
|
||||||
|
|||||||
@@ -330,9 +330,11 @@ float WiFiComponent::get_loop_priority() const {
|
|||||||
return 10.0f; // before other loop components
|
return 10.0f; // before other loop components
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WiFiComponent::init_sta(size_t count) { this->sta_.init(count); }
|
||||||
void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); }
|
void WiFiComponent::add_sta(const WiFiAP &ap) { this->sta_.push_back(ap); }
|
||||||
void WiFiComponent::set_sta(const WiFiAP &ap) {
|
void WiFiComponent::set_sta(const WiFiAP &ap) {
|
||||||
this->clear_sta();
|
this->clear_sta();
|
||||||
|
this->init_sta(1);
|
||||||
this->add_sta(ap);
|
this->add_sta(ap);
|
||||||
}
|
}
|
||||||
void WiFiComponent::clear_sta() { this->sta_.clear(); }
|
void WiFiComponent::clear_sta() { this->sta_.clear(); }
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ class WiFiComponent : public Component {
|
|||||||
|
|
||||||
void set_sta(const WiFiAP &ap);
|
void set_sta(const WiFiAP &ap);
|
||||||
WiFiAP get_sta() { return this->selected_ap_; }
|
WiFiAP get_sta() { return this->selected_ap_; }
|
||||||
|
void init_sta(size_t count);
|
||||||
void add_sta(const WiFiAP &ap);
|
void add_sta(const WiFiAP &ap);
|
||||||
void clear_sta();
|
void clear_sta();
|
||||||
|
|
||||||
@@ -393,7 +394,7 @@ class WiFiComponent : public Component {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string use_address_;
|
std::string use_address_;
|
||||||
std::vector<WiFiAP> sta_;
|
FixedVector<WiFiAP> sta_;
|
||||||
std::vector<WiFiSTAPriority> sta_priorities_;
|
std::vector<WiFiSTAPriority> sta_priorities_;
|
||||||
wifi_scan_vector_t<WiFiScanResult> scan_result_;
|
wifi_scan_vector_t<WiFiScanResult> scan_result_;
|
||||||
WiFiAP selected_ap_;
|
WiFiAP selected_ap_;
|
||||||
|
|||||||
@@ -194,12 +194,8 @@ template<typename T> class FixedVector {
|
|||||||
size_ = 0;
|
size_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
// Helper to assign from initializer list (shared by constructor and assignment operator)
|
||||||
FixedVector() = default;
|
void assign_from_initializer_list_(std::initializer_list<T> init_list) {
|
||||||
|
|
||||||
/// Constructor from initializer list - allocates exact size needed
|
|
||||||
/// This enables brace initialization: FixedVector<int> v = {1, 2, 3};
|
|
||||||
FixedVector(std::initializer_list<T> init_list) {
|
|
||||||
init(init_list.size());
|
init(init_list.size());
|
||||||
size_t idx = 0;
|
size_t idx = 0;
|
||||||
for (const auto &item : init_list) {
|
for (const auto &item : init_list) {
|
||||||
@@ -209,6 +205,13 @@ template<typename T> class FixedVector {
|
|||||||
size_ = init_list.size();
|
size_ = init_list.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
FixedVector() = default;
|
||||||
|
|
||||||
|
/// Constructor from initializer list - allocates exact size needed
|
||||||
|
/// This enables brace initialization: FixedVector<int> v = {1, 2, 3};
|
||||||
|
FixedVector(std::initializer_list<T> init_list) { assign_from_initializer_list_(init_list); }
|
||||||
|
|
||||||
~FixedVector() { cleanup_(); }
|
~FixedVector() { cleanup_(); }
|
||||||
|
|
||||||
// Disable copy operations (avoid accidental expensive copies)
|
// Disable copy operations (avoid accidental expensive copies)
|
||||||
@@ -234,6 +237,15 @@ template<typename T> class FixedVector {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assignment from initializer list - avoids temporary and move overhead
|
||||||
|
/// This enables: FixedVector<int> v; v = {1, 2, 3};
|
||||||
|
FixedVector &operator=(std::initializer_list<T> init_list) {
|
||||||
|
cleanup_();
|
||||||
|
reset_();
|
||||||
|
assign_from_initializer_list_(init_list);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate capacity - can be called multiple times to reinit
|
// Allocate capacity - can be called multiple times to reinit
|
||||||
void init(size_t n) {
|
void init(size_t n) {
|
||||||
cleanup_();
|
cleanup_();
|
||||||
|
|||||||
@@ -606,21 +606,23 @@ def main() -> None:
|
|||||||
# [list]: Changed components (already includes dependencies)
|
# [list]: Changed components (already includes dependencies)
|
||||||
changed_components_result = get_changed_components()
|
changed_components_result = get_changed_components()
|
||||||
|
|
||||||
|
# Always analyze component files, even if core files changed
|
||||||
|
# This is needed for component testing and memory impact analysis
|
||||||
|
changed = changed_files(args.branch)
|
||||||
|
component_files = [f for f in changed if filter_component_and_test_files(f)]
|
||||||
|
|
||||||
|
directly_changed_components = get_components_with_dependencies(
|
||||||
|
component_files, False
|
||||||
|
)
|
||||||
|
|
||||||
if changed_components_result is None:
|
if changed_components_result is None:
|
||||||
# Core files changed - will trigger full clang-tidy scan
|
# Core files changed - will trigger full clang-tidy scan
|
||||||
# No specific components to test
|
# But we still need to track changed components for testing and memory analysis
|
||||||
changed_components = []
|
changed_components = get_components_with_dependencies(component_files, True)
|
||||||
directly_changed_components = []
|
|
||||||
is_core_change = True
|
is_core_change = True
|
||||||
else:
|
else:
|
||||||
# Get both directly changed and all changed (with dependencies)
|
# Use the result from get_changed_components() which includes dependencies
|
||||||
changed = changed_files(args.branch)
|
changed_components = changed_components_result
|
||||||
component_files = [f for f in changed if filter_component_and_test_files(f)]
|
|
||||||
|
|
||||||
directly_changed_components = get_components_with_dependencies(
|
|
||||||
component_files, False
|
|
||||||
)
|
|
||||||
changed_components = get_components_with_dependencies(component_files, True)
|
|
||||||
is_core_change = False
|
is_core_change = False
|
||||||
|
|
||||||
# Filter to only components that have test files
|
# Filter to only components that have test files
|
||||||
|
|||||||
@@ -17,6 +17,20 @@ esphome:
|
|||||||
relative_brightness: 5%
|
relative_brightness: 5%
|
||||||
brightness_limits:
|
brightness_limits:
|
||||||
max_brightness: 90%
|
max_brightness: 90%
|
||||||
|
- light.turn_on:
|
||||||
|
id: test_addressable_transition
|
||||||
|
brightness: 50%
|
||||||
|
red: 100%
|
||||||
|
green: 0%
|
||||||
|
blue: 0%
|
||||||
|
transition_length: 500ms
|
||||||
|
- light.turn_on:
|
||||||
|
id: test_addressable_transition
|
||||||
|
brightness: 100%
|
||||||
|
red: 0%
|
||||||
|
green: 100%
|
||||||
|
blue: 0%
|
||||||
|
transition_length: 1s
|
||||||
|
|
||||||
light:
|
light:
|
||||||
- platform: binary
|
- platform: binary
|
||||||
@@ -163,3 +177,9 @@ light:
|
|||||||
blue: 0%
|
blue: 0%
|
||||||
duration: 1s
|
duration: 1s
|
||||||
transition_length: 500ms
|
transition_length: 500ms
|
||||||
|
- platform: partition
|
||||||
|
id: test_addressable_transition
|
||||||
|
name: Addressable Transition Test
|
||||||
|
default_transition_length: 1s
|
||||||
|
segments:
|
||||||
|
- single_light_id: test_rgb_light
|
||||||
|
|||||||
@@ -12,5 +12,8 @@ esphome:
|
|||||||
- logger.log: "Failed to connect to WiFi!"
|
- logger.log: "Failed to connect to WiFi!"
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: MySSID
|
networks:
|
||||||
password: password1
|
- ssid: MySSID
|
||||||
|
password: password1
|
||||||
|
- ssid: MySSID2
|
||||||
|
password: password2
|
||||||
|
|||||||
@@ -910,3 +910,60 @@ def test_clang_tidy_mode_targeted_scan(
|
|||||||
output = json.loads(captured.out)
|
output = json.loads(captured.out)
|
||||||
|
|
||||||
assert output["clang_tidy_mode"] == expected_mode
|
assert output["clang_tidy_mode"] == expected_mode
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_core_files_changed_still_detects_components(
|
||||||
|
mock_should_run_integration_tests: Mock,
|
||||||
|
mock_should_run_clang_tidy: Mock,
|
||||||
|
mock_should_run_clang_format: Mock,
|
||||||
|
mock_should_run_python_linters: Mock,
|
||||||
|
mock_changed_files: Mock,
|
||||||
|
mock_determine_cpp_unit_tests: Mock,
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Test that component changes are detected even when core files change."""
|
||||||
|
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
|
||||||
|
|
||||||
|
mock_should_run_integration_tests.return_value = True
|
||||||
|
mock_should_run_clang_tidy.return_value = True
|
||||||
|
mock_should_run_clang_format.return_value = True
|
||||||
|
mock_should_run_python_linters.return_value = True
|
||||||
|
mock_determine_cpp_unit_tests.return_value = (True, [])
|
||||||
|
|
||||||
|
mock_changed_files.return_value = [
|
||||||
|
"esphome/core/helpers.h",
|
||||||
|
"esphome/components/select/select_traits.h",
|
||||||
|
"esphome/components/select/select_traits.cpp",
|
||||||
|
"esphome/components/api/api.proto",
|
||||||
|
]
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("sys.argv", ["determine-jobs.py"]),
|
||||||
|
patch.object(determine_jobs, "_is_clang_tidy_full_scan", return_value=False),
|
||||||
|
patch.object(determine_jobs, "get_changed_components", return_value=None),
|
||||||
|
patch.object(
|
||||||
|
determine_jobs,
|
||||||
|
"filter_component_and_test_files",
|
||||||
|
side_effect=lambda f: f.startswith("esphome/components/"),
|
||||||
|
),
|
||||||
|
patch.object(
|
||||||
|
determine_jobs,
|
||||||
|
"get_components_with_dependencies",
|
||||||
|
side_effect=lambda files, deps: (
|
||||||
|
["select", "api"]
|
||||||
|
if not deps
|
||||||
|
else ["select", "api", "bluetooth_proxy", "logger"]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
determine_jobs.main()
|
||||||
|
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
output = json.loads(captured.out)
|
||||||
|
|
||||||
|
assert output["clang_tidy"] is True
|
||||||
|
assert output["clang_tidy_mode"] == "split"
|
||||||
|
assert "select" in output["changed_components"]
|
||||||
|
assert "api" in output["changed_components"]
|
||||||
|
assert len(output["changed_components"]) > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user