From 9fc1377b448fab25c91e3342834a0b38df0c4a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Mart=C3=ADn?= Date: Sun, 24 Nov 2024 23:06:21 +0100 Subject: [PATCH 1/5] feat(WiFi): Add wifi.configure action (#7335) --- esphome/components/wifi/__init__.py | 42 +++++++++++++ esphome/components/wifi/wifi_component.h | 79 ++++++++++++++++++++++++ tests/components/wifi/common.yaml | 7 +++ 3 files changed, 128 insertions(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index ea03cc16d1..ad1a4f5262 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -27,6 +27,7 @@ from esphome.const import ( CONF_NETWORKS, CONF_ON_CONNECT, CONF_ON_DISCONNECT, + CONF_ON_ERROR, CONF_PASSWORD, CONF_POWER_SAVE_MODE, CONF_PRIORITY, @@ -34,6 +35,7 @@ from esphome.const import ( CONF_SSID, CONF_STATIC_IP, CONF_SUBNET, + CONF_TIMEOUT, CONF_TTLS_PHASE_2, CONF_USE_ADDRESS, CONF_USERNAME, @@ -46,6 +48,7 @@ from . import wpa2_eap AUTO_LOAD = ["network"] NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2] +CONF_SAVE = "save" wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") @@ -63,6 +66,9 @@ WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition) WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition) WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action) WiFiDisableAction = wifi_ns.class_("WiFiDisableAction", automation.Action) +WiFiConfigureAction = wifi_ns.class_( + "WiFiConfigureAction", automation.Action, cg.Component +) def validate_password(value): @@ -483,3 +489,39 @@ async def wifi_enable_to_code(config, action_id, template_arg, args): @automation.register_action("wifi.disable", WiFiDisableAction, cv.Schema({})) async def wifi_disable_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg) + + +@automation.register_action( + "wifi.configure", + WiFiConfigureAction, + cv.Schema( + { + cv.Required(CONF_SSID): cv.templatable(cv.ssid), + cv.Required(CONF_PASSWORD): cv.templatable(validate_password), + cv.Optional(CONF_SAVE, default=True): cv.templatable(cv.boolean), + cv.Optional(CONF_TIMEOUT, default="30000ms"): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True), + cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), + } + ), +) +async def wifi_set_sta_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + ssid = await cg.templatable(config[CONF_SSID], args, cg.std_string) + password = await cg.templatable(config[CONF_PASSWORD], args, cg.std_string) + save = await cg.templatable(config[CONF_SAVE], args, cg.bool_) + timeout = await cg.templatable(config.get(CONF_TIMEOUT), args, cg.uint32) + cg.add(var.set_ssid(ssid)) + cg.add(var.set_password(password)) + cg.add(var.set_save(save)) + cg.add(var.set_connection_timeout(timeout)) + if on_connect_config := config.get(CONF_ON_CONNECT): + await automation.build_automation( + var.get_connect_trigger(), [], on_connect_config + ) + if on_error_config := config.get(CONF_ON_ERROR): + await automation.build_automation(var.get_error_trigger(), [], on_error_config) + await cg.register_component(var, config) + return var diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 5995f72e0b..abedfab3a6 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -209,6 +209,7 @@ class WiFiComponent : public Component { WiFiComponent(); void set_sta(const WiFiAP &ap); + WiFiAP get_sta() { return this->selected_ap_; } void add_sta(const WiFiAP &ap); void clear_sta(); @@ -443,6 +444,84 @@ template class WiFiDisableAction : public Action { void play(Ts... x) override { global_wifi_component->disable(); } }; +template class WiFiConfigureAction : public Action, public Component { + public: + TEMPLATABLE_VALUE(std::string, ssid) + TEMPLATABLE_VALUE(std::string, password) + TEMPLATABLE_VALUE(bool, save) + TEMPLATABLE_VALUE(uint32_t, connection_timeout) + + void play(Ts... x) override { + auto ssid = this->ssid_.value(x...); + auto password = this->password_.value(x...); + // Avoid multiple calls + if (this->connecting_) + return; + // If already connected to the same AP, do nothing + if (global_wifi_component->wifi_ssid() == ssid) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + return; + } + // Create a new WiFiAP object with the new SSID and password + this->new_sta_.set_ssid(ssid); + this->new_sta_.set_password(password); + // Save the current STA + this->old_sta_ = global_wifi_component->get_sta(); + // Disable WiFi + global_wifi_component->disable(); + // Set the state to connecting + this->connecting_ = true; + // Store the new STA so once the WiFi is enabled, it will connect to it + // This is necessary because the WiFiComponent will raise an error and fallback to the saved STA + // if trying to connect to a new STA while already connected to another one + if (this->save_.value(x...)) { + global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password()); + } else { + global_wifi_component->set_sta(new_sta_); + } + // Enable WiFi + global_wifi_component->enable(); + // Set timeout for the connection + this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this]() { + this->connecting_ = false; + // If the timeout is reached, stop connecting and revert to the old AP + global_wifi_component->disable(); + global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password()); + global_wifi_component->enable(); + // Callback to notify the user that the connection failed + this->error_trigger_->trigger(); + }); + } + + Trigger<> *get_connect_trigger() const { return this->connect_trigger_; } + Trigger<> *get_error_trigger() const { return this->error_trigger_; } + + void loop() override { + if (!this->connecting_) + return; + if (global_wifi_component->is_connected()) { + // The WiFi is connected, stop the timeout and reset the connecting flag + this->cancel_timeout("wifi-connect-timeout"); + this->connecting_ = false; + if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + } else { + // Callback to notify the user that the connection failed + this->error_trigger_->trigger(); + } + } + } + + protected: + bool connecting_{false}; + WiFiAP new_sta_; + WiFiAP old_sta_; + Trigger<> *connect_trigger_{new Trigger<>()}; + Trigger<> *error_trigger_{new Trigger<>()}; +}; + } // namespace wifi } // namespace esphome #endif diff --git a/tests/components/wifi/common.yaml b/tests/components/wifi/common.yaml index 003f6347be..343d44b177 100644 --- a/tests/components/wifi/common.yaml +++ b/tests/components/wifi/common.yaml @@ -3,6 +3,13 @@ esphome: then: - wifi.disable - wifi.enable + - wifi.configure: + ssid: MySSID + password: password1 + on_connect: + - logger.log: "Connected to WiFi!" + on_error: + - logger.log: "Failed to connect to WiFi!" wifi: ssid: MySSID From d4d630823ccc3bdddb6e6b39176c188d353d0807 Mon Sep 17 00:00:00 2001 From: TFGF Date: Sun, 24 Nov 2024 19:15:10 -0300 Subject: [PATCH 2/5] [Modbus Controller] Fix issue #6477. Online automation triggering Offline (#7801) --- esphome/components/modbus_controller/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 5c407d6fff..2a08075831 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -163,7 +163,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_ON_OFFLINE): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOfflineTrigger), } ), } From e02f3cdac7b7e8a58711763b5d7f9dfac8ea6991 Mon Sep 17 00:00:00 2001 From: Ramil Valitov Date: Mon, 25 Nov 2024 01:23:30 +0300 Subject: [PATCH 3/5] [fix] Status sensor does not check if required network component is missing (#7734) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/status/binary_sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index 1f2b7c9d18..adc342ed4d 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -6,6 +6,8 @@ from esphome.const import ( ENTITY_CATEGORY_DIAGNOSTIC, ) +DEPENDENCIES = ["network"] + status_ns = cg.esphome_ns.namespace("status") StatusBinarySensor = status_ns.class_( "StatusBinarySensor", binary_sensor.BinarySensor, cg.Component From 59653ec7853a349e9e736710867daedc017a725e Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sun, 24 Nov 2024 12:40:28 -1000 Subject: [PATCH 4/5] allow multiple graphical menus (#7809) Co-authored-by: Samuel Sieb --- esphome/components/display_menu_base/__init__.py | 2 -- esphome/components/graphical_display_menu/__init__.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/display_menu_base/__init__.py b/esphome/components/display_menu_base/__init__.py index 8ae9cbc2a4..f9c0424104 100644 --- a/esphome/components/display_menu_base/__init__.py +++ b/esphome/components/display_menu_base/__init__.py @@ -68,8 +68,6 @@ IsActiveCondition = display_menu_base_ns.class_( "IsActiveCondition", automation.Condition ) -MULTI_CONF = True - MenuItemType = display_menu_base_ns.enum("MenuItemType") MENU_ITEM_TYPES = { diff --git a/esphome/components/graphical_display_menu/__init__.py b/esphome/components/graphical_display_menu/__init__.py index f4d59b22b8..56b720e75c 100644 --- a/esphome/components/graphical_display_menu/__init__.py +++ b/esphome/components/graphical_display_menu/__init__.py @@ -36,6 +36,8 @@ CODEOWNERS = ["@MrMDavidson"] AUTO_LOAD = ["display_menu_base"] +MULTI_CONF = True + CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend( cv.Schema( { From b95b4a069417ea6fd31f96b5e9150ca16dd1c3f5 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sun, 24 Nov 2024 12:40:51 -1000 Subject: [PATCH 5/5] keypad binary sensors should be initially off (#7808) Co-authored-by: Samuel Sieb --- esphome/components/binary_sensor/binary_sensor.h | 2 +- .../matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 301a472810..57cae9e2f5 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -58,7 +58,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass { void publish_initial_state(bool state); /// The current reported state of the binary sensor. - bool state; + bool state{false}; void add_filter(Filter *filter); void add_filters(const std::vector &filters); diff --git a/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h b/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h index d8a217f55e..2c1ce96f0a 100644 --- a/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h +++ b/esphome/components/matrix_keypad/binary_sensor/matrix_keypad_binary_sensor.h @@ -6,7 +6,7 @@ namespace esphome { namespace matrix_keypad { -class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensor { +class MatrixKeypadBinarySensor : public MatrixKeypadListener, public binary_sensor::BinarySensorInitiallyOff { public: MatrixKeypadBinarySensor(uint8_t key) : has_key_(true), key_(key){}; MatrixKeypadBinarySensor(const char *key) : has_key_(true), key_((uint8_t) key[0]){};