mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			114 Commits
		
	
	
		
			2022.9.0b4
			...
			2022.10.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6124531479 | ||
| 
						 | 
					b8549d323c | ||
| 
						 | 
					01adece673 | ||
| 
						 | 
					0220934e4c | ||
| 
						 | 
					ca09693efa | ||
| 
						 | 
					e96d7483b3 | ||
| 
						 | 
					ccbfa20bb9 | ||
| 
						 | 
					58cd754e07 | ||
| 
						 | 
					d1263e583b | ||
| 
						 | 
					67c911c37f | ||
| 
						 | 
					288e3c3e3e | ||
| 
						 | 
					a84378c6c2 | ||
| 
						 | 
					b6073408f4 | ||
| 
						 | 
					b2d91ac5de | ||
| 
						 | 
					8bf34e09f4 | ||
| 
						 | 
					be914f2c15 | ||
| 
						 | 
					a6c999dea0 | ||
| 
						 | 
					f422fabab4 | ||
| 
						 | 
					01b130ec59 | ||
| 
						 | 
					48a1797e72 | ||
| 
						 | 
					b34d24735a | ||
| 
						 | 
					fe38b36c26 | ||
| 
						 | 
					03fca8d91e | ||
| 
						 | 
					9f9980e338 | ||
| 
						 | 
					19900b004b | ||
| 
						 | 
					a8ff0a8913 | ||
| 
						 | 
					45861456f1 | ||
| 
						 | 
					44b335e7e3 | ||
| 
						 | 
					de23bbace2 | ||
| 
						 | 
					edff9ae322 | ||
| 
						 | 
					3c2766448d | ||
| 
						 | 
					786c8b6cfe | ||
| 
						 | 
					e8de6a3a67 | ||
| 
						 | 
					3c320c4c83 | ||
| 
						 | 
					3b83f967e4 | ||
| 
						 | 
					5e96b8ef7c | ||
| 
						 | 
					5df0e82c37 | ||
| 
						 | 
					fd57b21aff | ||
| 
						 | 
					6087183a0c | ||
| 
						 | 
					01b7c4200e | ||
| 
						 | 
					7171286c3c | ||
| 
						 | 
					2d58239b74 | ||
| 
						 | 
					6b52f62531 | ||
| 
						 | 
					1001d9c04e | ||
| 
						 | 
					d220d41182 | ||
| 
						 | 
					c3a8972550 | ||
| 
						 | 
					263b603188 | ||
| 
						 | 
					584b722e7e | ||
| 
						 | 
					05edfd0e82 | ||
| 
						 | 
					16249c02a5 | ||
| 
						 | 
					e8ff36d1f3 | ||
| 
						 | 
					ed443c6153 | ||
| 
						 | 
					f4a84765cd | ||
| 
						 | 
					106de3530d | ||
| 
						 | 
					119c3f6f46 | ||
| 
						 | 
					d2c1c7507c | ||
| 
						 | 
					efdb3d1f40 | ||
| 
						 | 
					8095db6715 | ||
| 
						 | 
					ce2e161b08 | ||
| 
						 | 
					9dbc32b85f | ||
| 
						 | 
					66226abb48 | ||
| 
						 | 
					34bef2f2ca | ||
| 
						 | 
					6ef93452f5 | ||
| 
						 | 
					fdd4ca6837 | ||
| 
						 | 
					9655362f23 | ||
| 
						 | 
					130c9fad22 | ||
| 
						 | 
					3de0b601bf | ||
| 
						 | 
					91560ae4e9 | ||
| 
						 | 
					fd6135aebb | ||
| 
						 | 
					68ea59f3ae | ||
| 
						 | 
					9a69769a7e | ||
| 
						 | 
					3572c62315 | ||
| 
						 | 
					1444cddda9 | ||
| 
						 | 
					b2db524366 | ||
| 
						 | 
					ab8674a5c7 | ||
| 
						 | 
					d1c85fc3fa | ||
| 
						 | 
					55ad45e3ee | ||
| 
						 | 
					f6e5a8cb2a | ||
| 
						 | 
					7a91ca9809 | ||
| 
						 | 
					917bbc669c | ||
| 
						 | 
					0ac4c055de | ||
| 
						 | 
					78b55d86e9 | ||
| 
						 | 
					aaf50fc2e6 | ||
| 
						 | 
					6a8f4e92df | ||
| 
						 | 
					88943103a2 | ||
| 
						 | 
					041eb8f6cc | ||
| 
						 | 
					733a84df75 | ||
| 
						 | 
					acd0b50b40 | ||
| 
						 | 
					60e46d485e | ||
| 
						 | 
					5bf0c92318 | ||
| 
						 | 
					39d493c278 | ||
| 
						 | 
					d2ce62aa13 | ||
| 
						 | 
					c8eb30ef27 | ||
| 
						 | 
					31ad75d01b | ||
| 
						 | 
					aa2eb29274 | ||
| 
						 | 
					22eb4f9cb9 | ||
| 
						 | 
					3acc8e7479 | ||
| 
						 | 
					adc8c1aa38 | ||
| 
						 | 
					3a82f500d4 | ||
| 
						 | 
					7d1d4831a8 | ||
| 
						 | 
					ab86ddcf02 | ||
| 
						 | 
					bf8eddb13b | ||
| 
						 | 
					e5eaf7a3fe | ||
| 
						 | 
					50f32a3aa5 | ||
| 
						 | 
					eb878710c1 | ||
| 
						 | 
					311980e0e4 | ||
| 
						 | 
					df73170e5a | ||
| 
						 | 
					ccc13cc9e1 | ||
| 
						 | 
					020b2c05c8 | ||
| 
						 | 
					4c37c17df1 | ||
| 
						 | 
					8f67acadd8 | ||
| 
						 | 
					ca13c4c1a6 | ||
| 
						 | 
					d0c646c721 | ||
| 
						 | 
					8a055675af | 
							
								
								
									
										1
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -28,6 +28,7 @@ jobs:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -85,7 +85,7 @@ jobs:
 | 
			
		||||
        uses: actions/setup-python@v4
 | 
			
		||||
        id: python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.8"
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
 | 
			
		||||
      - name: Cache virtualenv
 | 
			
		||||
        uses: actions/cache@v3
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v2.37.3
 | 
			
		||||
    rev: v3.0.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py38-plus]
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,7 @@ esphome/components/ektf2232/* @jesserockz
 | 
			
		||||
esphome/components/ens210/* @itn3rd77
 | 
			
		||||
esphome/components/esp32/* @esphome/core
 | 
			
		||||
esphome/components/esp32_ble/* @jesserockz
 | 
			
		||||
esphome/components/esp32_ble_client/* @jesserockz
 | 
			
		||||
esphome/components/esp32_ble_server/* @jesserockz
 | 
			
		||||
esphome/components/esp32_camera_web_server/* @ayufan
 | 
			
		||||
esphome/components/esp32_can/* @Sympatron
 | 
			
		||||
@@ -257,4 +258,4 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
			
		||||
esphome/components/xiaomi_mhoc303/* @drug123
 | 
			
		||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
			
		||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
 | 
			
		||||
esphome/components/xpt2046/* @numo68
 | 
			
		||||
esphome/components/xpt2046/* @nielsnl68 @numo68
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ from esphome.cpp_generator import (  # noqa
 | 
			
		||||
    static_const_array,
 | 
			
		||||
    statement,
 | 
			
		||||
    variable,
 | 
			
		||||
    with_local_variable,
 | 
			
		||||
    new_variable,
 | 
			
		||||
    Pvariable,
 | 
			
		||||
    new_Pvariable,
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->conn_id)
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
@@ -88,8 +88,8 @@ void AirthingsWaveMini::update() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWaveMini::request_read_values_() {
 | 
			
		||||
  auto status =
 | 
			
		||||
      esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->conn_id)
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
@@ -109,8 +109,8 @@ void AirthingsWavePlus::update() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AirthingsWavePlus::request_read_values_() {
 | 
			
		||||
  auto status =
 | 
			
		||||
      esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -76,9 +76,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
 | 
			
		||||
      if (this->current_sensor_ > 0) {
 | 
			
		||||
        if (this->illuminance_ != nullptr) {
 | 
			
		||||
          auto *packet = this->encoder_->get_light_level_request();
 | 
			
		||||
          auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
 | 
			
		||||
                                                 ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
 | 
			
		||||
                                                 this->char_handle_, packet->length, packet->data,
 | 
			
		||||
                                                 ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          if (status) {
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
 | 
			
		||||
                     status);
 | 
			
		||||
@@ -102,8 +102,8 @@ void Am43::update() {
 | 
			
		||||
    if (this->battery_ != nullptr) {
 | 
			
		||||
      auto *packet = this->encoder_->get_battery_level_request();
 | 
			
		||||
      auto status =
 | 
			
		||||
          esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
			
		||||
                                   packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                   packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status)
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,8 @@ void Am43Component::loop() {
 | 
			
		||||
  if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
 | 
			
		||||
    auto *packet = this->encoder_->get_send_pin_request(this->pin_);
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
			
		||||
                                 packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
@@ -54,8 +54,8 @@ void Am43Component::control(const CoverCall &call) {
 | 
			
		||||
  if (call.get_stop()) {
 | 
			
		||||
    auto *packet = this->encoder_->get_stop_request();
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
			
		||||
                                 packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
  }
 | 
			
		||||
@@ -66,8 +66,8 @@ void Am43Component::control(const CoverCall &call) {
 | 
			
		||||
      pos = 1 - pos;
 | 
			
		||||
    auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
			
		||||
                                 packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
  }
 | 
			
		||||
@@ -92,7 +92,8 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      }
 | 
			
		||||
      this->char_handle_ = chr->handle;
 | 
			
		||||
 | 
			
		||||
      auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
 | 
			
		||||
      auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
 | 
			
		||||
                                                      chr->handle);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
			
		||||
      }
 | 
			
		||||
@@ -122,9 +123,9 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
        if (this->decoder_->pin_ok_) {
 | 
			
		||||
          ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
 | 
			
		||||
          auto *packet = this->encoder_->get_position_request();
 | 
			
		||||
          auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
 | 
			
		||||
                                                 ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
 | 
			
		||||
                                                 this->char_handle_, packet->length, packet->data,
 | 
			
		||||
                                                 ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          if (status)
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
 | 
			
		||||
DEPENDENCIES = ["display"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
Animation_ = display.display_ns.class_("Animation")
 | 
			
		||||
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
 | 
			
		||||
 | 
			
		||||
ANIMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,15 +34,17 @@ void Anova::control(const ClimateCall &call) {
 | 
			
		||||
        ESP_LOGW(TAG, "Unsupported mode: %d", mode);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                           pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_target_temperature().has_value()) {
 | 
			
		||||
    auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
 | 
			
		||||
    auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                           pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
  }
 | 
			
		||||
@@ -65,7 +67,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
 | 
			
		||||
      }
 | 
			
		||||
      this->char_handle_ = chr->handle;
 | 
			
		||||
 | 
			
		||||
      auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
 | 
			
		||||
      auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
 | 
			
		||||
                                                      chr->handle);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
			
		||||
      }
 | 
			
		||||
@@ -112,8 +115,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
 | 
			
		||||
        }
 | 
			
		||||
        if (pkt != nullptr) {
 | 
			
		||||
          auto status =
 | 
			
		||||
              esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
 | 
			
		||||
                                       pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
              esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                       pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          if (status) {
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
 | 
			
		||||
                     status);
 | 
			
		||||
@@ -137,8 +140,9 @@ void Anova::update() {
 | 
			
		||||
    auto *pkt = this->codec_->get_read_device_status_request();
 | 
			
		||||
    if (this->current_request_ == 0)
 | 
			
		||||
      this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
 | 
			
		||||
    auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
			
		||||
                                           pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    this->current_request_++;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ service APIConnection {
 | 
			
		||||
  rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
 | 
			
		||||
    option (needs_authentication) = false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -44,6 +43,16 @@ service APIConnection {
 | 
			
		||||
  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
			
		||||
  rpc lock_command (LockCommandRequest) returns (void) {}
 | 
			
		||||
  rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -78,6 +87,8 @@ message HelloRequest {
 | 
			
		||||
  // Not strictly necessary to send but nice for debugging
 | 
			
		||||
  // purposes.
 | 
			
		||||
  string client_info = 1;
 | 
			
		||||
  uint32 api_version_major = 2;
 | 
			
		||||
  uint32 api_version_minor = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confirmation of successful connection request.
 | 
			
		||||
@@ -192,7 +203,7 @@ message DeviceInfoResponse {
 | 
			
		||||
 | 
			
		||||
  uint32 webserver_port = 10;
 | 
			
		||||
 | 
			
		||||
  bool has_bluetooth_proxy = 11;
 | 
			
		||||
  uint32 bluetooth_proxy_version = 11;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesRequest {
 | 
			
		||||
@@ -1111,7 +1122,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
 | 
			
		||||
message BluetoothServiceData {
 | 
			
		||||
  string uuid = 1;
 | 
			
		||||
  repeated uint32 data = 2 [packed=false];
 | 
			
		||||
  repeated uint32 legacy_data = 2 [deprecated = true];
 | 
			
		||||
  bytes data = 3; // Changed in proto version 1.7
 | 
			
		||||
}
 | 
			
		||||
message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  option (id) = 67;
 | 
			
		||||
@@ -1127,3 +1139,190 @@ message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  repeated BluetoothServiceData service_data = 5;
 | 
			
		||||
  repeated BluetoothServiceData manufacturer_data = 6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum BluetoothDeviceRequestType {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceRequest {
 | 
			
		||||
  option (id) = 68;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  BluetoothDeviceRequestType request_type = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceConnectionResponse {
 | 
			
		||||
  option (id) = 69;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  bool connected = 2;
 | 
			
		||||
  uint32 mtu = 3;
 | 
			
		||||
  int32 error = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTGetServicesRequest {
 | 
			
		||||
  option (id) = 70;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTDescriptor {
 | 
			
		||||
  repeated uint64 uuid = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTCharacteristic {
 | 
			
		||||
  repeated uint64 uuid = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
  uint32 properties = 3;
 | 
			
		||||
  repeated BluetoothGATTDescriptor descriptors = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTService {
 | 
			
		||||
  repeated uint64 uuid = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
  repeated BluetoothGATTCharacteristic characteristics = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTGetServicesResponse {
 | 
			
		||||
  option (id) = 71;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  repeated BluetoothGATTService services = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTGetServicesDoneResponse {
 | 
			
		||||
  option (id) = 72;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTReadRequest {
 | 
			
		||||
  option (id) = 73;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTReadResponse {
 | 
			
		||||
  option (id) = 74;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
 | 
			
		||||
  bytes data = 3;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTWriteRequest {
 | 
			
		||||
  option (id) = 75;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
  bool response = 3;
 | 
			
		||||
 | 
			
		||||
  bytes data = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTReadDescriptorRequest {
 | 
			
		||||
  option (id) = 76;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTWriteDescriptorRequest {
 | 
			
		||||
  option (id) = 77;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
 | 
			
		||||
  bytes data = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTNotifyRequest {
 | 
			
		||||
  option (id) = 78;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
  bool enable = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTNotifyDataResponse {
 | 
			
		||||
  option (id) = 79;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
 | 
			
		||||
  bytes data = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message SubscribeBluetoothConnectionsFreeRequest {
 | 
			
		||||
  option (id) = 80;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothConnectionsFreeResponse {
 | 
			
		||||
  option (id) = 81;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint32 free = 1;
 | 
			
		||||
  uint32 limit = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTErrorResponse {
 | 
			
		||||
  option (id) = 82;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTWriteResponse {
 | 
			
		||||
  option (id) = 83;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTNotifyResponse {
 | 
			
		||||
  option (id) = 84;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
#include "api_connection.h"
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_DEEP_SLEEP
 | 
			
		||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
 | 
			
		||||
@@ -12,6 +12,9 @@
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -823,6 +826,56 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
 | 
			
		||||
  if (!this->bluetooth_le_advertisement_subscription_)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
 | 
			
		||||
    BluetoothLEAdvertisementResponse resp = msg;
 | 
			
		||||
    for (auto &service : resp.service_data) {
 | 
			
		||||
      service.legacy_data.assign(service.data.begin(), service.data.end());
 | 
			
		||||
      service.data.clear();
 | 
			
		||||
    }
 | 
			
		||||
    for (auto &manufacturer_data : resp.manufacturer_data) {
 | 
			
		||||
      manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end());
 | 
			
		||||
      manufacturer_data.data.clear();
 | 
			
		||||
    }
 | 
			
		||||
    return this->send_bluetooth_le_advertisement_response(resp);
 | 
			
		||||
  }
 | 
			
		||||
  return this->send_bluetooth_le_advertisement_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) {
 | 
			
		||||
  bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free(
 | 
			
		||||
    const SubscribeBluetoothConnectionsFreeRequest &msg) {
 | 
			
		||||
  BluetoothConnectionsFreeResponse resp;
 | 
			
		||||
  resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
 | 
			
		||||
  resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
 | 
			
		||||
  if (this->log_subscription_ < level)
 | 
			
		||||
    return false;
 | 
			
		||||
@@ -840,11 +893,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
 | 
			
		||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
			
		||||
  this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
 | 
			
		||||
  this->helper_->set_log_info(client_info_);
 | 
			
		||||
  ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
 | 
			
		||||
  this->client_api_version_major_ = msg.api_version_major;
 | 
			
		||||
  this->client_api_version_minor_ = msg.api_version_minor;
 | 
			
		||||
  ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(),
 | 
			
		||||
           this->client_api_version_major_, this->client_api_version_minor_);
 | 
			
		||||
 | 
			
		||||
  HelloResponse resp;
 | 
			
		||||
  resp.api_version_major = 1;
 | 
			
		||||
  resp.api_version_minor = 6;
 | 
			
		||||
  resp.api_version_minor = 7;
 | 
			
		||||
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
			
		||||
  resp.name = App.get_name();
 | 
			
		||||
 | 
			
		||||
@@ -888,7 +944,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  resp.webserver_port = USE_WEBSERVER_PORT;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  resp.has_bluetooth_proxy = true;
 | 
			
		||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1;
 | 
			
		||||
#endif
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,11 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "api_frame_helper.h"
 | 
			
		||||
#include "api_pb2.h"
 | 
			
		||||
#include "api_pb2_service.h"
 | 
			
		||||
#include "api_server.h"
 | 
			
		||||
#include "api_frame_helper.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -99,11 +95,18 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
    this->send_homeassistant_service_response(call);
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) {
 | 
			
		||||
    if (!this->bluetooth_le_advertisement_subscription_)
 | 
			
		||||
      return false;
 | 
			
		||||
    return this->send_bluetooth_le_advertisement_response(call);
 | 
			
		||||
  }
 | 
			
		||||
  bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
 | 
			
		||||
 | 
			
		||||
  void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
			
		||||
  void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
 | 
			
		||||
  void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override;
 | 
			
		||||
  void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override;
 | 
			
		||||
  void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
 | 
			
		||||
  void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
 | 
			
		||||
  void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
 | 
			
		||||
  BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
 | 
			
		||||
      const SubscribeBluetoothConnectionsFreeRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
  void send_time_request() {
 | 
			
		||||
@@ -181,6 +184,8 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  std::unique_ptr<APIFrameHelper> helper_;
 | 
			
		||||
 | 
			
		||||
  std::string client_info_;
 | 
			
		||||
  uint32_t client_api_version_major_{0};
 | 
			
		||||
  uint32_t client_api_version_minor_{0};
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  esp32_camera::CameraImageReader image_reader_;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -190,7 +195,7 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  uint32_t last_traffic_;
 | 
			
		||||
  bool sent_ping_{false};
 | 
			
		||||
  bool service_call_subscription_{false};
 | 
			
		||||
  bool bluetooth_le_advertisement_subscription_{true};
 | 
			
		||||
  bool bluetooth_le_advertisement_subscription_{false};
 | 
			
		||||
  bool next_close_ = false;
 | 
			
		||||
  APIServer *parent_;
 | 
			
		||||
  InitialStateIterator initial_state_iterator_;
 | 
			
		||||
 
 | 
			
		||||
@@ -340,6 +340,35 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
template<>
 | 
			
		||||
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->api_version_major = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->api_version_minor = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -350,7 +379,11 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
 | 
			
		||||
void HelloRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->client_info);
 | 
			
		||||
  buffer.encode_uint32(2, this->api_version_major);
 | 
			
		||||
  buffer.encode_uint32(3, this->api_version_minor);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void HelloRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
@@ -358,6 +391,16 @@ void HelloRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  client_info: ");
 | 
			
		||||
  out.append("'").append(this->client_info).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  api_version_major: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->api_version_major);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  api_version_minor: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->api_version_minor);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -496,7 +539,7 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->has_bluetooth_proxy = value.as_bool();
 | 
			
		||||
      this->bluetooth_proxy_version = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
@@ -548,7 +591,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(8, this->project_name);
 | 
			
		||||
  buffer.encode_string(9, this->project_version);
 | 
			
		||||
  buffer.encode_uint32(10, this->webserver_port);
 | 
			
		||||
  buffer.encode_bool(11, this->has_bluetooth_proxy);
 | 
			
		||||
  buffer.encode_uint32(11, this->bluetooth_proxy_version);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -595,8 +638,9 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_bluetooth_proxy: ");
 | 
			
		||||
  out.append(YESNO(this->has_bluetooth_proxy));
 | 
			
		||||
  out.append("  bluetooth_proxy_version: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->bluetooth_proxy_version);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
@@ -4872,7 +4916,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const
 | 
			
		||||
bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->data.push_back(value.as_uint32());
 | 
			
		||||
      this->legacy_data.push_back(value.as_uint32());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
@@ -4885,15 +4929,20 @@ bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited
 | 
			
		||||
      this->uuid = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->data = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->uuid);
 | 
			
		||||
  for (auto &it : this->data) {
 | 
			
		||||
  for (auto &it : this->legacy_data) {
 | 
			
		||||
    buffer.encode_uint32(2, it, true);
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_string(3, this->data);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothServiceData::dump_to(std::string &out) const {
 | 
			
		||||
@@ -4903,12 +4952,16 @@ void BluetoothServiceData::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("'").append(this->uuid).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  for (const auto &it : this->data) {
 | 
			
		||||
    out.append("  data: ");
 | 
			
		||||
  for (const auto &it : this->legacy_data) {
 | 
			
		||||
    out.append("  legacy_data: ");
 | 
			
		||||
    sprintf(buffer, "%u", it);
 | 
			
		||||
    out.append(buffer);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  out.append("  data: ");
 | 
			
		||||
  out.append("'").append(this->data).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -5000,6 +5053,811 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->request_type = value.as_enum<enums::BluetoothDeviceRequestType>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_enum<enums::BluetoothDeviceRequestType>(2, this->request_type);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothDeviceRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothDeviceRequest {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  request_type: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::BluetoothDeviceRequestType>(this->request_type));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->connected = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->mtu = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->error = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_bool(2, this->connected);
 | 
			
		||||
  buffer.encode_uint32(3, this->mtu);
 | 
			
		||||
  buffer.encode_int32(4, this->error);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothDeviceConnectionResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  connected: ");
 | 
			
		||||
  out.append(YESNO(this->connected));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  mtu: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->mtu);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  error: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->error);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); }
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTGetServicesRequest {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->uuid.push_back(value.as_uint64());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  for (auto &it : this->uuid) {
 | 
			
		||||
    buffer.encode_uint64(1, it, true);
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTDescriptor::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTDescriptor {\n");
 | 
			
		||||
  for (const auto &it : this->uuid) {
 | 
			
		||||
    out.append("  uuid: ");
 | 
			
		||||
    sprintf(buffer, "%llu", it);
 | 
			
		||||
    out.append(buffer);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->uuid.push_back(value.as_uint64());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->properties = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->descriptors.push_back(value.as_message<BluetoothGATTDescriptor>());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  for (auto &it : this->uuid) {
 | 
			
		||||
    buffer.encode_uint64(1, it, true);
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_uint32(3, this->properties);
 | 
			
		||||
  for (auto &it : this->descriptors) {
 | 
			
		||||
    buffer.encode_message<BluetoothGATTDescriptor>(4, it, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTCharacteristic {\n");
 | 
			
		||||
  for (const auto &it : this->uuid) {
 | 
			
		||||
    out.append("  uuid: ");
 | 
			
		||||
    sprintf(buffer, "%llu", it);
 | 
			
		||||
    out.append(buffer);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  properties: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->properties);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  for (const auto &it : this->descriptors) {
 | 
			
		||||
    out.append("  descriptors: ");
 | 
			
		||||
    it.dump_to(out);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->uuid.push_back(value.as_uint64());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->characteristics.push_back(value.as_message<BluetoothGATTCharacteristic>());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  for (auto &it : this->uuid) {
 | 
			
		||||
    buffer.encode_uint64(1, it, true);
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  for (auto &it : this->characteristics) {
 | 
			
		||||
    buffer.encode_message<BluetoothGATTCharacteristic>(3, it, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTService::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTService {\n");
 | 
			
		||||
  for (const auto &it : this->uuid) {
 | 
			
		||||
    out.append("  uuid: ");
 | 
			
		||||
    sprintf(buffer, "%llu", it);
 | 
			
		||||
    out.append(buffer);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  for (const auto &it : this->characteristics) {
 | 
			
		||||
    out.append("  characteristics: ");
 | 
			
		||||
    it.dump_to(out);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->services.push_back(value.as_message<BluetoothGATTService>());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  for (auto &it : this->services) {
 | 
			
		||||
    buffer.encode_message<BluetoothGATTService>(2, it, true);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTGetServicesResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  for (const auto &it : this->services) {
 | 
			
		||||
    out.append("  services: ");
 | 
			
		||||
    it.dump_to(out);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTGetServicesDoneResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTReadRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTReadRequest {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->data = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_string(3, this->data);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTReadResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTReadResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  data: ");
 | 
			
		||||
  out.append("'").append(this->data).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->response = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->data = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_bool(3, this->response);
 | 
			
		||||
  buffer.encode_string(4, this->data);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTWriteRequest {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  response: ");
 | 
			
		||||
  out.append(YESNO(this->response));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  data: ");
 | 
			
		||||
  out.append("'").append(this->data).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTReadDescriptorRequest {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->data = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_string(3, this->data);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTWriteDescriptorRequest {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  data: ");
 | 
			
		||||
  out.append("'").append(this->data).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->enable = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_bool(3, this->enable);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTNotifyRequest {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  enable: ");
 | 
			
		||||
  out.append(YESNO(this->enable));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->data = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_string(3, this->data);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTNotifyDataResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  data: ");
 | 
			
		||||
  out.append("'").append(this->data).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("SubscribeBluetoothConnectionsFreeRequest {}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->free = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->limit = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint32(1, this->free);
 | 
			
		||||
  buffer.encode_uint32(2, this->limit);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothConnectionsFreeResponse {\n");
 | 
			
		||||
  out.append("  free: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->free);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  limit: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->limit);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->error = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_int32(3, this->error);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTErrorResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  error: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->error);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTWriteResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTNotifyResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -155,12 +155,20 @@ enum MediaPlayerCommand : uint32_t {
 | 
			
		||||
  MEDIA_PLAYER_COMMAND_MUTE = 3,
 | 
			
		||||
  MEDIA_PLAYER_COMMAND_UNMUTE = 4,
 | 
			
		||||
};
 | 
			
		||||
enum BluetoothDeviceRequestType : uint32_t {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace enums
 | 
			
		||||
 | 
			
		||||
class HelloRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string client_info{};
 | 
			
		||||
  uint32_t api_version_major{0};
 | 
			
		||||
  uint32_t api_version_minor{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -168,6 +176,7 @@ class HelloRequest : public ProtoMessage {
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class HelloResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
@@ -263,7 +272,7 @@ class DeviceInfoResponse : public ProtoMessage {
 | 
			
		||||
  std::string project_name{};
 | 
			
		||||
  std::string project_version{};
 | 
			
		||||
  uint32_t webserver_port{0};
 | 
			
		||||
  bool has_bluetooth_proxy{false};
 | 
			
		||||
  uint32_t bluetooth_proxy_version{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -1227,7 +1236,8 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
 | 
			
		||||
class BluetoothServiceData : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string uuid{};
 | 
			
		||||
  std::vector<uint32_t> data{};
 | 
			
		||||
  std::vector<uint32_t> legacy_data{};
 | 
			
		||||
  std::string data{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -1254,6 +1264,260 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage {
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothDeviceRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  enums::BluetoothDeviceRequestType request_type{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothDeviceConnectionResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  bool connected{false};
 | 
			
		||||
  uint32_t mtu{0};
 | 
			
		||||
  int32_t error{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTGetServicesRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTDescriptor : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::vector<uint64_t> uuid{};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTCharacteristic : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::vector<uint64_t> uuid{};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  uint32_t properties{0};
 | 
			
		||||
  std::vector<BluetoothGATTDescriptor> descriptors{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTService : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::vector<uint64_t> uuid{};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  std::vector<BluetoothGATTCharacteristic> characteristics{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTGetServicesResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  std::vector<BluetoothGATTService> services{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTGetServicesDoneResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTReadRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTReadResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  std::string data{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTWriteRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  bool response{false};
 | 
			
		||||
  std::string data{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTReadDescriptorRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTWriteDescriptorRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  std::string data{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTNotifyRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  bool enable{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTNotifyDataResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  std::string data{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
};
 | 
			
		||||
class BluetoothConnectionsFreeResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t free{0};
 | 
			
		||||
  uint32_t limit{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTErrorResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  int32_t error{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTWriteResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTNotifyResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -336,6 +336,95 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu
 | 
			
		||||
  return this->send_message_<BluetoothLEAdvertisementResponse>(msg, 67);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_device_connection_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothDeviceConnectionResponse>(msg, 69);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTGetServicesResponse>(msg, 71);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_done_response(
 | 
			
		||||
    const BluetoothGATTGetServicesDoneResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_done_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTGetServicesDoneResponse>(msg, 72);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_read_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTReadResponse>(msg, 74);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_data_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTNotifyDataResponse>(msg, 79);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_connections_free_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothConnectionsFreeResponse>(msg, 81);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_error_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTErrorResponse>(msg, 82);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_write_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTWriteResponse>(msg, 83);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
@@ -612,6 +701,94 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      this->on_subscribe_bluetooth_le_advertisements_request(msg);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 68: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothDeviceRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_device_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 70: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothGATTGetServicesRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_gatt_get_services_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 73: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothGATTReadRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_gatt_read_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 75: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothGATTWriteRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_gatt_write_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 76: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothGATTReadDescriptorRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_gatt_read_descriptor_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 77: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothGATTWriteDescriptorRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_gatt_write_descriptor_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 78: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothGATTNotifyRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_gatt_notify_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 80: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      SubscribeBluetoothConnectionsFreeRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_subscribe_bluetooth_connections_free_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -708,18 +885,6 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
 | 
			
		||||
  }
 | 
			
		||||
  this->subscribe_home_assistant_states(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->subscribe_bluetooth_le_advertisements(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
@@ -884,6 +1049,126 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
 | 
			
		||||
  this->media_player_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->subscribe_bluetooth_le_advertisements(msg);
 | 
			
		||||
}
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_device_request(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_gatt_get_services(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_gatt_read(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_gatt_write(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_gatt_read_descriptor(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_gatt_write_descriptor(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_gatt_notify(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
 | 
			
		||||
    const SubscribeBluetoothConnectionsFreeRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg);
 | 
			
		||||
  if (!this->send_bluetooth_connections_free_response(ret)) {
 | 
			
		||||
    this->on_fatal_error();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -158,6 +158,57 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
      const SubscribeBluetoothLEAdvertisementsRequest &value){};
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
@@ -175,7 +226,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
  virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
 | 
			
		||||
  virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
 | 
			
		||||
  virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
 | 
			
		||||
  virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
  virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
 | 
			
		||||
  virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
@@ -210,6 +260,32 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
  virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
 | 
			
		||||
      const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_hello_request(const HelloRequest &msg) override;
 | 
			
		||||
@@ -222,7 +298,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
  void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
 | 
			
		||||
  void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
 | 
			
		||||
  void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
 | 
			
		||||
  void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
  void on_get_time_request(const GetTimeRequest &msg) override;
 | 
			
		||||
  void on_execute_service_request(const ExecuteServiceRequest &msg) override;
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
@@ -257,6 +332,31 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
  void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -297,6 +297,72 @@ void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementRe
 | 
			
		||||
    client->send_bluetooth_le_advertisement(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
 | 
			
		||||
  BluetoothDeviceConnectionResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.connected = connected;
 | 
			
		||||
  call.mtu = mtu;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_device_connection_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
 | 
			
		||||
  BluetoothConnectionsFreeResponse call;
 | 
			
		||||
  call.free = free;
 | 
			
		||||
  call.limit = limit;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_connections_free_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_read_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_write_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_notify_data_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_notify_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_get_services_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
 | 
			
		||||
  BluetoothGATTGetServicesDoneResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_get_services_done_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
 | 
			
		||||
  BluetoothGATTErrorResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.handle = handle;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_error_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
APIServer::APIServer() { global_api_server = this; }
 | 
			
		||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,15 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
 | 
			
		||||
  void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
 | 
			
		||||
  void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_services_done(uint64_t address);
 | 
			
		||||
  void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
 | 
			
		||||
#endif
 | 
			
		||||
  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
 
 | 
			
		||||
@@ -128,9 +128,9 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
 | 
			
		||||
    }
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
  auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_,
 | 
			
		||||
                                         pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP,
 | 
			
		||||
                                         ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
 | 
			
		||||
                                         this->char_handle_cmd_, pkt->data_length + 1, (uint8_t *) &pkt->command,
 | 
			
		||||
                                         ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -138,13 +138,13 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
 | 
			
		||||
uint8_t BedJetHub::set_notify_(const bool enable) {
 | 
			
		||||
  uint8_t status;
 | 
			
		||||
  if (enable) {
 | 
			
		||||
    status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
 | 
			
		||||
    status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
 | 
			
		||||
                                               this->char_handle_status_);
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
 | 
			
		||||
    status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
 | 
			
		||||
                                                 this->char_handle_status_);
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
			
		||||
@@ -204,8 +204,8 @@ bool BedJetHub::discover_characteristics_() {
 | 
			
		||||
      result = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->char_handle_name_ = chr->handle;
 | 
			
		||||
      auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_,
 | 
			
		||||
                                            ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
 | 
			
		||||
                                            this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status);
 | 
			
		||||
      }
 | 
			
		||||
@@ -230,22 +230,6 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
 | 
			
		||||
      this->dispatch_state_(false);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      // FIXME: bug in BLEClient
 | 
			
		||||
      this->parent_->conn_id = param->open.conn_id;
 | 
			
		||||
      this->open_conn_id_ = param->open.conn_id;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_CONNECT_EVT: {
 | 
			
		||||
      if (this->parent_->conn_id != param->connect.conn_id && this->open_conn_id_ != 0xff) {
 | 
			
		||||
        // FIXME: bug in BLEClient
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] CONNECT_EVT unexpected conn_id; open=%d, parent=%d, param=%d", this->get_name().c_str(),
 | 
			
		||||
                 this->open_conn_id_, this->parent_->conn_id, param->connect.conn_id);
 | 
			
		||||
        this->parent_->conn_id = this->open_conn_id_;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      auto result = this->discover_characteristics_();
 | 
			
		||||
 | 
			
		||||
@@ -301,7 +285,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent_->conn_id)
 | 
			
		||||
      if (param->read.conn_id != this->parent_->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
@@ -358,9 +342,9 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
 | 
			
		||||
      if (this->processing_)
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      if (param->notify.conn_id != this->parent_->conn_id) {
 | 
			
		||||
      if (param->notify.conn_id != this->parent_->get_conn_id()) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x",
 | 
			
		||||
                 this->get_name().c_str(), this->parent_->conn_id, param->notify.conn_id);
 | 
			
		||||
                 this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id);
 | 
			
		||||
        // FIXME: bug in BLEClient holding wrong conn_id.
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -394,7 +378,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
 | 
			
		||||
        if (needs_extra) {
 | 
			
		||||
          // This means the packet was partial, so read the status characteristic to get the second part.
 | 
			
		||||
          // Ideally this will complete quickly. We won't process additional notification events until it does.
 | 
			
		||||
          auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id,
 | 
			
		||||
          auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
 | 
			
		||||
                                                this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          if (status) {
 | 
			
		||||
            ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str());
 | 
			
		||||
@@ -438,15 +422,15 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
 | 
			
		||||
 | 
			
		||||
  // NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits.
 | 
			
		||||
  uint16_t notify_en = enable ? 1 : 0;
 | 
			
		||||
  auto status =
 | 
			
		||||
      esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en),
 | 
			
		||||
                                     (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_write_char_descr(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), handle,
 | 
			
		||||
                                               sizeof(notify_en), (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP,
 | 
			
		||||
                                               ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
 | 
			
		||||
    return status;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(),
 | 
			
		||||
           enable ? "true" : "false", handle, this->parent_->conn_id);
 | 
			
		||||
           enable ? "true" : "false", handle, this->parent_->get_conn_id());
 | 
			
		||||
  return ESP_GATT_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -500,7 +484,7 @@ void BedJetHub::update() { this->dispatch_status_(); }
 | 
			
		||||
void BedJetHub::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ble_client.app_id: %d", this->parent()->app_id);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ble_client.conn_id: %d", this->parent()->conn_id);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  ble_client.conn_id: %d", this->parent()->get_conn_id());
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this)
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Child components (%d):", this->children_.size());
 | 
			
		||||
  for (auto *child : this->children_) {
 | 
			
		||||
 
 | 
			
		||||
@@ -167,8 +167,6 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
 | 
			
		||||
  uint16_t char_handle_status_;
 | 
			
		||||
  uint16_t config_descr_status_;
 | 
			
		||||
 | 
			
		||||
  uint8_t open_conn_id_ = -1;
 | 
			
		||||
 | 
			
		||||
  uint8_t write_notify_config_descriptor_(bool enable);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import esp32_ble_tracker
 | 
			
		||||
from esphome.components import esp32_ble_tracker, esp32_ble_client
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CHARACTERISTIC_UUID,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
@@ -14,13 +14,12 @@ from esphome.const import (
 | 
			
		||||
)
 | 
			
		||||
from esphome import automation
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["esp32_ble_client"]
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
 | 
			
		||||
ble_client_ns = cg.esphome_ns.namespace("ble_client")
 | 
			
		||||
BLEClient = ble_client_ns.class_(
 | 
			
		||||
    "BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient
 | 
			
		||||
)
 | 
			
		||||
BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase)
 | 
			
		||||
BLEClientNode = ble_client_ns.class_("BLEClientNode")
 | 
			
		||||
BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const")
 | 
			
		||||
# Triggers
 | 
			
		||||
@@ -101,12 +100,40 @@ async def ble_write_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_value_simple(value))
 | 
			
		||||
 | 
			
		||||
    serv_uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
    cg.add(var.set_service_uuid128(serv_uuid128))
 | 
			
		||||
    char_uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
        config[CONF_CHARACTERISTIC_UUID]
 | 
			
		||||
    )
 | 
			
		||||
    cg.add(var.set_char_uuid128(char_uuid128))
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
        cg.add(var.set_service_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_char_uuid16(
 | 
			
		||||
                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid32_format
 | 
			
		||||
    ):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_char_uuid32(
 | 
			
		||||
                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid128_format
 | 
			
		||||
    ):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
            config[CONF_CHARACTERISTIC_UUID]
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_char_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#include "automation.h"
 | 
			
		||||
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
@@ -31,8 +31,8 @@ void BLEWriterClientNode::write(const std::vector<uint8_t> &value) {
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, value.size(),
 | 
			
		||||
                               const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_,
 | 
			
		||||
                               value.size(), const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,8 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
 | 
			
		||||
  void loop() override {}
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override {
 | 
			
		||||
    if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0)
 | 
			
		||||
    if (event == ESP_GATTC_DISCONNECT_EVT &&
 | 
			
		||||
        memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0)
 | 
			
		||||
      this->trigger();
 | 
			
		||||
    if (event == ESP_GATTC_SEARCH_CMPL_EVT)
 | 
			
		||||
      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
@@ -45,10 +46,14 @@ class BLEWriterClientNode : public BLEClientNode {
 | 
			
		||||
  // Attempts to write the contents of value to char_uuid_.
 | 
			
		||||
  void write(const std::vector<uint8_t> &value);
 | 
			
		||||
 | 
			
		||||
  void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
 | 
			
		||||
  void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
 | 
			
		||||
  void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
 | 
			
		||||
  void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
 | 
			
		||||
  void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
 | 
			
		||||
  void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
 | 
			
		||||
  void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "ble_client.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -11,22 +12,13 @@ namespace ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ble_client";
 | 
			
		||||
 | 
			
		||||
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
 | 
			
		||||
 | 
			
		||||
void BLEClient::setup() {
 | 
			
		||||
  auto ret = esp_ble_gattc_app_register(this->app_id);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
  this->set_states_(espbt::ClientState::IDLE);
 | 
			
		||||
  BLEClientBase::setup();
 | 
			
		||||
  this->enabled = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::loop() {
 | 
			
		||||
  if (this->state() == espbt::ClientState::DISCOVERED) {
 | 
			
		||||
    this->connect();
 | 
			
		||||
  }
 | 
			
		||||
  BLEClientBase::loop();
 | 
			
		||||
  for (auto *node : this->nodes_)
 | 
			
		||||
    node->loop();
 | 
			
		||||
}
 | 
			
		||||
@@ -39,34 +31,7 @@ void BLEClient::dump_config() {
 | 
			
		||||
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
 | 
			
		||||
  if (!this->enabled)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (device.address_uint64() != this->address)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (this->state() != espbt::ClientState::IDLE)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
 | 
			
		||||
  this->set_states_(espbt::ClientState::DISCOVERED);
 | 
			
		||||
 | 
			
		||||
  auto addr = device.address_uint64();
 | 
			
		||||
  this->remote_bda[0] = (addr >> 40) & 0xFF;
 | 
			
		||||
  this->remote_bda[1] = (addr >> 32) & 0xFF;
 | 
			
		||||
  this->remote_bda[2] = (addr >> 24) & 0xFF;
 | 
			
		||||
  this->remote_bda[3] = (addr >> 16) & 0xFF;
 | 
			
		||||
  this->remote_bda[4] = (addr >> 8) & 0xFF;
 | 
			
		||||
  this->remote_bda[5] = (addr >> 0) & 0xFF;
 | 
			
		||||
  this->remote_addr_type = device.get_address_type();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string BLEClient::address_str() const {
 | 
			
		||||
  char buf[20];
 | 
			
		||||
  sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff,
 | 
			
		||||
          (uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff,
 | 
			
		||||
          (uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff,
 | 
			
		||||
          (uint8_t)(this->address >> 0) & 0xff);
 | 
			
		||||
  std::string ret;
 | 
			
		||||
  ret = buf;
 | 
			
		||||
  return ret;
 | 
			
		||||
  return BLEClientBase::parse_device(device);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::set_enabled(bool enabled) {
 | 
			
		||||
@@ -74,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) {
 | 
			
		||||
    return;
 | 
			
		||||
  if (!enabled && this->state() != espbt::ClientState::IDLE) {
 | 
			
		||||
    ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
 | 
			
		||||
    auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
 | 
			
		||||
    auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
    if (ret) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
 | 
			
		||||
    }
 | 
			
		||||
@@ -82,125 +47,12 @@ void BLEClient::set_enabled(bool enabled) {
 | 
			
		||||
  this->enabled = enabled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::connect() {
 | 
			
		||||
  ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
 | 
			
		||||
  auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, this->remote_addr_type, true);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
 | 
			
		||||
    this->set_states_(espbt::ClientState::IDLE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->set_states_(espbt::ClientState::CONNECTING);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
                                    esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
 | 
			
		||||
    return;
 | 
			
		||||
  if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  bool all_established = this->all_nodes_established_();
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_REG_EVT: {
 | 
			
		||||
      if (param->reg.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
 | 
			
		||||
        this->gattc_if = esp_gattc_if;
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
 | 
			
		||||
      this->conn_id = param->open.conn_id;
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
 | 
			
		||||
        this->set_states_(espbt::ClientState::IDLE);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CONNECT_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
 | 
			
		||||
      if (this->conn_id != param->connect.conn_id) {
 | 
			
		||||
        ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
 | 
			
		||||
                 this->address_str().c_str(), param->connect.conn_id, this->conn_id);
 | 
			
		||||
      }
 | 
			
		||||
      auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id);
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CFG_MTU_EVT: {
 | 
			
		||||
      if (param->cfg_mtu.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
 | 
			
		||||
                 param->cfg_mtu.status);
 | 
			
		||||
        this->set_states_(espbt::ClientState::IDLE);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
 | 
			
		||||
      esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
 | 
			
		||||
      for (auto &svc : this->services_)
 | 
			
		||||
        delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
      this->services_.clear();
 | 
			
		||||
      this->set_states_(espbt::ClientState::IDLE);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_RES_EVT: {
 | 
			
		||||
      BLEService *ble_service = new BLEService();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
      ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
 | 
			
		||||
      ble_service->start_handle = param->search_res.start_handle;
 | 
			
		||||
      ble_service->end_handle = param->search_res.end_handle;
 | 
			
		||||
      ble_service->client = this;
 | 
			
		||||
      this->services_.push_back(ble_service);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
 | 
			
		||||
      for (auto &svc : this->services_) {
 | 
			
		||||
        ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
 | 
			
		||||
        ESP_LOGI(TAG, "  start_handle: 0x%x  end_handle: 0x%x", svc->start_handle, svc->end_handle);
 | 
			
		||||
        svc->parse_characteristics();
 | 
			
		||||
      }
 | 
			
		||||
      this->set_states_(espbt::ClientState::CONNECTED);
 | 
			
		||||
      this->set_state(espbt::ClientState::ESTABLISHED);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
 | 
			
		||||
      if (descr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
 | 
			
		||||
          descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
 | 
			
		||||
        ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
 | 
			
		||||
                 descr->uuid.to_string().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      uint16_t notify_en = 1;
 | 
			
		||||
      auto status =
 | 
			
		||||
          esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
 | 
			
		||||
                                         (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  BLEClientBase::gattc_event_handler(event, esp_gattc_if, param);
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  for (auto *node : this->nodes_)
 | 
			
		||||
    node->gattc_event_handler(event, esp_gattc_if, param);
 | 
			
		||||
 | 
			
		||||
@@ -213,236 +65,26 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    // This event is sent by the server when it requests security
 | 
			
		||||
    case ESP_GAP_BLE_SEC_REQ_EVT:
 | 
			
		||||
      ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
 | 
			
		||||
      esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
 | 
			
		||||
      break;
 | 
			
		||||
    // This event is sent once authentication has completed
 | 
			
		||||
    case ESP_GAP_BLE_AUTH_CMPL_EVT:
 | 
			
		||||
      esp_bd_addr_t bd_addr;
 | 
			
		||||
      memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
 | 
			
		||||
      ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
 | 
			
		||||
      if (!param->ble_security.auth_cmpl.success) {
 | 
			
		||||
        ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
 | 
			
		||||
                 param->ble_security.auth_cmpl.auth_mode);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    // There are other events we'll want to implement at some point to support things like pass key
 | 
			
		||||
    // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  BLEClientBase::gap_event_handler(event, param);
 | 
			
		||||
 | 
			
		||||
  for (auto *node : this->nodes_)
 | 
			
		||||
    node->gap_event_handler(event, param);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::set_state(espbt::ClientState state) {
 | 
			
		||||
  BLEClientBase::set_state(state);
 | 
			
		||||
  for (auto &node : nodes_)
 | 
			
		||||
    node->node_state = state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BLEClient::all_nodes_established_() {
 | 
			
		||||
  if (this->state() != espbt::ClientState::ESTABLISHED)
 | 
			
		||||
    return false;
 | 
			
		||||
  for (auto &node : nodes_) {
 | 
			
		||||
    if (node->node_state != espbt::ClientState::ESTABLISHED)
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse GATT values into a float for a sensor.
 | 
			
		||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
 | 
			
		||||
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
 | 
			
		||||
  // A length of one means a single octet value.
 | 
			
		||||
  if (length == 0)
 | 
			
		||||
    return 0;
 | 
			
		||||
  if (length == 1)
 | 
			
		||||
    return (float) ((uint8_t) value[0]);
 | 
			
		||||
 | 
			
		||||
  switch (value[0]) {
 | 
			
		||||
    case 0x1:  // boolean.
 | 
			
		||||
    case 0x2:  // 2bit.
 | 
			
		||||
    case 0x3:  // nibble.
 | 
			
		||||
    case 0x4:  // uint8.
 | 
			
		||||
      return (float) ((uint8_t) value[1]);
 | 
			
		||||
    case 0x5:  // uint12.
 | 
			
		||||
    case 0x6:  // uint16.
 | 
			
		||||
      if (length > 2) {
 | 
			
		||||
        return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]);
 | 
			
		||||
      }
 | 
			
		||||
    case 0x7:  // uint24.
 | 
			
		||||
      if (length > 3) {
 | 
			
		||||
        return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3]));
 | 
			
		||||
      }
 | 
			
		||||
    case 0x8:  // uint32.
 | 
			
		||||
      if (length > 4) {
 | 
			
		||||
        return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) +
 | 
			
		||||
                        (uint32_t)(value[4]));
 | 
			
		||||
      }
 | 
			
		||||
    case 0xC:  // int8.
 | 
			
		||||
      return (float) ((int8_t) value[1]);
 | 
			
		||||
    case 0xD:  // int12.
 | 
			
		||||
    case 0xE:  // int16.
 | 
			
		||||
      if (length > 2) {
 | 
			
		||||
        return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
 | 
			
		||||
      }
 | 
			
		||||
    case 0xF:  // int24.
 | 
			
		||||
      if (length > 3) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
 | 
			
		||||
      }
 | 
			
		||||
    case 0x10:  // int32.
 | 
			
		||||
      if (length > 4) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
 | 
			
		||||
                        (int32_t)(value[4]));
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
 | 
			
		||||
  return NAN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  for (auto *svc : this->services_) {
 | 
			
		||||
    if (svc->uuid == uuid)
 | 
			
		||||
      return svc;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
 | 
			
		||||
  auto *svc = this->get_service(service);
 | 
			
		||||
  if (svc == nullptr)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  return svc->get_characteristic(chr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) {
 | 
			
		||||
  return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
 | 
			
		||||
  for (auto &svc : this->services_) {
 | 
			
		||||
    for (auto &chr : svc->characteristics) {
 | 
			
		||||
      if (chr->handle == handle) {
 | 
			
		||||
        for (auto &desc : chr->descriptors) {
 | 
			
		||||
          if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
 | 
			
		||||
            return desc;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  for (auto &chr : this->characteristics) {
 | 
			
		||||
    if (chr->uuid == uuid)
 | 
			
		||||
      return chr;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
 | 
			
		||||
  return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
 | 
			
		||||
  auto *svc = this->get_service(service);
 | 
			
		||||
  if (svc == nullptr)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  auto *ch = svc->get_characteristic(chr);
 | 
			
		||||
  if (ch == nullptr)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  return ch->get_descriptor(descr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
 | 
			
		||||
  return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
 | 
			
		||||
                              espbt::ESPBTUUID::from_uint16(descr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEService::~BLEService() {
 | 
			
		||||
  for (auto &chr : this->characteristics)
 | 
			
		||||
    delete chr;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEService::parse_characteristics() {
 | 
			
		||||
  uint16_t offset = 0;
 | 
			
		||||
  esp_gattc_char_elem_t result;
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    uint16_t count = 1;
 | 
			
		||||
    esp_gatt_status_t status = esp_ble_gattc_get_all_char(
 | 
			
		||||
        this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset);
 | 
			
		||||
    if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (status != ESP_GATT_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (count == 0) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BLECharacteristic *characteristic = new BLECharacteristic();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
    characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
 | 
			
		||||
    characteristic->properties = result.properties;
 | 
			
		||||
    characteristic->handle = result.char_handle;
 | 
			
		||||
    characteristic->service = this;
 | 
			
		||||
    this->characteristics.push_back(characteristic);
 | 
			
		||||
    ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
 | 
			
		||||
             characteristic->handle, characteristic->properties);
 | 
			
		||||
    characteristic->parse_descriptors();
 | 
			
		||||
    offset++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic::~BLECharacteristic() {
 | 
			
		||||
  for (auto &desc : this->descriptors)
 | 
			
		||||
    delete desc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::parse_descriptors() {
 | 
			
		||||
  uint16_t offset = 0;
 | 
			
		||||
  esp_gattc_descr_elem_t result;
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    uint16_t count = 1;
 | 
			
		||||
    esp_gatt_status_t status = esp_ble_gattc_get_all_descr(
 | 
			
		||||
        this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset);
 | 
			
		||||
    if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (status != ESP_GATT_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (count == 0) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BLEDescriptor *desc = new BLEDescriptor();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
    desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
 | 
			
		||||
    desc->handle = result.handle;
 | 
			
		||||
    desc->characteristic = this;
 | 
			
		||||
    this->descriptors.push_back(desc);
 | 
			
		||||
    ESP_LOGV(TAG, "   descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
 | 
			
		||||
    offset++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  for (auto &desc : this->descriptors) {
 | 
			
		||||
    if (desc->uuid == uuid)
 | 
			
		||||
      return desc;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
 | 
			
		||||
  return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
 | 
			
		||||
  auto *client = this->service->client;
 | 
			
		||||
  auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
 | 
			
		||||
                                         write_type, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
 | 
			
		||||
  write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -18,15 +19,16 @@ namespace ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
using namespace esp32_ble_client;
 | 
			
		||||
 | 
			
		||||
class BLEClient;
 | 
			
		||||
class BLEService;
 | 
			
		||||
class BLECharacteristic;
 | 
			
		||||
 | 
			
		||||
class BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                   esp_ble_gattc_cb_param_t *param) = 0;
 | 
			
		||||
  virtual void loop(){};
 | 
			
		||||
  virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
 | 
			
		||||
  virtual void loop() {}
 | 
			
		||||
  void set_address(uint64_t address) { address_ = address; }
 | 
			
		||||
  espbt::ESPBTClient *client;
 | 
			
		||||
  // This should be transitioned to Established once the node no longer needs
 | 
			
		||||
@@ -42,57 +44,17 @@ class BLEClientNode {
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLEDescriptor {
 | 
			
		||||
 public:
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t handle;
 | 
			
		||||
 | 
			
		||||
  BLECharacteristic *characteristic;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLECharacteristic {
 | 
			
		||||
 public:
 | 
			
		||||
  ~BLECharacteristic();
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t handle;
 | 
			
		||||
  esp_gatt_char_prop_t properties;
 | 
			
		||||
  std::vector<BLEDescriptor *> descriptors;
 | 
			
		||||
  void parse_descriptors();
 | 
			
		||||
  BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t uuid);
 | 
			
		||||
  void write_value(uint8_t *new_val, int16_t new_val_size);
 | 
			
		||||
  void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
 | 
			
		||||
  BLEService *service;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLEService {
 | 
			
		||||
 public:
 | 
			
		||||
  ~BLEService();
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t start_handle;
 | 
			
		||||
  uint16_t end_handle;
 | 
			
		||||
  std::vector<BLECharacteristic *> characteristics;
 | 
			
		||||
  BLEClient *client;
 | 
			
		||||
  void parse_characteristics();
 | 
			
		||||
  BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLECharacteristic *get_characteristic(uint16_t uuid);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLEClient : public espbt::ESPBTClient, public Component {
 | 
			
		||||
class BLEClient : public BLEClientBase {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
  bool parse_device(const espbt::ESPBTDevice &device) override;
 | 
			
		||||
  void on_scan_end() override {}
 | 
			
		||||
  void connect() override;
 | 
			
		||||
 | 
			
		||||
  void set_address(uint64_t address) { this->address = address; }
 | 
			
		||||
 | 
			
		||||
  void set_enabled(bool enabled);
 | 
			
		||||
 | 
			
		||||
@@ -102,43 +64,14 @@ class BLEClient : public espbt::ESPBTClient, public Component {
 | 
			
		||||
    this->nodes_.push_back(node);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BLEService *get_service(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEService *get_service(uint16_t uuid);
 | 
			
		||||
  BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
 | 
			
		||||
  BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
 | 
			
		||||
  BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
 | 
			
		||||
  // Get the configuration descriptor for the given characteristic handle.
 | 
			
		||||
  BLEDescriptor *get_config_descriptor(uint16_t handle);
 | 
			
		||||
 | 
			
		||||
  float parse_char_value(uint8_t *value, uint16_t length);
 | 
			
		||||
 | 
			
		||||
  int gattc_if;
 | 
			
		||||
  esp_bd_addr_t remote_bda;
 | 
			
		||||
  esp_ble_addr_type_t remote_addr_type;
 | 
			
		||||
  uint16_t conn_id;
 | 
			
		||||
  uint64_t address;
 | 
			
		||||
  bool enabled;
 | 
			
		||||
  std::string address_str() const;
 | 
			
		||||
 | 
			
		||||
  void set_state(espbt::ClientState state) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void set_states_(espbt::ClientState st) {
 | 
			
		||||
    this->set_state(st);
 | 
			
		||||
    for (auto &node : nodes_)
 | 
			
		||||
      node->node_state = st;
 | 
			
		||||
  }
 | 
			
		||||
  bool all_nodes_established_() {
 | 
			
		||||
    if (this->state() != espbt::ClientState::ESTABLISHED)
 | 
			
		||||
      return false;
 | 
			
		||||
    for (auto &node : nodes_) {
 | 
			
		||||
      if (node->node_state != espbt::ClientState::ESTABLISHED)
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  bool all_nodes_established_();
 | 
			
		||||
 | 
			
		||||
  std::vector<BLEClientNode *> nodes_;
 | 
			
		||||
  std::vector<BLEService *> services_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,11 @@ from esphome.const import (
 | 
			
		||||
    CONF_CHARACTERISTIC_UUID,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_SERVICE_UUID,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
)
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from .. import ble_client_ns
 | 
			
		||||
@@ -16,6 +20,8 @@ CONF_DESCRIPTOR_UUID = "descriptor_uuid"
 | 
			
		||||
 | 
			
		||||
CONF_NOTIFY = "notify"
 | 
			
		||||
CONF_ON_NOTIFY = "on_notify"
 | 
			
		||||
TYPE_CHARACTERISTIC = "characteristic"
 | 
			
		||||
TYPE_RSSI = "rssi"
 | 
			
		||||
 | 
			
		||||
adv_data_t = cg.std_vector.template(cg.uint8)
 | 
			
		||||
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
 | 
			
		||||
@@ -27,33 +33,67 @@ BLESensorNotifyTrigger = ble_client_ns.class_(
 | 
			
		||||
    "BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        BLESensor,
 | 
			
		||||
        accuracy_decimals=0,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        BLESensorNotifyTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
BLEClientRssiSensor = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
def checkType(value):
 | 
			
		||||
    if CONF_TYPE not in value and CONF_SERVICE_UUID in value:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Looks like you're trying to create a ble characteristic sensor. Please add `type: characteristic` to your sensor config."
 | 
			
		||||
        )
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    checkType,
 | 
			
		||||
    cv.typed_schema(
 | 
			
		||||
        {
 | 
			
		||||
            TYPE_CHARACTERISTIC: sensor.sensor_schema(
 | 
			
		||||
                BLESensor,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
            )
 | 
			
		||||
            .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
            .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
                    cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
                    cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
                    cv.Optional(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
                    cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
 | 
			
		||||
                    cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
 | 
			
		||||
                        {
 | 
			
		||||
                            cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                                BLESensorNotifyTrigger
 | 
			
		||||
                            ),
 | 
			
		||||
                        }
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            TYPE_RSSI: sensor.sensor_schema(
 | 
			
		||||
                BLEClientRssiSensor,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
                device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            )
 | 
			
		||||
            .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
            .extend(ble_client.BLE_CLIENT_SCHEMA),
 | 
			
		||||
        },
 | 
			
		||||
        lower=True,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def rssi_sensor_to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def characteristic_sensor_to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
@@ -125,3 +165,10 @@ async def to_code(config):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await ble_client.register_ble_node(trigger, config)
 | 
			
		||||
        await automation.build_automation(trigger, [(float, "x")], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    if config[CONF_TYPE] == TYPE_RSSI:
 | 
			
		||||
        await rssi_sensor_to_code(config)
 | 
			
		||||
    elif config[CONF_TYPE] == TYPE_CHARACTERISTIC:
 | 
			
		||||
        await characteristic_sensor_to_code(config)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,8 @@ class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
        if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
 | 
			
		||||
        if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
 | 
			
		||||
            param->notify.handle != this->sensor_->handle)
 | 
			
		||||
          break;
 | 
			
		||||
        this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
#include "ble_rssi_sensor.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ble_rssi_sensor";
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::loop() {}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "BLE Client RSSI Sensor", this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MAC address        : %s", this->parent()->address_str().c_str());
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                              esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      this->publish_state(NAN);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT:
 | 
			
		||||
      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    // server response on RSSI request:
 | 
			
		||||
    case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
 | 
			
		||||
      if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) {
 | 
			
		||||
        int8_t rssi = param->read_rssi_cmpl.rssi;
 | 
			
		||||
        ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi);
 | 
			
		||||
        this->publish_state(rssi);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::update() {
 | 
			
		||||
  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str());
 | 
			
		||||
  auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda());
 | 
			
		||||
  if (status != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    this->publish_state(NAN);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										31
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
@@ -63,8 +63,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
 | 
			
		||||
        this->handle = descr->handle;
 | 
			
		||||
      }
 | 
			
		||||
      if (this->notify_) {
 | 
			
		||||
        auto status =
 | 
			
		||||
            esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
 | 
			
		||||
        auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
 | 
			
		||||
                                                        this->parent()->get_remote_bda(), chr->handle);
 | 
			
		||||
        if (status) {
 | 
			
		||||
          ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
 | 
			
		||||
        }
 | 
			
		||||
@@ -74,7 +74,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->conn_id)
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
@@ -87,7 +87,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
 | 
			
		||||
      if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
 | 
			
		||||
        break;
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
 | 
			
		||||
               param->notify.handle, param->notify.value[0]);
 | 
			
		||||
@@ -122,8 +122,8 @@ void BLESensor::update() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto status =
 | 
			
		||||
      esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    this->publish_state(NAN);
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,8 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
        if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
 | 
			
		||||
        if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
 | 
			
		||||
            param->notify.handle != this->sensor_->handle)
 | 
			
		||||
          break;
 | 
			
		||||
        this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -66,8 +66,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
        this->handle = descr->handle;
 | 
			
		||||
      }
 | 
			
		||||
      if (this->notify_) {
 | 
			
		||||
        auto status =
 | 
			
		||||
            esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
 | 
			
		||||
        auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
 | 
			
		||||
                                                        this->parent()->get_remote_bda(), chr->handle);
 | 
			
		||||
        if (status) {
 | 
			
		||||
          ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
 | 
			
		||||
        }
 | 
			
		||||
@@ -77,7 +77,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->conn_id)
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
@@ -90,7 +90,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
 | 
			
		||||
      if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
 | 
			
		||||
        break;
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
 | 
			
		||||
               param->notify.handle, param->notify.value[0]);
 | 
			
		||||
@@ -121,8 +121,8 @@ void BLETextSensor::update() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto status =
 | 
			
		||||
      esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    this->publish_state(EMPTY);
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
			
		||||
      case MATCH_BY_SERVICE_UUID:
 | 
			
		||||
        for (auto uuid : device.get_service_uuids()) {
 | 
			
		||||
          if (this->uuid_ == uuid) {
 | 
			
		||||
            this->publish_state(device.get_rssi());
 | 
			
		||||
            this->publish_state(true);
 | 
			
		||||
            this->found_ = true;
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
@@ -83,7 +83,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this->publish_state(device.get_rssi());
 | 
			
		||||
        this->publish_state(true);
 | 
			
		||||
        this->found_ = true;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_MAC_ADDRESS,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_DECIBEL,
 | 
			
		||||
    UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
@@ -31,7 +31,7 @@ def _validate(config):
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        BLERSSISensor,
 | 
			
		||||
        unit_of_measurement=UNIT_DECIBEL,
 | 
			
		||||
        unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
        accuracy_decimals=0,
 | 
			
		||||
        device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,23 @@
 | 
			
		||||
from esphome.components import esp32_ble_tracker
 | 
			
		||||
from esphome.components import esp32_ble_tracker, esp32_ble_client
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.const import CONF_ACTIVE, CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32", "esp32_ble_tracker"]
 | 
			
		||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
 | 
			
		||||
DEPENDENCIES = ["api", "esp32"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
 | 
			
		||||
 | 
			
		||||
BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component)
 | 
			
		||||
BluetoothProxy = bluetooth_proxy_ns.class_(
 | 
			
		||||
    "BluetoothProxy", esp32_ble_client.BLEClientBase
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BluetoothProxy),
 | 
			
		||||
        cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
 | 
			
		||||
@@ -22,6 +26,8 @@ async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    await esp32_ble_tracker.register_ble_device(var, config)
 | 
			
		||||
    cg.add(var.set_active(config[CONF_ACTIVE]))
 | 
			
		||||
 | 
			
		||||
    await esp32_ble_tracker.register_client(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_BLUETOOTH_PROXY")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,39 @@
 | 
			
		||||
#include "bluetooth_proxy.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#include "esphome/components/api/api_server.h"
 | 
			
		||||
#endif  // USE_API
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_server.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "bluetooth_proxy";
 | 
			
		||||
 | 
			
		||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
 | 
			
		||||
static const esp_err_t ESP_GATT_WRONG_ADDRESS = -2;
 | 
			
		||||
 | 
			
		||||
BluetoothProxy::BluetoothProxy() {
 | 
			
		||||
  global_bluetooth_proxy = this;
 | 
			
		||||
  this->address_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (!api::global_api_server->is_connected())
 | 
			
		||||
    return false;
 | 
			
		||||
  ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
 | 
			
		||||
           device.get_rssi());
 | 
			
		||||
  this->send_api_packet_(device);
 | 
			
		||||
 | 
			
		||||
  if (this->address_ == 0)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  BLEClientBase::parse_device(device);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
#ifndef USE_API
 | 
			
		||||
  return;
 | 
			
		||||
#else
 | 
			
		||||
  api::BluetoothLEAdvertisementResponse resp;
 | 
			
		||||
  resp.address = device.address_uint64();
 | 
			
		||||
  if (!device.get_name().empty())
 | 
			
		||||
@@ -35,23 +45,361 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
 | 
			
		||||
  for (auto &data : device.get_service_datas()) {
 | 
			
		||||
    api::BluetoothServiceData service_data;
 | 
			
		||||
    service_data.uuid = data.uuid.to_string();
 | 
			
		||||
    for (auto d : data.data)
 | 
			
		||||
      service_data.data.push_back(d);
 | 
			
		||||
    resp.service_data.push_back(service_data);
 | 
			
		||||
    service_data.data.assign(data.data.begin(), data.data.end());
 | 
			
		||||
    resp.service_data.push_back(std::move(service_data));
 | 
			
		||||
  }
 | 
			
		||||
  for (auto &data : device.get_manufacturer_datas()) {
 | 
			
		||||
    api::BluetoothServiceData manufacturer_data;
 | 
			
		||||
    manufacturer_data.uuid = data.uuid.to_string();
 | 
			
		||||
    for (auto d : data.data)
 | 
			
		||||
      manufacturer_data.data.push_back(d);
 | 
			
		||||
    resp.manufacturer_data.push_back(manufacturer_data);
 | 
			
		||||
    manufacturer_data.data.assign(data.data.begin(), data.data.end());
 | 
			
		||||
    resp.manufacturer_data.push_back(std::move(manufacturer_data));
 | 
			
		||||
  }
 | 
			
		||||
  api::global_api_server->send_bluetooth_le_advertisement(resp);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                         esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  BLEClientBase::gattc_event_handler(event, gattc_if, param);
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_,
 | 
			
		||||
                                                               param->disconnect.reason);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
      this->address_ = 0;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_READ_DESCR_EVT:
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char/descriptor at handle 0x%2X, status=%d", param->read.handle,
 | 
			
		||||
                 param->read.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTReadResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->read.handle;
 | 
			
		||||
      resp.data.reserve(param->read.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->read.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_read_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_WRITE_CHAR_EVT:
 | 
			
		||||
    case ESP_GATTC_WRITE_DESCR_EVT: {
 | 
			
		||||
      if (param->write.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->write.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error writing char/descriptor at handle 0x%2X, status=%d", param->write.handle,
 | 
			
		||||
                 param->write.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTWriteResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->write.handle;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_write_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      if (param->unreg_for_notify.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error unregistering notifications for handle 0x%2X, status=%d", param->unreg_for_notify.handle,
 | 
			
		||||
                 param->unreg_for_notify.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle,
 | 
			
		||||
                                                          param->unreg_for_notify.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTNotifyResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->unreg_for_notify.handle;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      if (param->reg_for_notify.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error registering notifications for handle 0x%2X, status=%d", param->reg_for_notify.handle,
 | 
			
		||||
                 param->reg_for_notify.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle,
 | 
			
		||||
                                                          param->reg_for_notify.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTNotifyResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->reg_for_notify.handle;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%2X", param->notify.handle);
 | 
			
		||||
      api::BluetoothGATTNotifyDataResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->notify.handle;
 | 
			
		||||
      resp.data.reserve(param->notify.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->notify.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); }
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::loop() {
 | 
			
		||||
  BLEClientBase::loop();
 | 
			
		||||
  if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) {
 | 
			
		||||
    ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
 | 
			
		||||
    auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
    if (err != ERR_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->send_service_ == this->services_.size()) {
 | 
			
		||||
    this->send_service_ = -1;
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_services_done(this->address_);
 | 
			
		||||
  } else if (this->send_service_ >= 0) {
 | 
			
		||||
    auto &service = this->services_[this->send_service_];
 | 
			
		||||
    api::BluetoothGATTGetServicesResponse resp;
 | 
			
		||||
    resp.address = this->address_;
 | 
			
		||||
    api::BluetoothGATTService service_resp;
 | 
			
		||||
    service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
 | 
			
		||||
    service_resp.handle = service->start_handle;
 | 
			
		||||
    for (auto &characteristic : service->characteristics) {
 | 
			
		||||
      api::BluetoothGATTCharacteristic characteristic_resp;
 | 
			
		||||
      characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
 | 
			
		||||
      characteristic_resp.handle = characteristic->handle;
 | 
			
		||||
      characteristic_resp.properties = characteristic->properties;
 | 
			
		||||
      for (auto &descriptor : characteristic->descriptors) {
 | 
			
		||||
        api::BluetoothGATTDescriptor descriptor_resp;
 | 
			
		||||
        descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
 | 
			
		||||
        descriptor_resp.handle = descriptor->handle;
 | 
			
		||||
        characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
 | 
			
		||||
      }
 | 
			
		||||
      service_resp.characteristics.push_back(std::move(characteristic_resp));
 | 
			
		||||
    }
 | 
			
		||||
    resp.services.push_back(std::move(service_resp));
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_services(resp);
 | 
			
		||||
    this->send_service_++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
 | 
			
		||||
  switch (msg.request_type) {
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
 | 
			
		||||
      this->address_ = msg.address;
 | 
			
		||||
      this->set_state(espbt::ClientState::SEARCHING);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
 | 
			
		||||
      if (this->state() != espbt::ClientState::IDLE) {
 | 
			
		||||
        ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
 | 
			
		||||
        auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
        if (err != ERR_OK) {
 | 
			
		||||
          ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *characteristic = this->get_characteristic(msg.handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic, not found.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *characteristic = this->get_characteristic(msg.handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic, not found.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str());
 | 
			
		||||
  auto err = characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(),
 | 
			
		||||
                                         msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *descriptor = this->get_descriptor(msg.handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *descriptor = this->get_descriptor(msg.handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(),
 | 
			
		||||
                                     (uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err);
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot get GATT services, not connected.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for service list request");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_WRONG_ADDRESS);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->send_service_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot configure notify, not connected.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for notify");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *characteristic = this->get_characteristic(msg.handle);
 | 
			
		||||
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found.");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  if (msg.enable) {
 | 
			
		||||
    err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err);
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BluetoothProxy *global_bluetooth_proxy = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,22 +4,53 @@
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
 | 
			
		||||
using namespace esp32_ble_client;
 | 
			
		||||
 | 
			
		||||
class BluetoothProxy : public BLEClientBase {
 | 
			
		||||
 public:
 | 
			
		||||
  BluetoothProxy();
 | 
			
		||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
 | 
			
		||||
 | 
			
		||||
  int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; }
 | 
			
		||||
  int get_bluetooth_connections_limit() { return 1; }
 | 
			
		||||
 | 
			
		||||
  void set_active(bool active) { this->active_ = active; }
 | 
			
		||||
  bool has_active() { return this->active_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
 | 
			
		||||
 | 
			
		||||
  int16_t send_service_{-1};
 | 
			
		||||
  bool active_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ from pathlib import Path
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components.packages import validate_source_shorthand
 | 
			
		||||
from esphome.const import CONF_WIFI
 | 
			
		||||
from esphome.wizard import wizard_file
 | 
			
		||||
from esphome.yaml_util import dump
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +44,9 @@ async def to_code(config):
 | 
			
		||||
    cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_config(path: str, name: str, project_name: str, import_url: str) -> None:
 | 
			
		||||
def import_config(
 | 
			
		||||
    path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI
 | 
			
		||||
) -> None:
 | 
			
		||||
    p = Path(path)
 | 
			
		||||
 | 
			
		||||
    if p.exists():
 | 
			
		||||
@@ -69,7 +72,9 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
 | 
			
		||||
                "name_add_mac_suffix": False,
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
        p.write_text(
 | 
			
		||||
            dump(config) + WIFI_CONFIG,
 | 
			
		||||
            encoding="utf8",
 | 
			
		||||
        )
 | 
			
		||||
        output = dump(config)
 | 
			
		||||
 | 
			
		||||
        if network == CONF_WIFI:
 | 
			
		||||
            output += WIFI_CONFIG
 | 
			
		||||
 | 
			
		||||
        p.write_text(output, encoding="utf8")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								esphome/components/esp32_ble_client/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/esp32_ble_client/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from esphome.components import esp32_ble_tracker
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["esp32_ble_tracker"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
 | 
			
		||||
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
 | 
			
		||||
BLEClientBase = esp32_ble_client_ns.class_(
 | 
			
		||||
    "BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										84
									
								
								esphome/components/esp32_ble_client/ble_characteristic.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								esphome/components/esp32_ble_client/ble_characteristic.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
#include "ble_client_base.h"
 | 
			
		||||
#include "ble_service.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_ble_client.characteristic";
 | 
			
		||||
 | 
			
		||||
BLECharacteristic::~BLECharacteristic() {
 | 
			
		||||
  for (auto &desc : this->descriptors)
 | 
			
		||||
    delete desc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::parse_descriptors() {
 | 
			
		||||
  uint16_t offset = 0;
 | 
			
		||||
  esp_gattc_descr_elem_t result;
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    uint16_t count = 1;
 | 
			
		||||
    esp_gatt_status_t status =
 | 
			
		||||
        esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(),
 | 
			
		||||
                                    this->handle, &result, &count, offset);
 | 
			
		||||
    if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (status != ESP_GATT_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (count == 0) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BLEDescriptor *desc = new BLEDescriptor();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
    desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
 | 
			
		||||
    desc->handle = result.handle;
 | 
			
		||||
    desc->characteristic = this;
 | 
			
		||||
    this->descriptors.push_back(desc);
 | 
			
		||||
    ESP_LOGV(TAG, "   descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
 | 
			
		||||
    offset++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  for (auto &desc : this->descriptors) {
 | 
			
		||||
    if (desc->uuid == uuid)
 | 
			
		||||
      return desc;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
 | 
			
		||||
  return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
 | 
			
		||||
}
 | 
			
		||||
BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
 | 
			
		||||
  for (auto &desc : this->descriptors) {
 | 
			
		||||
    if (desc->handle == handle)
 | 
			
		||||
      return desc;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
 | 
			
		||||
  auto *client = this->service->client;
 | 
			
		||||
  auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
 | 
			
		||||
                                         new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
 | 
			
		||||
  return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										35
									
								
								esphome/components/esp32_ble_client/ble_characteristic.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/esp32_ble_client/ble_characteristic.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#include "ble_descriptor.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
class BLEService;
 | 
			
		||||
 | 
			
		||||
class BLECharacteristic {
 | 
			
		||||
 public:
 | 
			
		||||
  ~BLECharacteristic();
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t handle;
 | 
			
		||||
  esp_gatt_char_prop_t properties;
 | 
			
		||||
  std::vector<BLEDescriptor *> descriptors;
 | 
			
		||||
  void parse_descriptors();
 | 
			
		||||
  BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
 | 
			
		||||
  esp_err_t write_value(uint8_t *new_val, int16_t new_val_size);
 | 
			
		||||
  esp_err_t write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
 | 
			
		||||
  BLEService *service;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										324
									
								
								esphome/components/esp32_ble_client/ble_client_base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								esphome/components/esp32_ble_client/ble_client_base.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,324 @@
 | 
			
		||||
#include "ble_client_base.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_ble_client";
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::setup() {
 | 
			
		||||
  auto ret = esp_ble_gattc_app_register(this->app_id);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
  this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::loop() {
 | 
			
		||||
  if (this->state_ == espbt::ClientState::DISCOVERED) {
 | 
			
		||||
    this->connect();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
 | 
			
		||||
 | 
			
		||||
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
 | 
			
		||||
  if (device.address_uint64() != this->address_)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (this->state_ != espbt::ClientState::IDLE)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
 | 
			
		||||
  this->set_state(espbt::ClientState::DISCOVERED);
 | 
			
		||||
 | 
			
		||||
  auto addr = device.address_uint64();
 | 
			
		||||
  this->remote_bda_[0] = (addr >> 40) & 0xFF;
 | 
			
		||||
  this->remote_bda_[1] = (addr >> 32) & 0xFF;
 | 
			
		||||
  this->remote_bda_[2] = (addr >> 24) & 0xFF;
 | 
			
		||||
  this->remote_bda_[3] = (addr >> 16) & 0xFF;
 | 
			
		||||
  this->remote_bda_[4] = (addr >> 8) & 0xFF;
 | 
			
		||||
  this->remote_bda_[5] = (addr >> 0) & 0xFF;
 | 
			
		||||
  this->remote_addr_type_ = device.get_address_type();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string BLEClientBase::address_str() const {
 | 
			
		||||
  return str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff,
 | 
			
		||||
                      (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
 | 
			
		||||
                      (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
 | 
			
		||||
                      (uint8_t)(this->address_ >> 0) & 0xff);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::connect() {
 | 
			
		||||
  ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
 | 
			
		||||
  auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
 | 
			
		||||
    this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->set_state(espbt::ClientState::CONNECTING);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
                                        esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
 | 
			
		||||
    return;
 | 
			
		||||
  if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_REG_EVT: {
 | 
			
		||||
      if (param->reg.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
 | 
			
		||||
        this->gattc_if_ = esp_gattc_if;
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
 | 
			
		||||
      this->conn_id_ = param->open.conn_id;
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
        ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
 | 
			
		||||
        this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CONNECT_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
 | 
			
		||||
      if (this->conn_id_ != param->connect.conn_id) {
 | 
			
		||||
        ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
 | 
			
		||||
                 this->address_str().c_str(), param->connect.conn_id, this->conn_id_);
 | 
			
		||||
      }
 | 
			
		||||
      auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CFG_MTU_EVT: {
 | 
			
		||||
      if (param->cfg_mtu.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
 | 
			
		||||
                 param->cfg_mtu.status);
 | 
			
		||||
        this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
 | 
			
		||||
      this->mtu_ = param->cfg_mtu.mtu;
 | 
			
		||||
      esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
 | 
			
		||||
      for (auto &svc : this->services_)
 | 
			
		||||
        delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
      this->services_.clear();
 | 
			
		||||
      this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_RES_EVT: {
 | 
			
		||||
      BLEService *ble_service = new BLEService();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
      ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
 | 
			
		||||
      ble_service->start_handle = param->search_res.start_handle;
 | 
			
		||||
      ble_service->end_handle = param->search_res.end_handle;
 | 
			
		||||
      ble_service->client = this;
 | 
			
		||||
      this->services_.push_back(ble_service);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
 | 
			
		||||
      for (auto &svc : this->services_) {
 | 
			
		||||
        ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
 | 
			
		||||
        ESP_LOGI(TAG, "  start_handle: 0x%x  end_handle: 0x%x", svc->start_handle, svc->end_handle);
 | 
			
		||||
        svc->parse_characteristics();
 | 
			
		||||
      }
 | 
			
		||||
      this->set_state(espbt::ClientState::CONNECTED);
 | 
			
		||||
      this->state_ = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
 | 
			
		||||
      if (descr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
 | 
			
		||||
          descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
 | 
			
		||||
        ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
 | 
			
		||||
                 descr->uuid.to_string().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      uint16_t notify_en = 1;
 | 
			
		||||
      auto status =
 | 
			
		||||
          esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en),
 | 
			
		||||
                                         (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    // This event is sent by the server when it requests security
 | 
			
		||||
    case ESP_GAP_BLE_SEC_REQ_EVT:
 | 
			
		||||
      ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
 | 
			
		||||
      esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
 | 
			
		||||
      break;
 | 
			
		||||
    // This event is sent once authentication has completed
 | 
			
		||||
    case ESP_GAP_BLE_AUTH_CMPL_EVT:
 | 
			
		||||
      esp_bd_addr_t bd_addr;
 | 
			
		||||
      memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
 | 
			
		||||
      ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
 | 
			
		||||
      if (!param->ble_security.auth_cmpl.success) {
 | 
			
		||||
        ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
 | 
			
		||||
                 param->ble_security.auth_cmpl.auth_mode);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    // There are other events we'll want to implement at some point to support things like pass key
 | 
			
		||||
    // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse GATT values into a float for a sensor.
 | 
			
		||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
 | 
			
		||||
float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
 | 
			
		||||
  // A length of one means a single octet value.
 | 
			
		||||
  if (length == 0)
 | 
			
		||||
    return 0;
 | 
			
		||||
  if (length == 1)
 | 
			
		||||
    return (float) ((uint8_t) value[0]);
 | 
			
		||||
 | 
			
		||||
  switch (value[0]) {
 | 
			
		||||
    case 0x1:  // boolean.
 | 
			
		||||
    case 0x2:  // 2bit.
 | 
			
		||||
    case 0x3:  // nibble.
 | 
			
		||||
    case 0x4:  // uint8.
 | 
			
		||||
      return (float) ((uint8_t) value[1]);
 | 
			
		||||
    case 0x5:  // uint12.
 | 
			
		||||
    case 0x6:  // uint16.
 | 
			
		||||
      if (length > 2) {
 | 
			
		||||
        return (float) encode_uint16(value[1], value[2]);
 | 
			
		||||
      }
 | 
			
		||||
    case 0x7:  // uint24.
 | 
			
		||||
      if (length > 3) {
 | 
			
		||||
        return (float) encode_uint24(value[1], value[2], value[3]);
 | 
			
		||||
      }
 | 
			
		||||
    case 0x8:  // uint32.
 | 
			
		||||
      if (length > 4) {
 | 
			
		||||
        return (float) encode_uint32(value[1], value[2], value[3], value[4]);
 | 
			
		||||
      }
 | 
			
		||||
    case 0xC:  // int8.
 | 
			
		||||
      return (float) ((int8_t) value[1]);
 | 
			
		||||
    case 0xD:  // int12.
 | 
			
		||||
    case 0xE:  // int16.
 | 
			
		||||
      if (length > 2) {
 | 
			
		||||
        return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
 | 
			
		||||
      }
 | 
			
		||||
    case 0xF:  // int24.
 | 
			
		||||
      if (length > 3) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
 | 
			
		||||
      }
 | 
			
		||||
    case 0x10:  // int32.
 | 
			
		||||
      if (length > 4) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
 | 
			
		||||
                        (int32_t)(value[4]));
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
 | 
			
		||||
  return NAN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  for (auto *svc : this->services_) {
 | 
			
		||||
    if (svc->uuid == uuid)
 | 
			
		||||
      return svc;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
 | 
			
		||||
  auto *svc = this->get_service(service);
 | 
			
		||||
  if (svc == nullptr)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  return svc->get_characteristic(chr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) {
 | 
			
		||||
  return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) {
 | 
			
		||||
  for (auto *svc : this->services_) {
 | 
			
		||||
    for (auto *chr : svc->characteristics) {
 | 
			
		||||
      if (chr->handle == handle)
 | 
			
		||||
        return chr;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) {
 | 
			
		||||
  auto *chr = this->get_characteristic(handle);
 | 
			
		||||
  if (chr != nullptr) {
 | 
			
		||||
    for (auto &desc : chr->descriptors) {
 | 
			
		||||
      if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
 | 
			
		||||
        return desc;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
 | 
			
		||||
  auto *svc = this->get_service(service);
 | 
			
		||||
  if (svc == nullptr)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  auto *ch = svc->get_characteristic(chr);
 | 
			
		||||
  if (ch == nullptr)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  return ch->get_descriptor(descr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
 | 
			
		||||
  return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
 | 
			
		||||
                              espbt::ESPBTUUID::from_uint16(descr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
 | 
			
		||||
  for (auto *svc : this->services_) {
 | 
			
		||||
    for (auto *chr : svc->characteristics) {
 | 
			
		||||
      for (auto *desc : chr->descriptors) {
 | 
			
		||||
        if (desc->handle == handle)
 | 
			
		||||
          return desc;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										72
									
								
								esphome/components/esp32_ble_client/ble_client_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								esphome/components/esp32_ble_client/ble_client_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
#include "ble_service.h"
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_gatt_common_api.h>
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  bool parse_device(const espbt::ESPBTDevice &device) override;
 | 
			
		||||
  void on_scan_end() override {}
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
  void connect() override;
 | 
			
		||||
 | 
			
		||||
  void set_address(uint64_t address) { this->address_ = address; }
 | 
			
		||||
  std::string address_str() const;
 | 
			
		||||
 | 
			
		||||
  BLEService *get_service(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEService *get_service(uint16_t uuid);
 | 
			
		||||
  BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
 | 
			
		||||
  BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
 | 
			
		||||
  BLECharacteristic *get_characteristic(uint16_t handle);
 | 
			
		||||
  BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t handle);
 | 
			
		||||
  // Get the configuration descriptor for the given characteristic handle.
 | 
			
		||||
  BLEDescriptor *get_config_descriptor(uint16_t handle);
 | 
			
		||||
 | 
			
		||||
  float parse_char_value(uint8_t *value, uint16_t length);
 | 
			
		||||
 | 
			
		||||
  int get_gattc_if() const { return this->gattc_if_; }
 | 
			
		||||
  uint8_t *get_remote_bda() { return this->remote_bda_; }
 | 
			
		||||
  esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; }
 | 
			
		||||
  uint16_t get_conn_id() const { return this->conn_id_; }
 | 
			
		||||
  uint64_t get_address() const { return this->address_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int gattc_if_;
 | 
			
		||||
  esp_bd_addr_t remote_bda_;
 | 
			
		||||
  esp_ble_addr_type_t remote_addr_type_;
 | 
			
		||||
  uint16_t conn_id_;
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
  uint16_t mtu_{23};
 | 
			
		||||
 | 
			
		||||
  std::vector<BLEService *> services_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										25
									
								
								esphome/components/esp32_ble_client/ble_descriptor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/esp32_ble_client/ble_descriptor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
class BLECharacteristic;
 | 
			
		||||
 | 
			
		||||
class BLEDescriptor {
 | 
			
		||||
 public:
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t handle;
 | 
			
		||||
 | 
			
		||||
  BLECharacteristic *characteristic;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										66
									
								
								esphome/components/esp32_ble_client/ble_service.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/esp32_ble_client/ble_service.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
#include "ble_service.h"
 | 
			
		||||
#include "ble_client_base.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_ble_client.service";
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  for (auto &chr : this->characteristics) {
 | 
			
		||||
    if (chr->uuid == uuid)
 | 
			
		||||
      return chr;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
 | 
			
		||||
  return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEService::~BLEService() {
 | 
			
		||||
  for (auto &chr : this->characteristics)
 | 
			
		||||
    delete chr;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEService::parse_characteristics() {
 | 
			
		||||
  uint16_t offset = 0;
 | 
			
		||||
  esp_gattc_char_elem_t result;
 | 
			
		||||
 | 
			
		||||
  while (true) {
 | 
			
		||||
    uint16_t count = 1;
 | 
			
		||||
    esp_gatt_status_t status =
 | 
			
		||||
        esp_ble_gattc_get_all_char(this->client->get_gattc_if(), this->client->get_conn_id(), this->start_handle,
 | 
			
		||||
                                   this->end_handle, &result, &count, offset);
 | 
			
		||||
    if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (status != ESP_GATT_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (count == 0) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BLECharacteristic *characteristic = new BLECharacteristic();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
    characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
 | 
			
		||||
    characteristic->properties = result.properties;
 | 
			
		||||
    characteristic->handle = result.char_handle;
 | 
			
		||||
    characteristic->service = this;
 | 
			
		||||
    this->characteristics.push_back(characteristic);
 | 
			
		||||
    ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
 | 
			
		||||
             characteristic->handle, characteristic->properties);
 | 
			
		||||
    characteristic->parse_descriptors();
 | 
			
		||||
    offset++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										32
									
								
								esphome/components/esp32_ble_client/ble_service.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphome/components/esp32_ble_client/ble_service.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
class BLEClientBase;
 | 
			
		||||
 | 
			
		||||
class BLEService {
 | 
			
		||||
 public:
 | 
			
		||||
  ~BLEService();
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t start_handle;
 | 
			
		||||
  uint16_t end_handle;
 | 
			
		||||
  std::vector<BLECharacteristic *> characteristics;
 | 
			
		||||
  BLEClientBase *client;
 | 
			
		||||
  void parse_characteristics();
 | 
			
		||||
  BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLECharacteristic *get_characteristic(uint16_t uuid);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -3,6 +3,7 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ACTIVE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INTERVAL,
 | 
			
		||||
    CONF_DURATION,
 | 
			
		||||
@@ -22,7 +23,6 @@ DEPENDENCIES = ["esp32"]
 | 
			
		||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
 | 
			
		||||
CONF_SCAN_PARAMETERS = "scan_parameters"
 | 
			
		||||
CONF_WINDOW = "window"
 | 
			
		||||
CONF_ACTIVE = "active"
 | 
			
		||||
CONF_CONTINUOUS = "continuous"
 | 
			
		||||
CONF_ON_SCAN_END = "on_scan_end"
 | 
			
		||||
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
 | 
			
		||||
 
 | 
			
		||||
@@ -518,28 +518,39 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
 | 
			
		||||
}
 | 
			
		||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
 | 
			
		||||
std::string ESPBTUUID::to_string() const {
 | 
			
		||||
  char sbuf[64];
 | 
			
		||||
  switch (this->uuid_.len) {
 | 
			
		||||
    case ESP_UUID_LEN_16:
 | 
			
		||||
      sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
 | 
			
		||||
      break;
 | 
			
		||||
      return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
 | 
			
		||||
    case ESP_UUID_LEN_32:
 | 
			
		||||
      sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff),
 | 
			
		||||
              (this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff);
 | 
			
		||||
      break;
 | 
			
		||||
      return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24,
 | 
			
		||||
                          (this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
 | 
			
		||||
                          this->uuid_.uuid.uuid32 & 0xff);
 | 
			
		||||
    default:
 | 
			
		||||
    case ESP_UUID_LEN_128:
 | 
			
		||||
      char *bpos = sbuf;
 | 
			
		||||
      std::string buf;
 | 
			
		||||
      for (int8_t i = 15; i >= 0; i--) {
 | 
			
		||||
        sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]);
 | 
			
		||||
        bpos += 2;
 | 
			
		||||
        buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
 | 
			
		||||
        if (i == 6 || i == 8 || i == 10 || i == 12)
 | 
			
		||||
          sprintf(bpos++, "-");
 | 
			
		||||
          buf += "-";
 | 
			
		||||
      }
 | 
			
		||||
      sbuf[47] = '\0';
 | 
			
		||||
      break;
 | 
			
		||||
      return buf;
 | 
			
		||||
  }
 | 
			
		||||
  return sbuf;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t ESPBTUUID::get_128bit_high() const {
 | 
			
		||||
  esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
 | 
			
		||||
  return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
 | 
			
		||||
         ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
 | 
			
		||||
         ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
 | 
			
		||||
         ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
 | 
			
		||||
}
 | 
			
		||||
uint64_t ESPBTUUID::get_128bit_low() const {
 | 
			
		||||
  esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
 | 
			
		||||
  return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
 | 
			
		||||
         ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
 | 
			
		||||
         ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
 | 
			
		||||
         ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
 | 
			
		||||
@@ -637,11 +648,17 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p
 | 
			
		||||
    // (called CSS here)
 | 
			
		||||
 | 
			
		||||
    switch (record_type) {
 | 
			
		||||
      case ESP_BLE_AD_TYPE_NAME_SHORT:
 | 
			
		||||
      case ESP_BLE_AD_TYPE_NAME_CMPL: {
 | 
			
		||||
        // CSS 1.2 LOCAL NAME
 | 
			
		||||
        // "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the
 | 
			
		||||
        // device." CSS 1: Optional in this context; shall not appear more than once in a block.
 | 
			
		||||
        this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
 | 
			
		||||
        // SHORTENED LOCAL NAME
 | 
			
		||||
        // "The Shortened Local Name data type defines a shortened version of the Local Name data type. The Shortened
 | 
			
		||||
        // Local Name data type shall not be used to advertise a name that is longer than the Local Name data type."
 | 
			
		||||
        if (record_length > this->name_.length()) {
 | 
			
		||||
          this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case ESP_BLE_AD_TYPE_TX_PWR: {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,9 @@ class ESPBTUUID {
 | 
			
		||||
 | 
			
		||||
  std::string to_string() const;
 | 
			
		||||
 | 
			
		||||
  uint64_t get_128bit_high() const;
 | 
			
		||||
  uint64_t get_128bit_low() const;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  esp_bt_uuid_t uuid_;
 | 
			
		||||
};
 | 
			
		||||
@@ -142,6 +145,8 @@ class ESPBTDeviceListener {
 | 
			
		||||
enum class ClientState {
 | 
			
		||||
  // Connection is idle, no device detected.
 | 
			
		||||
  IDLE,
 | 
			
		||||
  // Searching for device.
 | 
			
		||||
  SEARCHING,
 | 
			
		||||
  // Device advertisement found.
 | 
			
		||||
  DISCOVERED,
 | 
			
		||||
  // Connection in progress.
 | 
			
		||||
@@ -158,7 +163,7 @@ class ESPBTClient : public ESPBTDeviceListener {
 | 
			
		||||
                                   esp_ble_gattc_cb_param_t *param) = 0;
 | 
			
		||||
  virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
 | 
			
		||||
  virtual void connect() = 0;
 | 
			
		||||
  void set_state(ClientState st) { this->state_ = st; }
 | 
			
		||||
  virtual void set_state(ClientState st) { this->state_ = st; }
 | 
			
		||||
  ClientState state() const { return state_; }
 | 
			
		||||
  int app_id;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -72,13 +72,13 @@ class BLEEvent {
 | 
			
		||||
    // Need to also make a copy of relevant event data.
 | 
			
		||||
    switch (e) {
 | 
			
		||||
      case ESP_GATTC_NOTIFY_EVT:
 | 
			
		||||
        memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
 | 
			
		||||
        this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data;
 | 
			
		||||
        this->data.assign(p->notify.value, p->notify.value + p->notify.value_len);
 | 
			
		||||
        this->event_.gattc.gattc_param.notify.value = this->data.data();
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_GATTC_READ_CHAR_EVT:
 | 
			
		||||
      case ESP_GATTC_READ_DESCR_EVT:
 | 
			
		||||
        memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
 | 
			
		||||
        this->event_.gattc.gattc_param.read.value = this->event_.gattc.data;
 | 
			
		||||
        this->data.assign(p->read.value, p->read.value + p->read.value_len);
 | 
			
		||||
        this->event_.gattc.gattc_param.read.value = this->data.data();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
@@ -96,9 +96,9 @@ class BLEEvent {
 | 
			
		||||
      esp_gattc_cb_event_t gattc_event;
 | 
			
		||||
      esp_gatt_if_t gattc_if;
 | 
			
		||||
      esp_ble_gattc_cb_param_t gattc_param;
 | 
			
		||||
      uint8_t data[64];
 | 
			
		||||
    } gattc;
 | 
			
		||||
  } event_;
 | 
			
		||||
  std::vector<uint8_t> data{};
 | 
			
		||||
  uint8_t type_;  // 0=gap 1=gattc
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import logging
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
@@ -200,7 +199,7 @@ async def esp8266_pin_to_code(config):
 | 
			
		||||
@coroutine_with_priority(-999.0)
 | 
			
		||||
async def add_pin_initial_states_array():
 | 
			
		||||
    # Add includes at the very end, so that they override everything
 | 
			
		||||
    initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][
 | 
			
		||||
    initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
 | 
			
		||||
        KEY_PIN_INITIAL_STATES
 | 
			
		||||
    ]
 | 
			
		||||
    initial_modes_s = ", ".join(str(x.mode) for x in initial_states)
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,7 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_git_config(config: dict, refresh) -> str:
 | 
			
		||||
    repo_dir = git.clone_or_update(
 | 
			
		||||
    repo_dir, _ = git.clone_or_update(
 | 
			
		||||
        url=config[CONF_URL],
 | 
			
		||||
        ref=config.get(CONF_REF),
 | 
			
		||||
        refresh=refresh,
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ PROTOCOLS = {
 | 
			
		||||
    "sharp": Protocol.PROTOCOL_SHARP,
 | 
			
		||||
    "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
 | 
			
		||||
    "toshiba": Protocol.PROTOCOL_TOSHIBA,
 | 
			
		||||
    "zhlt01": Protocol.PROTOCOL_ZHLT01,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
 | 
			
		||||
    {PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }},                                // NOLINT
 | 
			
		||||
    {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }},         // NOLINT
 | 
			
		||||
    {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }},                              // NOLINT
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void HeatpumpIRClimate::setup() {
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ enum Protocol {
 | 
			
		||||
  PROTOCOL_SHARP,
 | 
			
		||||
  PROTOCOL_TOSHIBA_DAISEIKAI,
 | 
			
		||||
  PROTOCOL_TOSHIBA,
 | 
			
		||||
  PROTOCOL_ZHLT01,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Simple enum to represent horizontal directios
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,13 @@ void MCP23S17::setup() {
 | 
			
		||||
  this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing
 | 
			
		||||
  this->disable();
 | 
			
		||||
 | 
			
		||||
  this->enable();
 | 
			
		||||
  cmd = 0b01001000;
 | 
			
		||||
  this->transfer_byte(cmd);
 | 
			
		||||
  this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA);
 | 
			
		||||
  this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing
 | 
			
		||||
  this->disable();
 | 
			
		||||
 | 
			
		||||
  // Read current output register state
 | 
			
		||||
  this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_);
 | 
			
		||||
  this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_);
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,12 @@ void MDNSComponent::compile_records_() {
 | 
			
		||||
 | 
			
		||||
    service.txt_records.push_back({"board", ESPHOME_BOARD});
 | 
			
		||||
 | 
			
		||||
#if defined(USE_WIFI)
 | 
			
		||||
    service.txt_records.push_back({"network", "wifi"});
 | 
			
		||||
#elif defined(USE_ETHERNET)
 | 
			
		||||
    service.txt_records.push_back({"network", "ethernet"});
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_PROJECT_NAME
 | 
			
		||||
    service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME});
 | 
			
		||||
    service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION});
 | 
			
		||||
 
 | 
			
		||||
@@ -571,24 +571,16 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
 | 
			
		||||
          static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
 | 
			
		||||
    } break;
 | 
			
		||||
    case SensorValueType::U_QWORD:
 | 
			
		||||
      // Ignore bitmask for U_QWORD
 | 
			
		||||
      value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_QWORD:
 | 
			
		||||
      // Ignore bitmask for S_QWORD
 | 
			
		||||
      value = get_data<int64_t>(data, offset);
 | 
			
		||||
      // Ignore bitmask for QWORD
 | 
			
		||||
      value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::U_QWORD_R:
 | 
			
		||||
      // Ignore bitmask for U_QWORD
 | 
			
		||||
      value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      value = static_cast<uint64_t>(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 |
 | 
			
		||||
              static_cast<uint64_t>(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 |
 | 
			
		||||
              static_cast<uint64_t>(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16;
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_QWORD_R:
 | 
			
		||||
      // Ignore bitmask for S_QWORD
 | 
			
		||||
      value = get_data<int64_t>(data, offset);
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_QWORD_R: {
 | 
			
		||||
      // Ignore bitmask for QWORD
 | 
			
		||||
      uint64_t tmp = get_data<uint64_t>(data, offset);
 | 
			
		||||
      value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
 | 
			
		||||
    } break;
 | 
			
		||||
    case SensorValueType::RAW:
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
from typing import Any, List
 | 
			
		||||
from typing import Any
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
@@ -349,7 +349,7 @@ def _spi_extra_validate(config):
 | 
			
		||||
class MethodDescriptor:
 | 
			
		||||
    method_schema: Any
 | 
			
		||||
    to_code: Any
 | 
			
		||||
    supported_chips: List[str]
 | 
			
		||||
    supported_chips: list[str]
 | 
			
		||||
    extra_validate: Any = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,17 @@ from esphome.config_helpers import merge_config
 | 
			
		||||
 | 
			
		||||
from esphome import git, yaml_util
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_FILE,
 | 
			
		||||
    CONF_FILES,
 | 
			
		||||
    CONF_MIN_VERSION,
 | 
			
		||||
    CONF_PACKAGES,
 | 
			
		||||
    CONF_REF,
 | 
			
		||||
    CONF_REFRESH,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    CONF_USERNAME,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    __version__ as ESPHOME_VERSION,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
@@ -104,7 +107,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_base_package(config: dict) -> dict:
 | 
			
		||||
    repo_dir = git.clone_or_update(
 | 
			
		||||
    repo_dir, revert = git.clone_or_update(
 | 
			
		||||
        url=config[CONF_URL],
 | 
			
		||||
        ref=config.get(CONF_REF),
 | 
			
		||||
        refresh=config[CONF_REFRESH],
 | 
			
		||||
@@ -112,21 +115,51 @@ def _process_base_package(config: dict) -> dict:
 | 
			
		||||
        username=config.get(CONF_USERNAME),
 | 
			
		||||
        password=config.get(CONF_PASSWORD),
 | 
			
		||||
    )
 | 
			
		||||
    files: str = config[CONF_FILES]
 | 
			
		||||
    files: list[str] = config[CONF_FILES]
 | 
			
		||||
 | 
			
		||||
    def get_packages(files) -> dict:
 | 
			
		||||
        packages = {}
 | 
			
		||||
        for file in files:
 | 
			
		||||
            yaml_file: Path = repo_dir / file
 | 
			
		||||
 | 
			
		||||
            if not yaml_file.is_file():
 | 
			
		||||
                raise cv.Invalid(
 | 
			
		||||
                    f"{file} does not exist in repository", path=[CONF_FILES]
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                new_yaml = yaml_util.load_yaml(yaml_file)
 | 
			
		||||
                if (
 | 
			
		||||
                    CONF_ESPHOME in new_yaml
 | 
			
		||||
                    and CONF_MIN_VERSION in new_yaml[CONF_ESPHOME]
 | 
			
		||||
                ):
 | 
			
		||||
                    min_version = new_yaml[CONF_ESPHOME][CONF_MIN_VERSION]
 | 
			
		||||
                    if cv.Version.parse(min_version) > cv.Version.parse(
 | 
			
		||||
                        ESPHOME_VERSION
 | 
			
		||||
                    ):
 | 
			
		||||
                        raise cv.Invalid(
 | 
			
		||||
                            f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}"
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                packages[file] = new_yaml
 | 
			
		||||
            except EsphomeError as e:
 | 
			
		||||
                raise cv.Invalid(
 | 
			
		||||
                    f"{file} is not a valid YAML file. Please check the file contents."
 | 
			
		||||
                ) from e
 | 
			
		||||
        return packages
 | 
			
		||||
 | 
			
		||||
    packages = {}
 | 
			
		||||
    for file in files:
 | 
			
		||||
        yaml_file: Path = repo_dir / file
 | 
			
		||||
 | 
			
		||||
        if not yaml_file.is_file():
 | 
			
		||||
            raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES])
 | 
			
		||||
    try:
 | 
			
		||||
        packages = get_packages(files)
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        if revert is not None:
 | 
			
		||||
            revert()
 | 
			
		||||
            packages = get_packages(files)
 | 
			
		||||
    finally:
 | 
			
		||||
        if packages is None:
 | 
			
		||||
            raise cv.Invalid("Failed to load packages")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            packages[file] = yaml_util.load_yaml(yaml_file)
 | 
			
		||||
        except EsphomeError as e:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"{file} is not a valid YAML file. Please check the file contents."
 | 
			
		||||
            ) from e
 | 
			
		||||
    return {"packages": packages}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,62 +11,43 @@ void PulseMeterSensor::setup() {
 | 
			
		||||
  this->isr_pin_ = pin_->to_isr();
 | 
			
		||||
  this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
 | 
			
		||||
 | 
			
		||||
  this->pulse_width_us_ = 0;
 | 
			
		||||
  this->last_detected_edge_us_ = 0;
 | 
			
		||||
  this->last_valid_low_edge_us_ = 0;
 | 
			
		||||
  this->last_valid_high_edge_us_ = 0;
 | 
			
		||||
  this->last_valid_low_edge_us_ = 0;
 | 
			
		||||
  this->sensor_is_high_ = this->isr_pin_.digital_read();
 | 
			
		||||
  this->has_valid_high_edge_ = false;
 | 
			
		||||
  this->has_valid_low_edge_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PulseMeterSensor::loop() {
 | 
			
		||||
  // Get a local copy of the volatile sensor values, to make sure they are not
 | 
			
		||||
  // modified by the ISR. This could cause overflow in the following arithmetic
 | 
			
		||||
  const uint32_t last_valid_high_edge_us = this->last_valid_high_edge_us_;
 | 
			
		||||
  const bool has_valid_high_edge = this->has_valid_high_edge_;
 | 
			
		||||
  const uint32_t now = micros();
 | 
			
		||||
 | 
			
		||||
  // Check to see if we should filter this edge out
 | 
			
		||||
  if (this->filter_mode_ == FILTER_EDGE) {
 | 
			
		||||
    if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) {
 | 
			
		||||
      // Don't measure the first valid pulse (we need at least two pulses to measure the width)
 | 
			
		||||
      if (this->last_valid_high_edge_us_ != 0) {
 | 
			
		||||
        this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
 | 
			
		||||
      }
 | 
			
		||||
      this->total_pulses_++;
 | 
			
		||||
      this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // Make sure the signal has been stable long enough
 | 
			
		||||
    if ((now - this->last_detected_edge_us_) >= this->filter_us_) {
 | 
			
		||||
      // Only consider HIGH pulses and "new" edges if sensor state is LOW
 | 
			
		||||
      if (!this->sensor_is_high_ && this->isr_pin_.digital_read() &&
 | 
			
		||||
          (this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) {
 | 
			
		||||
        // Don't measure the first valid pulse (we need at least two pulses to measure the width)
 | 
			
		||||
        if (this->last_valid_high_edge_us_ != 0) {
 | 
			
		||||
          this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
 | 
			
		||||
        }
 | 
			
		||||
        this->sensor_is_high_ = true;
 | 
			
		||||
        this->total_pulses_++;
 | 
			
		||||
        this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
 | 
			
		||||
      }
 | 
			
		||||
      // Only consider LOW pulses and "new" edges if sensor state is HIGH
 | 
			
		||||
      else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() &&
 | 
			
		||||
               (this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) {
 | 
			
		||||
        this->sensor_is_high_ = false;
 | 
			
		||||
        this->last_valid_low_edge_us_ = this->last_detected_edge_us_;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until
 | 
			
		||||
  // we get at least two valid pulses.
 | 
			
		||||
  const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_;
 | 
			
		||||
  if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) &&
 | 
			
		||||
      (this->pulse_width_us_ != 0)) {
 | 
			
		||||
  // If we've exceeded our timeout interval without receiving any pulses, assume
 | 
			
		||||
  // 0 pulses/min until we get at least two valid pulses.
 | 
			
		||||
  const uint32_t time_since_valid_edge_us = now - last_valid_high_edge_us;
 | 
			
		||||
  if ((has_valid_high_edge) && (time_since_valid_edge_us > this->timeout_us_)) {
 | 
			
		||||
    ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
 | 
			
		||||
 | 
			
		||||
    this->pulse_width_us_ = 0;
 | 
			
		||||
    this->last_detected_edge_us_ = 0;
 | 
			
		||||
    this->last_valid_high_edge_us_ = 0;
 | 
			
		||||
    this->last_valid_low_edge_us_ = 0;
 | 
			
		||||
    this->has_detected_edge_ = false;
 | 
			
		||||
    this->has_valid_high_edge_ = false;
 | 
			
		||||
    this->has_valid_low_edge_ = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // We quantize our pulse widths to 1 ms to avoid unnecessary jitter
 | 
			
		||||
  const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000;
 | 
			
		||||
  if (this->pulse_width_dedupe_.next(pulse_width_ms)) {
 | 
			
		||||
    if (pulse_width_ms == 0) {
 | 
			
		||||
      // Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while)
 | 
			
		||||
      // Treat 0 pulse width as 0 pulses/min (normally because we've not
 | 
			
		||||
      // detected any pulses for a while)
 | 
			
		||||
      this->publish_state(0);
 | 
			
		||||
    } else {
 | 
			
		||||
      // Calculate pulses/min from the pulse width in ms
 | 
			
		||||
@@ -96,9 +77,11 @@ void PulseMeterSensor::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
 | 
			
		||||
  // This is an interrupt handler - we can't call any virtual method from this method
 | 
			
		||||
  // This is an interrupt handler - we can't call any virtual method from this
 | 
			
		||||
  // method
 | 
			
		||||
 | 
			
		||||
  // Get the current time before we do anything else so the measurements are consistent
 | 
			
		||||
  // Get the current time before we do anything else so the measurements are
 | 
			
		||||
  // consistent
 | 
			
		||||
  const uint32_t now = micros();
 | 
			
		||||
 | 
			
		||||
  // We only look at rising edges in EDGE mode, and all edges in PULSE mode
 | 
			
		||||
@@ -106,7 +89,45 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
 | 
			
		||||
    if (sensor->isr_pin_.digital_read()) {
 | 
			
		||||
      sensor->last_detected_edge_us_ = now;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Check to see if we should filter this edge out
 | 
			
		||||
  if (sensor->filter_mode_ == FILTER_EDGE) {
 | 
			
		||||
    if ((sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_) >= sensor->filter_us_) {
 | 
			
		||||
      // Don't measure the first valid pulse (we need at least two pulses to
 | 
			
		||||
      // measure the width)
 | 
			
		||||
      if (sensor->has_valid_high_edge_) {
 | 
			
		||||
        sensor->pulse_width_us_ = (sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_);
 | 
			
		||||
      }
 | 
			
		||||
      sensor->total_pulses_++;
 | 
			
		||||
      sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
 | 
			
		||||
      sensor->has_valid_high_edge_ = true;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // Filter Mode is PULSE
 | 
			
		||||
    bool pin_val = sensor->isr_pin_.digital_read();
 | 
			
		||||
    // Ignore false edges that may be caused by bouncing and exit the ISR ASAP
 | 
			
		||||
    if (pin_val == sensor->sensor_is_high_) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // Make sure the signal has been stable long enough
 | 
			
		||||
    if (sensor->has_detected_edge_ && (now - sensor->last_detected_edge_us_ >= sensor->filter_us_)) {
 | 
			
		||||
      if (pin_val) {
 | 
			
		||||
        sensor->has_valid_high_edge_ = true;
 | 
			
		||||
        sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
 | 
			
		||||
        sensor->sensor_is_high_ = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        // Count pulses when a sufficiently long high pulse is concluded.
 | 
			
		||||
        sensor->total_pulses_++;
 | 
			
		||||
        if (sensor->has_valid_low_edge_) {
 | 
			
		||||
          sensor->pulse_width_us_ = sensor->last_detected_edge_us_ - sensor->last_valid_low_edge_us_;
 | 
			
		||||
        }
 | 
			
		||||
        sensor->has_valid_low_edge_ = true;
 | 
			
		||||
        sensor->last_valid_low_edge_us_ = sensor->last_detected_edge_us_;
 | 
			
		||||
        sensor->sensor_is_high_ = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    sensor->has_detected_edge_ = true;
 | 
			
		||||
    sensor->last_detected_edge_us_ = now;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -42,11 +42,14 @@ class PulseMeterSensor : public sensor::Sensor, public Component {
 | 
			
		||||
  Deduplicator<uint32_t> total_dedupe_;
 | 
			
		||||
 | 
			
		||||
  volatile uint32_t last_detected_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t last_valid_low_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t last_valid_high_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t last_valid_low_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t pulse_width_us_ = 0;
 | 
			
		||||
  volatile uint32_t total_pulses_ = 0;
 | 
			
		||||
  volatile bool sensor_is_high_ = false;
 | 
			
		||||
  volatile bool has_detected_edge_ = false;
 | 
			
		||||
  volatile bool has_valid_high_edge_ = false;
 | 
			
		||||
  volatile bool has_valid_low_edge_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace pulse_meter
 | 
			
		||||
 
 | 
			
		||||
@@ -102,8 +102,9 @@ void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, size, blk,
 | 
			
		||||
                                         ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status =
 | 
			
		||||
      esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_, size,
 | 
			
		||||
                               blk, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
  } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->parent()->conn_id)
 | 
			
		||||
      if (param->read.conn_id != this->parent()->get_conn_id())
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
@@ -146,17 +146,17 @@ void RadonEyeRD200::update() {
 | 
			
		||||
void RadonEyeRD200::write_query_message_() {
 | 
			
		||||
  ESP_LOGV(TAG, "writing 0x50 to write service");
 | 
			
		||||
  int request = 0x50;
 | 
			
		||||
  auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_,
 | 
			
		||||
                                               sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP,
 | 
			
		||||
                                               ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
 | 
			
		||||
                                               this->write_handle_, sizeof(request), (uint8_t *) &request,
 | 
			
		||||
                                               ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RadonEyeRD200::request_read_values_() {
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_,
 | 
			
		||||
                                        ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
 | 
			
		||||
                                        this->read_handle_, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
from typing import List
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
@@ -60,7 +59,7 @@ SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_select_core_(var, config, *, options: List[str]):
 | 
			
		||||
async def setup_select_core_(var, config, *, options: list[str]):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.traits.set_options(options))
 | 
			
		||||
@@ -76,14 +75,14 @@ async def setup_select_core_(var, config, *, options: List[str]):
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def register_select(var, config, *, options: List[str]):
 | 
			
		||||
async def register_select(var, config, *, options: list[str]):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.Pvariable(config[CONF_ID], var)
 | 
			
		||||
    cg.add(cg.App.register_select(var))
 | 
			
		||||
    await setup_select_core_(var, config, options=options)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_select(config, *, options: List[str]):
 | 
			
		||||
async def new_select(config, *, options: list[str]):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await register_select(var, config, options=options)
 | 
			
		||||
    return var
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_WINDOW_SIZE,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_FORCE_UPDATE,
 | 
			
		||||
    DEVICE_CLASS_DISTANCE,
 | 
			
		||||
    DEVICE_CLASS_DURATION,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_APPARENT_POWER,
 | 
			
		||||
@@ -43,6 +44,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_GAS,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
    DEVICE_CLASS_MOISTURE,
 | 
			
		||||
    DEVICE_CLASS_MONETARY,
 | 
			
		||||
    DEVICE_CLASS_NITROGEN_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_NITROGEN_MONOXIDE,
 | 
			
		||||
@@ -53,14 +55,20 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_PM25,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
    DEVICE_CLASS_PRECIPITATION_INTENSITY,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_REACTIVE_POWER,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
    DEVICE_CLASS_SPEED,
 | 
			
		||||
    DEVICE_CLASS_SULPHUR_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_TIMESTAMP,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_VOLUME,
 | 
			
		||||
    DEVICE_CLASS_WATER,
 | 
			
		||||
    DEVICE_CLASS_WIND_SPEED,
 | 
			
		||||
    DEVICE_CLASS_WEIGHT,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
@@ -77,12 +85,14 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_CARBON_MONOXIDE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_DATE,
 | 
			
		||||
    DEVICE_CLASS_DISTANCE,
 | 
			
		||||
    DEVICE_CLASS_DURATION,
 | 
			
		||||
    DEVICE_CLASS_ENERGY,
 | 
			
		||||
    DEVICE_CLASS_FREQUENCY,
 | 
			
		||||
    DEVICE_CLASS_GAS,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
    DEVICE_CLASS_MOISTURE,
 | 
			
		||||
    DEVICE_CLASS_MONETARY,
 | 
			
		||||
    DEVICE_CLASS_NITROGEN_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_NITROGEN_MONOXIDE,
 | 
			
		||||
@@ -93,14 +103,20 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_PM25,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
    DEVICE_CLASS_PRECIPITATION_INTENSITY,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_REACTIVE_POWER,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
    DEVICE_CLASS_SPEED,
 | 
			
		||||
    DEVICE_CLASS_SULPHUR_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_TIMESTAMP,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_VOLUME,
 | 
			
		||||
    DEVICE_CLASS_WATER,
 | 
			
		||||
    DEVICE_CLASS_WIND_SPEED,
 | 
			
		||||
    DEVICE_CLASS_WEIGHT,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
sensor_ns = cg.esphome_ns.namespace("sensor")
 | 
			
		||||
 
 | 
			
		||||
@@ -195,11 +195,6 @@ class SPIComponent : public Component {
 | 
			
		||||
 | 
			
		||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, uint32_t DATA_RATE>
 | 
			
		||||
  void enable(GPIOPin *cs) {
 | 
			
		||||
    if (cs != nullptr) {
 | 
			
		||||
      this->active_cs_ = cs;
 | 
			
		||||
      this->active_cs_->digital_write(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
    if (this->hw_spi_ != nullptr) {
 | 
			
		||||
      uint8_t data_mode = SPI_MODE0;
 | 
			
		||||
@@ -220,6 +215,11 @@ class SPIComponent : public Component {
 | 
			
		||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
    }
 | 
			
		||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
 | 
			
		||||
    if (cs != nullptr) {
 | 
			
		||||
      this->active_cs_ = cs;
 | 
			
		||||
      this->active_cs_->digital_write(false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void disable();
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,8 @@ void SSD1327::setup() {
 | 
			
		||||
  this->command(0x55);
 | 
			
		||||
  this->command(SSD1327_SETVCOMHVOLTAGE);  // Set High Voltage Level of COM Pin
 | 
			
		||||
  this->command(0x1C);
 | 
			
		||||
  this->command(SSD1327_SETGPIO);  // Switch voltage converter on (for Aliexpress display)
 | 
			
		||||
  this->command(0x03);
 | 
			
		||||
  this->command(SSD1327_NORMALDISPLAY);  // set display mode
 | 
			
		||||
  set_brightness(this->brightness_);
 | 
			
		||||
  this->fill(Color::BLACK);  // clear display - ensures we do not see garbage at power-on
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ from esphome import pins
 | 
			
		||||
from esphome.components import display, spi
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BACKLIGHT_PIN,
 | 
			
		||||
    CONF_CS_PIN,
 | 
			
		||||
    CONF_DC_PIN,
 | 
			
		||||
    CONF_HEIGHT,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
@@ -69,7 +68,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Required(CONF_MODEL): ST7789V_MODEL,
 | 
			
		||||
            cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_HEIGHT): cv.int_,
 | 
			
		||||
@@ -79,7 +77,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("5s"))
 | 
			
		||||
    .extend(spi.spi_device_schema()),
 | 
			
		||||
    .extend(spi.spi_device_schema(cs_pin_required=False)),
 | 
			
		||||
    validate_st7789v,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,8 @@ from esphome.const import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_PRESET_CHANGE = "preset_change"
 | 
			
		||||
CONF_DEFAULT_PRESET = "default_preset"
 | 
			
		||||
CONF_ON_BOOT_RESTORE_FROM = "on_boot_restore_from"
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@kbx81"]
 | 
			
		||||
 | 
			
		||||
@@ -80,6 +82,13 @@ ThermostatClimate = thermostat_ns.class_(
 | 
			
		||||
ThermostatClimateTargetTempConfig = thermostat_ns.struct(
 | 
			
		||||
    "ThermostatClimateTargetTempConfig"
 | 
			
		||||
)
 | 
			
		||||
OnBootRestoreFrom = thermostat_ns.enum("OnBootRestoreFrom")
 | 
			
		||||
ON_BOOT_RESTORE_FROM = {
 | 
			
		||||
    "MEMORY": OnBootRestoreFrom.MEMORY,
 | 
			
		||||
    "DEFAULT_PRESET": OnBootRestoreFrom.DEFAULT_PRESET,
 | 
			
		||||
}
 | 
			
		||||
validate_on_boot_restore_from = cv.enum(ON_BOOT_RESTORE_FROM, upper=True)
 | 
			
		||||
 | 
			
		||||
ClimateMode = climate_ns.enum("ClimateMode")
 | 
			
		||||
CLIMATE_MODES = {
 | 
			
		||||
    "OFF": ClimateMode.CLIMATE_MODE_OFF,
 | 
			
		||||
@@ -125,6 +134,17 @@ def validate_temperature_preset(preset, root_config, name, requirements):
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_comparable_preset(config, name):
 | 
			
		||||
    comparable_preset = f"{CONF_PRESET}:\n" f"  -  {CONF_NAME}: {name}\n"
 | 
			
		||||
 | 
			
		||||
    if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
 | 
			
		||||
        comparable_preset += f"     {CONF_DEFAULT_TARGET_TEMPERATURE_LOW}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]}\n"
 | 
			
		||||
    if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
 | 
			
		||||
        comparable_preset += f"     {CONF_DEFAULT_TARGET_TEMPERATURE_HIGH}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]}\n"
 | 
			
		||||
 | 
			
		||||
    return comparable_preset
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_thermostat(config):
 | 
			
		||||
    # verify corresponding action(s) exist(s) for any defined climate mode or action
 | 
			
		||||
    requirements = {
 | 
			
		||||
@@ -277,13 +297,32 @@ def validate_thermostat(config):
 | 
			
		||||
            CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION],
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    # Validate temperature requirements for default configuraation
 | 
			
		||||
    validate_temperature_preset(config, config, "default", requirements)
 | 
			
		||||
    # Legacy high/low configs
 | 
			
		||||
    if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
 | 
			
		||||
        comparable_preset = generate_comparable_preset(config, "Your new preset")
 | 
			
		||||
 | 
			
		||||
    # Validate temperature requirements for away configuration
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_DEFAULT_TARGET_TEMPERATURE_LOW} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n"
 | 
			
		||||
            f"{comparable_preset}"
 | 
			
		||||
        )
 | 
			
		||||
    if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
 | 
			
		||||
        comparable_preset = generate_comparable_preset(config, "Your new preset")
 | 
			
		||||
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_DEFAULT_TARGET_TEMPERATURE_HIGH} is no longer valid. Please switch to using a preset for an equivalent experience.\nEquivalent configuration:\n\n"
 | 
			
		||||
            f"{comparable_preset}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    # Legacy away mode - raise an error instructing the user to switch to presets
 | 
			
		||||
    if CONF_AWAY_CONFIG in config:
 | 
			
		||||
        away = config[CONF_AWAY_CONFIG]
 | 
			
		||||
        validate_temperature_preset(away, config, "away", requirements)
 | 
			
		||||
        comparable_preset = generate_comparable_preset(config[CONF_AWAY_CONFIG], "Away")
 | 
			
		||||
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_AWAY_CONFIG} is no longer valid. Please switch to using a preset named "
 | 
			
		||||
            "Away"
 | 
			
		||||
            " for an equivalent experience.\nEquivalent configuration:\n\n"
 | 
			
		||||
            f"{comparable_preset}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    # Validate temperature requirements for presets
 | 
			
		||||
    if CONF_PRESET in config:
 | 
			
		||||
@@ -292,7 +331,12 @@ def validate_thermostat(config):
 | 
			
		||||
                preset_config, config, preset_config[CONF_NAME], requirements
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    # Verify default climate mode is valid given above configuration
 | 
			
		||||
    # Warn about using the removed CONF_DEFAULT_MODE and advise users
 | 
			
		||||
    if CONF_DEFAULT_MODE in config and config[CONF_DEFAULT_MODE] is not None:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_DEFAULT_MODE} is no longer valid. Please switch to using presets and specify a {CONF_DEFAULT_PRESET}."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    default_mode = config[CONF_DEFAULT_MODE]
 | 
			
		||||
    requirements = {
 | 
			
		||||
        "HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION],
 | 
			
		||||
@@ -403,6 +447,38 @@ def validate_thermostat(config):
 | 
			
		||||
                        f"{CONF_SWING_MODE} is set to {swing_mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration"
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
    # If a default preset is requested then ensure that preset is defined
 | 
			
		||||
    if CONF_DEFAULT_PRESET in config:
 | 
			
		||||
        default_preset = config[CONF_DEFAULT_PRESET]
 | 
			
		||||
 | 
			
		||||
        if CONF_PRESET not in config:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"{CONF_DEFAULT_PRESET} is specified but no presets are defined"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        presets = config[CONF_PRESET]
 | 
			
		||||
        found_preset = False
 | 
			
		||||
 | 
			
		||||
        for preset in presets:
 | 
			
		||||
            if preset[CONF_NAME] == default_preset:
 | 
			
		||||
                found_preset = True
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        if found_preset is False:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"{CONF_DEFAULT_PRESET} set to '{default_preset}' but no such preset has been defined. Available presets: {[preset[CONF_NAME] for preset in presets]}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    # If restoring default preset on boot is true then ensure we have a default preset
 | 
			
		||||
    if (
 | 
			
		||||
        CONF_ON_BOOT_RESTORE_FROM in config
 | 
			
		||||
        and config[CONF_ON_BOOT_RESTORE_FROM] is OnBootRestoreFrom.DEFAULT_PRESET
 | 
			
		||||
    ):
 | 
			
		||||
        if CONF_DEFAULT_PRESET not in config:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"{CONF_DEFAULT_PRESET} must be defined to use {CONF_ON_BOOT_RESTORE_FROM} in DEFAULT_PRESET mode"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_COOLING}"
 | 
			
		||||
@@ -502,9 +578,8 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_TARGET_TEMPERATURE_CHANGE_ACTION
 | 
			
		||||
            ): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Optional(CONF_DEFAULT_MODE, default="OFF"): cv.templatable(
 | 
			
		||||
                validate_climate_mode
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_DEFAULT_MODE, default=None): cv.valid,
 | 
			
		||||
            cv.Optional(CONF_DEFAULT_PRESET): cv.templatable(cv.string),
 | 
			
		||||
            cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
 | 
			
		||||
            cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
@@ -542,6 +617,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESET): cv.ensure_list(PRESET_CONFIG_SCHEMA),
 | 
			
		||||
            cv.Optional(CONF_ON_BOOT_RESTORE_FROM): validate_on_boot_restore_from,
 | 
			
		||||
            cv.Optional(CONF_PRESET_CHANGE): automation.validate_automation(
 | 
			
		||||
                single=True
 | 
			
		||||
            ),
 | 
			
		||||
@@ -564,9 +640,10 @@ async def to_code(config):
 | 
			
		||||
        CONF_COOL_ACTION in config
 | 
			
		||||
        or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config)
 | 
			
		||||
    )
 | 
			
		||||
    if two_points_available:
 | 
			
		||||
        cg.add(var.set_supports_two_points(True))
 | 
			
		||||
 | 
			
		||||
    sens = await cg.get_variable(config[CONF_SENSOR])
 | 
			
		||||
    cg.add(var.set_default_mode(config[CONF_DEFAULT_MODE]))
 | 
			
		||||
    cg.add(
 | 
			
		||||
        var.set_set_point_minimum_differential(
 | 
			
		||||
            config[CONF_SET_POINT_MINIMUM_DIFFERENTIAL]
 | 
			
		||||
@@ -579,23 +656,6 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_heat_deadband(config[CONF_HEAT_DEADBAND]))
 | 
			
		||||
    cg.add(var.set_heat_overrun(config[CONF_HEAT_OVERRUN]))
 | 
			
		||||
 | 
			
		||||
    if two_points_available is True:
 | 
			
		||||
        cg.add(var.set_supports_two_points(True))
 | 
			
		||||
        normal_config = ThermostatClimateTargetTempConfig(
 | 
			
		||||
            config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
 | 
			
		||||
            config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
 | 
			
		||||
        )
 | 
			
		||||
    elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in config:
 | 
			
		||||
        cg.add(var.set_supports_two_points(False))
 | 
			
		||||
        normal_config = ThermostatClimateTargetTempConfig(
 | 
			
		||||
            config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
 | 
			
		||||
        )
 | 
			
		||||
    elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
 | 
			
		||||
        cg.add(var.set_supports_two_points(False))
 | 
			
		||||
        normal_config = ThermostatClimateTargetTempConfig(
 | 
			
		||||
            config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if CONF_MAX_COOLING_RUN_TIME in config:
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_cooling_maximum_run_time_in_sec(config[CONF_MAX_COOLING_RUN_TIME])
 | 
			
		||||
@@ -661,7 +721,6 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_supports_fan_with_heating(config[CONF_FAN_WITH_HEATING]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_use_startup_delay(config[CONF_STARTUP_DELAY]))
 | 
			
		||||
    cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_HOME, normal_config))
 | 
			
		||||
 | 
			
		||||
    await automation.build_automation(
 | 
			
		||||
        var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
 | 
			
		||||
@@ -808,27 +867,8 @@ async def to_code(config):
 | 
			
		||||
            config[CONF_TARGET_TEMPERATURE_CHANGE_ACTION],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if CONF_AWAY_CONFIG in config:
 | 
			
		||||
        away = config[CONF_AWAY_CONFIG]
 | 
			
		||||
 | 
			
		||||
        if two_points_available is True:
 | 
			
		||||
            away_config = ThermostatClimateTargetTempConfig(
 | 
			
		||||
                away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
 | 
			
		||||
                away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
 | 
			
		||||
            )
 | 
			
		||||
        elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in away:
 | 
			
		||||
            away_config = ThermostatClimateTargetTempConfig(
 | 
			
		||||
                away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
 | 
			
		||||
            )
 | 
			
		||||
        elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in away:
 | 
			
		||||
            away_config = ThermostatClimateTargetTempConfig(
 | 
			
		||||
                away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]
 | 
			
		||||
            )
 | 
			
		||||
        cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_AWAY, away_config))
 | 
			
		||||
 | 
			
		||||
    if CONF_PRESET in config:
 | 
			
		||||
        for preset_config in config[CONF_PRESET]:
 | 
			
		||||
 | 
			
		||||
            name = preset_config[CONF_NAME]
 | 
			
		||||
            standard_preset = None
 | 
			
		||||
            if name.upper() in climate.CLIMATE_PRESETS:
 | 
			
		||||
@@ -872,6 +912,19 @@ async def to_code(config):
 | 
			
		||||
            else:
 | 
			
		||||
                cg.add(var.set_custom_preset_config(name, preset_target_variable))
 | 
			
		||||
 | 
			
		||||
    if CONF_DEFAULT_PRESET in config:
 | 
			
		||||
        default_preset_name = config[CONF_DEFAULT_PRESET]
 | 
			
		||||
 | 
			
		||||
        # if the name is a built in preset use the appropriate naming format
 | 
			
		||||
        if default_preset_name.upper() in climate.CLIMATE_PRESETS:
 | 
			
		||||
            climate_preset = climate.CLIMATE_PRESETS[default_preset_name.upper()]
 | 
			
		||||
            cg.add(var.set_default_preset(climate_preset))
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add(var.set_default_preset(default_preset_name))
 | 
			
		||||
 | 
			
		||||
    if CONF_ON_BOOT_RESTORE_FROM in config:
 | 
			
		||||
        cg.add(var.set_on_boot_restore_from(config[CONF_ON_BOOT_RESTORE_FROM]))
 | 
			
		||||
 | 
			
		||||
    if CONF_PRESET_CHANGE in config:
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            var.get_preset_change_trigger(), [], config[CONF_PRESET_CHANGE]
 | 
			
		||||
 
 | 
			
		||||
@@ -25,15 +25,27 @@ void ThermostatClimate::setup() {
 | 
			
		||||
    this->publish_state();
 | 
			
		||||
  });
 | 
			
		||||
  this->current_temperature = this->sensor_->state;
 | 
			
		||||
  // restore all climate data, if possible
 | 
			
		||||
  auto restore = this->restore_state_();
 | 
			
		||||
  if (restore.has_value()) {
 | 
			
		||||
    restore->to_call(this).perform();
 | 
			
		||||
  } else {
 | 
			
		||||
    // restore from defaults, change_away handles temps for us
 | 
			
		||||
    this->mode = this->default_mode_;
 | 
			
		||||
    this->change_preset_(climate::CLIMATE_PRESET_HOME);
 | 
			
		||||
 | 
			
		||||
  auto use_default_preset = true;
 | 
			
		||||
 | 
			
		||||
  if (this->on_boot_restore_from_ == thermostat::OnBootRestoreFrom::MEMORY) {
 | 
			
		||||
    // restore all climate data, if possible
 | 
			
		||||
    auto restore = this->restore_state_();
 | 
			
		||||
    if (restore.has_value()) {
 | 
			
		||||
      use_default_preset = false;
 | 
			
		||||
      restore->to_call(this).perform();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Either we failed to restore state or the user has requested we always apply the default preset
 | 
			
		||||
  if (use_default_preset) {
 | 
			
		||||
    if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) {
 | 
			
		||||
      this->change_preset_(this->default_preset_);
 | 
			
		||||
    } else if (!this->default_custom_preset_.empty()) {
 | 
			
		||||
      this->change_custom_preset_(this->default_custom_preset_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // refresh the climate action based on the restored settings, we'll publish_state() later
 | 
			
		||||
  this->switch_to_action_(this->compute_action_(), false);
 | 
			
		||||
  this->switch_to_supplemental_action_(this->compute_supplemental_action_());
 | 
			
		||||
@@ -923,9 +935,9 @@ bool ThermostatClimate::supplemental_heating_required_() {
 | 
			
		||||
          (this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ThermostatClimate::dump_preset_config_(const std::string &preset,
 | 
			
		||||
                                            const ThermostatClimateTargetTempConfig &config) {
 | 
			
		||||
  const auto *preset_name = preset.c_str();
 | 
			
		||||
void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
 | 
			
		||||
                                            bool is_default_preset) {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "      %s Is Default: %s", preset_name, YESNO(is_default_preset));
 | 
			
		||||
 | 
			
		||||
  if (this->supports_heat_) {
 | 
			
		||||
    if (this->supports_two_points_) {
 | 
			
		||||
@@ -962,9 +974,19 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
 | 
			
		||||
  auto config = this->preset_config_.find(preset);
 | 
			
		||||
 | 
			
		||||
  if (config != this->preset_config_.end()) {
 | 
			
		||||
    ESP_LOGI(TAG, "Switching to preset  %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
 | 
			
		||||
    this->change_preset_internal_(config->second);
 | 
			
		||||
    ESP_LOGI(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
 | 
			
		||||
    if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) ||
 | 
			
		||||
        this->preset.value() != preset) {
 | 
			
		||||
      // Fire any preset changed trigger if defined
 | 
			
		||||
      Trigger<> *trig = this->preset_change_trigger_;
 | 
			
		||||
      assert(trig != nullptr);
 | 
			
		||||
      trig->trigger();
 | 
			
		||||
 | 
			
		||||
      this->refresh();
 | 
			
		||||
      ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
 | 
			
		||||
    }
 | 
			
		||||
    this->custom_preset.reset();
 | 
			
		||||
    this->preset = preset;
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -976,9 +998,19 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
 | 
			
		||||
  auto config = this->custom_preset_config_.find(custom_preset);
 | 
			
		||||
 | 
			
		||||
  if (config != this->custom_preset_config_.end()) {
 | 
			
		||||
    ESP_LOGI(TAG, "Switching to custom preset  %s", custom_preset.c_str());
 | 
			
		||||
    this->change_preset_internal_(config->second);
 | 
			
		||||
    ESP_LOGI(TAG, "Custom preset %s requested", custom_preset.c_str());
 | 
			
		||||
    if (this->change_preset_internal_(config->second) || (!this->custom_preset.has_value()) ||
 | 
			
		||||
        this->custom_preset.value() != custom_preset) {
 | 
			
		||||
      // Fire any preset changed trigger if defined
 | 
			
		||||
      Trigger<> *trig = this->preset_change_trigger_;
 | 
			
		||||
      assert(trig != nullptr);
 | 
			
		||||
      trig->trigger();
 | 
			
		||||
 | 
			
		||||
      this->refresh();
 | 
			
		||||
      ESP_LOGI(TAG, "Custom preset %s applied", custom_preset.c_str());
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset.c_str());
 | 
			
		||||
    }
 | 
			
		||||
    this->preset.reset();
 | 
			
		||||
    this->custom_preset = custom_preset;
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -986,39 +1018,46 @@ void ThermostatClimate::change_custom_preset_(const std::string &custom_preset)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) {
 | 
			
		||||
bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) {
 | 
			
		||||
  bool something_changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->supports_two_points_) {
 | 
			
		||||
    this->target_temperature_low = config.default_temperature_low;
 | 
			
		||||
    this->target_temperature_high = config.default_temperature_high;
 | 
			
		||||
    if (this->target_temperature_low != config.default_temperature_low) {
 | 
			
		||||
      this->target_temperature_low = config.default_temperature_low;
 | 
			
		||||
      something_changed = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->target_temperature_high != config.default_temperature_high) {
 | 
			
		||||
      this->target_temperature_high = config.default_temperature_high;
 | 
			
		||||
      something_changed = true;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    this->target_temperature = config.default_temperature;
 | 
			
		||||
    if (this->target_temperature != config.default_temperature) {
 | 
			
		||||
      this->target_temperature = config.default_temperature;
 | 
			
		||||
      something_changed = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Note: The mode, fan_mode, and swing_mode can all be defined on the preset but if the climate.control call
 | 
			
		||||
  // also specifies them then the control's version will override these for that call
 | 
			
		||||
  if (config.mode_.has_value()) {
 | 
			
		||||
    this->mode = *config.mode_;
 | 
			
		||||
  // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call
 | 
			
		||||
  //  also specifies them then the climate.control call's values will override the preset's values for that call
 | 
			
		||||
  if (config.mode_.has_value() && (this->mode != config.mode_.value())) {
 | 
			
		||||
    ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
 | 
			
		||||
    this->mode = *config.mode_;
 | 
			
		||||
    something_changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (config.fan_mode_.has_value()) {
 | 
			
		||||
    this->fan_mode = *config.fan_mode_;
 | 
			
		||||
  if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_.value())) {
 | 
			
		||||
    ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
 | 
			
		||||
    this->fan_mode = *config.fan_mode_;
 | 
			
		||||
    something_changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (config.swing_mode_.has_value()) {
 | 
			
		||||
  if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) {
 | 
			
		||||
    ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
 | 
			
		||||
    this->swing_mode = *config.swing_mode_;
 | 
			
		||||
    something_changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Fire any preset changed trigger if defined
 | 
			
		||||
  if (this->preset != preset) {
 | 
			
		||||
    Trigger<> *trig = this->preset_change_trigger_;
 | 
			
		||||
    assert(trig != nullptr);
 | 
			
		||||
    trig->trigger();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->refresh();
 | 
			
		||||
  return something_changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ThermostatClimate::set_preset_config(climate::ClimatePreset preset,
 | 
			
		||||
@@ -1061,7 +1100,15 @@ ThermostatClimate::ThermostatClimate()
 | 
			
		||||
      temperature_change_trigger_(new Trigger<>()),
 | 
			
		||||
      preset_change_trigger_(new Trigger<>()) {}
 | 
			
		||||
 | 
			
		||||
void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { this->default_mode_ = default_mode; }
 | 
			
		||||
void ThermostatClimate::set_default_preset(const std::string &custom_preset) {
 | 
			
		||||
  this->default_custom_preset_ = custom_preset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ThermostatClimate::set_default_preset(climate::ClimatePreset preset) { this->default_preset_ = preset; }
 | 
			
		||||
 | 
			
		||||
void ThermostatClimate::set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from) {
 | 
			
		||||
  this->on_boot_restore_from_ = on_boot_restore_from;
 | 
			
		||||
}
 | 
			
		||||
void ThermostatClimate::set_set_point_minimum_differential(float differential) {
 | 
			
		||||
  this->set_point_minimum_differential_ = differential;
 | 
			
		||||
}
 | 
			
		||||
@@ -1213,8 +1260,9 @@ Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->p
 | 
			
		||||
void ThermostatClimate::dump_config() {
 | 
			
		||||
  LOG_CLIMATE("", "Thermostat", this);
 | 
			
		||||
 | 
			
		||||
  if (this->supports_two_points_)
 | 
			
		||||
  if (this->supports_two_points_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_));
 | 
			
		||||
  if (this->supports_cool_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Cooling Parameters:");
 | 
			
		||||
@@ -1284,7 +1332,7 @@ void ThermostatClimate::dump_config() {
 | 
			
		||||
    const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
 | 
			
		||||
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Supports %s: %s", preset_name, YESNO(true));
 | 
			
		||||
    this->dump_preset_config_(preset_name, it.second);
 | 
			
		||||
    this->dump_preset_config_(preset_name, it.second, it.first == this->default_preset_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Supported CUSTOM PRESETS: ");
 | 
			
		||||
@@ -1292,8 +1340,10 @@ void ThermostatClimate::dump_config() {
 | 
			
		||||
    const auto *preset_name = it.first.c_str();
 | 
			
		||||
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    Supports %s: %s", preset_name, YESNO(true));
 | 
			
		||||
    this->dump_preset_config_(preset_name, it.second);
 | 
			
		||||
    this->dump_preset_config_(preset_name, it.second, it.first == this->default_custom_preset_);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  On boot, restore from: %s",
 | 
			
		||||
                this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ enum ThermostatClimateTimerIndex : size_t {
 | 
			
		||||
  TIMER_IDLE_ON = 9,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 };
 | 
			
		||||
struct ThermostatClimateTimer {
 | 
			
		||||
  const std::string name;
 | 
			
		||||
  bool active;
 | 
			
		||||
@@ -57,7 +58,9 @@ class ThermostatClimate : public climate::Climate, public Component {
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  void set_default_mode(climate::ClimateMode default_mode);
 | 
			
		||||
  void set_default_preset(const std::string &custom_preset);
 | 
			
		||||
  void set_default_preset(climate::ClimatePreset preset);
 | 
			
		||||
  void set_on_boot_restore_from(thermostat::OnBootRestoreFrom on_boot_restore_from);
 | 
			
		||||
  void set_set_point_minimum_differential(float differential);
 | 
			
		||||
  void set_cool_deadband(float deadband);
 | 
			
		||||
  void set_cool_overrun(float overrun);
 | 
			
		||||
@@ -165,7 +168,8 @@ class ThermostatClimate : public climate::Climate, public Component {
 | 
			
		||||
 | 
			
		||||
  /// Applies the temperature, mode, fan, and swing modes of the provided config.
 | 
			
		||||
  /// This is agnostic of custom vs built in preset
 | 
			
		||||
  void change_preset_internal_(const ThermostatClimateTargetTempConfig &config);
 | 
			
		||||
  /// Returns true if something was changed
 | 
			
		||||
  bool change_preset_internal_(const ThermostatClimateTargetTempConfig &config);
 | 
			
		||||
 | 
			
		||||
  /// Return the traits of this controller.
 | 
			
		||||
  climate::ClimateTraits traits() override;
 | 
			
		||||
@@ -225,7 +229,8 @@ class ThermostatClimate : public climate::Climate, public Component {
 | 
			
		||||
  bool supplemental_cooling_required_();
 | 
			
		||||
  bool supplemental_heating_required_();
 | 
			
		||||
 | 
			
		||||
  void dump_preset_config_(const std::string &preset_name, const ThermostatClimateTargetTempConfig &config);
 | 
			
		||||
  void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
 | 
			
		||||
                           bool is_default_preset);
 | 
			
		||||
 | 
			
		||||
  /// The sensor used for getting the current temperature
 | 
			
		||||
  sensor::Sensor *sensor_{nullptr};
 | 
			
		||||
@@ -397,7 +402,6 @@ class ThermostatClimate : public climate::Climate, public Component {
 | 
			
		||||
  /// These are used to determine when a trigger/action needs to be called
 | 
			
		||||
  climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF};
 | 
			
		||||
  climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON};
 | 
			
		||||
  climate::ClimateMode default_mode_{climate::CLIMATE_MODE_OFF};
 | 
			
		||||
  climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF};
 | 
			
		||||
  climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
 | 
			
		||||
 | 
			
		||||
@@ -441,6 +445,15 @@ class ThermostatClimate : public climate::Climate, public Component {
 | 
			
		||||
  std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{};
 | 
			
		||||
  /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
 | 
			
		||||
  std::map<std::string, ThermostatClimateTargetTempConfig> custom_preset_config_{};
 | 
			
		||||
 | 
			
		||||
  /// Default standard preset to use on start up
 | 
			
		||||
  climate::ClimatePreset default_preset_{};
 | 
			
		||||
  /// Default custom preset to use on start up
 | 
			
		||||
  std::string default_custom_preset_{};
 | 
			
		||||
 | 
			
		||||
  /// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
 | 
			
		||||
  /// state will attempt to be restored if possible
 | 
			
		||||
  thermostat::OnBootRestoreFrom on_boot_restore_from_{thermostat::OnBootRestoreFrom::MEMORY};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace thermostat
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@ namespace esphome {
 | 
			
		||||
namespace time {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "automation";
 | 
			
		||||
static const int MAX_TIMESTAMP_DRIFT = 900;  // how far can the clock drift before we consider
 | 
			
		||||
                                             // there has been a drastic time synchronization
 | 
			
		||||
 | 
			
		||||
void CronTrigger::add_second(uint8_t second) { this->seconds_[second] = true; }
 | 
			
		||||
void CronTrigger::add_minute(uint8_t minute) { this->minutes_[minute] = true; }
 | 
			
		||||
@@ -23,12 +25,17 @@ void CronTrigger::loop() {
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->last_check_.has_value()) {
 | 
			
		||||
    if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) {
 | 
			
		||||
    if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > MAX_TIMESTAMP_DRIFT) {
 | 
			
		||||
      // We went back in time (a lot), probably caused by time synchronization
 | 
			
		||||
      ESP_LOGW(TAG, "Time has jumped back!");
 | 
			
		||||
    } else if (*this->last_check_ >= time) {
 | 
			
		||||
      // already handled this one
 | 
			
		||||
      return;
 | 
			
		||||
    } else if (time > *this->last_check_ && time.timestamp - this->last_check_->timestamp > MAX_TIMESTAMP_DRIFT) {
 | 
			
		||||
      // We went ahead in time (a lot), probably caused by time synchronization
 | 
			
		||||
      ESP_LOGW(TAG, "Time has jumped ahead!");
 | 
			
		||||
      this->last_check_ = time;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,12 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor,
 | 
			
		||||
    this->y_min_ = y_min;
 | 
			
		||||
    this->y_max_ = y_max;
 | 
			
		||||
  }
 | 
			
		||||
  int16_t get_x_min() { return this->x_min_; }
 | 
			
		||||
  int16_t get_x_max() { return this->x_max_; }
 | 
			
		||||
  int16_t get_y_min() { return this->y_min_; }
 | 
			
		||||
  int16_t get_y_max() { return this->y_max_; }
 | 
			
		||||
  int16_t get_width() { return this->x_max_ - this->x_min_; }
 | 
			
		||||
  int16_t get_height() { return this->y_max_ - this->y_min_; }
 | 
			
		||||
 | 
			
		||||
  void set_page(display::DisplayPage *page) { this->page_ = page; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ namespace esphome {
 | 
			
		||||
namespace web_server {
 | 
			
		||||
 | 
			
		||||
const uint8_t INDEX_GZ[] PROGMEM = {
 | 
			
		||||
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3,
 | 
			
		||||
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3,
 | 
			
		||||
    0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x30, 0x4a, 0x6d, 0x03, 0x25, 0x10, 0x22, 0x29, 0xcb, 0x76, 0x81, 0x02, 0x79, 0xe5,
 | 
			
		||||
    0xa5, 0xae, 0x5d, 0xe5, 0xad, 0x2c, 0xd9, 0x75, 0xab, 0x54, 0x2c, 0x0b, 0x22, 0x93, 0x22, 0xca, 0x20, 0xc0, 0x02,
 | 
			
		||||
    0x92, 0x5a, 0x8a, 0x42, 0x9f, 0x7e, 0xea, 0xa7, 0x39, 0x67, 0xd6, 0x87, 0x7e, 0x99, 0xd3, 0xfd, 0x30, 0x1f, 0x31,
 | 
			
		||||
@@ -524,62 +524,64 @@ const uint8_t INDEX_GZ[] PROGMEM = {
 | 
			
		||||
    0x5b, 0x06, 0xb9, 0xff, 0xfc, 0x07, 0x47, 0x40, 0x7e, 0x68, 0x06, 0xb9, 0xf7, 0xd8, 0x4a, 0xa0, 0xa3, 0xe9, 0xac,
 | 
			
		||||
    0x3d, 0x67, 0xce, 0xe1, 0x8f, 0x6c, 0x88, 0x69, 0xd3, 0xd0, 0xfa, 0x2f, 0xb5, 0x78, 0x50, 0x86, 0x1b, 0x79, 0x8f,
 | 
			
		||||
    0x89, 0x9d, 0xcc, 0xf8, 0x15, 0x04, 0xe1, 0xfc, 0x46, 0x82, 0xbc, 0xb8, 0x1d, 0x3d, 0x38, 0xff, 0x63, 0xe9, 0x41,
 | 
			
		||||
    0x5f, 0xee, 0x57, 0xf4, 0xa8, 0x45, 0xdc, 0x29, 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0xf6, 0x16, 0xc3, 0xb7,
 | 
			
		||||
    0xc2, 0x16, 0xb9, 0x80, 0xef, 0x3e, 0xe7, 0x74, 0x40, 0x64, 0x15, 0x87, 0xb6, 0x0c, 0xc0, 0xcc, 0xf1, 0xdb, 0xb4,
 | 
			
		||||
    0xea, 0xe5, 0x54, 0xdc, 0x5c, 0x09, 0xe9, 0xda, 0xd9, 0x8e, 0x0e, 0xde, 0x60, 0xa2, 0x77, 0xb8, 0xcc, 0x78, 0x84,
 | 
			
		||||
    0xbf, 0xfc, 0x88, 0xc7, 0x3c, 0xc1, 0x4b, 0xb1, 0xf2, 0x02, 0x19, 0xe6, 0x25, 0x7f, 0x87, 0x39, 0xd5, 0xea, 0x90,
 | 
			
		||||
    0x60, 0x86, 0x01, 0x83, 0x57, 0x6c, 0x1c, 0x47, 0x8e, 0xed, 0xcc, 0x61, 0xc7, 0xc2, 0x98, 0xad, 0x5a, 0x42, 0x33,
 | 
			
		||||
    0xe5, 0x32, 0xbb, 0xb6, 0xfa, 0x7d, 0x3b, 0x39, 0x7e, 0xbf, 0x2c, 0x3c, 0x94, 0x01, 0x46, 0x5b, 0x7a, 0x00, 0x30,
 | 
			
		||||
    0xbe, 0x2a, 0xc9, 0x51, 0xd8, 0x57, 0x56, 0x83, 0x2d, 0xcc, 0x86, 0x8e, 0xdf, 0x05, 0x37, 0x82, 0x8a, 0xf1, 0x7b,
 | 
			
		||||
    0x50, 0x3f, 0x38, 0xad, 0x6d, 0x30, 0x6b, 0x8c, 0x6e, 0x7a, 0xa0, 0xe1, 0x4a, 0x18, 0x49, 0x04, 0x07, 0x1a, 0xa5,
 | 
			
		||||
    0x9e, 0xfe, 0x05, 0x64, 0x55, 0xb8, 0xa8, 0x78, 0x7c, 0x71, 0x20, 0xef, 0x7c, 0xdb, 0x18, 0xb9, 0xa5, 0x88, 0x7d,
 | 
			
		||||
    0xf5, 0xbd, 0xa9, 0x4d, 0x50, 0x17, 0xf4, 0x5b, 0x20, 0xe9, 0xdc, 0x1b, 0x35, 0x02, 0xa6, 0x5c, 0x5b, 0xd2, 0x73,
 | 
			
		||||
    0x08, 0x6d, 0xa1, 0x0f, 0xc6, 0xec, 0x34, 0x1e, 0x49, 0xb1, 0xee, 0x59, 0xf2, 0xaa, 0x48, 0x8b, 0xb0, 0x08, 0x3b,
 | 
			
		||||
    0x9e, 0xf0, 0x9d, 0xe1, 0x05, 0xb5, 0x5a, 0x98, 0x66, 0x76, 0xff, 0x5e, 0x4f, 0x43, 0x52, 0xcf, 0x56, 0xb7, 0xf1,
 | 
			
		||||
    0x57, 0x52, 0x1e, 0x82, 0xaf, 0xf6, 0xf7, 0xe1, 0x3d, 0xfc, 0xa5, 0x94, 0xf7, 0x86, 0xb6, 0xeb, 0x93, 0x50, 0xbc,
 | 
			
		||||
    0x57, 0xfd, 0x66, 0x4a, 0x94, 0x08, 0x9b, 0xa0, 0xbf, 0xbc, 0xdb, 0x2a, 0x32, 0xa9, 0xb4, 0xba, 0x3b, 0x95, 0xd2,
 | 
			
		||||
    0x82, 0x67, 0x43, 0x4a, 0x81, 0x00, 0xed, 0xfa, 0x3b, 0x86, 0x28, 0x3c, 0x6d, 0xe1, 0xcf, 0x9a, 0x30, 0xbc, 0x0f,
 | 
			
		||||
    0x0d, 0x94, 0x34, 0x7c, 0x09, 0xcd, 0xb7, 0x85, 0xe0, 0x85, 0x7e, 0x3f, 0x92, 0xa8, 0x12, 0x62, 0xaa, 0xce, 0x31,
 | 
			
		||||
    0x6b, 0x0e, 0x91, 0x44, 0x8e, 0x80, 0xed, 0x19, 0xf1, 0x26, 0xc1, 0xae, 0x32, 0x9a, 0xf2, 0x14, 0xfa, 0x3a, 0xfa,
 | 
			
		||||
    0x33, 0xce, 0xeb, 0xea, 0xbc, 0xda, 0xce, 0x59, 0x33, 0x05, 0x32, 0x7c, 0xe3, 0xa0, 0x8a, 0xae, 0x2e, 0x88, 0xcf,
 | 
			
		||||
    0x99, 0x89, 0x6d, 0x5c, 0x7d, 0xf0, 0x6d, 0x4d, 0xf6, 0xad, 0xb9, 0x29, 0x58, 0xc5, 0x34, 0xb4, 0x2f, 0x30, 0x65,
 | 
			
		||||
    0x06, 0x7f, 0x56, 0xc5, 0xea, 0x41, 0x32, 0x94, 0x9f, 0x44, 0xf8, 0xdb, 0x58, 0xe8, 0x47, 0x59, 0x6d, 0x40, 0x4e,
 | 
			
		||||
    0xdf, 0xab, 0x24, 0x48, 0x5f, 0x8c, 0xcb, 0x26, 0x12, 0x60, 0x2f, 0xe0, 0x2f, 0xf7, 0xab, 0xae, 0x4a, 0xc8, 0x3b,
 | 
			
		||||
    0x90, 0x98, 0x53, 0x30, 0x8e, 0x73, 0xba, 0x5a, 0xab, 0xf0, 0xaf, 0x45, 0x34, 0x2b, 0x52, 0xd3, 0xae, 0x64, 0xc5,
 | 
			
		||||
    0xc0, 0xc6, 0x22, 0x3b, 0x90, 0xc9, 0x68, 0xe6, 0x07, 0x9b, 0xcd, 0xbb, 0x8f, 0x63, 0x91, 0x87, 0x86, 0x1f, 0xb4,
 | 
			
		||||
    0xb7, 0x05, 0x91, 0x6d, 0x10, 0x63, 0x57, 0xe2, 0x44, 0xc6, 0x0d, 0x5e, 0x19, 0xac, 0x7e, 0x43, 0x91, 0xb9, 0xe1,
 | 
			
		||||
    0x6d, 0x73, 0xb5, 0xf4, 0xb8, 0xb4, 0x0e, 0xae, 0x8c, 0xdf, 0x1d, 0xb3, 0x88, 0xfb, 0x51, 0x4a, 0xb9, 0x49, 0x8e,
 | 
			
		||||
    0x21, 0x16, 0xbc, 0x0e, 0xdb, 0x76, 0x4b, 0x90, 0x3c, 0xc6, 0xaf, 0x70, 0x12, 0xa4, 0xf7, 0xa1, 0xb0, 0x4a, 0xd8,
 | 
			
		||||
    0xda, 0x9d, 0x76, 0xfb, 0x6f, 0x0e, 0xf6, 0x2c, 0xb1, 0x9b, 0x77, 0xb7, 0xe0, 0x75, 0x97, 0xdc, 0x61, 0x91, 0x9f,
 | 
			
		||||
    0x11, 0x8a, 0xfc, 0x0c, 0x4b, 0x24, 0x74, 0x85, 0xf6, 0x96, 0x40, 0xd3, 0xb6, 0x58, 0x3a, 0x12, 0x31, 0xbc, 0x19,
 | 
			
		||||
    0xb8, 0x0b, 0x31, 0x7e, 0xd4, 0x6b, 0x0b, 0xbb, 0xb5, 0x70, 0xa5, 0x6d, 0x95, 0xe1, 0xa2, 0x0c, 0x04, 0x9e, 0xaa,
 | 
			
		||||
    0x88, 0x1f, 0xa8, 0x75, 0xa6, 0x92, 0x5d, 0xe4, 0x50, 0x3a, 0x27, 0x75, 0xb5, 0x75, 0xb1, 0x38, 0x9e, 0x81, 0x1c,
 | 
			
		||||
    0x52, 0x09, 0x2a, 0xef, 0x65, 0x87, 0x5d, 0x9a, 0x0a, 0x93, 0x62, 0x57, 0x23, 0x92, 0xd3, 0x4e, 0x7f, 0x37, 0x92,
 | 
			
		||||
    0xf6, 0x0e, 0xee, 0xdd, 0x02, 0x36, 0x2f, 0xa8, 0x39, 0x34, 0x2a, 0xfc, 0x38, 0xdb, 0x3a, 0x63, 0xc7, 0xad, 0x68,
 | 
			
		||||
    0x1e, 0x57, 0xe1, 0x3f, 0xd4, 0x7e, 0xfd, 0x5d, 0xa5, 0x08, 0x65, 0x9a, 0xa5, 0x7c, 0x8c, 0x8c, 0x2c, 0x0e, 0x24,
 | 
			
		||||
    0x1c, 0x31, 0x68, 0x29, 0x63, 0x8b, 0x64, 0x34, 0x02, 0xf1, 0x01, 0x56, 0xe2, 0x5f, 0x15, 0x83, 0x94, 0x9a, 0xa0,
 | 
			
		||||
    0xb4, 0xfb, 0x7f, 0xfd, 0x5f, 0xff, 0x5b, 0x86, 0x15, 0x81, 0xac, 0x00, 0x16, 0xa6, 0xc1, 0x54, 0x27, 0x8c, 0xec,
 | 
			
		||||
    0x1c, 0x1c, 0xd1, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04, 0x20, 0x28, 0x98, 0xb8, 0xca, 0x24, 0xeb, 0x81, 0x0b, 0x24,
 | 
			
		||||
    0x58, 0xe6, 0xe1, 0xbc, 0x04, 0xaf, 0x5e, 0x84, 0x2b, 0xf6, 0xbb, 0xf2, 0x56, 0x55, 0xbe, 0x30, 0x31, 0xb4, 0x91,
 | 
			
		||||
    0xc5, 0x6a, 0xf0, 0x5c, 0x2d, 0x93, 0x55, 0xfd, 0x82, 0x24, 0x29, 0x3c, 0x58, 0x2d, 0x8d, 0x15, 0x5a, 0xea, 0x83,
 | 
			
		||||
    0x90, 0x7f, 0xfb, 0xe7, 0xff, 0xfc, 0xdf, 0xd5, 0x2b, 0x9e, 0x6f, 0xfc, 0xf5, 0x9f, 0xfe, 0xe1, 0xff, 0xfe, 0x9f,
 | 
			
		||||
    0xff, 0x82, 0x59, 0xc2, 0xf2, 0x0c, 0x84, 0xb6, 0x92, 0x55, 0x1d, 0x80, 0x88, 0x3d, 0x65, 0x55, 0x0e, 0x47, 0x3d,
 | 
			
		||||
    0xdd, 0x75, 0x9f, 0x26, 0x24, 0xde, 0x94, 0xd0, 0x11, 0x5f, 0x53, 0x7a, 0x34, 0x51, 0xed, 0x1a, 0xf2, 0xc1, 0x52,
 | 
			
		||||
    0x5a, 0x74, 0xac, 0x6f, 0xef, 0xb4, 0xed, 0x6a, 0x79, 0xfb, 0x46, 0xdf, 0x2d, 0x5c, 0x98, 0x5b, 0x65, 0xe0, 0xf8,
 | 
			
		||||
    0x7a, 0xd9, 0x96, 0x2a, 0x8c, 0x85, 0x25, 0x65, 0x55, 0x6e, 0x61, 0x7c, 0x79, 0x89, 0xaf, 0x41, 0xd7, 0x28, 0xa6,
 | 
			
		||||
    0x55, 0xae, 0xf5, 0xe9, 0xfd, 0xb2, 0x00, 0x44, 0x27, 0xb8, 0x34, 0x22, 0x58, 0x46, 0x67, 0xa7, 0x2d, 0xb4, 0x4e,
 | 
			
		||||
    0x92, 0x8b, 0x92, 0x46, 0x11, 0xde, 0xcc, 0xfd, 0x47, 0x7f, 0x57, 0xfe, 0x69, 0x86, 0x56, 0x81, 0xe5, 0xcc, 0xa2,
 | 
			
		||||
    0x73, 0xe9, 0xe3, 0x3c, 0x68, 0xb7, 0xe7, 0xe7, 0xee, 0xb2, 0x9a, 0xc1, 0xbb, 0x6a, 0x32, 0x0a, 0xb0, 0x99, 0x03,
 | 
			
		||||
    0xd2, 0xa1, 0xab, 0x8e, 0xe5, 0x81, 0x59, 0xdf, 0xc6, 0xd0, 0x4f, 0x59, 0x7e, 0xb9, 0xa4, 0x70, 0x52, 0xfc, 0x1b,
 | 
			
		||||
    0x1e, 0x8e, 0xca, 0xc8, 0x1b, 0x94, 0x18, 0x58, 0x2c, 0x8d, 0x5e, 0x5d, 0xd1, 0x6b, 0xda, 0x59, 0xcd, 0x4d, 0x31,
 | 
			
		||||
    0x0f, 0x77, 0xcd, 0x63, 0xd9, 0xfb, 0x78, 0xd0, 0x3a, 0xed, 0x78, 0xd3, 0xee, 0x52, 0x0f, 0xcf, 0x79, 0x36, 0x33,
 | 
			
		||||
    0x4f, 0x73, 0x59, 0xc4, 0x46, 0x6c, 0xa2, 0x22, 0x96, 0xb2, 0x5e, 0x9c, 0xd4, 0x96, 0x5f, 0xe0, 0x76, 0x03, 0xda,
 | 
			
		||||
    0x66, 0x11, 0x0f, 0x88, 0x69, 0x7b, 0xe6, 0x79, 0x6f, 0x84, 0x27, 0xe9, 0xd9, 0xd2, 0x98, 0xab, 0x27, 0x9a, 0x62,
 | 
			
		||||
    0x5c, 0xb0, 0x9e, 0xf7, 0x53, 0xfa, 0xd4, 0xdd, 0x1c, 0x4a, 0x84, 0x15, 0x5e, 0xc8, 0x63, 0xd4, 0x77, 0x35, 0x7f,
 | 
			
		||||
    0x5c, 0x8a, 0x62, 0x70, 0x81, 0xd7, 0xd6, 0x0b, 0xb5, 0x28, 0x6a, 0x5f, 0x80, 0xb5, 0x43, 0x60, 0xda, 0xcd, 0x56,
 | 
			
		||||
    0x54, 0x88, 0xad, 0xde, 0x85, 0x2f, 0xb4, 0xed, 0x1d, 0xcd, 0xe7, 0xd4, 0xd0, 0x05, 0x6e, 0x24, 0x1b, 0x1a, 0x25,
 | 
			
		||||
    0x05, 0xa5, 0x08, 0x88, 0x13, 0x79, 0xd9, 0x46, 0xb2, 0xad, 0x78, 0x92, 0x67, 0xf5, 0xf4, 0xfb, 0xb6, 0xff, 0x1f,
 | 
			
		||||
    0x22, 0x28, 0x4d, 0x5d, 0x85, 0x7b, 0x00, 0x00};
 | 
			
		||||
    0x5f, 0xee, 0x57, 0xf4, 0xd0, 0x49, 0xe1, 0x85, 0xf1, 0xfb, 0x57, 0x16, 0x79, 0xa2, 0xaf, 0xa8, 0x65, 0x23, 0xca,
 | 
			
		||||
    0xf4, 0xf4, 0x91, 0x97, 0xe8, 0x9e, 0x20, 0x54, 0x6e, 0x86, 0xf0, 0x9f, 0xf1, 0x09, 0xb0, 0x2d, 0xfc, 0xd4, 0x9c,
 | 
			
		||||
    0x1d, 0xc0, 0x4f, 0xaa, 0xb5, 0x19, 0xc6, 0x0a, 0x0a, 0xbb, 0xac, 0x85, 0xf3, 0x29, 0x1c, 0x41, 0x51, 0x84, 0x7d,
 | 
			
		||||
    0x7a, 0x77, 0x70, 0x46, 0x91, 0x63, 0xf8, 0xee, 0x73, 0x4e, 0x1d, 0x44, 0xb6, 0x72, 0x68, 0xcb, 0xc0, 0xce, 0x1c,
 | 
			
		||||
    0xbf, 0x79, 0xab, 0x5e, 0x4e, 0xc5, 0x8d, 0x98, 0x90, 0xae, 0xb3, 0xed, 0xe8, 0xa0, 0x10, 0x26, 0x90, 0x87, 0xcb,
 | 
			
		||||
    0x8c, 0x47, 0xf8, 0x4b, 0x95, 0x78, 0xcc, 0x13, 0xbc, 0x6c, 0x2b, 0x2f, 0xa6, 0x61, 0xbe, 0xf3, 0x77, 0x98, 0xab,
 | 
			
		||||
    0xad, 0x30, 0x9e, 0x61, 0x20, 0xe2, 0x15, 0x1b, 0xc7, 0x91, 0x63, 0x3b, 0x73, 0x90, 0x04, 0x30, 0x66, 0xab, 0x96,
 | 
			
		||||
    0x28, 0x4d, 0x39, 0xd2, 0xae, 0xad, 0x7e, 0x8f, 0x4f, 0x8e, 0xdf, 0x45, 0x0b, 0x0f, 0x65, 0xe0, 0xd2, 0x96, 0x9e,
 | 
			
		||||
    0x05, 0x8c, 0xaf, 0x4a, 0x72, 0x54, 0x22, 0x95, 0x35, 0x62, 0x0b, 0x73, 0xa4, 0xe3, 0x77, 0xc1, 0x3d, 0xa1, 0x62,
 | 
			
		||||
    0xfc, 0xce, 0xd4, 0x0f, 0x4e, 0x6b, 0x1b, 0xcc, 0x25, 0xa3, 0x9b, 0x1e, 0x68, 0xb8, 0x12, 0x9e, 0x12, 0x41, 0x87,
 | 
			
		||||
    0x46, 0xa9, 0xa7, 0x7f, 0xb1, 0x59, 0x15, 0x86, 0x2a, 0x1e, 0x5f, 0x1c, 0xc8, 0xbb, 0xe4, 0x36, 0x46, 0x84, 0xe9,
 | 
			
		||||
    0x24, 0xa0, 0xfa, 0x8e, 0xd5, 0x26, 0xa8, 0x21, 0xfa, 0xed, 0x92, 0x74, 0x9e, 0x8e, 0x9a, 0x06, 0x53, 0xb9, 0x2d,
 | 
			
		||||
    0xe9, 0x91, 0x84, 0xb6, 0xd0, 0x33, 0x63, 0x76, 0x1a, 0x8f, 0xa4, 0xba, 0xf0, 0x2c, 0x79, 0x05, 0xa5, 0x45, 0x58,
 | 
			
		||||
    0x84, 0x1d, 0x4f, 0xf8, 0xe4, 0xf0, 0x82, 0xda, 0x32, 0x4c, 0x33, 0xbb, 0x7f, 0xaf, 0xa7, 0x21, 0xa9, 0x67, 0xc1,
 | 
			
		||||
    0xdb, 0xf8, 0xab, 0x2e, 0x0f, 0xc1, 0x07, 0xfc, 0xfb, 0xf0, 0x1e, 0xfe, 0xb2, 0xcb, 0x7b, 0x43, 0xdb, 0xf5, 0x49,
 | 
			
		||||
    0xd8, 0xde, 0xab, 0x7e, 0xe3, 0x25, 0x4a, 0x9a, 0x4d, 0xd0, 0x8b, 0xde, 0x6d, 0x15, 0xa4, 0x54, 0x86, 0xdd, 0x9d,
 | 
			
		||||
    0x4a, 0x19, 0xc2, 0xb3, 0x21, 0xfd, 0x40, 0x30, 0x77, 0xfd, 0x1d, 0x43, 0xc4, 0x9e, 0xb6, 0xf0, 0x67, 0x4d, 0xc8,
 | 
			
		||||
    0xde, 0x87, 0x06, 0x4a, 0xca, 0xbe, 0x84, 0xe6, 0xdb, 0x42, 0xa0, 0x43, 0xbf, 0x1f, 0x49, 0x04, 0x0a, 0xf1, 0x57,
 | 
			
		||||
    0xe7, 0x98, 0x35, 0x87, 0x53, 0x22, 0xf7, 0xc0, 0xf6, 0x8c, 0x38, 0x96, 0x60, 0x57, 0x19, 0xa5, 0x79, 0x0a, 0x7d,
 | 
			
		||||
    0x1d, 0xfd, 0x79, 0xe8, 0x75, 0x75, 0x5e, 0x6d, 0xd3, 0xac, 0x99, 0x02, 0x19, 0xbe, 0x71, 0x00, 0x46, 0x57, 0x22,
 | 
			
		||||
    0xc4, 0x67, 0xd2, 0x84, 0x78, 0xa8, 0x3e, 0x24, 0xb7, 0x26, 0xab, 0xd7, 0xdc, 0x14, 0xac, 0x62, 0x1a, 0xda, 0x17,
 | 
			
		||||
    0x98, 0x8a, 0x83, 0x3f, 0xab, 0x62, 0xf5, 0x20, 0x19, 0xca, 0x4f, 0x22, 0xfc, 0x2d, 0x2f, 0xf4, 0xa3, 0xac, 0x36,
 | 
			
		||||
    0x20, 0xa7, 0xef, 0x60, 0x12, 0xa4, 0x2f, 0xc6, 0x65, 0x13, 0x09, 0xb0, 0x43, 0xf0, 0x97, 0x06, 0x56, 0x57, 0x30,
 | 
			
		||||
    0xe4, 0xdd, 0x4a, 0xcc, 0x55, 0x18, 0xc7, 0x39, 0x5d, 0xd9, 0x55, 0xf8, 0xd7, 0x22, 0xa5, 0x15, 0xa9, 0x69, 0x57,
 | 
			
		||||
    0xb2, 0x62, 0x60, 0x63, 0x11, 0x88, 0x1a, 0x91, 0xe4, 0x66, 0x7e, 0x08, 0xda, 0xbc, 0x53, 0x39, 0x16, 0xf9, 0x6d,
 | 
			
		||||
    0xf8, 0xa1, 0x7c, 0x5b, 0x10, 0xd9, 0x06, 0xf1, 0x78, 0x25, 0x4e, 0x64, 0x34, 0xe1, 0x55, 0xc4, 0xea, 0x37, 0x1f,
 | 
			
		||||
    0x99, 0x1b, 0xde, 0x36, 0x57, 0x4b, 0x8f, 0x4b, 0xeb, 0xe0, 0xca, 0xb8, 0xe0, 0x31, 0x8b, 0xb8, 0x1f, 0xa5, 0x94,
 | 
			
		||||
    0xf3, 0xe4, 0x18, 0x62, 0xc1, 0xeb, 0xb0, 0x6d, 0xb7, 0x04, 0xc9, 0x63, 0xfc, 0x6a, 0x28, 0x41, 0x7a, 0x1f, 0x0a,
 | 
			
		||||
    0xab, 0x44, 0xb0, 0xdd, 0x69, 0xb7, 0xff, 0xe6, 0x60, 0xcf, 0x12, 0xbb, 0x79, 0x77, 0x0b, 0x5e, 0x77, 0xc9, 0xcd,
 | 
			
		||||
    0x16, 0x79, 0x1f, 0xa1, 0xc8, 0xfb, 0xb0, 0x44, 0xa2, 0x58, 0x68, 0x6f, 0x09, 0x34, 0x6d, 0x8b, 0xa5, 0x23, 0x11,
 | 
			
		||||
    0x1b, 0x9c, 0x81, 0x1b, 0x12, 0xe3, 0xc7, 0xc2, 0xb6, 0xb0, 0x5b, 0x0b, 0x57, 0xda, 0x56, 0x99, 0x33, 0xca, 0xf0,
 | 
			
		||||
    0xe0, 0xa9, 0x8a, 0x24, 0x82, 0xb9, 0xc0, 0x54, 0x12, 0x8d, 0x1c, 0x4a, 0xe7, 0xba, 0xae, 0xb6, 0x2e, 0x16, 0xc7,
 | 
			
		||||
    0x33, 0x90, 0x43, 0x2a, 0xf1, 0xe5, 0xbd, 0xec, 0xb0, 0x4b, 0x53, 0x61, 0xb2, 0xed, 0x6a, 0xa4, 0x73, 0xda, 0xe9,
 | 
			
		||||
    0xef, 0x46, 0xd2, 0x8e, 0xc2, 0xbd, 0x5b, 0xc0, 0xe6, 0x05, 0xf5, 0x89, 0xc6, 0x8a, 0x1f, 0x67, 0x5b, 0x67, 0xec,
 | 
			
		||||
    0xb8, 0x15, 0xcd, 0xe3, 0x2a, 0xac, 0x88, 0x5a, 0xb5, 0xbf, 0xab, 0x14, 0xac, 0x4c, 0xdf, 0x94, 0x8f, 0x91, 0x91,
 | 
			
		||||
    0x1d, 0x82, 0x84, 0x23, 0x06, 0x2d, 0x65, 0xcc, 0x92, 0x8c, 0x51, 0x20, 0x3e, 0xc0, 0x4a, 0xfc, 0xab, 0x62, 0x9b,
 | 
			
		||||
    0x52, 0x13, 0x94, 0x76, 0xff, 0xaf, 0xff, 0xeb, 0x7f, 0xcb, 0x70, 0x25, 0x90, 0x15, 0xc0, 0xc2, 0xf4, 0x9a, 0xea,
 | 
			
		||||
    0xe4, 0x92, 0x9d, 0x83, 0x83, 0x1b, 0x8f, 0x5b, 0xd3, 0x28, 0x99, 0x00, 0x04, 0x05, 0x13, 0xda, 0x50, 0xd6, 0x03,
 | 
			
		||||
    0x17, 0x48, 0xb0, 0xcc, 0x43, 0x7f, 0x09, 0x5e, 0xbd, 0x08, 0x57, 0xec, 0x77, 0xe5, 0xc3, 0xaa, 0x3c, 0x64, 0x62,
 | 
			
		||||
    0x68, 0x23, 0x3b, 0xd6, 0xe0, 0xb9, 0x5a, 0x86, 0xac, 0xfa, 0xc5, 0x4b, 0x52, 0x78, 0xb0, 0x5a, 0x7a, 0x2c, 0xb4,
 | 
			
		||||
    0xd4, 0x07, 0x2c, 0xff, 0xf6, 0xcf, 0xff, 0xf9, 0xbf, 0xab, 0x57, 0x3c, 0x37, 0xf9, 0xeb, 0x3f, 0xfd, 0xc3, 0xff,
 | 
			
		||||
    0xfd, 0x3f, 0xff, 0x05, 0xb3, 0x8f, 0xe5, 0xd9, 0x0a, 0x6d, 0x25, 0xab, 0x3a, 0x58, 0x11, 0x7b, 0xca, 0xaa, 0x1c,
 | 
			
		||||
    0x99, 0x7a, 0x1a, 0xed, 0x3e, 0x4d, 0x48, 0xbc, 0x29, 0xa1, 0x23, 0xbe, 0xa6, 0xb4, 0x6b, 0xa2, 0xda, 0x35, 0xe4,
 | 
			
		||||
    0x83, 0xa5, 0xb4, 0x28, 0x5d, 0xc0, 0xde, 0x69, 0xdb, 0xd5, 0xf2, 0xf6, 0x8d, 0xbe, 0x5b, 0xb8, 0x30, 0xb7, 0xca,
 | 
			
		||||
    0xec, 0xf1, 0xf5, 0xb2, 0x2d, 0x55, 0x78, 0x0c, 0x4b, 0xca, 0xaa, 0xdc, 0xc2, 0xb8, 0xf5, 0x12, 0x5f, 0x83, 0xae,
 | 
			
		||||
    0x51, 0x4c, 0xab, 0x5c, 0xeb, 0xd3, 0xfb, 0x65, 0x01, 0x88, 0x4e, 0x70, 0x69, 0x44, 0x10, 0x8e, 0xce, 0x64, 0x5b,
 | 
			
		||||
    0x68, 0xc0, 0x24, 0x17, 0x25, 0x8d, 0x22, 0xbc, 0xa4, 0xfb, 0x8f, 0xfe, 0xae, 0xfc, 0xd3, 0x0c, 0xad, 0x02, 0xcb,
 | 
			
		||||
    0x99, 0x45, 0xe7, 0xd2, 0x77, 0x7a, 0xd0, 0x6e, 0xcf, 0xcf, 0xdd, 0x65, 0x35, 0x83, 0x77, 0xd5, 0x64, 0x14, 0xb8,
 | 
			
		||||
    0x33, 0x07, 0xa4, 0xc3, 0x5c, 0x1d, 0x23, 0x04, 0x77, 0xa1, 0x8d, 0x21, 0xa5, 0xb2, 0xfc, 0x72, 0x49, 0x61, 0xaa,
 | 
			
		||||
    0xf8, 0x37, 0x3c, 0x74, 0x95, 0x11, 0x3d, 0x28, 0x31, 0xb0, 0x58, 0x1a, 0xbd, 0xba, 0xa2, 0xd7, 0xb4, 0xb3, 0x9a,
 | 
			
		||||
    0xf3, 0x62, 0x1e, 0x1a, 0x9b, 0xc7, 0xbd, 0xf7, 0xf1, 0x00, 0x77, 0xda, 0xf1, 0xa6, 0xdd, 0xa5, 0x1e, 0x9e, 0xf3,
 | 
			
		||||
    0x6c, 0x66, 0x9e, 0x12, 0xb3, 0x88, 0x8d, 0xd8, 0x44, 0x45, 0x42, 0x65, 0xbd, 0x38, 0x01, 0x2e, 0xbf, 0xc0, 0xed,
 | 
			
		||||
    0x06, 0xb4, 0xcd, 0x22, 0x1e, 0x10, 0xd3, 0xf6, 0xcc, 0x73, 0xe4, 0x08, 0x4f, 0xe8, 0xb3, 0xa5, 0x31, 0x57, 0x4f,
 | 
			
		||||
    0x34, 0xc5, 0x78, 0x63, 0x3d, 0x9f, 0xa8, 0xf4, 0xa9, 0xbb, 0x39, 0x94, 0x08, 0x57, 0xbc, 0x90, 0xc7, 0xb3, 0xef,
 | 
			
		||||
    0x6a, 0x7e, 0xbe, 0x14, 0xc5, 0xe0, 0x5a, 0xaf, 0xad, 0x17, 0x6a, 0x51, 0xd4, 0xbe, 0x00, 0x6b, 0x87, 0xc0, 0xb4,
 | 
			
		||||
    0x9b, 0xad, 0xa8, 0x10, 0x5b, 0xbd, 0x0b, 0x5f, 0x68, 0x9b, 0x3e, 0x9a, 0xcf, 0xa9, 0xa1, 0x0b, 0xdc, 0x48, 0xb6,
 | 
			
		||||
    0x39, 0x4a, 0x0a, 0x4a, 0x3d, 0x10, 0x27, 0xfd, 0xb2, 0x8d, 0x64, 0x5b, 0xf1, 0x24, 0x73, 0x00, 0xe8, 0xf7, 0x78,
 | 
			
		||||
    0xff, 0x3f, 0x32, 0x18, 0x26, 0x95, 0xdd, 0x7b, 0x00, 0x00};
 | 
			
		||||
 | 
			
		||||
}  // namespace web_server
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -81,6 +81,7 @@ class WebServerBase : public Component {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this->server_ = std::make_shared<AsyncWebServer>(this->port_);
 | 
			
		||||
    DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
 | 
			
		||||
    this->server_->begin();
 | 
			
		||||
 | 
			
		||||
    for (auto *handler : this->handlers_)
 | 
			
		||||
 
 | 
			
		||||
@@ -332,8 +332,7 @@ def manual_ip(config):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wifi_network(config, static_ip):
 | 
			
		||||
    ap = cg.variable(config[CONF_ID], WiFiAP())
 | 
			
		||||
def wifi_network(config, ap, static_ip):
 | 
			
		||||
    if CONF_SSID in config:
 | 
			
		||||
        cg.add(ap.set_ssid(config[CONF_SSID]))
 | 
			
		||||
    if CONF_PASSWORD in config:
 | 
			
		||||
@@ -360,14 +359,21 @@ async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
 | 
			
		||||
 | 
			
		||||
    for network in config.get(CONF_NETWORKS, []):
 | 
			
		||||
    def add_sta(ap, network):
 | 
			
		||||
        ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
 | 
			
		||||
        cg.add(var.add_sta(wifi_network(network, ip_config)))
 | 
			
		||||
        cg.add(var.add_sta(wifi_network(network, ap, ip_config)))
 | 
			
		||||
 | 
			
		||||
    for network in config.get(CONF_NETWORKS, []):
 | 
			
		||||
        cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
 | 
			
		||||
 | 
			
		||||
    if CONF_AP in config:
 | 
			
		||||
        conf = config[CONF_AP]
 | 
			
		||||
        ip_config = conf.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
 | 
			
		||||
        cg.add(var.set_ap(wifi_network(conf, ip_config)))
 | 
			
		||||
        cg.with_local_variable(
 | 
			
		||||
            conf[CONF_ID],
 | 
			
		||||
            WiFiAP(),
 | 
			
		||||
            lambda ap: cg.add(var.set_ap(wifi_network(conf, ap, ip_config))),
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_ap_timeout(conf[CONF_AP_TIMEOUT]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,129 +1,5 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import spi
 | 
			
		||||
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_THRESHOLD, CONF_TRIGGER_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@numo68"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor"]
 | 
			
		||||
DEPENDENCIES = ["spi"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_REPORT_INTERVAL = "report_interval"
 | 
			
		||||
CONF_CALIBRATION_X_MIN = "calibration_x_min"
 | 
			
		||||
CONF_CALIBRATION_X_MAX = "calibration_x_max"
 | 
			
		||||
CONF_CALIBRATION_Y_MIN = "calibration_y_min"
 | 
			
		||||
CONF_CALIBRATION_Y_MAX = "calibration_y_max"
 | 
			
		||||
CONF_DIMENSION_X = "dimension_x"
 | 
			
		||||
CONF_DIMENSION_Y = "dimension_y"
 | 
			
		||||
CONF_SWAP_X_Y = "swap_x_y"
 | 
			
		||||
CONF_IRQ_PIN = "irq_pin"
 | 
			
		||||
 | 
			
		||||
xpt2046_ns = cg.esphome_ns.namespace("xpt2046")
 | 
			
		||||
CONF_XPT2046_ID = "xpt2046_id"
 | 
			
		||||
 | 
			
		||||
XPT2046Component = xpt2046_ns.class_(
 | 
			
		||||
    "XPT2046Component", cg.PollingComponent, spi.SPIDevice
 | 
			
		||||
CONFIG_SCHEMA = cv.invalid(
 | 
			
		||||
    "This component sould now be used as platform of the Touchscreen component."
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
XPT2046OnStateTrigger = xpt2046_ns.class_(
 | 
			
		||||
    "XPT2046OnStateTrigger", automation.Trigger.template(cg.int_, cg.int_, cg.bool_)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_xpt2046(config):
 | 
			
		||||
    if (
 | 
			
		||||
        abs(
 | 
			
		||||
            cv.int_(config[CONF_CALIBRATION_X_MAX])
 | 
			
		||||
            - cv.int_(config[CONF_CALIBRATION_X_MIN])
 | 
			
		||||
        )
 | 
			
		||||
        < 1000
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid("Calibration X values difference < 1000")
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        abs(
 | 
			
		||||
            cv.int_(config[CONF_CALIBRATION_Y_MAX])
 | 
			
		||||
            - cv.int_(config[CONF_CALIBRATION_Y_MIN])
 | 
			
		||||
        )
 | 
			
		||||
        < 1000
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid("Calibration Y values difference < 1000")
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def report_interval(value):
 | 
			
		||||
    if value == "never":
 | 
			
		||||
        return 4294967295  # uint32_t max
 | 
			
		||||
    return cv.positive_time_period_milliseconds(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(XPT2046Component),
 | 
			
		||||
            cv.Optional(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_DIMENSION_X, default=100): cv.positive_not_null_int,
 | 
			
		||||
            cv.Optional(CONF_DIMENSION_Y, default=100): cv.positive_not_null_int,
 | 
			
		||||
            cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095),
 | 
			
		||||
            cv.Optional(CONF_REPORT_INTERVAL, default="never"): report_interval,
 | 
			
		||||
            cv.Optional(CONF_SWAP_X_Y, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        XPT2046OnStateTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("50ms"))
 | 
			
		||||
    .extend(spi.spi_device_schema()),
 | 
			
		||||
    validate_xpt2046,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await spi.register_spi_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_threshold(config[CONF_THRESHOLD]))
 | 
			
		||||
    cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL]))
 | 
			
		||||
    cg.add(var.set_dimensions(config[CONF_DIMENSION_X], config[CONF_DIMENSION_Y]))
 | 
			
		||||
    cg.add(
 | 
			
		||||
        var.set_calibration(
 | 
			
		||||
            config[CONF_CALIBRATION_X_MIN],
 | 
			
		||||
            config[CONF_CALIBRATION_X_MAX],
 | 
			
		||||
            config[CONF_CALIBRATION_Y_MIN],
 | 
			
		||||
            config[CONF_CALIBRATION_Y_MAX],
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if CONF_SWAP_X_Y in config:
 | 
			
		||||
        cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y]))
 | 
			
		||||
 | 
			
		||||
    if CONF_IRQ_PIN in config:
 | 
			
		||||
        pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
 | 
			
		||||
        cg.add(var.set_irq_pin(pin))
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_STATE, []):
 | 
			
		||||
        await automation.build_automation(
 | 
			
		||||
            var.get_on_state_trigger(),
 | 
			
		||||
            [(cg.int_, "x"), (cg.int_, "y"), (cg.bool_, "touched")],
 | 
			
		||||
            conf,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,55 +1,3 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
 | 
			
		||||
from . import (
 | 
			
		||||
    xpt2046_ns,
 | 
			
		||||
    XPT2046Component,
 | 
			
		||||
    CONF_XPT2046_ID,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_X_MIN = "x_min"
 | 
			
		||||
CONF_X_MAX = "x_max"
 | 
			
		||||
CONF_Y_MIN = "y_min"
 | 
			
		||||
CONF_Y_MAX = "y_max"
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["xpt2046"]
 | 
			
		||||
XPT2046Button = xpt2046_ns.class_("XPT2046Button", binary_sensor.BinarySensor)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_xpt2046_button(config):
 | 
			
		||||
    if cv.int_(config[CONF_X_MAX]) < cv.int_(config[CONF_X_MIN]) or cv.int_(
 | 
			
		||||
        config[CONF_Y_MAX]
 | 
			
		||||
    ) < cv.int_(config[CONF_Y_MIN]):
 | 
			
		||||
        raise cv.Invalid("x_max is less than x_min or y_max is less than y_min")
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    binary_sensor.binary_sensor_schema(XPT2046Button).extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_XPT2046_ID): cv.use_id(XPT2046Component),
 | 
			
		||||
            cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095),
 | 
			
		||||
            cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095),
 | 
			
		||||
            cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=4095),
 | 
			
		||||
            cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=4095),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    validate_xpt2046_button,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = await binary_sensor.new_binary_sensor(config)
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_XPT2046_ID])
 | 
			
		||||
    cg.add(
 | 
			
		||||
        var.set_area(
 | 
			
		||||
            config[CONF_X_MIN],
 | 
			
		||||
            config[CONF_X_MAX],
 | 
			
		||||
            config[CONF_Y_MIN],
 | 
			
		||||
            config[CONF_Y_MAX],
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    cg.add(hub.register_button(var))
 | 
			
		||||
CONFIG_SCHEMA = cv.invalid("Rename this platform component to Touchscreen.")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								esphome/components/xpt2046/touchscreen.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								esphome/components/xpt2046/touchscreen.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import spi, touchscreen
 | 
			
		||||
from esphome.const import CONF_ID, CONF_THRESHOLD
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@numo68", "@nielsnl68"]
 | 
			
		||||
DEPENDENCIES = ["spi"]
 | 
			
		||||
 | 
			
		||||
XPT2046_ns = cg.esphome_ns.namespace("xpt2046")
 | 
			
		||||
XPT2046Component = XPT2046_ns.class_(
 | 
			
		||||
    "XPT2046Component", touchscreen.Touchscreen, cg.PollingComponent, spi.SPIDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_INTERRUPT_PIN = "interrupt_pin"
 | 
			
		||||
 | 
			
		||||
CONF_REPORT_INTERVAL = "report_interval"
 | 
			
		||||
CONF_CALIBRATION_X_MIN = "calibration_x_min"
 | 
			
		||||
CONF_CALIBRATION_X_MAX = "calibration_x_max"
 | 
			
		||||
CONF_CALIBRATION_Y_MIN = "calibration_y_min"
 | 
			
		||||
CONF_CALIBRATION_Y_MAX = "calibration_y_max"
 | 
			
		||||
CONF_SWAP_X_Y = "swap_x_y"
 | 
			
		||||
 | 
			
		||||
# obsolete Keys
 | 
			
		||||
CONF_DIMENSION_X = "dimension_x"
 | 
			
		||||
CONF_DIMENSION_Y = "dimension_y"
 | 
			
		||||
CONF_IRQ_PIN = "irq_pin"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_xpt2046(config):
 | 
			
		||||
    if (
 | 
			
		||||
        abs(
 | 
			
		||||
            cv.int_(config[CONF_CALIBRATION_X_MAX])
 | 
			
		||||
            - cv.int_(config[CONF_CALIBRATION_X_MIN])
 | 
			
		||||
        )
 | 
			
		||||
        < 1000
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid("Calibration X values difference < 1000")
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        abs(
 | 
			
		||||
            cv.int_(config[CONF_CALIBRATION_Y_MAX])
 | 
			
		||||
            - cv.int_(config[CONF_CALIBRATION_Y_MIN])
 | 
			
		||||
        )
 | 
			
		||||
        < 1000
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid("Calibration Y values difference < 1000")
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def report_interval(value):
 | 
			
		||||
    if value == "never":
 | 
			
		||||
        return 4294967295  # uint32_t max
 | 
			
		||||
    return cv.positive_time_period_milliseconds(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(XPT2046Component),
 | 
			
		||||
            cv.Optional(CONF_INTERRUPT_PIN): cv.All(
 | 
			
		||||
                pins.internal_gpio_input_pin_schema
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_X_MIN, default=0): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_X_MAX, default=4095): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_Y_MIN, default=0): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CALIBRATION_Y_MAX, default=4095): cv.int_range(
 | 
			
		||||
                min=0, max=4095
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095),
 | 
			
		||||
            cv.Optional(CONF_REPORT_INTERVAL, default="never"): report_interval,
 | 
			
		||||
            cv.Optional(CONF_SWAP_X_Y, default=False): cv.boolean,
 | 
			
		||||
            # obsolete Keys
 | 
			
		||||
            cv.Optional(CONF_IRQ_PIN): cv.invalid("Rename IRQ_PIN to INTERUPT_PIN"),
 | 
			
		||||
            cv.Optional(CONF_DIMENSION_X): cv.invalid(
 | 
			
		||||
                "This key is now obsolete, please remove it"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_DIMENSION_Y): cv.invalid(
 | 
			
		||||
                "This key is now obsolete, please remove it"
 | 
			
		||||
            ),
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("50ms"))
 | 
			
		||||
    .extend(spi.spi_device_schema()),
 | 
			
		||||
).add_extra(validate_xpt2046)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await spi.register_spi_device(var, config)
 | 
			
		||||
    await touchscreen.register_touchscreen(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_threshold(config[CONF_THRESHOLD]))
 | 
			
		||||
    cg.add(var.set_report_interval(config[CONF_REPORT_INTERVAL]))
 | 
			
		||||
    cg.add(var.set_swap_x_y(config[CONF_SWAP_X_Y]))
 | 
			
		||||
    cg.add(
 | 
			
		||||
        var.set_calibration(
 | 
			
		||||
            config[CONF_CALIBRATION_X_MIN],
 | 
			
		||||
            config[CONF_CALIBRATION_X_MAX],
 | 
			
		||||
            config[CONF_CALIBRATION_Y_MIN],
 | 
			
		||||
            config[CONF_CALIBRATION_Y_MAX],
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if CONF_INTERRUPT_PIN in config:
 | 
			
		||||
        pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
 | 
			
		||||
        cg.add(var.set_irq_pin(pin))
 | 
			
		||||
@@ -9,118 +9,126 @@ namespace xpt2046 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "xpt2046";
 | 
			
		||||
 | 
			
		||||
void XPT2046TouchscreenStore::gpio_intr(XPT2046TouchscreenStore *store) { store->touch = true; }
 | 
			
		||||
 | 
			
		||||
void XPT2046Component::setup() {
 | 
			
		||||
  if (this->irq_pin_ != nullptr) {
 | 
			
		||||
    // The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state
 | 
			
		||||
    // while the channels are read and wiring it as an interrupt is not straightforward and would
 | 
			
		||||
    // need careful masking. A GPIO poll is cheap so we'll just use that.
 | 
			
		||||
 | 
			
		||||
    this->irq_pin_->setup();  // INPUT
 | 
			
		||||
    this->irq_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
    this->irq_pin_->setup();
 | 
			
		||||
    this->irq_pin_->attach_interrupt(XPT2046TouchscreenStore::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
 | 
			
		||||
  }
 | 
			
		||||
  spi_setup();
 | 
			
		||||
  read_adc_(0xD0);  // ADC powerdown, enable PENIRQ pin
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XPT2046Component::loop() {
 | 
			
		||||
  if (this->irq_pin_ != nullptr) {
 | 
			
		||||
    // Force immediate update if a falling edge (= touched is seen) Ignore if still active
 | 
			
		||||
    // (that would mean that we missed the release because of a too long update interval)
 | 
			
		||||
    bool val = this->irq_pin_->digital_read();
 | 
			
		||||
    if (!val && this->last_irq_ && !this->touched) {
 | 
			
		||||
      ESP_LOGD(TAG, "Falling penirq edge, forcing update");
 | 
			
		||||
      update();
 | 
			
		||||
    }
 | 
			
		||||
    this->last_irq_ = val;
 | 
			
		||||
  if ((this->irq_pin_ != nullptr) && (this->store_.touch || this->touched)) {
 | 
			
		||||
    this->store_.touch = false;
 | 
			
		||||
    check_touch_();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XPT2046Component::update() {
 | 
			
		||||
  if (this->irq_pin_ == nullptr)
 | 
			
		||||
    check_touch_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XPT2046Component::check_touch_() {
 | 
			
		||||
  int16_t data[6];
 | 
			
		||||
  bool touch = false;
 | 
			
		||||
  uint32_t now = millis();
 | 
			
		||||
 | 
			
		||||
  this->z_raw = 0;
 | 
			
		||||
  enable();
 | 
			
		||||
 | 
			
		||||
  // In case the penirq pin is present only do the SPI transaction if it reports a touch (is low).
 | 
			
		||||
  // The touch has to be also confirmed with checking the pressure over threshold
 | 
			
		||||
  if (this->irq_pin_ == nullptr || !this->irq_pin_->digital_read()) {
 | 
			
		||||
    enable();
 | 
			
		||||
  int16_t touch_pressure_1 = read_adc_(0xB1 /* touch_pressure_1 */);
 | 
			
		||||
  int16_t touch_pressure_2 = read_adc_(0xC1 /* touch_pressure_2 */);
 | 
			
		||||
 | 
			
		||||
    int16_t z1 = read_adc_(0xB1 /* Z1 */);
 | 
			
		||||
    int16_t z2 = read_adc_(0xC1 /* Z2 */);
 | 
			
		||||
  this->z_raw = touch_pressure_1 + 0Xfff - touch_pressure_2;
 | 
			
		||||
 | 
			
		||||
    this->z_raw = z1 + 4095 - z2;
 | 
			
		||||
 | 
			
		||||
    touch = (this->z_raw >= this->threshold_);
 | 
			
		||||
    if (touch) {
 | 
			
		||||
      read_adc_(0x91 /* Y */);  // dummy Y measure, 1st is always noisy
 | 
			
		||||
      data[0] = read_adc_(0xD1 /* X */);
 | 
			
		||||
      data[1] = read_adc_(0x91 /* Y */);  // make 3 x-y measurements
 | 
			
		||||
      data[2] = read_adc_(0xD1 /* X */);
 | 
			
		||||
      data[3] = read_adc_(0x91 /* Y */);
 | 
			
		||||
      data[4] = read_adc_(0xD1 /* X */);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data[5] = read_adc_(0x90 /* Y */);  // Last Y touch power down
 | 
			
		||||
 | 
			
		||||
    disable();
 | 
			
		||||
  touch = (this->z_raw >= this->threshold_);
 | 
			
		||||
  if (touch) {
 | 
			
		||||
    read_adc_(0xD1 /* X */);  // dummy Y measure, 1st is always noisy
 | 
			
		||||
    data[0] = read_adc_(0x91 /* Y */);
 | 
			
		||||
    data[1] = read_adc_(0xD1 /* X */);  // make 3 x-y measurements
 | 
			
		||||
    data[2] = read_adc_(0x91 /* Y */);
 | 
			
		||||
    data[3] = read_adc_(0xD1 /* X */);
 | 
			
		||||
    data[4] = read_adc_(0x91 /* Y */);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (touch) {
 | 
			
		||||
    this->x_raw = best_two_avg(data[0], data[2], data[4]);
 | 
			
		||||
    this->y_raw = best_two_avg(data[1], data[3], data[5]);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->x_raw = this->y_raw = 0;
 | 
			
		||||
  }
 | 
			
		||||
  data[5] = read_adc_(0xD0 /* X */);  // Last X touch power down
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Update [x, y] = [%d, %d], z = %d%s", this->x_raw, this->y_raw, this->z_raw, (touch ? " touched" : ""));
 | 
			
		||||
  disable();
 | 
			
		||||
 | 
			
		||||
  if (touch) {
 | 
			
		||||
    // Normalize raw data according to calibration min and max
 | 
			
		||||
    this->x_raw = best_two_avg(data[1], data[3], data[5]);
 | 
			
		||||
    this->y_raw = best_two_avg(data[0], data[2], data[4]);
 | 
			
		||||
 | 
			
		||||
    int16_t x_val = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_);
 | 
			
		||||
    int16_t y_val = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_);
 | 
			
		||||
    ESP_LOGVV(TAG, "Update [x, y] = [%d, %d], z = %d", this->x_raw, this->y_raw, this->z_raw);
 | 
			
		||||
 | 
			
		||||
    TouchPoint touchpoint;
 | 
			
		||||
 | 
			
		||||
    touchpoint.x = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_);
 | 
			
		||||
    touchpoint.y = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_);
 | 
			
		||||
 | 
			
		||||
    if (this->swap_x_y_) {
 | 
			
		||||
      std::swap(x_val, y_val);
 | 
			
		||||
      std::swap(touchpoint.x, touchpoint.y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this->invert_x_) {
 | 
			
		||||
      x_val = 0x7fff - x_val;
 | 
			
		||||
      touchpoint.x = 0xfff - touchpoint.x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this->invert_y_) {
 | 
			
		||||
      y_val = 0x7fff - y_val;
 | 
			
		||||
      touchpoint.y = 0xfff - touchpoint.y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    x_val = (int16_t)((int) x_val * this->x_dim_ / 0x7fff);
 | 
			
		||||
    y_val = (int16_t)((int) y_val * this->y_dim_ / 0x7fff);
 | 
			
		||||
    switch (static_cast<TouchRotation>(this->display_->get_rotation())) {
 | 
			
		||||
      case ROTATE_0_DEGREES:
 | 
			
		||||
        break;
 | 
			
		||||
      case ROTATE_90_DEGREES:
 | 
			
		||||
        std::swap(touchpoint.x, touchpoint.y);
 | 
			
		||||
        touchpoint.y = 0xfff - touchpoint.y;
 | 
			
		||||
        break;
 | 
			
		||||
      case ROTATE_180_DEGREES:
 | 
			
		||||
        touchpoint.x = 0xfff - touchpoint.x;
 | 
			
		||||
        touchpoint.y = 0xfff - touchpoint.y;
 | 
			
		||||
        break;
 | 
			
		||||
      case ROTATE_270_DEGREES:
 | 
			
		||||
        std::swap(touchpoint.x, touchpoint.y);
 | 
			
		||||
        touchpoint.x = 0xfff - touchpoint.x;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    touchpoint.x = (int16_t)((int) touchpoint.x * this->display_->get_width() / 0xfff);
 | 
			
		||||
    touchpoint.y = (int16_t)((int) touchpoint.y * this->display_->get_height() / 0xfff);
 | 
			
		||||
 | 
			
		||||
    if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
 | 
			
		||||
      ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val);
 | 
			
		||||
      ESP_LOGV(TAG, "Touching at [%03X, %03X] => [%3d, %3d]", this->x_raw, this->y_raw, touchpoint.x, touchpoint.y);
 | 
			
		||||
 | 
			
		||||
      this->x = x_val;
 | 
			
		||||
      this->y = y_val;
 | 
			
		||||
      this->defer([this, touchpoint]() { this->send_touch_(touchpoint); });
 | 
			
		||||
 | 
			
		||||
      this->x = touchpoint.x;
 | 
			
		||||
      this->y = touchpoint.y;
 | 
			
		||||
      this->touched = true;
 | 
			
		||||
      this->last_pos_ms_ = now;
 | 
			
		||||
 | 
			
		||||
      this->on_state_trigger_->process(this->x, this->y, true);
 | 
			
		||||
      for (auto *button : this->buttons_)
 | 
			
		||||
        button->touch(this->x, this->y);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->touched) {
 | 
			
		||||
      ESP_LOGD(TAG, "Released [%d, %d]", this->x, this->y);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      this->touched = false;
 | 
			
		||||
 | 
			
		||||
      this->on_state_trigger_->process(this->x, this->y, false);
 | 
			
		||||
      for (auto *button : this->buttons_)
 | 
			
		||||
        button->release();
 | 
			
		||||
    }
 | 
			
		||||
  if (!touch && this->touched) {
 | 
			
		||||
    this->x_raw = this->y_raw = this->z_raw = 0;
 | 
			
		||||
    ESP_LOGV(TAG, "Released [%d, %d]", this->x, this->y);
 | 
			
		||||
    this->touched = false;
 | 
			
		||||
    for (auto *listener : this->touch_listeners_)
 | 
			
		||||
      listener->release();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
 | 
			
		||||
void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {  // NOLINT
 | 
			
		||||
  this->x_raw_min_ = std::min(x_min, x_max);
 | 
			
		||||
  this->x_raw_max_ = std::max(x_min, x_max);
 | 
			
		||||
  this->y_raw_min_ = std::min(y_min, y_max);
 | 
			
		||||
@@ -137,11 +145,11 @@ void XPT2046Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  X max: %d", this->x_raw_max_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Y min: %d", this->y_raw_min_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Y max: %d", this->y_raw_max_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  X dim: %d", this->x_dim_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Y dim: %d", this->y_dim_);
 | 
			
		||||
  if (this->swap_x_y_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Swap X/Y");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Swap X/Y: %s", YESNO(this->swap_x_y_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Invert X: %s", YESNO(this->invert_x_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Invert Y: %s", YESNO(this->invert_y_));
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  threshold: %d", this->threshold_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Report interval: %u", this->report_millis_);
 | 
			
		||||
 | 
			
		||||
@@ -150,8 +158,8 @@ void XPT2046Component::dump_config() {
 | 
			
		||||
 | 
			
		||||
float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) {
 | 
			
		||||
  int16_t da, db, dc;
 | 
			
		||||
int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) {  // NOLINT
 | 
			
		||||
  int16_t da, db, dc;                                                      // NOLINT
 | 
			
		||||
  int16_t reta = 0;
 | 
			
		||||
 | 
			
		||||
  da = (x > y) ? x - y : y - x;
 | 
			
		||||
@@ -175,43 +183,24 @@ int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_va
 | 
			
		||||
  if (val <= min_val) {
 | 
			
		||||
    ret = 0;
 | 
			
		||||
  } else if (val >= max_val) {
 | 
			
		||||
    ret = 0x7fff;
 | 
			
		||||
    ret = 0xfff;
 | 
			
		||||
  } else {
 | 
			
		||||
    ret = (int16_t)((int) 0x7fff * (val - min_val) / (max_val - min_val));
 | 
			
		||||
    ret = (int16_t)((int) 0xfff * (val - min_val) / (max_val - min_val));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int16_t XPT2046Component::read_adc_(uint8_t ctrl) {
 | 
			
		||||
int16_t XPT2046Component::read_adc_(uint8_t ctrl) {  // NOLINT
 | 
			
		||||
  uint8_t data[2];
 | 
			
		||||
 | 
			
		||||
  write_byte(ctrl);
 | 
			
		||||
  delay(1);
 | 
			
		||||
  data[0] = read_byte();
 | 
			
		||||
  data[1] = read_byte();
 | 
			
		||||
 | 
			
		||||
  return ((data[0] << 8) | data[1]) >> 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XPT2046OnStateTrigger::process(int x, int y, bool touched) { this->trigger(x, y, touched); }
 | 
			
		||||
 | 
			
		||||
void XPT2046Button::touch(int16_t x, int16_t y) {
 | 
			
		||||
  bool touched = (x >= this->x_min_ && x <= this->x_max_ && y >= this->y_min_ && y <= this->y_max_);
 | 
			
		||||
 | 
			
		||||
  if (touched) {
 | 
			
		||||
    this->publish_state(true);
 | 
			
		||||
    this->state_ = true;
 | 
			
		||||
  } else {
 | 
			
		||||
    release();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XPT2046Button::release() {
 | 
			
		||||
  if (this->state_) {
 | 
			
		||||
    this->publish_state(false);
 | 
			
		||||
    this->state_ = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace xpt2046
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -3,42 +3,29 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#include "esphome/components/touchscreen/touchscreen.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace xpt2046 {
 | 
			
		||||
 | 
			
		||||
class XPT2046OnStateTrigger : public Trigger<int, int, bool> {
 | 
			
		||||
 public:
 | 
			
		||||
  void process(int x, int y, bool touched);
 | 
			
		||||
using namespace touchscreen;
 | 
			
		||||
 | 
			
		||||
struct XPT2046TouchscreenStore {
 | 
			
		||||
  volatile bool touch;
 | 
			
		||||
  static void gpio_intr(XPT2046TouchscreenStore *store);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class XPT2046Button : public binary_sensor::BinarySensor {
 | 
			
		||||
 public:
 | 
			
		||||
  /// Set the touch screen area where the button will detect the touch.
 | 
			
		||||
  void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
 | 
			
		||||
    this->x_min_ = x_min;
 | 
			
		||||
    this->x_max_ = x_max;
 | 
			
		||||
    this->y_min_ = y_min;
 | 
			
		||||
    this->y_max_ = y_max;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void touch(int16_t x, int16_t y);
 | 
			
		||||
  void release();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int16_t x_min_, x_max_, y_min_, y_max_;
 | 
			
		||||
  bool state_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class XPT2046Component : public PollingComponent,
 | 
			
		||||
class XPT2046Component : public Touchscreen,
 | 
			
		||||
                         public PollingComponent,
 | 
			
		||||
                         public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
 | 
			
		||||
                                               spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
 | 
			
		||||
 public:
 | 
			
		||||
  /// Set the logical touch screen dimensions.
 | 
			
		||||
  void set_dimensions(int16_t x, int16_t y) {
 | 
			
		||||
    this->x_dim_ = x;
 | 
			
		||||
    this->y_dim_ = y;
 | 
			
		||||
    this->display_width_ = x;
 | 
			
		||||
    this->display_height_ = y;
 | 
			
		||||
  }
 | 
			
		||||
  /// Set the coordinates for the touch screen edges.
 | 
			
		||||
  void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max);
 | 
			
		||||
@@ -47,14 +34,12 @@ class XPT2046Component : public PollingComponent,
 | 
			
		||||
 | 
			
		||||
  /// Set the interval to report the touch point perodically.
 | 
			
		||||
  void set_report_interval(uint32_t interval) { this->report_millis_ = interval; }
 | 
			
		||||
  uint32_t get_report_interval() { return this->report_millis_; }
 | 
			
		||||
 | 
			
		||||
  /// Set the threshold for the touch detection.
 | 
			
		||||
  void set_threshold(int16_t threshold) { this->threshold_ = threshold; }
 | 
			
		||||
  /// Set the pin used to detect the touch.
 | 
			
		||||
  void set_irq_pin(GPIOPin *pin) { this->irq_pin_ = pin; }
 | 
			
		||||
  /// Get an access to the on_state automation trigger
 | 
			
		||||
  XPT2046OnStateTrigger *get_on_state_trigger() const { return this->on_state_trigger_; }
 | 
			
		||||
  /// Register a virtual button to the component.
 | 
			
		||||
  void register_button(XPT2046Button *button) { this->buttons_.push_back(button); }
 | 
			
		||||
  void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -103,21 +88,19 @@ class XPT2046Component : public PollingComponent,
 | 
			
		||||
  static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val);
 | 
			
		||||
 | 
			
		||||
  int16_t read_adc_(uint8_t ctrl);
 | 
			
		||||
  void check_touch_();
 | 
			
		||||
 | 
			
		||||
  int16_t threshold_;
 | 
			
		||||
  int16_t x_raw_min_, x_raw_max_, y_raw_min_, y_raw_max_;
 | 
			
		||||
  int16_t x_dim_, y_dim_;
 | 
			
		||||
 | 
			
		||||
  bool invert_x_, invert_y_;
 | 
			
		||||
  bool swap_x_y_;
 | 
			
		||||
 | 
			
		||||
  uint32_t report_millis_;
 | 
			
		||||
  uint32_t last_pos_ms_{0};
 | 
			
		||||
 | 
			
		||||
  GPIOPin *irq_pin_{nullptr};
 | 
			
		||||
  bool last_irq_{true};
 | 
			
		||||
 | 
			
		||||
  XPT2046OnStateTrigger *on_state_trigger_{new XPT2046OnStateTrigger()};
 | 
			
		||||
  std::vector<XPT2046Button *> buttons_{};
 | 
			
		||||
  InternalGPIOPin *irq_pin_{nullptr};
 | 
			
		||||
  XPT2046TouchscreenStore store_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace xpt2046
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ from esphome.core import CORE, EsphomeError
 | 
			
		||||
from esphome.helpers import indent
 | 
			
		||||
from esphome.util import safe_print, OrderedDict
 | 
			
		||||
 | 
			
		||||
from typing import List, Optional, Tuple, Union
 | 
			
		||||
from typing import Optional, Union
 | 
			
		||||
from esphome.loader import get_component, get_platform, ComponentManifest
 | 
			
		||||
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
 | 
			
		||||
from esphome.voluptuous_schema import ExtraKeysInvalid
 | 
			
		||||
@@ -50,10 +50,10 @@ def iter_components(config):
 | 
			
		||||
                yield p_name, platform, p_config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ConfigPath = List[Union[str, int]]
 | 
			
		||||
ConfigPath = list[Union[str, int]]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _path_begins_with(path, other):  # type: (ConfigPath, ConfigPath) -> bool
 | 
			
		||||
def _path_begins_with(path: ConfigPath, other: ConfigPath) -> bool:
 | 
			
		||||
    if len(path) < len(other):
 | 
			
		||||
        return False
 | 
			
		||||
    return path[: len(other)] == other
 | 
			
		||||
@@ -67,7 +67,7 @@ class _ValidationStepTask:
 | 
			
		||||
        self.step = step
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def _cmp_tuple(self) -> Tuple[float, int]:
 | 
			
		||||
    def _cmp_tuple(self) -> tuple[float, int]:
 | 
			
		||||
        return (-self.priority, self.id_number)
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
@@ -84,21 +84,20 @@ class Config(OrderedDict, fv.FinalValidateConfig):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        # A list of voluptuous errors
 | 
			
		||||
        self.errors = []  # type: List[vol.Invalid]
 | 
			
		||||
        self.errors: list[vol.Invalid] = []
 | 
			
		||||
        # A list of paths that should be fully outputted
 | 
			
		||||
        # The values will be the paths to all "domain", for example (['logger'], 'logger')
 | 
			
		||||
        # or (['sensor', 'ultrasonic'], 'sensor.ultrasonic')
 | 
			
		||||
        self.output_paths = []  # type: List[Tuple[ConfigPath, str]]
 | 
			
		||||
        self.output_paths: list[tuple[ConfigPath, str]] = []
 | 
			
		||||
        # A list of components ids with the config path
 | 
			
		||||
        self.declare_ids = []  # type: List[Tuple[core.ID, ConfigPath]]
 | 
			
		||||
        self.declare_ids: list[tuple[core.ID, ConfigPath]] = []
 | 
			
		||||
        self._data = {}
 | 
			
		||||
        # Store pending validation tasks (in heap order)
 | 
			
		||||
        self._validation_tasks: List[_ValidationStepTask] = []
 | 
			
		||||
        self._validation_tasks: list[_ValidationStepTask] = []
 | 
			
		||||
        # ID to ensure stable order for keys with equal priority
 | 
			
		||||
        self._validation_tasks_id = 0
 | 
			
		||||
 | 
			
		||||
    def add_error(self, error):
 | 
			
		||||
        # type: (vol.Invalid) -> None
 | 
			
		||||
    def add_error(self, error: vol.Invalid) -> None:
 | 
			
		||||
        if isinstance(error, vol.MultipleInvalid):
 | 
			
		||||
            for err in error.errors:
 | 
			
		||||
                self.add_error(err)
 | 
			
		||||
@@ -132,20 +131,16 @@ class Config(OrderedDict, fv.FinalValidateConfig):
 | 
			
		||||
            e.prepend(path)
 | 
			
		||||
            self.add_error(e)
 | 
			
		||||
 | 
			
		||||
    def add_str_error(self, message, path):
 | 
			
		||||
        # type: (str, ConfigPath) -> None
 | 
			
		||||
    def add_str_error(self, message: str, path: ConfigPath) -> None:
 | 
			
		||||
        self.add_error(vol.Invalid(message, path))
 | 
			
		||||
 | 
			
		||||
    def add_output_path(self, path, domain):
 | 
			
		||||
        # type: (ConfigPath, str) -> None
 | 
			
		||||
    def add_output_path(self, path: ConfigPath, domain: str) -> None:
 | 
			
		||||
        self.output_paths.append((path, domain))
 | 
			
		||||
 | 
			
		||||
    def remove_output_path(self, path, domain):
 | 
			
		||||
        # type: (ConfigPath, str) -> None
 | 
			
		||||
    def remove_output_path(self, path: ConfigPath, domain: str) -> None:
 | 
			
		||||
        self.output_paths.remove((path, domain))
 | 
			
		||||
 | 
			
		||||
    def is_in_error_path(self, path):
 | 
			
		||||
        # type: (ConfigPath) -> bool
 | 
			
		||||
    def is_in_error_path(self, path: ConfigPath) -> bool:
 | 
			
		||||
        for err in self.errors:
 | 
			
		||||
            if _path_begins_with(err.path, path):
 | 
			
		||||
                return True
 | 
			
		||||
@@ -157,16 +152,16 @@ class Config(OrderedDict, fv.FinalValidateConfig):
 | 
			
		||||
            conf = conf[key]
 | 
			
		||||
        conf[path[-1]] = value
 | 
			
		||||
 | 
			
		||||
    def get_error_for_path(self, path):
 | 
			
		||||
        # type: (ConfigPath) -> Optional[vol.Invalid]
 | 
			
		||||
    def get_error_for_path(self, path: ConfigPath) -> Optional[vol.Invalid]:
 | 
			
		||||
        for err in self.errors:
 | 
			
		||||
            if self.get_deepest_path(err.path) == path:
 | 
			
		||||
                self.errors.remove(err)
 | 
			
		||||
                return err
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def get_deepest_document_range_for_path(self, path, get_key=False):
 | 
			
		||||
        # type: (ConfigPath, bool) -> Optional[ESPHomeDataBase]
 | 
			
		||||
    def get_deepest_document_range_for_path(
 | 
			
		||||
        self, path: ConfigPath, get_key: bool = False
 | 
			
		||||
    ) -> Optional[ESPHomeDataBase]:
 | 
			
		||||
        data = self
 | 
			
		||||
        doc_range = None
 | 
			
		||||
        for index, path_item in enumerate(path):
 | 
			
		||||
@@ -207,8 +202,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
 | 
			
		||||
                return {}
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
    def get_deepest_path(self, path):
 | 
			
		||||
        # type: (ConfigPath) -> ConfigPath
 | 
			
		||||
    def get_deepest_path(self, path: ConfigPath) -> ConfigPath:
 | 
			
		||||
        """Return the path that is the deepest reachable by following path."""
 | 
			
		||||
        data = self
 | 
			
		||||
        part = []
 | 
			
		||||
@@ -532,7 +526,7 @@ class IDPassValidationStep(ConfigValidationStep):
 | 
			
		||||
            # because the component that did not validate doesn't have any IDs set
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        searching_ids = []  # type: List[Tuple[core.ID, ConfigPath]]
 | 
			
		||||
        searching_ids: list[tuple[core.ID, ConfigPath]] = []
 | 
			
		||||
        for id, path in iter_ids(result):
 | 
			
		||||
            if id.is_declaration:
 | 
			
		||||
                if id.id is not None:
 | 
			
		||||
@@ -780,8 +774,7 @@ def _get_parent_name(path, config):
 | 
			
		||||
    return path[-1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _format_vol_invalid(ex, config):
 | 
			
		||||
    # type: (vol.Invalid, Config) -> str
 | 
			
		||||
def _format_vol_invalid(ex: vol.Invalid, config: Config) -> str:
 | 
			
		||||
    message = ""
 | 
			
		||||
 | 
			
		||||
    paren = _get_parent_name(ex.path[:-1], config)
 | 
			
		||||
@@ -862,8 +855,9 @@ def _print_on_next_line(obj):
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dump_dict(config, path, at_root=True):
 | 
			
		||||
    # type: (Config, ConfigPath, bool) -> Tuple[str, bool]
 | 
			
		||||
def dump_dict(
 | 
			
		||||
    config: Config, path: ConfigPath, at_root: bool = True
 | 
			
		||||
) -> tuple[str, bool]:
 | 
			
		||||
    conf = config.get_nested_item(path)
 | 
			
		||||
    ret = ""
 | 
			
		||||
    multiline = False
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,7 @@ from esphome.core import CORE
 | 
			
		||||
from esphome.helpers import read_file
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read_config_file(path):
 | 
			
		||||
    # type: (str) -> str
 | 
			
		||||
def read_config_file(path: str) -> str:
 | 
			
		||||
    if CORE.vscode and (
 | 
			
		||||
        not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
 | 
			
		||||
    ):
 | 
			
		||||
 
 | 
			
		||||
@@ -1689,7 +1689,7 @@ class Version:
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def parse(cls, value: str) -> "Version":
 | 
			
		||||
        match = re.match(r"(\d+).(\d+).(\d+)", value)
 | 
			
		||||
        match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
 | 
			
		||||
        if match is None:
 | 
			
		||||
            raise ValueError(f"Not a valid version number {value}")
 | 
			
		||||
        major = int(match[1])
 | 
			
		||||
@@ -1703,7 +1703,7 @@ def version_number(value):
 | 
			
		||||
    try:
 | 
			
		||||
        return str(Version.parse(value))
 | 
			
		||||
    except ValueError as e:
 | 
			
		||||
        raise Invalid("Not a version number") from e
 | 
			
		||||
        raise Invalid("Not a valid version number") from e
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def platformio_version_constraint(value):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
__version__ = "2022.9.0b4"
 | 
			
		||||
__version__ = "2022.10.1"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +23,7 @@ CONF_ACCURACY = "accuracy"
 | 
			
		||||
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
 | 
			
		||||
CONF_ACTION_ID = "action_id"
 | 
			
		||||
CONF_ACTION_STATE_TOPIC = "action_state_topic"
 | 
			
		||||
CONF_ACTIVE = "active"
 | 
			
		||||
CONF_ACTIVE_POWER = "active_power"
 | 
			
		||||
CONF_ADDRESS = "address"
 | 
			
		||||
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
 | 
			
		||||
@@ -395,6 +396,7 @@ CONF_MIN_POWER = "min_power"
 | 
			
		||||
CONF_MIN_RANGE = "min_range"
 | 
			
		||||
CONF_MIN_TEMPERATURE = "min_temperature"
 | 
			
		||||
CONF_MIN_VALUE = "min_value"
 | 
			
		||||
CONF_MIN_VERSION = "min_version"
 | 
			
		||||
CONF_MINUTE = "minute"
 | 
			
		||||
CONF_MINUTES = "minutes"
 | 
			
		||||
CONF_MISO_PIN = "miso_pin"
 | 
			
		||||
@@ -903,7 +905,6 @@ DEVICE_CLASS_GARAGE_DOOR = "garage_door"
 | 
			
		||||
DEVICE_CLASS_HEAT = "heat"
 | 
			
		||||
DEVICE_CLASS_LIGHT = "light"
 | 
			
		||||
DEVICE_CLASS_LOCK = "lock"
 | 
			
		||||
DEVICE_CLASS_MOISTURE = "moisture"
 | 
			
		||||
DEVICE_CLASS_MOTION = "motion"
 | 
			
		||||
DEVICE_CLASS_MOVING = "moving"
 | 
			
		||||
DEVICE_CLASS_OCCUPANCY = "occupancy"
 | 
			
		||||
@@ -921,15 +922,17 @@ DEVICE_CLASS_WINDOW = "window"
 | 
			
		||||
# device classes of both binary_sensor and sensor component
 | 
			
		||||
DEVICE_CLASS_EMPTY = ""
 | 
			
		||||
DEVICE_CLASS_BATTERY = "battery"
 | 
			
		||||
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
 | 
			
		||||
DEVICE_CLASS_GAS = "gas"
 | 
			
		||||
DEVICE_CLASS_MOISTURE = "moisture"
 | 
			
		||||
DEVICE_CLASS_POWER = "power"
 | 
			
		||||
# device classes of sensor component
 | 
			
		||||
DEVICE_CLASS_APPARENT_POWER = "apparent_power"
 | 
			
		||||
DEVICE_CLASS_AQI = "aqi"
 | 
			
		||||
DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide"
 | 
			
		||||
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
 | 
			
		||||
DEVICE_CLASS_CURRENT = "current"
 | 
			
		||||
DEVICE_CLASS_DATE = "date"
 | 
			
		||||
DEVICE_CLASS_DISTANCE = "distance"
 | 
			
		||||
DEVICE_CLASS_DURATION = "duration"
 | 
			
		||||
DEVICE_CLASS_ENERGY = "energy"
 | 
			
		||||
DEVICE_CLASS_FREQUENCY = "frequency"
 | 
			
		||||
@@ -944,14 +947,20 @@ DEVICE_CLASS_PM1 = "pm1"
 | 
			
		||||
DEVICE_CLASS_PM10 = "pm10"
 | 
			
		||||
DEVICE_CLASS_PM25 = "pm25"
 | 
			
		||||
DEVICE_CLASS_POWER_FACTOR = "power_factor"
 | 
			
		||||
DEVICE_CLASS_PRECIPITATION_INTENSITY = "precipitation_intensity"
 | 
			
		||||
DEVICE_CLASS_PRESSURE = "pressure"
 | 
			
		||||
DEVICE_CLASS_REACTIVE_POWER = "reactive_power"
 | 
			
		||||
DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength"
 | 
			
		||||
DEVICE_CLASS_SPEED = "speed"
 | 
			
		||||
DEVICE_CLASS_SULPHUR_DIOXIDE = "sulphur_dioxide"
 | 
			
		||||
DEVICE_CLASS_TEMPERATURE = "temperature"
 | 
			
		||||
DEVICE_CLASS_TIMESTAMP = "timestamp"
 | 
			
		||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS = "volatile_organic_compounds"
 | 
			
		||||
DEVICE_CLASS_VOLTAGE = "voltage"
 | 
			
		||||
DEVICE_CLASS_VOLUME = "volume"
 | 
			
		||||
DEVICE_CLASS_WATER = "water"
 | 
			
		||||
DEVICE_CLASS_WIND_SPEED = "wind_speed"
 | 
			
		||||
DEVICE_CLASS_WEIGHT = "weight"
 | 
			
		||||
# device classes of both binary_sensor and button component
 | 
			
		||||
DEVICE_CLASS_UPDATE = "update"
 | 
			
		||||
# device classes of button component
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import logging
 | 
			
		||||
import math
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
 | 
			
		||||
from typing import TYPE_CHECKING, Optional, Union
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_COMMENT,
 | 
			
		||||
@@ -469,19 +469,19 @@ class EsphomeCore:
 | 
			
		||||
        # Task counter for pending tasks
 | 
			
		||||
        self.task_counter = 0
 | 
			
		||||
        # The variable cache, for each ID this holds a MockObj of the variable obj
 | 
			
		||||
        self.variables: Dict[str, "MockObj"] = {}
 | 
			
		||||
        self.variables: dict[str, "MockObj"] = {}
 | 
			
		||||
        # A list of statements that go in the main setup() block
 | 
			
		||||
        self.main_statements: List["Statement"] = []
 | 
			
		||||
        self.main_statements: list["Statement"] = []
 | 
			
		||||
        # A list of statements to insert in the global block (includes and global variables)
 | 
			
		||||
        self.global_statements: List["Statement"] = []
 | 
			
		||||
        self.global_statements: list["Statement"] = []
 | 
			
		||||
        # A set of platformio libraries to add to the project
 | 
			
		||||
        self.libraries: List[Library] = []
 | 
			
		||||
        self.libraries: list[Library] = []
 | 
			
		||||
        # A set of build flags to set in the platformio project
 | 
			
		||||
        self.build_flags: Set[str] = set()
 | 
			
		||||
        self.build_flags: set[str] = set()
 | 
			
		||||
        # A set of defines to set for the compile process in esphome/core/defines.h
 | 
			
		||||
        self.defines: Set["Define"] = set()
 | 
			
		||||
        self.defines: set["Define"] = set()
 | 
			
		||||
        # A map of all platformio options to apply
 | 
			
		||||
        self.platformio_options: Dict[str, Union[str, List[str]]] = {}
 | 
			
		||||
        self.platformio_options: dict[str, Union[str, list[str]]] = {}
 | 
			
		||||
        # A set of strings of names of loaded integrations, used to find namespace ID conflicts
 | 
			
		||||
        self.loaded_integrations = set()
 | 
			
		||||
        # A set of component IDs to track what Component subclasses are declared
 | 
			
		||||
@@ -701,7 +701,7 @@ class EsphomeCore:
 | 
			
		||||
        _LOGGER.debug("Adding define: %s", define)
 | 
			
		||||
        return define
 | 
			
		||||
 | 
			
		||||
    def add_platformio_option(self, key: str, value: Union[str, List[str]]) -> None:
 | 
			
		||||
    def add_platformio_option(self, key: str, value: Union[str, list[str]]) -> None:
 | 
			
		||||
        new_val = value
 | 
			
		||||
        old_val = self.platformio_options.get(key)
 | 
			
		||||
        if isinstance(old_val, list):
 | 
			
		||||
@@ -734,7 +734,7 @@ class EsphomeCore:
 | 
			
		||||
            _LOGGER.debug("Waiting for variable %s", id)
 | 
			
		||||
            yield
 | 
			
		||||
 | 
			
		||||
    async def get_variable_with_full_id(self, id: ID) -> Tuple[ID, "MockObj"]:
 | 
			
		||||
    async def get_variable_with_full_id(self, id: ID) -> tuple[ID, "MockObj"]:
 | 
			
		||||
        if not isinstance(id, ID):
 | 
			
		||||
            raise ValueError(f"ID {id!r} must be of type ID!")
 | 
			
		||||
        return await _FakeAwaitable(self._get_variable_with_full_id_generator(id))
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_FRAMEWORK,
 | 
			
		||||
    CONF_INCLUDES,
 | 
			
		||||
    CONF_LIBRARIES,
 | 
			
		||||
    CONF_MIN_VERSION,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_ON_BOOT,
 | 
			
		||||
    CONF_ON_LOOP,
 | 
			
		||||
@@ -30,6 +31,7 @@ from esphome.const import (
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    TARGET_PLATFORMS,
 | 
			
		||||
    PLATFORM_ESP8266,
 | 
			
		||||
    __version__ as ESPHOME_VERSION,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.helpers import copy_file_if_changed, walk_files
 | 
			
		||||
@@ -96,6 +98,16 @@ def valid_project_name(value: str):
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_version(value: str):
 | 
			
		||||
    min_version = cv.Version.parse(value)
 | 
			
		||||
    current_version = cv.Version.parse(ESPHOME_VERSION)
 | 
			
		||||
    if current_version < min_version:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"Your ESPHome version is too old. Please update to at least {min_version}"
 | 
			
		||||
        )
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash"
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
@@ -136,6 +148,9 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                    cv.Required(CONF_VERSION): cv.string_strict,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All(
 | 
			
		||||
                cv.version_number, validate_version
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    validate_hostname,
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,8 @@ import heapq
 | 
			
		||||
import inspect
 | 
			
		||||
import logging
 | 
			
		||||
import types
 | 
			
		||||
from typing import Any, Awaitable, Callable, Generator, Iterator, List, Tuple
 | 
			
		||||
from typing import Any, Callable
 | 
			
		||||
from collections.abc import Awaitable, Generator, Iterator
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -177,7 +178,7 @@ class _Task:
 | 
			
		||||
        return _Task(priority, self.id_number, self.iterator, self.original_function)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def _cmp_tuple(self) -> Tuple[float, int]:
 | 
			
		||||
    def _cmp_tuple(self) -> tuple[float, int]:
 | 
			
		||||
        return (-self.priority, self.id_number)
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
@@ -194,7 +195,7 @@ class FakeEventLoop:
 | 
			
		||||
    """Emulate an asyncio EventLoop to run some registered coroutine jobs in sequence."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self._pending_tasks: List[_Task] = []
 | 
			
		||||
        self._pending_tasks: list[_Task] = []
 | 
			
		||||
        self._task_counter = 0
 | 
			
		||||
 | 
			
		||||
    def add_job(self, func, *args, **kwargs):
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,13 @@ import re
 | 
			
		||||
from esphome.yaml_util import ESPHomeDataBase
 | 
			
		||||
 | 
			
		||||
# pylint: disable=unused-import, wrong-import-order
 | 
			
		||||
from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence
 | 
			
		||||
from typing import (
 | 
			
		||||
    Any,
 | 
			
		||||
    Callable,
 | 
			
		||||
    Optional,
 | 
			
		||||
    Union,
 | 
			
		||||
)
 | 
			
		||||
from collections.abc import Generator, Sequence
 | 
			
		||||
 | 
			
		||||
from esphome.core import (  # noqa
 | 
			
		||||
    CORE,
 | 
			
		||||
@@ -44,9 +50,9 @@ SafeExpType = Union[
 | 
			
		||||
    int,
 | 
			
		||||
    float,
 | 
			
		||||
    TimePeriod,
 | 
			
		||||
    Type[bool],
 | 
			
		||||
    Type[int],
 | 
			
		||||
    Type[float],
 | 
			
		||||
    type[bool],
 | 
			
		||||
    type[int],
 | 
			
		||||
    type[float],
 | 
			
		||||
    Sequence[Any],
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -140,7 +146,7 @@ class CallExpression(Expression):
 | 
			
		||||
class StructInitializer(Expression):
 | 
			
		||||
    __slots__ = ("base", "args")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, base: Expression, *args: Tuple[str, Optional[SafeExpType]]):
 | 
			
		||||
    def __init__(self, base: Expression, *args: tuple[str, Optional[SafeExpType]]):
 | 
			
		||||
        self.base = base
 | 
			
		||||
        # TODO: args is always a Tuple, is this check required?
 | 
			
		||||
        if not isinstance(args, OrderedDict):
 | 
			
		||||
@@ -200,7 +206,7 @@ class ParameterListExpression(Expression):
 | 
			
		||||
    __slots__ = ("parameters",)
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self, *parameters: Union[ParameterExpression, Tuple[SafeExpType, str]]
 | 
			
		||||
        self, *parameters: Union[ParameterExpression, tuple[SafeExpType, str]]
 | 
			
		||||
    ):
 | 
			
		||||
        self.parameters = []
 | 
			
		||||
        for parameter in parameters:
 | 
			
		||||
@@ -468,7 +474,9 @@ def statement(expression: Union[Expression, Statement]) -> Statement:
 | 
			
		||||
    return ExpressionStatement(expression)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
 | 
			
		||||
def variable(
 | 
			
		||||
    id_: ID, rhs: SafeExpType, type_: "MockObj" = None, register=True
 | 
			
		||||
) -> "MockObj":
 | 
			
		||||
    """Declare a new variable, not pointer type, in the code generation.
 | 
			
		||||
 | 
			
		||||
    :param id_: The ID used to declare the variable.
 | 
			
		||||
@@ -485,10 +493,37 @@ def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
 | 
			
		||||
        id_.type = type_
 | 
			
		||||
    assignment = AssignmentExpression(id_.type, "", id_, rhs)
 | 
			
		||||
    CORE.add(assignment)
 | 
			
		||||
    CORE.register_variable(id_, obj)
 | 
			
		||||
    if register:
 | 
			
		||||
        CORE.register_variable(id_, obj)
 | 
			
		||||
    return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def with_local_variable(
 | 
			
		||||
    id_: ID, rhs: SafeExpType, callback: Callable[["MockObj"], None], *args
 | 
			
		||||
) -> None:
 | 
			
		||||
    """Declare a new variable, not pointer type, in the code generation, within a scoped block
 | 
			
		||||
    The variable is only usable within the callback
 | 
			
		||||
    The callback cannot be async.
 | 
			
		||||
 | 
			
		||||
    :param id_: The ID used to declare the variable.
 | 
			
		||||
    :param rhs: The expression to place on the right hand side of the assignment.
 | 
			
		||||
    :param callback: The function to invoke that will receive the temporary variable
 | 
			
		||||
    :param args: args to pass to the callback in addition to the temporary variable
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # throw if the callback is async:
 | 
			
		||||
    assert not inspect.iscoroutinefunction(
 | 
			
		||||
        callback
 | 
			
		||||
    ), "with_local_variable() callback cannot be async!"
 | 
			
		||||
 | 
			
		||||
    CORE.add(RawStatement("{"))  # output opening curly brace
 | 
			
		||||
    obj = variable(id_, rhs, None, True)
 | 
			
		||||
    # invoke user-provided callback to generate code with this local variable
 | 
			
		||||
    callback(obj, *args)
 | 
			
		||||
    CORE.add(RawStatement("}"))  # output closing curly brace
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
 | 
			
		||||
    """Declare and define a new variable, not pointer type, in the code generation.
 | 
			
		||||
 | 
			
		||||
@@ -590,7 +625,7 @@ def add_define(name: str, value: SafeExpType = None):
 | 
			
		||||
        CORE.add_define(Define(name, safe_exp(value)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_platformio_option(key: str, value: Union[str, List[str]]):
 | 
			
		||||
def add_platformio_option(key: str, value: Union[str, list[str]]):
 | 
			
		||||
    CORE.add_platformio_option(key, value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -607,7 +642,7 @@ async def get_variable(id_: ID) -> "MockObj":
 | 
			
		||||
    return await CORE.get_variable(id_)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]:
 | 
			
		||||
async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]:
 | 
			
		||||
    """
 | 
			
		||||
    Wait for the given ID to be defined in the code generation and
 | 
			
		||||
    return it as a MockObj.
 | 
			
		||||
@@ -622,7 +657,7 @@ async def get_variable_with_full_id(id_: ID) -> Tuple[ID, "MockObj"]:
 | 
			
		||||
 | 
			
		||||
async def process_lambda(
 | 
			
		||||
    value: Lambda,
 | 
			
		||||
    parameters: List[Tuple[SafeExpType, str]],
 | 
			
		||||
    parameters: list[tuple[SafeExpType, str]],
 | 
			
		||||
    capture: str = "=",
 | 
			
		||||
    return_type: SafeExpType = None,
 | 
			
		||||
) -> Generator[LambdaExpression, None, None]:
 | 
			
		||||
@@ -676,7 +711,7 @@ def is_template(value):
 | 
			
		||||
 | 
			
		||||
async def templatable(
 | 
			
		||||
    value: Any,
 | 
			
		||||
    args: List[Tuple[SafeExpType, str]],
 | 
			
		||||
    args: list[tuple[SafeExpType, str]],
 | 
			
		||||
    output_type: Optional[SafeExpType],
 | 
			
		||||
    to_exp: Any = None,
 | 
			
		||||
):
 | 
			
		||||
@@ -724,7 +759,7 @@ class MockObj(Expression):
 | 
			
		||||
            attr = attr[1:]
 | 
			
		||||
        return MockObj(f"{self.base}{self.op}{attr}", next_op)
 | 
			
		||||
 | 
			
		||||
    def __call__(self, *args):  # type: (SafeExpType) -> MockObj
 | 
			
		||||
    def __call__(self, *args: SafeExpType) -> "MockObj":
 | 
			
		||||
        call = CallExpression(self.base, *args)
 | 
			
		||||
        return MockObj(call, self.op)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ from esphome.const import (
 | 
			
		||||
 | 
			
		||||
# pylint: disable=unused-import
 | 
			
		||||
from esphome.core import coroutine, ID, CORE
 | 
			
		||||
from esphome.types import ConfigType
 | 
			
		||||
from esphome.types import ConfigType, ConfigFragmentType
 | 
			
		||||
from esphome.cpp_generator import add, get_variable
 | 
			
		||||
from esphome.cpp_types import App
 | 
			
		||||
from esphome.util import Registry, RegistryEntry
 | 
			
		||||
@@ -107,8 +107,10 @@ async def setup_entity(var, config):
 | 
			
		||||
        add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extract_registry_entry_config(registry, full_config):
 | 
			
		||||
    # type: (Registry, ConfigType) -> RegistryEntry
 | 
			
		||||
def extract_registry_entry_config(
 | 
			
		||||
    registry: Registry,
 | 
			
		||||
    full_config: ConfigType,
 | 
			
		||||
) -> tuple[RegistryEntry, ConfigFragmentType]:
 | 
			
		||||
    key, config = next((k, v) for k, v in full_config.items() if k in registry)
 | 
			
		||||
    return registry[key], config
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -395,11 +395,22 @@ class ImportRequestHandler(BaseHandler):
 | 
			
		||||
        args = json.loads(self.request.body.decode())
 | 
			
		||||
        try:
 | 
			
		||||
            name = args["name"]
 | 
			
		||||
 | 
			
		||||
            imported_device = next(
 | 
			
		||||
                (res for res in IMPORT_RESULT.values() if res.device_name == name), None
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            if imported_device is not None:
 | 
			
		||||
                network = imported_device.network
 | 
			
		||||
            else:
 | 
			
		||||
                network = const.CONF_WIFI
 | 
			
		||||
 | 
			
		||||
            import_config(
 | 
			
		||||
                settings.rel_path(f"{name}.yaml"),
 | 
			
		||||
                name,
 | 
			
		||||
                args["project_name"],
 | 
			
		||||
                args["package_import_url"],
 | 
			
		||||
                network,
 | 
			
		||||
            )
 | 
			
		||||
        except FileExistsError:
 | 
			
		||||
            self.set_status(500)
 | 
			
		||||
@@ -522,7 +533,7 @@ class DashboardEntry:
 | 
			
		||||
        return os.path.basename(self.path)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def storage(self):  # type: () -> Optional[StorageJSON]
 | 
			
		||||
    def storage(self) -> Optional[StorageJSON]:
 | 
			
		||||
        if not self._loaded_storage:
 | 
			
		||||
            self._storage = StorageJSON.load(
 | 
			
		||||
                ext_storage_path(settings.config_dir, self.filename)
 | 
			
		||||
@@ -613,6 +624,7 @@ class ListDevicesHandler(BaseHandler):
 | 
			
		||||
                            "package_import_url": res.package_import_url,
 | 
			
		||||
                            "project_name": res.project_name,
 | 
			
		||||
                            "project_version": res.project_version,
 | 
			
		||||
                            "network": res.network,
 | 
			
		||||
                        }
 | 
			
		||||
                        for res in IMPORT_RESULT.values()
 | 
			
		||||
                        if res.device_name not in configured
 | 
			
		||||
@@ -817,7 +829,7 @@ class UndoDeleteRequestHandler(BaseHandler):
 | 
			
		||||
        shutil.move(os.path.join(trash_path, configuration), config_file)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
PING_RESULT = {}  # type: dict
 | 
			
		||||
PING_RESULT: dict = {}
 | 
			
		||||
IMPORT_RESULT = {}
 | 
			
		||||
STOP_EVENT = threading.Event()
 | 
			
		||||
PING_REQUEST = threading.Event()
 | 
			
		||||
@@ -933,7 +945,7 @@ def get_static_path(*args):
 | 
			
		||||
    return os.path.join(get_base_frontend_path(), "static", *args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@functools.lru_cache(maxsize=None)
 | 
			
		||||
@functools.cache
 | 
			
		||||
def get_static_file_url(name):
 | 
			
		||||
    base = f"./static/{name}"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
from abc import ABC, abstractmethod
 | 
			
		||||
from typing import Dict, Any
 | 
			
		||||
from typing import Any
 | 
			
		||||
import contextvars
 | 
			
		||||
 | 
			
		||||
from esphome.types import ConfigFragmentType, ID, ConfigPathType
 | 
			
		||||
@@ -9,7 +9,7 @@ import esphome.config_validation as cv
 | 
			
		||||
class FinalValidateConfig(ABC):
 | 
			
		||||
    @property
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def data(self) -> Dict[str, Any]:
 | 
			
		||||
    def data(self) -> dict[str, Any]:
 | 
			
		||||
        """A dictionary that can be used by post validation functions to store
 | 
			
		||||
        global data during the validation phase. Each component should store its
 | 
			
		||||
        data under a unique key
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ from pathlib import Path
 | 
			
		||||
import subprocess
 | 
			
		||||
import hashlib
 | 
			
		||||
import logging
 | 
			
		||||
from typing import Callable, Optional
 | 
			
		||||
import urllib.parse
 | 
			
		||||
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
@@ -12,7 +13,7 @@ import esphome.config_validation as cv
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_git_command(cmd, cwd=None):
 | 
			
		||||
def run_git_command(cmd, cwd=None) -> str:
 | 
			
		||||
    try:
 | 
			
		||||
        ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
 | 
			
		||||
    except FileNotFoundError as err:
 | 
			
		||||
@@ -28,6 +29,8 @@ def run_git_command(cmd, cwd=None):
 | 
			
		||||
            raise cv.Invalid(lines[-1][len("fatal: ") :])
 | 
			
		||||
        raise cv.Invalid(err_str)
 | 
			
		||||
 | 
			
		||||
    return ret.stdout.decode("utf-8").strip()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _compute_destination_path(key: str, domain: str) -> Path:
 | 
			
		||||
    base_dir = Path(CORE.config_dir) / ".esphome" / domain
 | 
			
		||||
@@ -44,7 +47,7 @@ def clone_or_update(
 | 
			
		||||
    domain: str,
 | 
			
		||||
    username: str = None,
 | 
			
		||||
    password: str = None,
 | 
			
		||||
) -> Path:
 | 
			
		||||
) -> tuple[Path, Optional[Callable[[], None]]]:
 | 
			
		||||
    key = f"{url}@{ref}"
 | 
			
		||||
 | 
			
		||||
    if username is not None and password is not None:
 | 
			
		||||
@@ -78,6 +81,7 @@ def clone_or_update(
 | 
			
		||||
            file_timestamp = Path(repo_dir / ".git" / "HEAD")
 | 
			
		||||
        age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime)
 | 
			
		||||
        if age.total_seconds() > refresh.total_seconds:
 | 
			
		||||
            old_sha = run_git_command(["git", "rev-parse", "HEAD"], str(repo_dir))
 | 
			
		||||
            _LOGGER.info("Updating %s", key)
 | 
			
		||||
            _LOGGER.debug("Location: %s", repo_dir)
 | 
			
		||||
            # Stash local changes (if any)
 | 
			
		||||
@@ -92,4 +96,10 @@ def clone_or_update(
 | 
			
		||||
            # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
 | 
			
		||||
            run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
 | 
			
		||||
 | 
			
		||||
    return repo_dir
 | 
			
		||||
            def revert():
 | 
			
		||||
                _LOGGER.info("Reverting changes to %s -> %s", key, old_sha)
 | 
			
		||||
                run_git_command(["git", "reset", "--hard", old_sha], str(repo_dir))
 | 
			
		||||
 | 
			
		||||
            return repo_dir, revert
 | 
			
		||||
 | 
			
		||||
    return repo_dir, None
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Union
 | 
			
		||||
import tempfile
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +41,7 @@ def indent(text, padding="  "):
 | 
			
		||||
 | 
			
		||||
# From https://stackoverflow.com/a/14945195/8924614
 | 
			
		||||
def cpp_string_escape(string, encoding="utf-8"):
 | 
			
		||||
    def _should_escape(byte):  # type: (int) -> bool
 | 
			
		||||
    def _should_escape(byte: int) -> bool:
 | 
			
		||||
        if not 32 <= byte < 127:
 | 
			
		||||
            return True
 | 
			
		||||
        if byte in (ord("\\"), ord('"')):
 | 
			
		||||
@@ -134,7 +135,8 @@ def resolve_ip_address(host):
 | 
			
		||||
            errs.append(str(err))
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        return socket.gethostbyname(host)
 | 
			
		||||
        host_url = host if (urlparse(host).scheme != "") else "http://" + host
 | 
			
		||||
        return socket.gethostbyname(urlparse(host_url).hostname)
 | 
			
		||||
    except OSError as err:
 | 
			
		||||
        errs.append(str(err))
 | 
			
		||||
        raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import logging
 | 
			
		||||
from typing import Callable, List, Optional, Any, ContextManager
 | 
			
		||||
from typing import Callable, Optional, Any, ContextManager
 | 
			
		||||
from types import ModuleType
 | 
			
		||||
import importlib
 | 
			
		||||
import importlib.util
 | 
			
		||||
@@ -62,19 +62,19 @@ class ComponentManifest:
 | 
			
		||||
        return getattr(self.module, "to_code", None)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def dependencies(self) -> List[str]:
 | 
			
		||||
    def dependencies(self) -> list[str]:
 | 
			
		||||
        return getattr(self.module, "DEPENDENCIES", [])
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def conflicts_with(self) -> List[str]:
 | 
			
		||||
    def conflicts_with(self) -> list[str]:
 | 
			
		||||
        return getattr(self.module, "CONFLICTS_WITH", [])
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def auto_load(self) -> List[str]:
 | 
			
		||||
    def auto_load(self) -> list[str]:
 | 
			
		||||
        return getattr(self.module, "AUTO_LOAD", [])
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def codeowners(self) -> List[str]:
 | 
			
		||||
    def codeowners(self) -> list[str]:
 | 
			
		||||
        return getattr(self.module, "CODEOWNERS", [])
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@@ -87,7 +87,7 @@ class ComponentManifest:
 | 
			
		||||
        return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def resources(self) -> List[FileResource]:
 | 
			
		||||
    def resources(self) -> list[FileResource]:
 | 
			
		||||
        """Return a list of all file resources defined in the package of this component.
 | 
			
		||||
 | 
			
		||||
        This will return all cpp source files that are located in the same folder as the
 | 
			
		||||
@@ -106,7 +106,7 @@ class ComponentManifest:
 | 
			
		||||
 | 
			
		||||
class ComponentMetaFinder(importlib.abc.MetaPathFinder):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self, components_path: Path, allowed_components: Optional[List[str]] = None
 | 
			
		||||
        self, components_path: Path, allowed_components: Optional[list[str]] = None
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self._allowed_components = allowed_components
 | 
			
		||||
        self._finders = []
 | 
			
		||||
@@ -117,7 +117,7 @@ class ComponentMetaFinder(importlib.abc.MetaPathFinder):
 | 
			
		||||
                continue
 | 
			
		||||
            self._finders.append(finder)
 | 
			
		||||
 | 
			
		||||
    def find_spec(self, fullname: str, path: Optional[List[str]], target=None):
 | 
			
		||||
    def find_spec(self, fullname: str, path: Optional[list[str]], target=None):
 | 
			
		||||
        if not fullname.startswith("esphome.components."):
 | 
			
		||||
            return None
 | 
			
		||||
        parts = fullname.split(".")
 | 
			
		||||
@@ -144,7 +144,7 @@ def clear_component_meta_finders():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def install_meta_finder(
 | 
			
		||||
    components_path: Path, allowed_components: Optional[List[str]] = None
 | 
			
		||||
    components_path: Path, allowed_components: Optional[list[str]] = None
 | 
			
		||||
):
 | 
			
		||||
    sys.meta_path.insert(0, ComponentMetaFinder(components_path, allowed_components))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
import json
 | 
			
		||||
from typing import List, Union
 | 
			
		||||
from typing import Union
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
@@ -310,7 +310,7 @@ class IDEData:
 | 
			
		||||
        return str(Path(self.firmware_elf_path).with_suffix(".bin"))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def extra_flash_images(self) -> List[FlashImage]:
 | 
			
		||||
    def extra_flash_images(self) -> list[FlashImage]:
 | 
			
		||||
        return [
 | 
			
		||||
            FlashImage(path=entry["path"], offset=entry["offset"])
 | 
			
		||||
            for entry in self.raw["extra"]["flash_images"]
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ from datetime import datetime
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
from typing import Any, Optional, List
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from esphome import const
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
@@ -15,19 +15,19 @@ from esphome.types import CoreType
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def storage_path():  # type: () -> str
 | 
			
		||||
def storage_path() -> str:
 | 
			
		||||
    return CORE.relative_internal_path(f"{CORE.config_filename}.json")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ext_storage_path(base_path, config_filename):  # type: (str, str) -> str
 | 
			
		||||
def ext_storage_path(base_path: str, config_filename: str) -> str:
 | 
			
		||||
    return os.path.join(base_path, ".esphome", f"{config_filename}.json")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esphome_storage_path(base_path):  # type: (str) -> str
 | 
			
		||||
def esphome_storage_path(base_path: str) -> str:
 | 
			
		||||
    return os.path.join(base_path, ".esphome", "esphome.json")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def trash_storage_path(base_path):  # type: (str) -> str
 | 
			
		||||
def trash_storage_path(base_path: str) -> str:
 | 
			
		||||
    return os.path.join(base_path, ".esphome", "trash")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -49,29 +49,29 @@ class StorageJSON:
 | 
			
		||||
    ):
 | 
			
		||||
        # Version of the storage JSON schema
 | 
			
		||||
        assert storage_version is None or isinstance(storage_version, int)
 | 
			
		||||
        self.storage_version = storage_version  # type: int
 | 
			
		||||
        self.storage_version: int = storage_version
 | 
			
		||||
        # The name of the node
 | 
			
		||||
        self.name = name  # type: str
 | 
			
		||||
        self.name: str = name
 | 
			
		||||
        # The comment of the node
 | 
			
		||||
        self.comment = comment  # type: str
 | 
			
		||||
        self.comment: str = comment
 | 
			
		||||
        # The esphome version this was compiled with
 | 
			
		||||
        self.esphome_version = esphome_version  # type: str
 | 
			
		||||
        self.esphome_version: str = esphome_version
 | 
			
		||||
        # The version of the file in src/main.cpp - Used to migrate the file
 | 
			
		||||
        assert src_version is None or isinstance(src_version, int)
 | 
			
		||||
        self.src_version = src_version  # type: int
 | 
			
		||||
        self.src_version: int = src_version
 | 
			
		||||
        # Address of the ESP, for example livingroom.local or a static IP
 | 
			
		||||
        self.address = address  # type: str
 | 
			
		||||
        self.address: str = address
 | 
			
		||||
        # Web server port of the ESP, for example 80
 | 
			
		||||
        assert web_port is None or isinstance(web_port, int)
 | 
			
		||||
        self.web_port = web_port  # type: int
 | 
			
		||||
        self.web_port: int = web_port
 | 
			
		||||
        # The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc.
 | 
			
		||||
        self.target_platform = target_platform  # type: str
 | 
			
		||||
        self.target_platform: str = target_platform
 | 
			
		||||
        # The absolute path to the platformio project
 | 
			
		||||
        self.build_path = build_path  # type: str
 | 
			
		||||
        self.build_path: str = build_path
 | 
			
		||||
        # The absolute path to the firmware binary
 | 
			
		||||
        self.firmware_bin_path = firmware_bin_path  # type: str
 | 
			
		||||
        self.firmware_bin_path: str = firmware_bin_path
 | 
			
		||||
        # A list of strings of names of loaded integrations
 | 
			
		||||
        self.loaded_integrations = loaded_integrations  # type: List[str]
 | 
			
		||||
        self.loaded_integrations: list[str] = loaded_integrations
 | 
			
		||||
        self.loaded_integrations.sort()
 | 
			
		||||
 | 
			
		||||
    def as_dict(self):
 | 
			
		||||
@@ -97,8 +97,8 @@ class StorageJSON:
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def from_esphome_core(
 | 
			
		||||
        esph, old
 | 
			
		||||
    ):  # type: (CoreType, Optional[StorageJSON]) -> StorageJSON
 | 
			
		||||
        esph: CoreType, old: Optional["StorageJSON"]
 | 
			
		||||
    ) -> "StorageJSON":
 | 
			
		||||
        hardware = esph.target_platform.upper()
 | 
			
		||||
        if esph.is_esp32:
 | 
			
		||||
            from esphome.components import esp32
 | 
			
		||||
@@ -135,7 +135,7 @@ class StorageJSON:
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _load_impl(path):  # type: (str) -> Optional[StorageJSON]
 | 
			
		||||
    def _load_impl(path: str) -> Optional["StorageJSON"]:
 | 
			
		||||
        with codecs.open(path, "r", encoding="utf-8") as f_handle:
 | 
			
		||||
            storage = json.load(f_handle)
 | 
			
		||||
        storage_version = storage["storage_version"]
 | 
			
		||||
@@ -166,13 +166,13 @@ class StorageJSON:
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def load(path):  # type: (str) -> Optional[StorageJSON]
 | 
			
		||||
    def load(path: str) -> Optional["StorageJSON"]:
 | 
			
		||||
        try:
 | 
			
		||||
            return StorageJSON._load_impl(path)
 | 
			
		||||
        except Exception:  # pylint: disable=broad-except
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, o):  # type: (Any) -> bool
 | 
			
		||||
    def __eq__(self, o) -> bool:
 | 
			
		||||
        return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -182,15 +182,15 @@ class EsphomeStorageJSON:
 | 
			
		||||
    ):
 | 
			
		||||
        # Version of the storage JSON schema
 | 
			
		||||
        assert storage_version is None or isinstance(storage_version, int)
 | 
			
		||||
        self.storage_version = storage_version  # type: int
 | 
			
		||||
        self.storage_version: int = storage_version
 | 
			
		||||
        # The cookie secret for the dashboard
 | 
			
		||||
        self.cookie_secret = cookie_secret  # type: str
 | 
			
		||||
        self.cookie_secret: str = cookie_secret
 | 
			
		||||
        # The last time ESPHome checked for an update as an isoformat encoded str
 | 
			
		||||
        self.last_update_check_str = last_update_check  # type: str
 | 
			
		||||
        self.last_update_check_str: str = last_update_check
 | 
			
		||||
        # Cache of the version gotten in the last version check
 | 
			
		||||
        self.remote_version = remote_version  # type: Optional[str]
 | 
			
		||||
        self.remote_version: Optional[str] = remote_version
 | 
			
		||||
 | 
			
		||||
    def as_dict(self):  # type: () -> dict
 | 
			
		||||
    def as_dict(self) -> dict:
 | 
			
		||||
        return {
 | 
			
		||||
            "storage_version": self.storage_version,
 | 
			
		||||
            "cookie_secret": self.cookie_secret,
 | 
			
		||||
@@ -199,24 +199,24 @@ class EsphomeStorageJSON:
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def last_update_check(self):  # type: () -> Optional[datetime]
 | 
			
		||||
    def last_update_check(self) -> Optional[datetime]:
 | 
			
		||||
        try:
 | 
			
		||||
            return datetime.strptime(self.last_update_check_str, "%Y-%m-%dT%H:%M:%S")
 | 
			
		||||
        except Exception:  # pylint: disable=broad-except
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    @last_update_check.setter
 | 
			
		||||
    def last_update_check(self, new):  # type: (datetime) -> None
 | 
			
		||||
    def last_update_check(self, new: datetime) -> None:
 | 
			
		||||
        self.last_update_check_str = new.strftime("%Y-%m-%dT%H:%M:%S")
 | 
			
		||||
 | 
			
		||||
    def to_json(self):  # type: () -> dict
 | 
			
		||||
    def to_json(self) -> dict:
 | 
			
		||||
        return f"{json.dumps(self.as_dict(), indent=2)}\n"
 | 
			
		||||
 | 
			
		||||
    def save(self, path):  # type: (str) -> None
 | 
			
		||||
    def save(self, path: str) -> None:
 | 
			
		||||
        write_file_if_changed(path, self.to_json())
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _load_impl(path):  # type: (str) -> Optional[EsphomeStorageJSON]
 | 
			
		||||
    def _load_impl(path: str) -> Optional["EsphomeStorageJSON"]:
 | 
			
		||||
        with codecs.open(path, "r", encoding="utf-8") as f_handle:
 | 
			
		||||
            storage = json.load(f_handle)
 | 
			
		||||
        storage_version = storage["storage_version"]
 | 
			
		||||
@@ -228,14 +228,14 @@ class EsphomeStorageJSON:
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def load(path):  # type: (str) -> Optional[EsphomeStorageJSON]
 | 
			
		||||
    def load(path: str) -> Optional["EsphomeStorageJSON"]:
 | 
			
		||||
        try:
 | 
			
		||||
            return EsphomeStorageJSON._load_impl(path)
 | 
			
		||||
        except Exception:  # pylint: disable=broad-except
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_default():  # type: () -> EsphomeStorageJSON
 | 
			
		||||
    def get_default() -> "EsphomeStorageJSON":
 | 
			
		||||
        return EsphomeStorageJSON(
 | 
			
		||||
            storage_version=1,
 | 
			
		||||
            cookie_secret=binascii.hexlify(os.urandom(64)).decode(),
 | 
			
		||||
@@ -243,5 +243,5 @@ class EsphomeStorageJSON:
 | 
			
		||||
            remote_version=None,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, o):  # type: (Any) -> bool
 | 
			
		||||
    def __eq__(self, o) -> bool:
 | 
			
		||||
        return isinstance(o, EsphomeStorageJSON) and self.as_dict() == o.as_dict()
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user