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
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
import esphome.config_validation as cv
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"]
DEPENDENCIES = ["wifi", "esp32"]
@@ -20,6 +20,7 @@ CONF_ON_STOP = "on_stop"
CONF_STATUS_INDICATOR = "status_indicator"
CONF_WIFI_TIMEOUT = "wifi_timeout"
improv_ns = cg.esphome_ns.namespace("improv")
Error = improv_ns.enum("Error")
State = improv_ns.enum("State")
@@ -43,7 +44,8 @@ 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(
@@ -75,12 +77,16 @@ CONFIG_SCHEMA = cv.Schema(
),
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.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ESP32ImprovStateTrigger
),
}
),
cv.Optional(CONF_ON_STOP): automation.validate_automation(
@@ -91,7 +97,10 @@ CONFIG_SCHEMA = cv.Schema(
}
),
}
).extend(cv.COMPONENT_SCHEMA)
)
.extend(improv_base.IMPROV_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
@@ -102,7 +111,8 @@ async def to_code(config):
await cg.register_component(var, config)
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_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -1,10 +1,10 @@
#include "esp32_improv_component.h"
#include "esphome/components/bytebuffer/bytebuffer.h"
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/components/esp32_ble_server/ble_2902.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/components/bytebuffer/bytebuffer.h"
#ifdef USE_ESP32
@@ -384,7 +384,16 @@ void ESP32ImprovComponent::check_wifi_connection_() {
this->connecting_sta_ = {};
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
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
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_server.h"
#include "esphome/components/improv_base/improv_base.h"
#include "esphome/components/wifi/wifi_component.h"
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
@@ -32,7 +33,7 @@ namespace esp32_improv {
using namespace esp32_ble_server;
class ESP32ImprovComponent : public Component {
class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
public:
ESP32ImprovComponent();
void dump_config() override;

View File

@@ -10,27 +10,36 @@ 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) {
std::string formatted_url = this->next_url_;
// Replace all occurrences of {{device_name}}
const std::string device_name_placeholder = "{{device_name}}";
const std::string &device_name = App.get_name();
copy.replace(pos, 15, device_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
pos = this->next_url_.find("{{ip_address}}");
if (pos != std::string::npos) {
// Replace all occurrences of {{ip_address}}
const std::string ip_address_placeholder = "{{ip_address}}";
std::string ip_address_str;
for (auto &ip : network::get_ip_addresses()) {
if (ip.is_ip4()) {
std::string ipa = ip.str();
copy.replace(pos, 14, ipa);
ip_address_str = ip.str();
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

View File

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