diff --git a/CODEOWNERS b/CODEOWNERS index c9b59c099a..49b3df9b50 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -107,6 +107,7 @@ esphome/components/hte501/* @Stock-M esphome/components/hydreon_rgxx/* @functionpointer esphome/components/i2c/* @esphome/core esphome/components/i2s_audio/* @jesserockz +esphome/components/improv_base/* @esphome/core esphome/components/improv_serial/* @esphome/core esphome/components/ina260/* @MrEditor97 esphome/components/inkbird_ibsth1_mini/* @fkirill diff --git a/esphome/components/improv_base/__init__.py b/esphome/components/improv_base/__init__.py new file mode 100644 index 0000000000..5c2853a5c6 --- /dev/null +++ b/esphome/components/improv_base/__init__.py @@ -0,0 +1,42 @@ +import re + +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.const import __version__ + +CODEOWNERS = ["@esphome/core"] + +CONF_NEXT_URL = "next_url" + +VALID_SUBSTITUTIONS = ["esphome_version", "ip_address", "device_name"] + + +def validate_next_url(value): + value = cv.url(value) + test = r"{{(?!" + r"\b|".join(VALID_SUBSTITUTIONS) + r"\b)(\w+)}}" + result = re.search(test, value) + if result: + raise cv.Invalid( + f"Invalid substitution(s) ({', '.join(result.groups())}) in next_url. Valid substitutions are: {', '.join(VALID_SUBSTITUTIONS)}" + ) + return value + + +IMPROV_SCHEMA = cv.Schema( + { + cv.Optional(CONF_NEXT_URL): validate_next_url, + } +) + + +def _process_next_url(url: str): + if "{{esphome_version}}" in url: + url = url.replace("{{esphome_version}}", __version__) + return url + + +async def setup_improv_core(var, config): + if CONF_NEXT_URL in config: + cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL]))) + cg.add_library("esphome/Improv", "1.2.3") diff --git a/esphome/components/improv_base/improv_base.cpp b/esphome/components/improv_base/improv_base.cpp new file mode 100644 index 0000000000..c95b3a77ec --- /dev/null +++ b/esphome/components/improv_base/improv_base.cpp @@ -0,0 +1,32 @@ +#include "improv_base.h" + +#include "esphome/components/network/util.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace improv_base { + +std::string ImprovBase::get_formatted_next_url_() { + if (this->next_url_.empty()) { + return ""; + } + std::string copy = this->next_url_; + // Device name + std::size_t pos = this->next_url_.find("{{device_name}}"); + if (pos != std::string::npos) { + const std::string &device_name = App.get_name(); + copy.replace(pos, 15, device_name); + } + + // Ip address + pos = this->next_url_.find("{{ip_address}}"); + if (pos != std::string::npos) { + std::string ip = network::IPAddress(network::get_ip_address()).str(); + copy.replace(pos, 14, ip); + } + + return copy; +} + +} // namespace improv_base +} // namespace esphome diff --git a/esphome/components/improv_base/improv_base.h b/esphome/components/improv_base/improv_base.h new file mode 100644 index 0000000000..90cd02a4ab --- /dev/null +++ b/esphome/components/improv_base/improv_base.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace esphome { +namespace improv_base { + +class ImprovBase { + public: + void set_next_url(const std::string &next_url) { this->next_url_ = next_url; } + + protected: + std::string get_formatted_next_url_(); + std::string next_url_; +}; + +} // namespace improv_base +} // namespace esphome diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 4a6da6bee0..311256804b 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -4,7 +4,9 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE import esphome.final_validate as fv +from esphome.components import improv_base +AUTO_LOAD = ["improv_base"] CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["logger", "wifi"] @@ -12,11 +14,15 @@ improv_serial_ns = cg.esphome_ns.namespace("improv_serial") ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(ImprovSerialComponent), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ImprovSerialComponent), + } + ) + .extend(improv_base.IMPROV_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) def validate_logger(config): @@ -37,4 +43,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - cg.add_library("esphome/Improv", "1.2.3") + await improv_base.setup_improv_core(var, config) diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 0dab71060c..fe19e2f085 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -95,6 +95,9 @@ void ImprovSerialComponent::loop() { std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { std::vector urls; + if (!this->next_url_.empty()) { + urls.push_back(this->get_formatted_next_url_()); + } #ifdef USE_WEBSERVER auto ip = wifi::global_wifi_component->wifi_sta_ip(); std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 3d8478252d..731f9f9984 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/components/improv_base/improv_base.h" #include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" @@ -27,7 +28,7 @@ enum ImprovSerialType : uint8_t { static const uint8_t IMPROV_SERIAL_VERSION = 1; -class ImprovSerialComponent : public Component { +class ImprovSerialComponent : public Component, public improv_base::ImprovBase { public: void setup() override; void loop() override; diff --git a/tests/test3.yaml b/tests/test3.yaml index 5244f41d9a..2856a3c7d7 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -298,6 +298,7 @@ logger: esp8266_store_log_strings_in_flash: true improv_serial: + next_url: https://esphome.io/?name={{device_name}}&version={{esphome_version}}&ip={{ip_address}} deep_sleep: run_duration: 20s