13namespace esp32_improv {
15using namespace bytebuffer;
17static const char *
const TAG =
"esp32_improv.component";
18static constexpr size_t IMPROV_MAX_LOG_BYTES = 128;
19static const char *
const ESPHOME_MY_LINK =
"https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
20static constexpr uint16_t STOP_ADVERTISING_DELAY =
22static constexpr uint16_t NAME_ADVERTISING_INTERVAL = 60000;
23static constexpr uint16_t NAME_ADVERTISING_DURATION = 1000;
26static constexpr uint8_t IMPROV_SERVICE_DATA_SIZE = 8;
27static constexpr uint8_t IMPROV_PROTOCOL_ID_1 = 0x77;
28static constexpr uint8_t IMPROV_PROTOCOL_ID_2 = 0x46;
33#ifdef USE_BINARY_SENSOR
61 this->
rpc_->
on_write([
this](std::span<const uint8_t> data, uint16_t
id) {
78 uint8_t capabilities = 0x00;
81 capabilities |= improv::CAPABILITY_IDENTIFY;
89 if (this->
state_ != improv::STATE_STOPPED) {
90 this->
state_ = improv::STATE_STOPPED;
91#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
100 ESP_LOGD(TAG,
"Creating Improv service");
110 if (this->
state_ != improv::STATE_STOPPED && this->
state_ != improv::STATE_PROVISIONED) {
115 case improv::STATE_STOPPED:
123 ESP_LOGV(TAG,
"Starting with device name advertising");
133 ESP_LOGD(TAG,
"Service started!");
137 case improv::STATE_AWAITING_AUTHORIZATION: {
138#ifdef USE_BINARY_SENSOR
152 case improv::STATE_AUTHORIZED: {
153#ifdef USE_BINARY_SENSOR
155 ESP_LOGD(TAG,
"Authorization timeout");
156 this->
set_state_(improv::STATE_AWAITING_AUTHORIZATION);
166 case improv::STATE_PROVISIONING: {
171 case improv::STATE_PROVISIONED: {
196#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
199 case improv::STATE_STOPPED:
201 case improv::STATE_AWAITING_AUTHORIZATION:
202 return "AWAITING_AUTHORIZATION";
203 case improv::STATE_AUTHORIZED:
205 case improv::STATE_PROVISIONING:
206 return "PROVISIONING";
207 case improv::STATE_PROVISIONED:
208 return "PROVISIONED";
221 uint32_t time = now % 1000;
229 if (this->
state_ == state) {
233#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
240 if (
state != improv::STATE_STOPPED)
247 if (state != improv::STATE_STOPPED && update_advertising) {
255#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
261 if (error != improv::ERROR_NONE) {
262 ESP_LOGE(TAG,
"Error: %d", error);
269 if (this->
error_ !=
nullptr && (this->
error_->
get_value().empty() || this->error_->get_value()[0] != error)) {
271 if (this->
state_ != improv::STATE_STOPPED)
278 if (this->
state_ != improv::STATE_STOPPED)
286 ESP_LOGD(TAG,
"Setting Improv to start");
296 this->
set_timeout(
"end-service", STOP_ADVERTISING_DELAY, [
this] {
297 if (this->
state_ == improv::STATE_STOPPED || this->
service_ ==
nullptr)
307 ESP_LOGCONFIG(TAG,
"ESP32 Improv:");
308#ifdef USE_BINARY_SENSOR
309 LOG_BINARY_SENSOR(
" ",
"Authorizer", this->
authorizer_);
312 ESP_LOGCONFIG(TAG,
" Status Indicator: '%s'", YESNO(this->
status_indicator_ !=
nullptr));
319#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
321 ESP_LOGV(TAG,
"Processing bytes - %s",
326 improv::ImprovCommand command = improv::parse_improv_data(this->
incoming_data_);
327 switch (command.command) {
328 case improv::BAD_CHECKSUM:
329 ESP_LOGW(TAG,
"Error decoding Improv payload");
333 case improv::WIFI_SETTINGS: {
334 if (this->
state_ != improv::STATE_AUTHORIZED) {
335 ESP_LOGW(TAG,
"Settings received, but not authorized");
336 this->
set_error_(improv::ERROR_NOT_AUTHORIZED);
342 sta.set_password(command.password);
348 ESP_LOGD(TAG,
"Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET(
"%s"), command.ssid.c_str(),
349 command.password.c_str());
352 this->
set_timeout(
"wifi-connect-timeout", 30000, f);
356 case improv::IDENTIFY:
361 ESP_LOGW(TAG,
"Unknown Improv payload");
366 ESP_LOGV(TAG,
"Too much data received or data malformed; resetting buffer");
369 ESP_LOGV(TAG,
"Waiting for split data packets");
374 this->
set_error_(improv::ERROR_UNABLE_TO_CONNECT);
376#ifdef USE_BINARY_SENSOR
380 ESP_LOGW(TAG,
"Timed out while connecting to Wi-Fi network");
389 if (this->
state_ == improv::STATE_PROVISIONING) {
396 std::string url_strings[3];
397 size_t url_count = 0;
399#ifdef USE_ESP32_IMPROV_NEXT_URL
402 char url_buffer[384];
405 url_strings[url_count++] = std::string(url_buffer,
len);
411 url_strings[url_count++] = ESPHOME_MY_LINK;
417 memcpy(url_buffer,
"http://", 7);
418 ip.str_to(url_buffer + 7);
419 size_t len = strlen(url_buffer);
420 snprintf(url_buffer +
len,
sizeof(url_buffer) -
len,
":%d", USE_WEBSERVER_PORT);
421 url_strings[url_count++] = url_buffer;
426 this->
send_response_(improv::build_rpc_response(improv::WIFI_SETTINGS,
427 std::vector<std::string>(url_strings, url_strings + url_count)));
428 }
else if (this->
is_active() && this->
state_ != improv::STATE_PROVISIONED) {
429 ESP_LOGD(TAG,
"WiFi provisioned externally");
437 uint8_t service_data[IMPROV_SERVICE_DATA_SIZE] = {};
438 service_data[0] = IMPROV_PROTOCOL_ID_1;
439 service_data[1] = IMPROV_PROTOCOL_ID_2;
440 service_data[2] =
static_cast<uint8_t
>(this->
state_);
442 uint8_t capabilities = 0x00;
445 capabilities |= improv::CAPABILITY_IDENTIFY;
448 service_data[3] = capabilities;
461 ESP_LOGV(TAG,
"Switching back to service data advertising");
471 ESP_LOGV(TAG,
"Switching to device name advertising");
481#ifdef USE_BINARY_SENSOR
483 return this->
authorizer_ ==
nullptr ? improv::STATE_AUTHORIZED : improv::STATE_AWAITING_AUTHORIZATION;
486 return improv::STATE_AUTHORIZED;
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std boo cancel_timeout)(const char *name)
Cancel a timeout function.
void add_on_state_callback(std::function< void(T)> &&callback)
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
void advertising_set_service_data_and_name(std::span< const uint8_t > data, bool include_name)
static ESPBTUUID from_raw(const uint8_t *data)
void set_value(ByteBuffer buffer)
void on_write(std::function< void(std::span< const uint8_t >, uint16_t)> &&callback)
static const uint32_t PROPERTY_NOTIFY
static const uint32_t PROPERTY_WRITE
void add_descriptor(BLEDescriptor *descriptor)
static const uint32_t PROPERTY_READ
std::vector< uint8_t > & get_value()
BLEService * create_service(ESPBTUUID uuid, bool advertise=false, uint16_t num_handles=15)
void on_disconnect(std::function< void(uint16_t)> &&callback)
BLECharacteristic * create_characteristic(const std::string &uuid, esp_gatt_char_prop_t properties)
void on_wifi_connect_timeout_()
void check_wifi_connection_()
binary_sensor::BinarySensor * authorizer_
bool status_indicator_state_
improv::Error error_state_
BLECharacteristic * status_
void send_response_(std::vector< uint8_t > &&response)
uint32_t authorized_duration_
float get_setup_priority() const override
void set_status_indicator_state_(bool state)
void advertise_service_data_()
uint32_t identify_duration_
uint32_t authorized_start_
CallbackManager< void(improv::State, improv::Error)> state_callback_
void dump_config() override
bool advertising_device_name_
std::vector< uint8_t > incoming_data_
void set_error_(improv::Error error)
BLECharacteristic * rpc_response_
void process_incoming_data_()
void setup_characteristics()
BLECharacteristic * capabilities_
void set_state_(improv::State state, bool update_advertising=true)
improv::State get_initial_state_() const
BLECharacteristic * error_
wifi::WiFiAP connecting_sta_
output::BinaryOutput * status_indicator_
uint32_t last_name_adv_time_
const char * state_to_string_(improv::State state)
void update_advertising_type_()
size_t get_formatted_next_url_(char *buffer, size_t buffer_size)
Format next_url_ into buffer, replacing placeholders. Returns length written.
virtual void turn_off()
Disable this binary output.
virtual void turn_on()
Enable this binary output.
const std::string & get_ssid() const
void set_ssid(const std::string &ssid)
void set_sta(const WiFiAP &ap)
void save_wifi_sta(const std::string &ssid, const std::string &password)
void start_connecting(const WiFiAP &ap)
BLEServer * global_ble_server
ESP32ImprovComponent * global_improv_component
const float AFTER_BLUETOOTH
WiFiComponent * global_wifi_component
Providing packet encoding functions for exchanging data with a remote host.
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.