diff --git a/CODEOWNERS b/CODEOWNERS index c630db7948..7129e15a76 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -323,6 +323,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/__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..4dc37119e8 --- /dev/null +++ b/esphome/components/smartconfig/smartconfig_component.cpp @@ -0,0 +1,183 @@ +#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_; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +#ifdef USE_ESP32 +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); +#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; } + +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 == nullptr) { + 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) && + 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()); + }); + } +#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 = // 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 + +#endif diff --git a/esphome/components/smartconfig/smartconfig_component.h b/esphome/components/smartconfig/smartconfig_component.h new file mode 100644 index 0000000000..63bfd29c65 --- /dev/null +++ b/esphome/components/smartconfig/smartconfig_component.h @@ -0,0 +1,49 @@ +#pragma once + +#include "esphome/components/wifi/wifi_component.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.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); +}; + +} // 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 075e683bb5..65ceedb41c 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 { @@ -106,6 +110,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_(); } @@ -187,6 +197,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_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 44d77b4eed..64ebff8f7b 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 { @@ -451,6 +455,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/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index ebb2fb92ea..64d5700788 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 { @@ -623,6 +627,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"); diff --git a/tests/test5.yaml b/tests/test5.yaml index afd3359098..faaa929e98 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