mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Merge remote-tracking branch 'upstream/dev' into light-addr
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -92,8 +92,8 @@ class DoubleClickTrigger : public Trigger<> {
|
||||
|
||||
class MultiClickTrigger : public Trigger<>, public Component {
|
||||
public:
|
||||
explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing)
|
||||
: parent_(parent), timing_(std::move(timing)) {}
|
||||
explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
||||
: parent_(parent), timing_(timing) {}
|
||||
|
||||
void setup() override {
|
||||
this->last_state_ = this->parent_->get_state_default(false);
|
||||
@@ -115,7 +115,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
void trigger_();
|
||||
|
||||
BinarySensor *parent_;
|
||||
std::vector<MultiClickTriggerEvent> timing_;
|
||||
FixedVector<MultiClickTriggerEvent> timing_;
|
||||
uint32_t invalid_cooldown_{1000};
|
||||
optional<size_t> at_index_{};
|
||||
bool last_state_{false};
|
||||
|
||||
@@ -8,12 +8,19 @@ namespace event {
|
||||
static const char *const TAG = "event";
|
||||
|
||||
void Event::trigger(const std::string &event_type) {
|
||||
auto found = types_.find(event_type);
|
||||
if (found == types_.end()) {
|
||||
// Linear search - faster than std::set for small datasets (1-5 items typical)
|
||||
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());
|
||||
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());
|
||||
this->event_callback_.call(event_type);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
@@ -26,13 +25,13 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
const std::string *last_event_type;
|
||||
|
||||
void trigger(const std::string &event_type);
|
||||
void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; }
|
||||
std::set<std::string> get_event_types() const { return this->types_; }
|
||||
void set_event_types(const std::initializer_list<std::string> &event_types) { this->types_ = event_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);
|
||||
|
||||
protected:
|
||||
CallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
std::set<std::string> types_;
|
||||
FixedVector<std::string> types_;
|
||||
};
|
||||
|
||||
} // namespace event
|
||||
|
||||
@@ -378,11 +378,16 @@ async def to_code(config):
|
||||
# Track if any network uses Enterprise authentication
|
||||
has_eap = False
|
||||
|
||||
def add_sta(ap, network):
|
||||
# Initialize FixedVector with the count of networks
|
||||
networks = config.get(CONF_NETWORKS, [])
|
||||
if networks:
|
||||
cg.add(var.init_sta(len(networks)))
|
||||
|
||||
def add_sta(ap: cg.MockObj, network: dict) -> None:
|
||||
ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
|
||||
cg.add(var.add_sta(wifi_network(network, ap, ip_config)))
|
||||
|
||||
for network in config.get(CONF_NETWORKS, []):
|
||||
for network in networks:
|
||||
if CONF_EAP in network:
|
||||
has_eap = True
|
||||
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
|
||||
|
||||
@@ -330,9 +330,11 @@ float WiFiComponent::get_loop_priority() const {
|
||||
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::set_sta(const WiFiAP &ap) {
|
||||
this->clear_sta();
|
||||
this->init_sta(1);
|
||||
this->add_sta(ap);
|
||||
}
|
||||
void WiFiComponent::clear_sta() { this->sta_.clear(); }
|
||||
|
||||
@@ -219,6 +219,7 @@ class WiFiComponent : public Component {
|
||||
|
||||
void set_sta(const WiFiAP &ap);
|
||||
WiFiAP get_sta() { return this->selected_ap_; }
|
||||
void init_sta(size_t count);
|
||||
void add_sta(const WiFiAP &ap);
|
||||
void clear_sta();
|
||||
|
||||
@@ -393,7 +394,7 @@ class WiFiComponent : public Component {
|
||||
#endif
|
||||
|
||||
std::string use_address_;
|
||||
std::vector<WiFiAP> sta_;
|
||||
FixedVector<WiFiAP> sta_;
|
||||
std::vector<WiFiSTAPriority> sta_priorities_;
|
||||
wifi_scan_vector_t<WiFiScanResult> scan_result_;
|
||||
WiFiAP selected_ap_;
|
||||
|
||||
@@ -194,12 +194,8 @@ template<typename T> class FixedVector {
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
// Helper to assign from initializer list (shared by constructor and assignment operator)
|
||||
void assign_from_initializer_list_(std::initializer_list<T> init_list) {
|
||||
init(init_list.size());
|
||||
size_t idx = 0;
|
||||
for (const auto &item : init_list) {
|
||||
@@ -209,6 +205,13 @@ template<typename T> class FixedVector {
|
||||
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_(); }
|
||||
|
||||
// Disable copy operations (avoid accidental expensive copies)
|
||||
@@ -234,6 +237,15 @@ template<typename T> class FixedVector {
|
||||
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
|
||||
void init(size_t n) {
|
||||
cleanup_();
|
||||
|
||||
@@ -606,21 +606,23 @@ def main() -> None:
|
||||
# [list]: Changed components (already includes dependencies)
|
||||
changed_components_result = get_changed_components()
|
||||
|
||||
if changed_components_result is None:
|
||||
# Core files changed - will trigger full clang-tidy scan
|
||||
# No specific components to test
|
||||
changed_components = []
|
||||
directly_changed_components = []
|
||||
is_core_change = True
|
||||
else:
|
||||
# Get both directly changed and all changed (with dependencies)
|
||||
# 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:
|
||||
# Core files changed - will trigger full clang-tidy scan
|
||||
# But we still need to track changed components for testing and memory analysis
|
||||
changed_components = get_components_with_dependencies(component_files, True)
|
||||
is_core_change = True
|
||||
else:
|
||||
# Use the result from get_changed_components() which includes dependencies
|
||||
changed_components = changed_components_result
|
||||
is_core_change = False
|
||||
|
||||
# Filter to only components that have test files
|
||||
|
||||
@@ -12,5 +12,8 @@ esphome:
|
||||
- logger.log: "Failed to connect to WiFi!"
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
networks:
|
||||
- ssid: MySSID
|
||||
password: password1
|
||||
- ssid: MySSID2
|
||||
password: password2
|
||||
|
||||
@@ -910,3 +910,60 @@ def test_clang_tidy_mode_targeted_scan(
|
||||
output = json.loads(captured.out)
|
||||
|
||||
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