21using namespace bytebuffer;
25static constexpr uint32_t STOP_SERVICE_TIMEOUT_ID = 0;
26static constexpr uint32_t WIFI_CONNECT_TIMEOUT_ID = 1;
28static constexpr size_t IMPROV_MAX_LOG_BYTES = 128;
29static constexpr char ESPHOME_MY_LINK[] =
"https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
31static constexpr size_t RPC_RESPONSE_OVERHEAD = 3;
34static constexpr size_t MAX_NEXT_URL_LEN =
35 improv::RPC_RESPONSE_MAX_SIZE - RPC_RESPONSE_OVERHEAD - 1 -
sizeof(ESPHOME_MY_LINK);
36static constexpr uint16_t STOP_ADVERTISING_DELAY =
38static constexpr uint16_t NAME_ADVERTISING_INTERVAL = 60000;
39static constexpr uint16_t NAME_ADVERTISING_DURATION = 1000;
42static constexpr uint8_t IMPROV_SERVICE_DATA_SIZE = 8;
43static constexpr uint8_t IMPROV_PROTOCOL_ID_1 = 0x77;
44static constexpr uint8_t IMPROV_PROTOCOL_ID_2 = 0x46;
49#ifdef USE_BINARY_SENSOR
61#ifdef USE_PROVISIONING
64 ESP_LOGD(TAG,
"Provisioning window closed; stopping Improv");
86 this->
rpc_->
on_write([
this](std::span<const uint8_t> data, uint16_t
id) {
103 uint8_t capabilities = 0x00;
106 capabilities |= improv::CAPABILITY_IDENTIFY;
114 if (this->
state_ != improv::STATE_STOPPED) {
115 this->
state_ = improv::STATE_STOPPED;
116#ifdef USE_IMPROV_BLE_STATE_CALLBACK
126 ESP_LOGD(TAG,
"Creating Improv service");
136 if (this->
state_ != improv::STATE_STOPPED && this->
state_ != improv::STATE_PROVISIONED) {
141 case improv::STATE_STOPPED:
150 ESP_LOGV(TAG,
"Starting with device name advertising");
161 ESP_LOGD(TAG,
"Service started!");
165 case improv::STATE_AWAITING_AUTHORIZATION: {
166#ifdef USE_BINARY_SENSOR
180 case improv::STATE_AUTHORIZED: {
181#ifdef USE_BINARY_SENSOR
183 ESP_LOGD(TAG,
"Authorization timeout");
184 this->
set_state_(improv::STATE_AWAITING_AUTHORIZATION);
194 case improv::STATE_PROVISIONING: {
199 case improv::STATE_PROVISIONED: {
220#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
223 case improv::STATE_STOPPED:
225 case improv::STATE_AWAITING_AUTHORIZATION:
226 return "AWAITING_AUTHORIZATION";
227 case improv::STATE_AUTHORIZED:
229 case improv::STATE_PROVISIONING:
230 return "PROVISIONING";
231 case improv::STATE_PROVISIONED:
232 return "PROVISIONED";
253 if (this->
state_ == state) {
257#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
264 if (
state != improv::STATE_STOPPED)
271 if (state != improv::STATE_STOPPED && update_advertising) {
279#ifdef USE_IMPROV_BLE_STATE_CALLBACK
285 if (error != improv::ERROR_NONE) {
286 ESP_LOGE(TAG,
"Error: %d", error);
293 if (this->
error_ !=
nullptr && (this->
error_->
get_value().empty() || this->error_->get_value()[0] != error)) {
295 if (this->
state_ != improv::STATE_STOPPED)
303 if (this->
state_ != improv::STATE_STOPPED)
311#ifdef USE_PROVISIONING
315 ESP_LOGD(TAG,
"Provisioning window closed; not starting Improv");
320 ESP_LOGD(TAG,
"Setting Improv to start");
330 this->
set_timeout(STOP_SERVICE_TIMEOUT_ID, STOP_ADVERTISING_DELAY, [
this] {
331 if (this->
state_ == improv::STATE_STOPPED || this->
service_ ==
nullptr)
343 ESP_LOGCONFIG(TAG,
"Improv BLE:");
344#ifdef USE_BINARY_SENSOR
345 LOG_BINARY_SENSOR(
" ",
"Authorizer", this->
authorizer_);
348 ESP_LOGCONFIG(TAG,
" Status Indicator: '%s'", YESNO(this->
status_indicator_ !=
nullptr));
357#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
359 ESP_LOGV(TAG,
"Processing bytes - %s",
364 improv::ImprovCommand command = improv::parse_improv_data(this->
incoming_data_);
365 switch (command.command) {
366 case improv::BAD_CHECKSUM:
367 ESP_LOGW(TAG,
"Error decoding Improv payload");
371 case improv::WIFI_SETTINGS: {
372 if (this->
state_ != improv::STATE_AUTHORIZED) {
373 ESP_LOGW(TAG,
"Settings received, but not authorized");
374 this->
set_error_(improv::ERROR_NOT_AUTHORIZED);
378#ifdef USE_PROVISIONING
381 ESP_LOGW(TAG,
"Provisioning window closed; refusing settings");
382 this->
set_error_(improv::ERROR_NOT_AUTHORIZED);
390 ESP_LOGW(TAG,
"Wi-Fi is disabled; cannot provision");
391 this->
set_error_(improv::ERROR_UNABLE_TO_CONNECT);
397 sta.set_password(command.password.c_str());
403 ESP_LOGD(TAG,
"Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET(
"%s"), command.ssid.c_str(),
404 command.password.c_str());
410 case improv::IDENTIFY:
415 ESP_LOGW(TAG,
"Unknown Improv payload");
420 ESP_LOGV(TAG,
"Too much data received or data malformed; resetting buffer");
423 ESP_LOGV(TAG,
"Waiting for split data packets");
428 this->
set_error_(improv::ERROR_UNABLE_TO_CONNECT);
430#ifdef USE_BINARY_SENSOR
434 ESP_LOGW(TAG,
"Timed out while connecting to Wi-Fi network");
443 if (this->
state_ == improv::STATE_PROVISIONING) {
449 std::array<uint8_t, improv::RPC_RESPONSE_MAX_SIZE> buf;
450 improv::RpcResponseBuilder builder(buf, improv::WIFI_SETTINGS);
452#ifdef USE_IMPROV_NEXT_URL
459 builder.add_string(ESPHOME_MY_LINK,
sizeof(ESPHOME_MY_LINK) - 1);
463 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
466 char webserver_url[7 + network::IP_ADDRESS_BUFFER_SIZE + 1 + 5 + 1];
468 buf_append_printf(webserver_url,
sizeof(webserver_url), 0,
"http://%s:%u", ip_buf, USE_WEBSERVER_PORT);
469 if (!builder.add_string(webserver_url,
len)) {
470 ESP_LOGW(TAG,
"Response full; URL dropped");
477 }
else if (this->
is_active() && this->
state_ != improv::STATE_PROVISIONED) {
478 ESP_LOGD(TAG,
"WiFi provisioned externally");
486 uint8_t service_data[IMPROV_SERVICE_DATA_SIZE] = {};
487 service_data[0] = IMPROV_PROTOCOL_ID_1;
488 service_data[1] = IMPROV_PROTOCOL_ID_2;
489 service_data[2] =
static_cast<uint8_t
>(this->
state_);
491 uint8_t capabilities = 0x00;
494 capabilities |= improv::CAPABILITY_IDENTIFY;
497 service_data[3] = capabilities;
510 ESP_LOGV(TAG,
"Switching back to service data advertising");
520 ESP_LOGV(TAG,
"Switching to device name advertising");
544#ifdef USE_BINARY_SENSOR
546 return this->
authorizer_ ==
nullptr ? improv::STATE_AUTHORIZED : improv::STATE_AWAITING_AUTHORIZATION;
549 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.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void enable_loop()
Enable this component's loop.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
void disable_loop()
Disable this component's loop.
void add_on_state_callback(F &&callback)
static ESPBTUUID from_raw(const uint8_t *data)
Construct from raw 16-byte little-endian UUID.
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
void advertising_start()
Request advertising on behalf of a component.
void advertising_set_service_data_and_name(std::span< const uint8_t > data, bool include_name)
void advertising_stop()
Release a request made with advertising_start(); advertising stops at the last release.
static constexpr uint32_t PROPERTY_WRITE
void set_value(ByteBuffer buffer)
void on_write(std::function< void(std::span< const uint8_t >, uint16_t)> &&callback)
static constexpr uint32_t PROPERTY_NOTIFY
static constexpr uint32_t PROPERTY_READ
void add_descriptor(BLEDescriptor *descriptor)
std::vector< uint8_t > & get_value()
ESPHOME_ALWAYS_INLINE bool is_running()
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 add_next_url_(improv::RpcResponseBuilder &builder, size_t max_len)
Append the formatted next_url to the RPC response, warning if it does not fit.
void check_wifi_connection_()
std::vector< uint8_t > incoming_data_
uint32_t authorized_start_
void request_advertising_()
float get_setup_priority() const override
void set_state_(improv::State state, bool update_advertising=true)
bool status_indicator_state_
const char * state_to_string_(improv::State state)
binary_sensor::BinarySensor * authorizer_
CallbackManager< void(improv::State, improv::Error)> state_callback_
void release_advertising_()
BLECharacteristic * capabilities_
BLECharacteristic * rpc_response_
uint32_t last_name_adv_time_
BLECharacteristic * status_
void set_status_indicator_state_(bool state)
void setup_characteristics()
void update_advertising_type_()
uint32_t identify_duration_
output::BinaryOutput * status_indicator_
BLECharacteristic * error_
wifi::WiFiAP connecting_sta_
improv::Error error_state_
uint32_t authorized_duration_
improv::State get_initial_state_() const
bool advertising_requested_
void advertise_service_data_()
void dump_config() override
void on_wifi_connect_timeout_()
bool advertising_device_name_
void set_error_(improv::Error error)
void process_incoming_data_()
void send_response_(std::span< const uint8_t > response)
virtual void set_state(bool state)
Enable or disable this binary output.
void add_on_closed_callback(F &&callback)
StringRef 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
ESPHOME_LOG_TAG(TAG, "esp32_ble")
ImprovBLEComponent * global_improv_component
ProvisioningManager * global_provisioning_manager
constexpr float AFTER_BLUETOOTH
WiFiComponent * global_wifi_component
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.