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:
@@ -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]))
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}}"
|
||||||
|
|||||||
Reference in New Issue
Block a user