From c4d1b1317adcabac1c82ede8fd61f40c29fb7a4c Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Fri, 8 Aug 2025 00:55:54 +0200 Subject: [PATCH 1/8] [switch] Add `switch.control` automation action (#10105) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/switch/__init__.py | 19 +++++++++++++++++++ esphome/components/switch/automation.h | 21 +++++++++++++++++++++ tests/components/switch/common.yaml | 8 ++++++++ 3 files changed, 48 insertions(+) diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index a595d43445..4668a1458c 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -13,6 +13,7 @@ from esphome.const import ( CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_RESTORE_MODE, + CONF_STATE, CONF_TRIGGER_ID, CONF_WEB_SERVER, DEVICE_CLASS_EMPTY, @@ -48,6 +49,7 @@ RESTORE_MODES = { } +ControlAction = switch_ns.class_("ControlAction", automation.Action) ToggleAction = switch_ns.class_("ToggleAction", automation.Action) TurnOffAction = switch_ns.class_("TurnOffAction", automation.Action) TurnOnAction = switch_ns.class_("TurnOnAction", automation.Action) @@ -177,6 +179,23 @@ SWITCH_ACTION_SCHEMA = maybe_simple_id( cv.Required(CONF_ID): cv.use_id(Switch), } ) +SWITCH_CONTROL_ACTION_SCHEMA = automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Switch), + cv.Required(CONF_STATE): cv.templatable(cv.boolean), + } +) + + +@automation.register_action( + "switch.control", ControlAction, SWITCH_CONTROL_ACTION_SCHEMA +) +async def switch_control_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_STATE], args, bool) + cg.add(var.set_state(template_)) + return var @automation.register_action("switch.toggle", ToggleAction, SWITCH_ACTION_SCHEMA) diff --git a/esphome/components/switch/automation.h b/esphome/components/switch/automation.h index 579daf4d24..51ac5d7a3a 100644 --- a/esphome/components/switch/automation.h +++ b/esphome/components/switch/automation.h @@ -37,6 +37,27 @@ template class ToggleAction : public Action { Switch *switch_; }; +template class ControlAction : public Action { + public: + explicit ControlAction(Switch *a_switch) : switch_(a_switch) {} + + TEMPLATABLE_VALUE(bool, state) + + void play(Ts... x) override { + auto state = this->state_.optional_value(x...); + if (state.has_value()) { + if (*state) { + this->switch_->turn_on(); + } else { + this->switch_->turn_off(); + } + } + } + + protected: + Switch *switch_; +}; + template class SwitchCondition : public Condition { public: SwitchCondition(Switch *parent, bool state) : parent_(parent), state_(state) {} diff --git a/tests/components/switch/common.yaml b/tests/components/switch/common.yaml index 8d6972f91b..b69e36a1c0 100644 --- a/tests/components/switch/common.yaml +++ b/tests/components/switch/common.yaml @@ -9,3 +9,11 @@ switch: name: "Template Switch" id: the_switch optimistic: true + +esphome: + on_boot: + - switch.turn_on: the_switch + - switch.turn_off: the_switch + - switch.control: + id: the_switch + state: !lambda return (1 > 2); From 76fd104fb6d41cfbde810aa9433c49ee9c84665f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Aug 2025 14:32:35 -1000 Subject: [PATCH 2/8] [mdns] Conditionally compile extra services to reduce flash usage (#10129) --- esphome/components/mdns/__init__.py | 3 +++ esphome/components/mdns/mdns_component.cpp | 2 ++ esphome/components/mdns/mdns_component.h | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index e32d39cede..469fe8ada6 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -93,6 +93,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + if config[CONF_SERVICES]: + cg.add_define("USE_MDNS_EXTRA_SERVICES") + for service in config[CONF_SERVICES]: txt = [ cg.StructInitializer( diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 06ca99b402..640750720d 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -104,7 +104,9 @@ void MDNSComponent::compile_records_() { } #endif +#ifdef USE_MDNS_EXTRA_SERVICES this->services_.insert(this->services_.end(), this->services_extra_.begin(), this->services_extra_.end()); +#endif if (this->services_.empty()) { // Publish "http" service if not using native API diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 93a16f40d2..f87ef08bcd 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -35,14 +35,18 @@ class MDNSComponent : public Component { #endif float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } +#ifdef USE_MDNS_EXTRA_SERVICES void add_extra_service(MDNSService service) { services_extra_.push_back(std::move(service)); } +#endif std::vector get_services(); void on_shutdown() override; protected: +#ifdef USE_MDNS_EXTRA_SERVICES std::vector services_extra_{}; +#endif std::vector services_{}; std::string hostname_; void compile_records_(); From d4d1a96f9b259902d068d346a47273c4054376bc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Aug 2025 14:42:03 -1000 Subject: [PATCH 3/8] [esp32_ble_client] Reduce flash usage by optimizing logging strings (#10119) --- .../esp32_ble_client/ble_client_base.cpp | 30 ++++++++----------- .../esp32_ble_client/ble_client_base.h | 1 + 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 37b41326f7..2d84436d84 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -107,7 +107,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { #endif void BLEClientBase::connect() { - ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), + ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_.c_str(), this->remote_addr_type_); this->paired_ = false; @@ -137,7 +137,7 @@ void BLEClientBase::connect() { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_, this->address_str_.c_str(), param_ret); } else { - ESP_LOGD(TAG, "[%d] [%s] Set %s conn params", this->connection_index_, this->address_str_.c_str(), param_type); + this->log_connection_params_(param_type); } // Now open the connection @@ -153,14 +153,9 @@ void BLEClientBase::connect() { esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); } void BLEClientBase::disconnect() { - if (this->state_ == espbt::ClientState::IDLE) { - ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already idle.", this->connection_index_, - this->address_str_.c_str()); - return; - } - if (this->state_ == espbt::ClientState::DISCONNECTING) { - ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already disconnecting.", this->connection_index_, - this->address_str_.c_str()); + if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) { + ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_.c_str(), + espbt::client_state_to_string(this->state_)); return; } if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) { @@ -195,8 +190,7 @@ void BLEClientBase::unconditional_disconnect() { // In the future we might consider App.reboot() here since // the BLE stack is in an indeterminate state. // - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(), - err); + this->log_gattc_warning_("esp_ble_gattc_close", err); } if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT || @@ -236,6 +230,10 @@ void BLEClientBase::log_gattc_warning_(const char *operation, esp_err_t err) { ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_.c_str(), operation, err); } +void BLEClientBase::log_connection_params_(const char *param_type) { + ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type); +} + void BLEClientBase::restore_medium_conn_params_() { // Restore to medium connection parameters after initial connection phase // This balances performance with bandwidth usage for normal operation @@ -245,7 +243,7 @@ void BLEClientBase::restore_medium_conn_params_() { conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL; conn_params.latency = 0; conn_params.timeout = MEDIUM_CONN_TIMEOUT; - ESP_LOGD(TAG, "[%d] [%s] Restoring medium conn params", this->connection_index_, this->address_str_.c_str()); + this->log_connection_params_("medium"); esp_ble_gap_update_conn_params(&conn_params); } @@ -303,11 +301,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ this->set_state(espbt::ClientState::CONNECTED); ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str()); if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { - ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str()); - // Restore to medium connection parameters for cached connections too this->restore_medium_conn_params_(); - // only set our state, subclients might have more stuff to do yet. this->state_ = espbt::ClientState::ESTABLISHED; break; @@ -327,8 +322,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // This saves ~3ms in the connection process. auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id); if (ret) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_, - this->address_str_.c_str(), ret); + this->log_gattc_warning_("esp_ble_gattc_send_mtu_req", ret); } break; } diff --git a/esphome/components/esp32_ble_client/ble_client_base.h b/esphome/components/esp32_ble_client/ble_client_base.h index 093d04640b..2d00688dbd 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.h +++ b/esphome/components/esp32_ble_client/ble_client_base.h @@ -136,6 +136,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { void restore_medium_conn_params_(); void log_gattc_warning_(const char *operation, esp_gatt_status_t status); void log_gattc_warning_(const char *operation, esp_err_t err); + void log_connection_params_(const char *param_type); }; } // namespace esphome::esp32_ble_client From 83b69519dd14cdc93cce0fe1745fc939a50892dd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Aug 2025 14:43:13 -1000 Subject: [PATCH 4/8] [wifi] Reduce flash usage by optimizing logging (#10127) --- esphome/components/wifi/wifi_component.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index f815ab73c2..987e276e0c 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -457,9 +457,11 @@ void WiFiComponent::print_connect_params_() { " Signal strength: %d dB %s", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi))); +#ifdef ESPHOME_LOG_HAS_VERBOSE if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } +#endif ESP_LOGCONFIG(TAG, " Channel: %" PRId32 "\n" " Subnet: %s\n" @@ -594,8 +596,10 @@ void WiFiComponent::check_scanning_finished() { if (res.get_matches()) { ESP_LOGI(TAG, "- '%s' %s" LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), res.get_is_hidden() ? "(HIDDEN) " : "", bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); - ESP_LOGD(TAG, " Channel: %u", res.get_channel()); - ESP_LOGD(TAG, " RSSI: %d dB", res.get_rssi()); + ESP_LOGD(TAG, + " Channel: %u\n" + " RSSI: %d dB", + res.get_channel(), res.get_rssi()); } else { ESP_LOGD(TAG, "- " LOG_SECRET("'%s'") " " LOG_SECRET("(%s) ") "%s", res.get_ssid().c_str(), bssid_s, LOG_STR_ARG(get_signal_bars(res.get_rssi()))); From 58504662d84559e575a9e3f3d92e5252ad0f8307 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Aug 2025 14:44:47 -1000 Subject: [PATCH 5/8] [cover] Reduce flash usage by optimizing validation messages (#10130) --- esphome/components/cover/cover.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index d139bab8ee..68dfab111b 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -99,43 +99,39 @@ const optional &CoverCall::get_tilt() const { return this->tilt_; } const optional &CoverCall::get_toggle() const { return this->toggle_; } void CoverCall::validate_() { auto traits = this->parent_->get_traits(); + const char *name = this->parent_->get_name().c_str(); + if (this->position_.has_value()) { auto pos = *this->position_; if (!traits.get_supports_position() && pos != COVER_OPEN && pos != COVER_CLOSED) { - ESP_LOGW(TAG, "'%s' - This cover device does not support setting position!", this->parent_->get_name().c_str()); + ESP_LOGW(TAG, "'%s': position unsupported", name); this->position_.reset(); } else if (pos < 0.0f || pos > 1.0f) { - ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos); + ESP_LOGW(TAG, "'%s': position %.2f out of range", name, pos); this->position_ = clamp(pos, 0.0f, 1.0f); } } if (this->tilt_.has_value()) { auto tilt = *this->tilt_; if (!traits.get_supports_tilt()) { - ESP_LOGW(TAG, "'%s' - This cover device does not support tilt!", this->parent_->get_name().c_str()); + ESP_LOGW(TAG, "'%s': tilt unsupported", name); this->tilt_.reset(); } else if (tilt < 0.0f || tilt > 1.0f) { - ESP_LOGW(TAG, "'%s' - Tilt %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), tilt); + ESP_LOGW(TAG, "'%s': tilt %.2f out of range", name, tilt); this->tilt_ = clamp(tilt, 0.0f, 1.0f); } } if (this->toggle_.has_value()) { if (!traits.get_supports_toggle()) { - ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str()); + ESP_LOGW(TAG, "'%s': toggle unsupported", name); this->toggle_.reset(); } } if (this->stop_) { - if (this->position_.has_value()) { - ESP_LOGW(TAG, "Cannot set position when stopping a cover!"); + if (this->position_.has_value() || this->tilt_.has_value() || this->toggle_.has_value()) { + ESP_LOGW(TAG, "'%s': cannot position/tilt/toggle when stopping", name); this->position_.reset(); - } - if (this->tilt_.has_value()) { - ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!"); this->tilt_.reset(); - } - if (this->toggle_.has_value()) { - ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!"); this->toggle_.reset(); } } From 7e4d09dbd89fb07522825f315f62584299cc82de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Aug 2025 16:24:26 -1000 Subject: [PATCH 6/8] [bluetooth_proxy] Optimize connection loop to reduce CPU usage (#10133) --- .../bluetooth_proxy/bluetooth_connection.cpp | 21 ++++++++++++++----- .../bluetooth_proxy/bluetooth_connection.h | 2 +- .../bluetooth_proxy/bluetooth_proxy.cpp | 5 ++--- .../bluetooth_proxy/bluetooth_proxy.h | 1 + 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index fd77f9bd5b..b16b894188 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -107,13 +107,24 @@ void BluetoothConnection::set_address(uint64_t address) { void BluetoothConnection::loop() { BLEClientBase::loop(); - // Early return if no active connection or not in service discovery phase - if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) { + // Early return if no active connection + if (this->address_ == 0) { return; } - // Handle service discovery - this->send_service_for_discovery_(); + // Handle service discovery if in valid range + if (this->send_service_ >= 0 && this->send_service_ <= this->service_count_) { + this->send_service_for_discovery_(); + } + + // Check if we should disable the loop + // - For V3_WITH_CACHE: Services are never sent, disable after INIT state + // - For other connections: Disable only after service discovery is complete + // (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent) + if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || + this->send_service_ == DONE_SENDING_SERVICES)) { + this->disable_loop(); + } } void BluetoothConnection::reset_connection_(esp_err_t reason) { @@ -127,7 +138,7 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) { // to detect incomplete service discovery rather than relying on us to // tell them about a partial list. this->set_address(0); - this->send_service_ = DONE_SENDING_SERVICES; + this->send_service_ = INIT_SENDING_SERVICES; this->proxy_->send_connections_free(); } diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index 7feb3c80bc..a975d25d91 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -44,7 +44,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { BluetoothProxy *proxy_; // Group 2: 2-byte types - int16_t send_service_{-2}; // Needs to handle negative values and service count + int16_t send_service_{-3}; // -3 = INIT_SENDING_SERVICES, -2 = DONE_SENDING_SERVICES, >=0 = service index // Group 3: 1-byte types bool seen_mtu_or_services_{false}; diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 1986ea90d5..04b85fc3f0 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -192,7 +192,7 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese for (uint8_t i = 0; i < this->connection_count_; i++) { auto *connection = this->connections_[i]; if (connection->get_address() == 0) { - connection->send_service_ = DONE_SENDING_SERVICES; + connection->send_service_ = INIT_SENDING_SERVICES; connection->set_address(address); // All connections must start at INIT // We only set the state if we allocate the connection @@ -373,8 +373,7 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer this->send_gatt_services_done(msg.address); return; } - if (connection->send_service_ == - DONE_SENDING_SERVICES) // Only start sending services if we're not already sending them + if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet connection->send_service_ = 0; } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 4f22a179d6..21695d9819 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -23,6 +23,7 @@ namespace esphome::bluetooth_proxy { static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; static const int DONE_SENDING_SERVICES = -2; +static const int INIT_SENDING_SERVICES = -3; using namespace esp32_ble_client; From cdcf5fd74c306ea0c4cd84c8372ed79c2317409b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Aug 2025 18:47:54 -1000 Subject: [PATCH 7/8] [dashboard] Fix port fallback regression when device is offline --- esphome/dashboard/web_server.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 46f09336bb..9db389c39a 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -324,14 +324,13 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket): configuration = json_message["configuration"] config_file = settings.rel_path(configuration) port = json_message["port"] - addresses: list[str] = [port] + addresses: list[str] = [] if ( port == "OTA" # pylint: disable=too-many-boolean-expressions and (entry := entries.get(config_file)) and entry.loaded_integrations and "api" in entry.loaded_integrations ): - addresses = [] # First priority: entry.address AKA use_address if ( (use_address := entry.address) @@ -359,6 +358,13 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket): # since MQTT logging will not work otherwise addresses.extend(sort_ip_addresses(new_addresses)) + if not addresses: + # If no address was found, use the port directly + # as otherwise they will get the chooser which + # does not work with the dashboard as there is no + # interactive way to get keyboard input + addresses = [port] + device_args: list[str] = [ arg for address in addresses for arg in ("--device", address) ] From 676c51ffa05a2853defb867565284bf7d0818ecc Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Fri, 8 Aug 2025 07:51:19 +0200 Subject: [PATCH 8/8] [switch] Add `control()` method to API (#10118) --- esphome/components/switch/automation.h | 6 +----- esphome/components/switch/switch.cpp | 8 ++++++++ esphome/components/switch/switch.h | 8 ++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/esphome/components/switch/automation.h b/esphome/components/switch/automation.h index 51ac5d7a3a..66818a80be 100644 --- a/esphome/components/switch/automation.h +++ b/esphome/components/switch/automation.h @@ -46,11 +46,7 @@ template class ControlAction : public Action { void play(Ts... x) override { auto state = this->state_.optional_value(x...); if (state.has_value()) { - if (*state) { - this->switch_->turn_on(); - } else { - this->switch_->turn_off(); - } + this->switch_->control(*state); } } diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index c204895755..13c12c1213 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -8,6 +8,14 @@ static const char *const TAG = "switch"; Switch::Switch() : state(false) {} +void Switch::control(bool target_state) { + ESP_LOGV(TAG, "'%s' Control: %s", this->get_name().c_str(), ONOFF(target_state)); + if (target_state) { + this->turn_on(); + } else { + this->turn_off(); + } +} void Switch::turn_on() { ESP_LOGD(TAG, "'%s' Turning ON.", this->get_name().c_str()); this->write_state(!this->inverted_); diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index b999296564..6371e35292 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -55,6 +55,14 @@ class Switch : public EntityBase, public EntityBase_DeviceClass { /// The current reported state of the binary sensor. bool state; + /** Control this switch using a boolean state value. + * + * This method provides a unified interface for setting the switch state based on a boolean parameter. + * It automatically calls turn_on() when state is true or turn_off() when state is false. + * + * @param target_state The desired state: true to turn the switch ON, false to turn it OFF. + */ + void control(bool target_state); /** Turn this switch on. This is called by the front-end. * * For implementing switches, please override write_state.