mirror of
https://github.com/esphome/esphome.git
synced 2026-02-04 09:39:39 -07:00
Compare commits
13 Commits
wifi_conne
...
template_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a1fa05c8f | ||
|
|
cb9fbf8970 | ||
|
|
5a2774876a | ||
|
|
ede2f205d3 | ||
|
|
8cf29c40a9 | ||
|
|
0332cbfdd4 | ||
|
|
89bd9b610e | ||
|
|
9dbcf1447b | ||
|
|
6c853cae57 | ||
|
|
48e6efb6aa | ||
|
|
cfc3b3336f | ||
|
|
9ca394d1e5 | ||
|
|
e62a87afe1 |
@@ -94,29 +94,3 @@ DriverChip(
|
||||
(0x29, 0x00),
|
||||
],
|
||||
)
|
||||
|
||||
DriverChip(
|
||||
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B",
|
||||
height=600,
|
||||
width=1024,
|
||||
hsync_back_porch=160,
|
||||
hsync_pulse_width=10,
|
||||
hsync_front_porch=160,
|
||||
vsync_back_porch=23,
|
||||
vsync_pulse_width=1,
|
||||
vsync_front_porch=12,
|
||||
pclk_frequency="52MHz",
|
||||
lane_bit_rate="900Mbps",
|
||||
no_transform=True,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0x80, 0x8B),
|
||||
(0x81, 0x78),
|
||||
(0x82, 0x84),
|
||||
(0x83, 0x88),
|
||||
(0x84, 0xA8),
|
||||
(0x85, 0xE3),
|
||||
(0x86, 0x88),
|
||||
(0xB2, 0x10),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -10,35 +10,65 @@ from esphome.const import (
|
||||
CONF_OPTIONS,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_SET_ACTION,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
SCHEDULER_DONT_RUN,
|
||||
)
|
||||
from esphome.core import TimePeriodMilliseconds
|
||||
from esphome.cpp_generator import TemplateArguments
|
||||
|
||||
from .. import template_ns
|
||||
|
||||
TemplateSelect = template_ns.class_(
|
||||
"TemplateSelect", select.Select, cg.PollingComponent
|
||||
)
|
||||
TemplateSelectWithSetAction = template_ns.class_(
|
||||
"TemplateSelectWithSetAction", TemplateSelect
|
||||
)
|
||||
|
||||
|
||||
def validate(config):
|
||||
errors = []
|
||||
if CONF_LAMBDA in config:
|
||||
if config[CONF_OPTIMISTIC]:
|
||||
raise cv.Invalid("optimistic cannot be used with lambda")
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"optimistic cannot be used with lambda", path=[CONF_OPTIMISTIC]
|
||||
)
|
||||
)
|
||||
if CONF_INITIAL_OPTION in config:
|
||||
raise cv.Invalid("initial_value cannot be used with lambda")
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"initial_value cannot be used with lambda",
|
||||
path=[CONF_INITIAL_OPTION],
|
||||
)
|
||||
)
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
raise cv.Invalid("restore_value cannot be used with lambda")
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"restore_value cannot be used with lambda",
|
||||
path=[CONF_RESTORE_VALUE],
|
||||
)
|
||||
)
|
||||
elif CONF_INITIAL_OPTION in config:
|
||||
if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]:
|
||||
raise cv.Invalid(
|
||||
f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]"
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]",
|
||||
path=[CONF_INITIAL_OPTION],
|
||||
)
|
||||
)
|
||||
else:
|
||||
config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0]
|
||||
|
||||
if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
|
||||
raise cv.Invalid(
|
||||
"Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
|
||||
errors.append(
|
||||
cv.Invalid(
|
||||
"Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
|
||||
)
|
||||
)
|
||||
if errors:
|
||||
raise cv.MultipleInvalid(errors)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -62,29 +92,34 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await select.register_select(var, config, options=config[CONF_OPTIONS])
|
||||
var_id = config[CONF_ID]
|
||||
if CONF_SET_ACTION in config:
|
||||
var_id.type = TemplateSelectWithSetAction
|
||||
has_lambda = CONF_LAMBDA in config
|
||||
optimistic = config.get(CONF_OPTIMISTIC, False)
|
||||
restore_value = config.get(CONF_RESTORE_VALUE, False)
|
||||
options = config[CONF_OPTIONS]
|
||||
initial_option = config.get(CONF_INITIAL_OPTION, 0)
|
||||
initial_option_index = options.index(initial_option) if not has_lambda else 0
|
||||
|
||||
var = cg.new_Pvariable(
|
||||
var_id,
|
||||
TemplateArguments(has_lambda, optimistic, restore_value, initial_option_index),
|
||||
)
|
||||
component_config = config.copy()
|
||||
if not has_lambda:
|
||||
# No point in polling if not using a lambda
|
||||
component_config[CONF_UPDATE_INTERVAL] = TimePeriodMilliseconds(
|
||||
milliseconds=SCHEDULER_DONT_RUN
|
||||
)
|
||||
await cg.register_component(var, component_config)
|
||||
await select.register_select(var, config, options=options)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
else:
|
||||
# Only set if non-default to avoid bloating setup() function
|
||||
if config[CONF_OPTIMISTIC]:
|
||||
cg.add(var.set_optimistic(True))
|
||||
initial_option_index = config[CONF_OPTIONS].index(config[CONF_INITIAL_OPTION])
|
||||
# Only set if non-zero to avoid bloating setup() function
|
||||
# (initial_option_index_ is zero-initialized in the header)
|
||||
if initial_option_index != 0:
|
||||
cg.add(var.set_initial_option_index(initial_option_index))
|
||||
|
||||
# Only set if True (default is False)
|
||||
if config.get(CONF_RESTORE_VALUE):
|
||||
cg.add(var.set_restore_value(True))
|
||||
cg.add(var.set_lambda(lambda_))
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
|
||||
@@ -3,63 +3,17 @@
|
||||
|
||||
namespace esphome::template_ {
|
||||
|
||||
static const char *const TAG = "template.select";
|
||||
|
||||
void TemplateSelect::setup() {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
|
||||
size_t index = this->initial_option_index_;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = this->make_entity_preference<size_t>();
|
||||
size_t restored_index;
|
||||
if (this->pref_.load(&restored_index) && this->has_index(restored_index)) {
|
||||
index = restored_index;
|
||||
ESP_LOGD(TAG, "State from restore: %s", this->option_at(index));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->option_at(index));
|
||||
}
|
||||
void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda,
|
||||
const size_t initial_option_index, bool restore_value) {
|
||||
LOG_SELECT("", "Template Select", sel_comp);
|
||||
if (has_lambda) {
|
||||
LOG_UPDATE_INTERVAL(sel_comp);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "State from initial: %s", this->option_at(index));
|
||||
}
|
||||
|
||||
this->publish_state(index);
|
||||
}
|
||||
|
||||
void TemplateSelect::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
if (!this->has_option(*val)) {
|
||||
ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str());
|
||||
return;
|
||||
}
|
||||
this->publish_state(*val);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Optimistic: %s\n"
|
||||
" Initial Option: %s\n"
|
||||
" Restore Value: %s",
|
||||
YESNO(optimistic), sel_comp->option_at(initial_option_index), YESNO(restore_value));
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateSelect::control(size_t index) {
|
||||
this->set_trigger_->trigger(StringRef(this->option_at(index)));
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(index);
|
||||
|
||||
if (this->restore_value_)
|
||||
this->pref_.save(&index);
|
||||
}
|
||||
|
||||
void TemplateSelect::dump_config() {
|
||||
LOG_SELECT("", "Template Select", this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Optimistic: %s\n"
|
||||
" Initial Option: %s\n"
|
||||
" Restore Value: %s",
|
||||
YESNO(this->optimistic_), this->option_at(this->initial_option_index_), YESNO(this->restore_value_));
|
||||
}
|
||||
|
||||
} // namespace esphome::template_
|
||||
|
||||
@@ -8,30 +8,83 @@
|
||||
#include "esphome/core/template_lambda.h"
|
||||
|
||||
namespace esphome::template_ {
|
||||
static const char *const TAG = "template.select";
|
||||
struct Empty {};
|
||||
class BaseTemplateSelect : public select::Select, public PollingComponent {};
|
||||
|
||||
class TemplateSelect final : public select::Select, public PollingComponent {
|
||||
void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda, size_t initial_option_index,
|
||||
bool restore_value);
|
||||
|
||||
/// Base template select class - used when no set_action is configured
|
||||
|
||||
template<bool HAS_LAMBDA, bool OPTIMISTIC, bool RESTORE_VALUE, size_t INITIAL_OPTION_INDEX>
|
||||
class TemplateSelect : public BaseTemplateSelect {
|
||||
public:
|
||||
template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
|
||||
template<typename F> void set_lambda(F &&f) {
|
||||
if constexpr (HAS_LAMBDA) {
|
||||
this->f_.set(std::forward<F>(f));
|
||||
}
|
||||
}
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
void setup() override {
|
||||
if constexpr (!HAS_LAMBDA) {
|
||||
size_t index = INITIAL_OPTION_INDEX;
|
||||
if constexpr (RESTORE_VALUE) {
|
||||
this->pref_ = this->template make_entity_preference<size_t>();
|
||||
if (this->pref_.load(&index) && this->has_index(index)) {
|
||||
esph_log_d(TAG, "State from restore: %s", this->option_at(index));
|
||||
} else {
|
||||
index = INITIAL_OPTION_INDEX;
|
||||
esph_log_d(TAG, "State from initial (no valid stored index): %s", this->option_at(INITIAL_OPTION_INDEX));
|
||||
}
|
||||
} else {
|
||||
esph_log_d(TAG, "State from initial: %s", this->option_at(INITIAL_OPTION_INDEX));
|
||||
}
|
||||
this->publish_state(index);
|
||||
}
|
||||
}
|
||||
|
||||
void update() override {
|
||||
if constexpr (HAS_LAMBDA) {
|
||||
auto val = this->f_();
|
||||
if (val.has_value()) {
|
||||
if (!this->has_option(*val)) {
|
||||
esph_log_e(TAG, "Lambda returned an invalid option: %s", (*val).c_str());
|
||||
return;
|
||||
}
|
||||
this->publish_state(*val);
|
||||
}
|
||||
}
|
||||
}
|
||||
void dump_config() override {
|
||||
dump_config_helper(this, OPTIMISTIC, HAS_LAMBDA, INITIAL_OPTION_INDEX, RESTORE_VALUE);
|
||||
};
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<StringRef> *get_set_trigger() const { return this->set_trigger_; }
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
protected:
|
||||
void control(size_t index) override {
|
||||
if constexpr (OPTIMISTIC)
|
||||
this->publish_state(index);
|
||||
if constexpr (RESTORE_VALUE)
|
||||
this->pref_.save(&index);
|
||||
}
|
||||
[[no_unique_address]] std::conditional_t<HAS_LAMBDA, TemplateLambda<std::string>, Empty> f_{};
|
||||
[[no_unique_address]] std::conditional_t<RESTORE_VALUE, ESPPreferenceObject, Empty> pref_{};
|
||||
};
|
||||
|
||||
/// Template select with set_action trigger - only instantiated when set_action is configured
|
||||
template<bool HAS_LAMBDA, bool OPTIMISTIC, bool RESTORE_VALUE, size_t INITIAL_OPTION_INDEX>
|
||||
class TemplateSelectWithSetAction final
|
||||
: public TemplateSelect<HAS_LAMBDA, OPTIMISTIC, RESTORE_VALUE, INITIAL_OPTION_INDEX> {
|
||||
public:
|
||||
Trigger<StringRef> *get_set_trigger() { return &this->set_trigger_; }
|
||||
|
||||
protected:
|
||||
void control(size_t index) override;
|
||||
bool optimistic_ = false;
|
||||
size_t initial_option_index_{0};
|
||||
bool restore_value_ = false;
|
||||
Trigger<StringRef> *set_trigger_ = new Trigger<StringRef>();
|
||||
TemplateLambda<std::string> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
void control(size_t index) override {
|
||||
this->set_trigger_.trigger(StringRef(this->option_at(index)));
|
||||
TemplateSelect<HAS_LAMBDA, OPTIMISTIC, RESTORE_VALUE, INITIAL_OPTION_INDEX>::control(index);
|
||||
}
|
||||
Trigger<StringRef> set_trigger_;
|
||||
};
|
||||
|
||||
} // namespace esphome::template_
|
||||
|
||||
@@ -1464,12 +1464,6 @@ void WiFiComponent::check_connecting_finished(uint32_t now) {
|
||||
|
||||
this->release_scan_results_();
|
||||
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Notify listeners now that state machine has reached STA_CONNECTED
|
||||
// This ensures wifi.connected condition returns true in listener automations
|
||||
this->notify_connect_state_listeners_();
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2189,21 +2183,6 @@ void WiFiComponent::release_scan_results_() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
void WiFiComponent::notify_connect_state_listeners_() {
|
||||
if (!this->pending_.connect_state)
|
||||
return;
|
||||
this->pending_.connect_state = false;
|
||||
// Get current SSID and BSSID from the WiFi driver
|
||||
char ssid_buf[SSID_BUFFER_SIZE];
|
||||
const char *ssid = this->wifi_ssid_to(ssid_buf);
|
||||
bssid_t bssid = this->wifi_bssid();
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(ssid, strlen(ssid)), bssid);
|
||||
}
|
||||
}
|
||||
#endif // USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
|
||||
void WiFiComponent::check_roaming_(uint32_t now) {
|
||||
// Guard: not for hidden networks (may not appear in scan)
|
||||
const WiFiAP *selected = this->get_selected_sta_();
|
||||
|
||||
@@ -632,11 +632,6 @@ class WiFiComponent : public Component {
|
||||
/// Free scan results memory unless a component needs them
|
||||
void release_scan_results_();
|
||||
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
/// Notify connect state listeners (called after state machine reaches STA_CONNECTED)
|
||||
void notify_connect_state_listeners_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
static void wifi_event_callback(System_Event_t *event);
|
||||
void wifi_scan_done_callback_(void *arg, STATUS status);
|
||||
@@ -744,16 +739,6 @@ class WiFiComponent : public Component {
|
||||
SemaphoreHandle_t high_performance_semaphore_{nullptr};
|
||||
#endif
|
||||
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Pending listener notifications deferred until state machine reaches appropriate state.
|
||||
// Listeners are notified after state transitions complete so conditions like
|
||||
// wifi.connected return correct values in automations.
|
||||
// Uses bitfields to minimize memory; more flags may be added as needed.
|
||||
struct {
|
||||
bool connect_state : 1; // Notify connect state listeners after STA_CONNECTED
|
||||
} pending_{};
|
||||
#endif
|
||||
|
||||
#ifdef USE_WIFI_CONNECT_TRIGGER
|
||||
Trigger<> connect_trigger_;
|
||||
#endif
|
||||
|
||||
@@ -500,10 +500,6 @@ const LogString *get_disconnect_reason_str(uint8_t reason) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This callback runs in ESP8266 system context with limited stack (~2KB).
|
||||
// All listener notifications should be deferred to wifi_loop_() via pending_ flags
|
||||
// to avoid stack overflow. Currently only connect_state is deferred; disconnect,
|
||||
// IP, and scan listeners still run in this context and should be migrated.
|
||||
void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
switch (event->event) {
|
||||
case EVENT_STAMODE_CONNECTED: {
|
||||
@@ -516,9 +512,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
#endif
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Defer listener notification until state machine reaches STA_CONNECTED
|
||||
// This ensures wifi.connected condition returns true in listener automations
|
||||
global_wifi_component->pending_.connect_state = true;
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
|
||||
}
|
||||
#endif
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
|
||||
|
||||
@@ -710,9 +710,6 @@ void WiFiComponent::wifi_loop_() {
|
||||
delete data; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
}
|
||||
// Events are processed from queue in main loop context, but listener notifications
|
||||
// must be deferred until after the state machine transitions (in check_connecting_finished)
|
||||
// so that conditions like wifi.connected return correct values in automations.
|
||||
void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
esp_err_t err;
|
||||
if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_START) {
|
||||
@@ -746,9 +743,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
#endif
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Defer listener notification until state machine reaches STA_CONNECTED
|
||||
// This ensures wifi.connected condition returns true in listener automations
|
||||
this->pending_.connect_state = true;
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
|
||||
}
|
||||
#endif
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
|
||||
|
||||
@@ -423,10 +423,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
}
|
||||
}
|
||||
|
||||
// Process a single event from the queue - runs in main loop context.
|
||||
// Listener notifications must be deferred until after the state machine transitions
|
||||
// (in check_connecting_finished) so that conditions like wifi.connected return
|
||||
// correct values in automations.
|
||||
// Process a single event from the queue - runs in main loop context
|
||||
void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
|
||||
switch (event->event_id) {
|
||||
case ESPHOME_EVENT_ID_WIFI_READY: {
|
||||
@@ -459,9 +456,9 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
|
||||
// This matches ESP32 IDF behavior where s_sta_connected is set but
|
||||
// wifi_sta_connect_status_() also checks got_ipv4_address_
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Defer listener notification until state machine reaches STA_CONNECTED
|
||||
// This ensures wifi.connected condition returns true in listener automations
|
||||
this->pending_.connect_state = true;
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
|
||||
}
|
||||
#endif
|
||||
// For static IP configurations, GOT_IP event may not fire, so set connected state here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
|
||||
@@ -252,10 +252,6 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
|
||||
return network::IPAddress(dns_ip);
|
||||
}
|
||||
|
||||
// Pico W uses polling for connection state detection.
|
||||
// Connect state listener notifications are deferred until after the state machine
|
||||
// transitions (in check_connecting_finished) so that conditions like wifi.connected
|
||||
// return correct values in automations.
|
||||
void WiFiComponent::wifi_loop_() {
|
||||
// Handle scan completion
|
||||
if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) {
|
||||
@@ -282,9 +278,11 @@ void WiFiComponent::wifi_loop_() {
|
||||
s_sta_was_connected = true;
|
||||
ESP_LOGV(TAG, "Connected");
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Defer listener notification until state machine reaches STA_CONNECTED
|
||||
// This ensures wifi.connected condition returns true in listener automations
|
||||
this->pending_.connect_state = true;
|
||||
String ssid = WiFi.SSID();
|
||||
bssid_t bssid = this->wifi_bssid();
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(ssid.c_str(), ssid.length()), bssid);
|
||||
}
|
||||
#endif
|
||||
// For static IP configurations, notify IP listeners immediately as the IP is already configured
|
||||
#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
|
||||
|
||||
@@ -296,6 +296,16 @@ select:
|
||||
// Migration guide: Store in std::string
|
||||
std::string stored_option(id(template_select).current_option());
|
||||
ESP_LOGI("test", "Stored: %s", stored_option.c_str());
|
||||
- platform: template
|
||||
id: template_select_with_action
|
||||
name: "Template select with action"
|
||||
options:
|
||||
- option_a
|
||||
- option_b
|
||||
set_action:
|
||||
- logger.log:
|
||||
format: "Selected: %s"
|
||||
args: ["x.c_str()"]
|
||||
|
||||
lock:
|
||||
- platform: template
|
||||
|
||||
@@ -56,7 +56,21 @@ select:
|
||||
std::string prefix = x.substr(0, 6);
|
||||
ESP_LOGI("test", "Substr prefix: %s", prefix.c_str());
|
||||
|
||||
# Second select with numeric options to test ADL functions
|
||||
# Second select with set_action trigger (uses TemplateSelectWithSetAction subclass)
|
||||
- platform: template
|
||||
name: "Action Select"
|
||||
id: action_select
|
||||
options:
|
||||
- "Action A"
|
||||
- "Action B"
|
||||
set_action:
|
||||
then:
|
||||
# Test: set_action trigger receives StringRef
|
||||
- logger.log:
|
||||
format: "set_action triggered: %s"
|
||||
args: ['x.c_str()']
|
||||
|
||||
# Third select with numeric options to test ADL functions
|
||||
- platform: template
|
||||
name: "Baud Rate"
|
||||
id: baud_select
|
||||
|
||||
@@ -28,6 +28,8 @@ async def test_select_stringref_trigger(
|
||||
find_substr_future = loop.create_future()
|
||||
find_char_future = loop.create_future()
|
||||
substr_future = loop.create_future()
|
||||
# set_action trigger (TemplateSelectWithSetAction subclass)
|
||||
set_action_future = loop.create_future()
|
||||
# ADL functions
|
||||
stoi_future = loop.create_future()
|
||||
stol_future = loop.create_future()
|
||||
@@ -43,6 +45,8 @@ async def test_select_stringref_trigger(
|
||||
find_substr_pattern = re.compile(r"Found 'Option' in value")
|
||||
find_char_pattern = re.compile(r"Space at position: 6") # space at index 6
|
||||
substr_pattern = re.compile(r"Substr prefix: Option")
|
||||
# set_action trigger pattern (TemplateSelectWithSetAction subclass)
|
||||
set_action_pattern = re.compile(r"set_action triggered: Action B")
|
||||
# ADL function patterns (115200 from baud rate select)
|
||||
stoi_pattern = re.compile(r"stoi result: 115200")
|
||||
stol_pattern = re.compile(r"stol result: 115200")
|
||||
@@ -67,6 +71,9 @@ async def test_select_stringref_trigger(
|
||||
find_char_future.set_result(True)
|
||||
if not substr_future.done() and substr_pattern.search(line):
|
||||
substr_future.set_result(True)
|
||||
# set_action trigger
|
||||
if not set_action_future.done() and set_action_pattern.search(line):
|
||||
set_action_future.set_result(True)
|
||||
# ADL functions
|
||||
if not stoi_future.done() and stoi_pattern.search(line):
|
||||
stoi_future.set_result(True)
|
||||
@@ -89,22 +96,21 @@ async def test_select_stringref_trigger(
|
||||
# List entities to find our select
|
||||
entities, _ = await client.list_entities_services()
|
||||
|
||||
select_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Test Select"),
|
||||
None,
|
||||
)
|
||||
select_entity = next((e for e in entities if e.name == "Test Select"), None)
|
||||
assert select_entity is not None, "Test Select entity not found"
|
||||
|
||||
baud_entity = next(
|
||||
(e for e in entities if hasattr(e, "options") and e.name == "Baud Rate"),
|
||||
None,
|
||||
)
|
||||
baud_entity = next((e for e in entities if e.name == "Baud Rate"), None)
|
||||
assert baud_entity is not None, "Baud Rate entity not found"
|
||||
|
||||
action_entity = next((e for e in entities if e.name == "Action Select"), None)
|
||||
assert action_entity is not None, "Action Select entity not found"
|
||||
|
||||
# Change select to Option B - this should trigger on_value with StringRef
|
||||
client.select_command(select_entity.key, "Option B")
|
||||
# Change baud to 115200 - this tests ADL functions (stoi, stol, stof, stod)
|
||||
client.select_command(baud_entity.key, "115200")
|
||||
# Change action select - tests set_action trigger (TemplateSelectWithSetAction)
|
||||
client.select_command(action_entity.key, "Action B")
|
||||
|
||||
# Wait for all log messages confirming StringRef operations work
|
||||
try:
|
||||
@@ -118,6 +124,7 @@ async def test_select_stringref_trigger(
|
||||
find_substr_future,
|
||||
find_char_future,
|
||||
substr_future,
|
||||
set_action_future,
|
||||
stoi_future,
|
||||
stol_future,
|
||||
stof_future,
|
||||
@@ -135,6 +142,7 @@ async def test_select_stringref_trigger(
|
||||
"find_substr": find_substr_future.done(),
|
||||
"find_char": find_char_future.done(),
|
||||
"substr": substr_future.done(),
|
||||
"set_action": set_action_future.done(),
|
||||
"stoi": stoi_future.done(),
|
||||
"stol": stol_future.done(),
|
||||
"stof": stof_future.done(),
|
||||
|
||||
Reference in New Issue
Block a user