mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	imporv_name
This commit is contained in:
		| @@ -73,6 +73,28 @@ void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &dat | ||||
|   this->advertising_start(); | ||||
| } | ||||
|  | ||||
| void ESP32BLE::advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name) { | ||||
|   // This method atomically updates both service data and device name inclusion in BLE advertising. | ||||
|   // When include_name is true, the device name is included in the advertising packet making it | ||||
|   // visible to passive BLE scanners. When false, the name is only visible in scan response | ||||
|   // (requires active scanning). This atomic operation ensures we only restart advertising once | ||||
|   // when changing both properties, avoiding the brief gap that would occur with separate calls. | ||||
|  | ||||
|   this->advertising_init_(); | ||||
|   bool needs_restart = false; | ||||
|  | ||||
|   this->advertising_->set_service_data(data); | ||||
|  | ||||
|   if (this->advertising_->get_include_name() != include_name) { | ||||
|     this->advertising_->set_include_name(include_name); | ||||
|     needs_restart = true; | ||||
|   } | ||||
|  | ||||
|   if (needs_restart || !data.empty()) { | ||||
|     this->advertising_start(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) { | ||||
|   this->advertising_init_(); | ||||
|   this->advertising_->register_raw_advertisement_callback(std::move(callback)); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| #endif | ||||
|  | ||||
| #include <functional> | ||||
| #include <span> | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| @@ -118,6 +119,7 @@ class ESP32BLE : public Component { | ||||
|   void advertising_set_service_data(const std::vector<uint8_t> &data); | ||||
|   void advertising_set_manufacturer_data(const std::vector<uint8_t> &data); | ||||
|   void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; } | ||||
|   void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name); | ||||
|   void advertising_add_service_uuid(ESPBTUUID uuid); | ||||
|   void advertising_remove_service_uuid(ESPBTUUID uuid); | ||||
|   void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback); | ||||
|   | ||||
| @@ -43,7 +43,7 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) { | ||||
|                                  this->advertising_uuids_.end()); | ||||
| } | ||||
|  | ||||
| void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) { | ||||
| void BLEAdvertising::set_service_data(std::span<const uint8_t> data) { | ||||
|   delete[] this->advertising_data_.p_service_data; | ||||
|   this->advertising_data_.p_service_data = nullptr; | ||||
|   this->advertising_data_.service_data_len = data.size(); | ||||
| @@ -54,6 +54,10 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) { | ||||
|   this->set_service_data(std::span<const uint8_t>(data)); | ||||
| } | ||||
|  | ||||
| void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) { | ||||
|   delete[] this->advertising_data_.p_manufacturer_data; | ||||
|   this->advertising_data_.p_manufacturer_data = nullptr; | ||||
| @@ -84,7 +88,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { | ||||
|   esp_err_t err; | ||||
|  | ||||
|   this->advertising_data_.set_scan_rsp = false; | ||||
|   this->advertising_data_.include_name = !this->scan_response_; | ||||
|   this->advertising_data_.include_name = this->include_name_in_adv_ || !this->scan_response_; | ||||
|   this->advertising_data_.include_txpower = !this->scan_response_; | ||||
|   err = esp_ble_gap_config_adv_data(&this->advertising_data_); | ||||
|   if (err != ESP_OK) { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| #include <array> | ||||
| #include <functional> | ||||
| #include <span> | ||||
| #include <vector> | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| @@ -36,6 +37,9 @@ class BLEAdvertising { | ||||
|   void set_manufacturer_data(const std::vector<uint8_t> &data); | ||||
|   void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; } | ||||
|   void set_service_data(const std::vector<uint8_t> &data); | ||||
|   void set_service_data(std::span<const uint8_t> data); | ||||
|   void set_include_name(bool include_name) { this->include_name_in_adv_ = include_name; } | ||||
|   bool get_include_name() const { return this->include_name_in_adv_; } | ||||
|   void register_raw_advertisement_callback(std::function<void(bool)> &&callback); | ||||
|  | ||||
|   void start(); | ||||
| @@ -45,6 +49,7 @@ class BLEAdvertising { | ||||
|   esp_err_t services_advertisement_(); | ||||
|  | ||||
|   bool scan_response_; | ||||
|   bool include_name_in_adv_{false}; | ||||
|   esp_ble_adv_data_t advertising_data_; | ||||
|   esp_ble_adv_data_t scan_response_data_; | ||||
|   esp_ble_adv_params_t advertising_params_; | ||||
|   | ||||
| @@ -17,6 +17,8 @@ static const char *const TAG = "esp32_improv.component"; | ||||
| static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; | ||||
| static constexpr uint16_t STOP_ADVERTISING_DELAY = | ||||
|     10000;  // Delay (ms) before stopping service to allow BLE clients to read the final state | ||||
| static constexpr uint16_t NAME_ADVERTISING_INTERVAL = 60000;  // Advertise name every 60 seconds | ||||
| static constexpr uint16_t NAME_ADVERTISING_DURATION = 1000;   // Advertise name for 1 second | ||||
|  | ||||
| ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } | ||||
|  | ||||
| @@ -99,6 +101,11 @@ void ESP32ImprovComponent::loop() { | ||||
|     this->process_incoming_data_(); | ||||
|   uint32_t now = App.get_loop_component_start_time(); | ||||
|  | ||||
|   // Check if we need to update advertising type | ||||
|   if (this->state_ != improv::STATE_STOPPED && this->state_ != improv::STATE_PROVISIONED) { | ||||
|     this->update_advertising_type_(); | ||||
|   } | ||||
|  | ||||
|   switch (this->state_) { | ||||
|     case improv::STATE_STOPPED: | ||||
|       this->set_status_indicator_state_(false); | ||||
| @@ -107,9 +114,22 @@ void ESP32ImprovComponent::loop() { | ||||
|         if (this->service_->is_created()) { | ||||
|           this->service_->start(); | ||||
|         } else if (this->service_->is_running()) { | ||||
|           // Start by advertising the device name first BEFORE setting any state | ||||
|           ESP_LOGV(TAG, "Starting with device name advertising"); | ||||
|           this->advertising_device_name_ = true; | ||||
|           this->last_name_adv_time_ = App.get_loop_component_start_time(); | ||||
|           esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>{}, true); | ||||
|           esp32_ble::global_ble->advertising_start(); | ||||
|  | ||||
|           this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); | ||||
|           // Set initial state based on whether we have an authorizer | ||||
|           // authorizer_ member only exists when USE_BINARY_SENSOR is defined | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|           this->set_state_( | ||||
|               this->authorizer_ == nullptr ? improv::STATE_AUTHORIZED : improv::STATE_AWAITING_AUTHORIZATION, false); | ||||
| #else | ||||
|           // No binary_sensor support = no authorizer possible, start as authorized | ||||
|           this->set_state_(improv::STATE_AUTHORIZED, false); | ||||
| #endif | ||||
|           this->set_error_(improv::ERROR_NONE); | ||||
|           ESP_LOGD(TAG, "Service started!"); | ||||
|         } | ||||
| @@ -226,12 +246,15 @@ bool ESP32ImprovComponent::check_identify_() { | ||||
|   return identify; | ||||
| } | ||||
|  | ||||
| void ESP32ImprovComponent::set_state_(improv::State state) { | ||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG | ||||
|   if (this->state_ != state) { | ||||
|     ESP_LOGD(TAG, "State transition: %s (0x%02X) -> %s (0x%02X)", this->state_to_string_(this->state_), this->state_, | ||||
|              this->state_to_string_(state), state); | ||||
| void ESP32ImprovComponent::set_state_(improv::State state, bool update_advertising) { | ||||
|   // Skip if state hasn't changed | ||||
|   if (this->state_ == state) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG | ||||
|   ESP_LOGD(TAG, "State transition: %s (0x%02X) -> %s (0x%02X)", this->state_to_string_(this->state_), this->state_, | ||||
|            this->state_to_string_(state), state); | ||||
| #endif | ||||
|   this->state_ = state; | ||||
|   if (this->status_ != nullptr && (this->status_->get_value().empty() || this->status_->get_value()[0] != state)) { | ||||
| @@ -243,25 +266,13 @@ void ESP32ImprovComponent::set_state_(improv::State state) { | ||||
|   // STATE_STOPPED (0x00) is internal only and not part of the Improv spec. | ||||
|   // Advertising 0x00 causes undefined behavior in some clients and makes them | ||||
|   // repeatedly connect trying to determine the actual state. | ||||
|   if (state != improv::STATE_STOPPED) { | ||||
|     std::vector<uint8_t> service_data(8, 0); | ||||
|     service_data[0] = 0x77;  // PR | ||||
|     service_data[1] = 0x46;  // IM | ||||
|     service_data[2] = static_cast<uint8_t>(state); | ||||
|  | ||||
|     uint8_t capabilities = 0x00; | ||||
| #ifdef USE_OUTPUT | ||||
|     if (this->status_indicator_ != nullptr) | ||||
|       capabilities |= improv::CAPABILITY_IDENTIFY; | ||||
| #endif | ||||
|  | ||||
|     service_data[3] = capabilities; | ||||
|     service_data[4] = 0x00;  // Reserved | ||||
|     service_data[5] = 0x00;  // Reserved | ||||
|     service_data[6] = 0x00;  // Reserved | ||||
|     service_data[7] = 0x00;  // Reserved | ||||
|  | ||||
|     esp32_ble::global_ble->advertising_set_service_data(service_data); | ||||
|   if (state != improv::STATE_STOPPED && update_advertising) { | ||||
|     // State change always overrides name advertising and resets the timer | ||||
|     this->advertising_device_name_ = false; | ||||
|     // Reset the timer so we wait another 60 seconds before advertising name | ||||
|     this->last_name_adv_time_ = App.get_loop_component_start_time(); | ||||
|     // Advertise the new state via service data | ||||
|     this->advertise_service_data_(); | ||||
|   } | ||||
| #ifdef USE_ESP32_IMPROV_STATE_CALLBACK | ||||
|   this->state_callback_.call(this->state_, this->error_state_); | ||||
| @@ -388,6 +399,50 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() { | ||||
|   wifi::global_wifi_component->clear_sta(); | ||||
| } | ||||
|  | ||||
| void ESP32ImprovComponent::advertise_service_data_() { | ||||
|   uint8_t service_data[8] = {}; | ||||
|   service_data[0] = 0x77;  // PR | ||||
|   service_data[1] = 0x46;  // IM | ||||
|   service_data[2] = static_cast<uint8_t>(this->state_); | ||||
|  | ||||
|   uint8_t capabilities = 0x00; | ||||
| #ifdef USE_OUTPUT | ||||
|   if (this->status_indicator_ != nullptr) | ||||
|     capabilities |= improv::CAPABILITY_IDENTIFY; | ||||
| #endif | ||||
|  | ||||
|   service_data[3] = capabilities; | ||||
|   // service_data[4-7] are already 0 (Reserved) | ||||
|  | ||||
|   // Atomically set service data and disable name in advertising | ||||
|   esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>(service_data), false); | ||||
| } | ||||
|  | ||||
| void ESP32ImprovComponent::update_advertising_type_() { | ||||
|   uint32_t now = App.get_loop_component_start_time(); | ||||
|  | ||||
|   // If we're advertising the device name and it's been more than NAME_ADVERTISING_DURATION, switch back to service data | ||||
|   if (this->advertising_device_name_) { | ||||
|     if (now - this->last_name_adv_time_ >= NAME_ADVERTISING_DURATION) { | ||||
|       ESP_LOGV(TAG, "Switching back to service data advertising"); | ||||
|       this->advertising_device_name_ = false; | ||||
|       // Restore service data advertising | ||||
|       this->advertise_service_data_(); | ||||
|     } | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Check if it's time to advertise the device name (every NAME_ADVERTISING_INTERVAL) | ||||
|   if (now - this->last_name_adv_time_ >= NAME_ADVERTISING_INTERVAL) { | ||||
|     ESP_LOGV(TAG, "Switching to device name advertising"); | ||||
|     this->advertising_device_name_ = true; | ||||
|     this->last_name_adv_time_ = now; | ||||
|  | ||||
|     // Atomically clear service data and enable name in advertising data | ||||
|     esp32_ble::global_ble->advertising_set_service_data_and_name(std::span<const uint8_t>{}, true); | ||||
|   } | ||||
| } | ||||
|  | ||||
| ESP32ImprovComponent *global_improv_component = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
|  | ||||
| }  // namespace esp32_improv | ||||
|   | ||||
| @@ -100,14 +100,18 @@ class ESP32ImprovComponent : public Component { | ||||
| #endif | ||||
|  | ||||
|   bool status_indicator_state_{false}; | ||||
|   uint32_t last_name_adv_time_{0}; | ||||
|   bool advertising_device_name_{false}; | ||||
|   void set_status_indicator_state_(bool state); | ||||
|   void update_advertising_type_(); | ||||
|  | ||||
|   void set_state_(improv::State state); | ||||
|   void set_state_(improv::State state, bool update_advertising = true); | ||||
|   void set_error_(improv::Error error); | ||||
|   void send_response_(std::vector<uint8_t> &response); | ||||
|   void process_incoming_data_(); | ||||
|   void on_wifi_connect_timeout_(); | ||||
|   bool check_identify_(); | ||||
|   void advertise_service_data_(); | ||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG | ||||
|   const char *state_to_string_(improv::State state); | ||||
| #endif | ||||
|   | ||||
		Reference in New Issue
	
	Block a user