1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-25 21:23:53 +01:00

[esp32_improv]: add next_url support for WiFi provisioning (#10757)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Aman kumar
2025-10-20 11:40:38 +05:30
committed by GitHub
parent 6f5e36ffc3
commit 3d82c5baf7
5 changed files with 101 additions and 71 deletions

View File

@@ -1,11 +1,11 @@
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import binary_sensor, esp32_ble, output from esphome.components import binary_sensor, esp32_ble, improv_base, output
from esphome.components.esp32_ble import BTLoggers from esphome.components.esp32_ble import BTLoggers
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
AUTO_LOAD = ["esp32_ble_server"] AUTO_LOAD = ["esp32_ble_server", "improv_base"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["wifi", "esp32"] DEPENDENCIES = ["wifi", "esp32"]
@@ -20,6 +20,7 @@ CONF_ON_STOP = "on_stop"
CONF_STATUS_INDICATOR = "status_indicator" CONF_STATUS_INDICATOR = "status_indicator"
CONF_WIFI_TIMEOUT = "wifi_timeout" CONF_WIFI_TIMEOUT = "wifi_timeout"
improv_ns = cg.esphome_ns.namespace("improv") improv_ns = cg.esphome_ns.namespace("improv")
Error = improv_ns.enum("Error") Error = improv_ns.enum("Error")
State = improv_ns.enum("State") State = improv_ns.enum("State")
@@ -43,55 +44,63 @@ ESP32ImprovStoppedTrigger = esp32_improv_ns.class_(
) )
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = (
{ cv.Schema(
cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), {
cv.Required(CONF_AUTHORIZER): cv.Any( cv.GenerateID(): cv.declare_id(ESP32ImprovComponent),
cv.none, cv.use_id(binary_sensor.BinarySensor) cv.Required(CONF_AUTHORIZER): cv.Any(
), cv.none, cv.use_id(binary_sensor.BinarySensor)
cv.Optional(CONF_STATUS_INDICATOR): cv.use_id(output.BinaryOutput), ),
cv.Optional( cv.Optional(CONF_STATUS_INDICATOR): cv.use_id(output.BinaryOutput),
CONF_IDENTIFY_DURATION, default="10s" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_IDENTIFY_DURATION, default="10s"
cv.Optional( ): cv.positive_time_period_milliseconds,
CONF_AUTHORIZED_DURATION, default="1min" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_AUTHORIZED_DURATION, default="1min"
cv.Optional( ): cv.positive_time_period_milliseconds,
CONF_WIFI_TIMEOUT, default="1min" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_WIFI_TIMEOUT, default="1min"
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation( ): cv.positive_time_period_milliseconds,
{ cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( {
ESP32ImprovProvisionedTrigger cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
), ESP32ImprovProvisionedTrigger
} ),
), }
cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation( ),
{ cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( {
ESP32ImprovProvisioningTrigger cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
), ESP32ImprovProvisioningTrigger
} ),
), }
cv.Optional(CONF_ON_START): automation.validate_automation( ),
{ cv.Optional(CONF_ON_START): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32ImprovStartTrigger), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
), ESP32ImprovStartTrigger
cv.Optional(CONF_ON_STATE): automation.validate_automation( ),
{ }
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32ImprovStateTrigger), ),
} cv.Optional(CONF_ON_STATE): automation.validate_automation(
), {
cv.Optional(CONF_ON_STOP): automation.validate_automation( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
{ ESP32ImprovStateTrigger
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( ),
ESP32ImprovStoppedTrigger }
), ),
} cv.Optional(CONF_ON_STOP): automation.validate_automation(
), {
} cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
).extend(cv.COMPONENT_SCHEMA) ESP32ImprovStoppedTrigger
),
}
),
}
)
.extend(improv_base.IMPROV_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config): async def to_code(config):
@@ -102,7 +111,8 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add_define("USE_IMPROV") cg.add_define("USE_IMPROV")
cg.add_library("improv/Improv", "1.2.4")
await improv_base.setup_improv_core(var, config)
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -1,10 +1,10 @@
#include "esp32_improv_component.h" #include "esp32_improv_component.h"
#include "esphome/components/bytebuffer/bytebuffer.h"
#include "esphome/components/esp32_ble/ble.h" #include "esphome/components/esp32_ble/ble.h"
#include "esphome/components/esp32_ble_server/ble_2902.h" #include "esphome/components/esp32_ble_server/ble_2902.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/components/bytebuffer/bytebuffer.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -384,7 +384,16 @@ void ESP32ImprovComponent::check_wifi_connection_() {
this->connecting_sta_ = {}; this->connecting_sta_ = {};
this->cancel_timeout("wifi-connect-timeout"); this->cancel_timeout("wifi-connect-timeout");
std::vector<std::string> urls = {ESPHOME_MY_LINK}; std::vector<std::string> urls;
// Add next_url if configured (should be first per Improv BLE spec)
std::string next_url = this->get_formatted_next_url_();
if (!next_url.empty()) {
urls.push_back(next_url);
}
// Add default URLs for backward compatibility
urls.emplace_back(ESPHOME_MY_LINK);
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
if (ip.is_ip4()) { if (ip.is_ip4()) {

View File

@@ -7,6 +7,7 @@
#include "esphome/components/esp32_ble_server/ble_characteristic.h" #include "esphome/components/esp32_ble_server/ble_characteristic.h"
#include "esphome/components/esp32_ble_server/ble_server.h" #include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/improv_base/improv_base.h"
#include "esphome/components/wifi/wifi_component.h" #include "esphome/components/wifi/wifi_component.h"
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK #ifdef USE_ESP32_IMPROV_STATE_CALLBACK
@@ -32,7 +33,7 @@ namespace esp32_improv {
using namespace esp32_ble_server; using namespace esp32_ble_server;
class ESP32ImprovComponent : public Component { class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
public: public:
ESP32ImprovComponent(); ESP32ImprovComponent();
void dump_config() override; void dump_config() override;

View File

@@ -10,27 +10,36 @@ std::string ImprovBase::get_formatted_next_url_() {
if (this->next_url_.empty()) { if (this->next_url_.empty()) {
return ""; return "";
} }
std::string copy = this->next_url_;
// Device name std::string formatted_url = this->next_url_;
std::size_t pos = this->next_url_.find("{{device_name}}");
if (pos != std::string::npos) { // Replace all occurrences of {{device_name}}
const std::string &device_name = App.get_name(); const std::string device_name_placeholder = "{{device_name}}";
copy.replace(pos, 15, device_name); const std::string &device_name = App.get_name();
size_t pos = 0;
while ((pos = formatted_url.find(device_name_placeholder, pos)) != std::string::npos) {
formatted_url.replace(pos, device_name_placeholder.length(), device_name);
pos += device_name.length();
} }
// Ip address // Replace all occurrences of {{ip_address}}
pos = this->next_url_.find("{{ip_address}}"); const std::string ip_address_placeholder = "{{ip_address}}";
if (pos != std::string::npos) { std::string ip_address_str;
for (auto &ip : network::get_ip_addresses()) { for (auto &ip : network::get_ip_addresses()) {
if (ip.is_ip4()) { if (ip.is_ip4()) {
std::string ipa = ip.str(); ip_address_str = ip.str();
copy.replace(pos, 14, ipa); break;
break;
}
} }
} }
pos = 0;
while ((pos = formatted_url.find(ip_address_placeholder, pos)) != std::string::npos) {
formatted_url.replace(pos, ip_address_placeholder.length(), ip_address_str);
pos += ip_address_str.length();
}
return copy; // Note: {{esphome_version}} is replaced at code generation time in Python
return formatted_url;
} }
} // namespace improv_base } // namespace improv_base

View File

@@ -16,3 +16,4 @@ esp32_improv:
authorizer: io0_button authorizer: io0_button
authorized_duration: 1min authorized_duration: 1min
status_indicator: built_in_led status_indicator: built_in_led
next_url: "https://example.com/setup?device={{device_name}}&ip={{ip_address}}&version={{esphome_version}}"