From de3cb58ca35b8735aaa31436ab432cbdd35d8373 Mon Sep 17 00:00:00 2001 From: Vinh Phan Date: Thu, 28 Dec 2023 17:15:58 +0700 Subject: [PATCH 1/2] Add support smartconfig ESP32/ESP8266 --- esphome/components/smartconfig/__init__.py | 36 ++++ .../smartconfig/smartconfig_component.cpp | 175 ++++++++++++++++++ .../smartconfig/smartconfig_component.h | 56 ++++++ esphome/components/wifi/__init__.py | 3 +- esphome/components/wifi/wifi_component.cpp | 15 ++ .../wifi/wifi_component_esp_idf.cpp | 8 + 6 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 esphome/components/smartconfig/__init__.py create mode 100644 esphome/components/smartconfig/smartconfig_component.cpp create mode 100644 esphome/components/smartconfig/smartconfig_component.h diff --git a/esphome/components/smartconfig/__init__.py b/esphome/components/smartconfig/__init__.py new file mode 100644 index 0000000000..dbd3af992d --- /dev/null +++ b/esphome/components/smartconfig/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.const import CONF_ID, CONF_TRIGGER_ID + +CODEOWNERS = ["@vinhpn96"] +DEPENDENCIES = ["wifi"] + +CONF_ON_READY = "on_ready" + +smartconfig_ns = cg.esphome_ns.namespace("smartconfig") +SmartConfigComponent = smartconfig_ns.class_("SmartConfigComponent", cg.Component) +SmartConfigReadyTrigger = smartconfig_ns.class_( + "SmartConfigReadyTrigger", automation.Trigger.template() +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(SmartConfigComponent), + cv.Optional(CONF_ON_READY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SmartConfigReadyTrigger), + } + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add_define("USE_SMARTCONFIG") + + for conf in config.get(CONF_ON_READY, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/smartconfig/smartconfig_component.cpp b/esphome/components/smartconfig/smartconfig_component.cpp new file mode 100644 index 0000000000..4d9d7776fd --- /dev/null +++ b/esphome/components/smartconfig/smartconfig_component.cpp @@ -0,0 +1,175 @@ +#include "smartconfig_component.h" + +#ifdef USE_ESP32 +#include "freertos/FreeRTOS.h" +#include "esp_event.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_smartconfig.h" +#endif + +#ifdef USE_ESP8266 +#include "smartconfig.h" +#endif + +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +#if defined(USE_ESP32) || defined(USE_ESP8266) + +namespace esphome { +namespace smartconfig { + +#ifdef USE_ESP32 +#define SC_START_BIT BIT0 +#define SC_READY_BIT BIT1 +#define SC_DONE_BIT BIT2 +#endif + +static const char *const TAG = "smartconfig"; +static wifi::WiFiAP connecting_sta_; +static bool is_sc_ready {false}; + +#ifdef USE_ESP32 +static EventGroupHandle_t sce_group = NULL; +static void smartconfig_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data); +static void smartconfig_got_ssid_pwd(void *data); +#endif + +#ifdef USE_ESP8266 +static void smartconfig_done(sc_status status, void *pdata); +#endif + +SmartConfigComponent::SmartConfigComponent() { global_smartconfig_component = this; } + +float SmartConfigComponent::get_setup_priority() const { return setup_priority::WIFI; } + +void SmartConfigComponent::config() { + ESP_LOGD(TAG, "config SmartConfig"); + +#if defined(USE_ESP32) + sce_group = xEventGroupCreate(); + esp_err_t err; + esp_event_handler_instance_t instance_sc_id; + err = esp_event_handler_instance_register(SC_EVENT, ESP_EVENT_ANY_ID, &smartconfig_event_handler, nullptr, + &instance_sc_id); + ESP_ERROR_CHECK(err); +#elif defined(USE_ESP8266) + smartconfig_set_type(SC_TYPE_ESPTOUCH); + smartconfig_start(smartconfig_done); + this->state_ = SmartConfigState::SC_START; +#endif +} + +void SmartConfigComponent::loop() { +#if defined(USE_ESP32) + if (sce_group == NULL) { + return; + } + EventBits_t xbits; + xbits = xEventGroupWaitBits(sce_group, SC_START_BIT | SC_READY_BIT | SC_DONE_BIT, true, false, 0); + if (xbits & SC_START_BIT) { + ESP_LOGD(TAG, "Starting SmartConfig"); + smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); + esp_smartconfig_set_type(SC_TYPE_ESPTOUCH); + esp_smartconfig_start(&cfg); + this->state_ = SmartConfigState::SC_START; + } + if (xbits & SC_READY_BIT) { + ESP_LOGD(TAG, "Smartconfig ready"); + this->state_ = SmartConfigState::SC_READY; + on_smartconfig_ready_.call(); + } + if (xbits & SC_DONE_BIT) { + ESP_LOGD(TAG, "Smartconfig over"); + esp_smartconfig_stop(); + this->state_ = SmartConfigState::SC_DONE; + this->set_timeout("save-wifi", 1000, [] { + wifi::global_wifi_component->save_wifi_sta(connecting_sta_.get_ssid(), connecting_sta_.get_password()); + }); + } +#elif defined(USE_ESP8266) + if (is_sc_ready && (this->state_ != SmartConfigState::SC_READY)) { + this->state_ = SmartConfigState::SC_READY; + on_smartconfig_ready_.call(); + } else if (!is_sc_ready && (this->state_ == SmartConfigState::SC_READY)) { + this->state_ = SmartConfigState::SC_DONE; + this->set_timeout("save-wifi", 1000, [] { + wifi::global_wifi_component->save_wifi_sta(connecting_sta_.get_ssid(), connecting_sta_.get_password()); + }); + } +#endif +} + +#ifdef USE_ESP32 +void SmartConfigComponent::start() { + if (this->state_ == SmartConfigState::SC_IDLE) { + xEventGroupSetBits(sce_group, SC_START_BIT); + } +} + +void smartconfig_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { + if (event_base == SC_EVENT && event == SC_EVENT_SCAN_DONE) { + ESP_LOGD(TAG, "SC_EVENT_SCAN_DONE"); + xEventGroupSetBits(sce_group, SC_READY_BIT); + } else if (event_base == SC_EVENT && event == SC_EVENT_FOUND_CHANNEL) { + ESP_LOGD(TAG, "Found channel"); + } else if (event_base == SC_EVENT && event == SC_EVENT_GOT_SSID_PSWD) { + smartconfig_got_ssid_pwd(event_data); + } else if (event_base == SC_EVENT && event == SC_EVENT_SEND_ACK_DONE) { + xEventGroupSetBits(sce_group, SC_DONE_BIT); + } +} +#endif + +static void smartconfig_got_ssid_pwd(void *data) { +#if defined(USE_ESP32) + smartconfig_event_got_ssid_pswd_t *sta_conf = (smartconfig_event_got_ssid_pswd_t *) data; +#elif defined(USE_ESP8266) + struct station_config *sta_conf = (struct station_config *) data; +#endif + std::string ssid((char *) sta_conf->ssid); + std::string password((char *) sta_conf->password); + connecting_sta_.set_ssid(ssid); + connecting_sta_.set_password(password); + + wifi::global_wifi_component->set_sta(connecting_sta_); + wifi::global_wifi_component->start_scanning(); + ESP_LOGD(TAG, "Received smartconfig wifi settings ssid=%s, password=" LOG_SECRET("%s"), ssid.c_str(), + password.c_str()); +} + +#ifdef USE_ESP8266 +void smartconfig_done(sc_status status, void *pdata) +{ + switch(status) { + case SC_STATUS_WAIT: + ESP_LOGD(TAG, "SC_STATUS_WAIT"); + break; + case SC_STATUS_FIND_CHANNEL: + ESP_LOGD(TAG, "SC_STATUS_FIND_CHANNEL"); + is_sc_ready = true; + break; + case SC_STATUS_GETTING_SSID_PSWD: + ESP_LOGD(TAG, "SC_STATUS_GETTING_SSID_PSWD"); + break; + case SC_STATUS_LINK: + ESP_LOGD(TAG, "SC_STATUS_LINK"); + smartconfig_got_ssid_pwd(pdata); + break; + case SC_STATUS_LINK_OVER: + ESP_LOGD(TAG, "SC_STATUS_LINK_OVER"); + smartconfig_stop(); + is_sc_ready = false; + break; + } +} +#endif + +SmartConfigComponent *global_smartconfig_component = + nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace smartconfig +} // namespace esphome + +#endif diff --git a/esphome/components/smartconfig/smartconfig_component.h b/esphome/components/smartconfig/smartconfig_component.h new file mode 100644 index 0000000000..2478bfc0ef --- /dev/null +++ b/esphome/components/smartconfig/smartconfig_component.h @@ -0,0 +1,56 @@ +#pragma once + +#include "esphome/components/wifi/wifi_component.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/preferences.h" +#include "esphome/core/log.h" + +#if defined(USE_ESP32) || defined(USE_ESP8266) + +namespace esphome { +namespace smartconfig { + +using on_smartconfig_ready_cb_t = std::function; + +class SmartConfigComponent : public Component { + public: + enum class SmartConfigState { + SC_IDLE, + SC_START, + SC_READY, + SC_DONE, + }; + SmartConfigComponent(); + + void loop() override; +#ifdef USE_ESP32 + void start(); +#endif + + float get_setup_priority() const override; + void config(); + void add_on_ready(on_smartconfig_ready_cb_t callback) { on_smartconfig_ready_.add(std::move(callback)); } + + protected: + CallbackManager on_smartconfig_ready_; + SmartConfigState state_ {SmartConfigState::SC_IDLE}; +}; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +extern SmartConfigComponent *global_smartconfig_component; + +class SmartConfigReadyTrigger : public Trigger<> { + public: + explicit SmartConfigReadyTrigger(SmartConfigComponent *&sc_component) { + sc_component->add_on_ready([this]() { + ESP_LOGD("smartconfig", "add_on_ready Smartconfig ready"); + this->trigger(); + }); + } +}; + +} // namespace smartconfig +} // namespace esphome + +#endif diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 32c9d07046..bee1ca0630 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -164,7 +164,8 @@ def final_validate(config): has_ap = CONF_AP in config has_improv = "esp32_improv" in fv.full_config.get() has_improv_serial = "improv_serial" in fv.full_config.get() - if not (has_sta or has_ap or has_improv or has_improv_serial): + has_smartconfig = "smartconfig" in fv.full_config.get() + if not (has_sta or has_ap or has_improv or has_improv_serial or has_smartconfig): raise cv.Invalid( "Please specify at least an SSID or an Access Point to create." ) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 519489097a..7aea539095 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -27,6 +27,10 @@ #include "esphome/components/esp32_improv/esp32_improv_component.h" #endif +#ifdef USE_SMARTCONFIG +#include "esphome/components/smartconfig/smartconfig_component.h" +#endif + namespace esphome { namespace wifi { @@ -102,6 +106,12 @@ void WiFiComponent::start() { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); } +#endif +#ifdef USE_SMARTCONFIG + if (!this->has_sta() && smartconfig::global_smartconfig_component != nullptr) { + if (this->wifi_mode_(true, {})) + smartconfig::global_smartconfig_component->config(); + } #endif this->wifi_apply_hostname_(); } @@ -183,6 +193,11 @@ void WiFiComponent::loop() { } } +#endif +#ifdef USE_SMARTCONFIG + if ((smartconfig::global_smartconfig_component != nullptr) && (!this->is_connected())) { + this->wifi_mode_(true, {}); + } #endif if (!this->has_ap() && this->reboot_timeout_ != 0) { diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 0035733553..7d32559b0b 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -31,6 +31,10 @@ #include "esphome/core/application.h" #include "esphome/core/util.h" +#ifdef USE_SMARTCONFIG +#include "esphome/components/smartconfig/smartconfig_component.h" +#endif + namespace esphome { namespace wifi { @@ -615,6 +619,10 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { s_sta_started = true; // re-apply power save mode wifi_apply_power_save_(); +#ifdef USE_SMARTCONFIG + if (!this->has_sta()) + smartconfig::global_smartconfig_component->start(); +#endif } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_STOP) { ESP_LOGV(TAG, "Event: WiFi STA stop"); From 1a8913f910916676e989c49b7c24b34ac6575e3b Mon Sep 17 00:00:00 2001 From: Vinh Phan Date: Wed, 17 Jan 2024 16:48:42 +0700 Subject: [PATCH 2/2] support smartconfig ESP32-Arduino --- CODEOWNERS | 1 + .../smartconfig/smartconfig_component.cpp | 30 ++++++++++++------- .../smartconfig/smartconfig_component.h | 11 ++----- .../wifi/wifi_component_esp32_arduino.cpp | 8 +++++ tests/test5.yaml | 2 ++ 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index dff5d8beb5..8126b2958e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -294,6 +294,7 @@ esphome/components/sm10bit_base/* @Cossid esphome/components/sm2135/* @BoukeHaarsma23 @dd32 @matika77 esphome/components/sm2235/* @Cossid esphome/components/sm2335/* @Cossid +esphome/components/smartconfig/* @vinhpn96 esphome/components/sml/* @alengwenus esphome/components/smt100/* @piechade esphome/components/sn74hc165/* @jesserockz diff --git a/esphome/components/smartconfig/smartconfig_component.cpp b/esphome/components/smartconfig/smartconfig_component.cpp index 4d9d7776fd..4dc37119e8 100644 --- a/esphome/components/smartconfig/smartconfig_component.cpp +++ b/esphome/components/smartconfig/smartconfig_component.cpp @@ -27,19 +27,20 @@ namespace smartconfig { #endif static const char *const TAG = "smartconfig"; -static wifi::WiFiAP connecting_sta_; -static bool is_sc_ready {false}; +static wifi::WiFiAP connecting_sta_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #ifdef USE_ESP32 -static EventGroupHandle_t sce_group = NULL; +static EventGroupHandle_t sce_group = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static void smartconfig_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data); -static void smartconfig_got_ssid_pwd(void *data); #endif #ifdef USE_ESP8266 +static bool is_sc_ready{false}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static void smartconfig_done(sc_status status, void *pdata); #endif +static void smartconfig_got_ssid_pwd(void *data); + SmartConfigComponent::SmartConfigComponent() { global_smartconfig_component = this; } float SmartConfigComponent::get_setup_priority() const { return setup_priority::WIFI; } @@ -63,7 +64,7 @@ void SmartConfigComponent::config() { void SmartConfigComponent::loop() { #if defined(USE_ESP32) - if (sce_group == NULL) { + if (sce_group == nullptr) { return; } EventBits_t xbits; @@ -92,7 +93,8 @@ void SmartConfigComponent::loop() { if (is_sc_ready && (this->state_ != SmartConfigState::SC_READY)) { this->state_ = SmartConfigState::SC_READY; on_smartconfig_ready_.call(); - } else if (!is_sc_ready && (this->state_ == SmartConfigState::SC_READY)) { + } else if (!is_sc_ready && (this->state_ == SmartConfigState::SC_READY) && + wifi::global_wifi_component->is_connected()) { this->state_ = SmartConfigState::SC_DONE; this->set_timeout("save-wifi", 1000, [] { wifi::global_wifi_component->save_wifi_sta(connecting_sta_.get_ssid(), connecting_sta_.get_password()); @@ -140,9 +142,8 @@ static void smartconfig_got_ssid_pwd(void *data) { } #ifdef USE_ESP8266 -void smartconfig_done(sc_status status, void *pdata) -{ - switch(status) { +void smartconfig_done(sc_status status, void *pdata) { + switch (status) { case SC_STATUS_WAIT: ESP_LOGD(TAG, "SC_STATUS_WAIT"); break; @@ -166,8 +167,15 @@ void smartconfig_done(sc_status status, void *pdata) } #endif -SmartConfigComponent *global_smartconfig_component = - nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +SmartConfigComponent *global_smartconfig_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + nullptr; + +SmartConfigReadyTrigger::SmartConfigReadyTrigger(SmartConfigComponent *&sc_component) { + sc_component->add_on_ready([this]() { + ESP_LOGD(TAG, "Smartconfig is ready"); + this->trigger(); + }); +} } // namespace smartconfig } // namespace esphome diff --git a/esphome/components/smartconfig/smartconfig_component.h b/esphome/components/smartconfig/smartconfig_component.h index 2478bfc0ef..63bfd29c65 100644 --- a/esphome/components/smartconfig/smartconfig_component.h +++ b/esphome/components/smartconfig/smartconfig_component.h @@ -3,8 +3,6 @@ #include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/core/preferences.h" -#include "esphome/core/log.h" #if defined(USE_ESP32) || defined(USE_ESP8266) @@ -34,7 +32,7 @@ class SmartConfigComponent : public Component { protected: CallbackManager on_smartconfig_ready_; - SmartConfigState state_ {SmartConfigState::SC_IDLE}; + SmartConfigState state_{SmartConfigState::SC_IDLE}; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) @@ -42,12 +40,7 @@ extern SmartConfigComponent *global_smartconfig_component; class SmartConfigReadyTrigger : public Trigger<> { public: - explicit SmartConfigReadyTrigger(SmartConfigComponent *&sc_component) { - sc_component->add_on_ready([this]() { - ESP_LOGD("smartconfig", "add_on_ready Smartconfig ready"); - this->trigger(); - }); - } + explicit SmartConfigReadyTrigger(SmartConfigComponent *&sc_component); }; } // namespace smartconfig diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 5d8aa7f749..0e0463aee5 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -19,6 +19,10 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" +#ifdef USE_SMARTCONFIG +#include "esphome/components/smartconfig/smartconfig_component.h" +#endif + namespace esphome { namespace wifi { @@ -427,6 +431,10 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ case ESPHOME_EVENT_ID_WIFI_STA_START: { ESP_LOGV(TAG, "Event: WiFi STA start"); tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); +#ifdef USE_SMARTCONFIG + if (!this->has_sta()) + smartconfig::global_smartconfig_component->start(); +#endif break; } case ESPHOME_EVENT_ID_WIFI_STA_STOP: { diff --git a/tests/test5.yaml b/tests/test5.yaml index bf4247fb92..73679caab4 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -22,6 +22,8 @@ wifi: gateway: 192.168.1.1 subnet: 255.255.255.0 +smartconfig: + network: enable_ipv6: true