mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'integration' into memory_api
This commit is contained in:
		| @@ -1298,6 +1298,8 @@ enum MediaPlayerState { | |||||||
|   MEDIA_PLAYER_STATE_PLAYING = 2; |   MEDIA_PLAYER_STATE_PLAYING = 2; | ||||||
|   MEDIA_PLAYER_STATE_PAUSED = 3; |   MEDIA_PLAYER_STATE_PAUSED = 3; | ||||||
|   MEDIA_PLAYER_STATE_ANNOUNCING = 4; |   MEDIA_PLAYER_STATE_ANNOUNCING = 4; | ||||||
|  |   MEDIA_PLAYER_STATE_OFF = 5; | ||||||
|  |   MEDIA_PLAYER_STATE_ON = 6; | ||||||
| } | } | ||||||
| enum MediaPlayerCommand { | enum MediaPlayerCommand { | ||||||
|   MEDIA_PLAYER_COMMAND_PLAY = 0; |   MEDIA_PLAYER_COMMAND_PLAY = 0; | ||||||
| @@ -1312,6 +1314,8 @@ enum MediaPlayerCommand { | |||||||
|   MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9; |   MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9; | ||||||
|   MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10; |   MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10; | ||||||
|   MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11; |   MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11; | ||||||
|  |   MEDIA_PLAYER_COMMAND_TURN_ON = 12; | ||||||
|  |   MEDIA_PLAYER_COMMAND_TURN_OFF = 13; | ||||||
| } | } | ||||||
| enum MediaPlayerFormatPurpose { | enum MediaPlayerFormatPurpose { | ||||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0; |   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0; | ||||||
| @@ -1501,7 +1505,7 @@ message BluetoothGATTGetServicesResponse { | |||||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; |   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||||
|  |  | ||||||
|   uint64 address = 1; |   uint64 address = 1; | ||||||
|   repeated BluetoothGATTService services = 2 [(fixed_array_size) = 1]; |   repeated BluetoothGATTService services = 2; | ||||||
| } | } | ||||||
|  |  | ||||||
| message BluetoothGATTGetServicesDoneResponse { | message BluetoothGATTGetServicesDoneResponse { | ||||||
|   | |||||||
| @@ -1929,11 +1929,13 @@ void BluetoothGATTService::calculate_size(ProtoSize &size) const { | |||||||
| } | } | ||||||
| void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { | void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { | ||||||
|   buffer.encode_uint64(1, this->address); |   buffer.encode_uint64(1, this->address); | ||||||
|   buffer.encode_message(2, this->services[0], true); |   for (auto &it : this->services) { | ||||||
|  |     buffer.encode_message(2, it, true); | ||||||
|  |   } | ||||||
| } | } | ||||||
| void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const { | void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const { | ||||||
|   size.add_uint64(1, this->address); |   size.add_uint64(1, this->address); | ||||||
|   size.add_message_object_force(1, this->services[0]); |   size.add_repeated_message(1, this->services); | ||||||
| } | } | ||||||
| void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const { | void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const { | ||||||
|   buffer.encode_uint64(1, this->address); |   buffer.encode_uint64(1, this->address); | ||||||
|   | |||||||
| @@ -151,6 +151,8 @@ enum MediaPlayerState : uint32_t { | |||||||
|   MEDIA_PLAYER_STATE_PLAYING = 2, |   MEDIA_PLAYER_STATE_PLAYING = 2, | ||||||
|   MEDIA_PLAYER_STATE_PAUSED = 3, |   MEDIA_PLAYER_STATE_PAUSED = 3, | ||||||
|   MEDIA_PLAYER_STATE_ANNOUNCING = 4, |   MEDIA_PLAYER_STATE_ANNOUNCING = 4, | ||||||
|  |   MEDIA_PLAYER_STATE_OFF = 5, | ||||||
|  |   MEDIA_PLAYER_STATE_ON = 6, | ||||||
| }; | }; | ||||||
| enum MediaPlayerCommand : uint32_t { | enum MediaPlayerCommand : uint32_t { | ||||||
|   MEDIA_PLAYER_COMMAND_PLAY = 0, |   MEDIA_PLAYER_COMMAND_PLAY = 0, | ||||||
| @@ -165,6 +167,8 @@ enum MediaPlayerCommand : uint32_t { | |||||||
|   MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9, |   MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9, | ||||||
|   MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10, |   MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10, | ||||||
|   MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11, |   MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11, | ||||||
|  |   MEDIA_PLAYER_COMMAND_TURN_ON = 12, | ||||||
|  |   MEDIA_PLAYER_COMMAND_TURN_OFF = 13, | ||||||
| }; | }; | ||||||
| enum MediaPlayerFormatPurpose : uint32_t { | enum MediaPlayerFormatPurpose : uint32_t { | ||||||
|   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, |   MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, | ||||||
| @@ -1891,12 +1895,12 @@ class BluetoothGATTService : public ProtoMessage { | |||||||
| class BluetoothGATTGetServicesResponse : public ProtoMessage { | class BluetoothGATTGetServicesResponse : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint8_t MESSAGE_TYPE = 71; |   static constexpr uint8_t MESSAGE_TYPE = 71; | ||||||
|   static constexpr uint8_t ESTIMATED_SIZE = 21; |   static constexpr uint8_t ESTIMATED_SIZE = 38; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   const char *message_name() const override { return "bluetooth_gatt_get_services_response"; } |   const char *message_name() const override { return "bluetooth_gatt_get_services_response"; } | ||||||
| #endif | #endif | ||||||
|   uint64_t address{0}; |   uint64_t address{0}; | ||||||
|   std::array<BluetoothGATTService, 1> services{}; |   std::vector<BluetoothGATTService> services{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(ProtoSize &size) const override; |   void calculate_size(ProtoSize &size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   | |||||||
| @@ -385,6 +385,10 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerState>(enums::Medi | |||||||
|       return "MEDIA_PLAYER_STATE_PAUSED"; |       return "MEDIA_PLAYER_STATE_PAUSED"; | ||||||
|     case enums::MEDIA_PLAYER_STATE_ANNOUNCING: |     case enums::MEDIA_PLAYER_STATE_ANNOUNCING: | ||||||
|       return "MEDIA_PLAYER_STATE_ANNOUNCING"; |       return "MEDIA_PLAYER_STATE_ANNOUNCING"; | ||||||
|  |     case enums::MEDIA_PLAYER_STATE_OFF: | ||||||
|  |       return "MEDIA_PLAYER_STATE_OFF"; | ||||||
|  |     case enums::MEDIA_PLAYER_STATE_ON: | ||||||
|  |       return "MEDIA_PLAYER_STATE_ON"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| @@ -415,6 +419,10 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me | |||||||
|       return "MEDIA_PLAYER_COMMAND_REPEAT_OFF"; |       return "MEDIA_PLAYER_COMMAND_REPEAT_OFF"; | ||||||
|     case enums::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST: |     case enums::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST: | ||||||
|       return "MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST"; |       return "MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST"; | ||||||
|  |     case enums::MEDIA_PLAYER_COMMAND_TURN_ON: | ||||||
|  |       return "MEDIA_PLAYER_COMMAND_TURN_ON"; | ||||||
|  |     case enums::MEDIA_PLAYER_COMMAND_TURN_OFF: | ||||||
|  |       return "MEDIA_PLAYER_COMMAND_TURN_OFF"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void BluetoothConnection::send_service_for_discovery_() { | void BluetoothConnection::send_service_for_discovery_() { | ||||||
|   if (this->send_service_ == this->service_count_) { |   if (this->send_service_ >= this->service_count_) { | ||||||
|     this->send_service_ = DONE_SENDING_SERVICES; |     this->send_service_ = DONE_SENDING_SERVICES; | ||||||
|     this->proxy_->send_gatt_services_done(this->address_); |     this->proxy_->send_gatt_services_done(this->address_); | ||||||
|     if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || |     if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || | ||||||
| @@ -73,116 +73,128 @@ void BluetoothConnection::send_service_for_discovery_() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Send next service |   // Prepare response for up to 3 services | ||||||
|   esp_gattc_service_elem_t service_result; |  | ||||||
|   uint16_t service_count = 1; |  | ||||||
|   esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr, |  | ||||||
|                                                                &service_result, &service_count, this->send_service_); |  | ||||||
|   this->send_service_++; |  | ||||||
|  |  | ||||||
|   if (service_status != ESP_GATT_OK || service_count == 0) { |  | ||||||
|     ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d", |  | ||||||
|              this->connection_index_, this->address_str().c_str(), service_status != ESP_GATT_OK ? "error" : "missing", |  | ||||||
|              service_status, service_count, this->send_service_ - 1); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   api::BluetoothGATTGetServicesResponse resp; |   api::BluetoothGATTGetServicesResponse resp; | ||||||
|   resp.address = this->address_; |   resp.address = this->address_; | ||||||
|   auto &service_resp = resp.services[0]; |  | ||||||
|   fill_128bit_uuid_array(service_resp.uuid, service_result.uuid); |  | ||||||
|   service_resp.handle = service_result.start_handle; |  | ||||||
|  |  | ||||||
|   // Get the number of characteristics directly with one call |   // Process up to 3 services in this iteration | ||||||
|   uint16_t total_char_count = 0; |   uint8_t services_to_process = | ||||||
|   esp_gatt_status_t char_count_status = |       std::min(MAX_SERVICES_PER_BATCH, static_cast<uint8_t>(this->service_count_ - this->send_service_)); | ||||||
|       esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, |   uint8_t services_processed = 0; | ||||||
|                                    service_result.start_handle, service_result.end_handle, 0, &total_char_count); |   resp.services.reserve(services_to_process); | ||||||
|  |  | ||||||
|   if (char_count_status != ESP_GATT_OK) { |   for (int service_idx = 0; service_idx < services_to_process; service_idx++) { | ||||||
|     ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_, |     esp_gattc_service_elem_t service_result; | ||||||
|              this->address_str().c_str(), char_count_status); |     uint16_t service_count = 1; | ||||||
|     return; |     esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr, | ||||||
|   } |                                                                  &service_result, &service_count, this->send_service_); | ||||||
|  |  | ||||||
|   if (total_char_count == 0) { |     if (service_status != ESP_GATT_OK || service_count == 0) { | ||||||
|     // No characteristics, just send the service response |       ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d", | ||||||
|     api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); |                this->connection_index_, this->address_str().c_str(), | ||||||
|     return; |                service_status != ESP_GATT_OK ? "error" : "missing", service_status, service_count, this->send_service_); | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Reserve space and process characteristics |  | ||||||
|   service_resp.characteristics.reserve(total_char_count); |  | ||||||
|   uint16_t char_offset = 0; |  | ||||||
|   esp_gattc_char_elem_t char_result; |  | ||||||
|   while (true) {  // characteristics |  | ||||||
|     uint16_t char_count = 1; |  | ||||||
|     esp_gatt_status_t char_status = |  | ||||||
|         esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle, |  | ||||||
|                                    service_result.end_handle, &char_result, &char_count, char_offset); |  | ||||||
|     if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|     if (char_status != ESP_GATT_OK) { |  | ||||||
|       ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, |  | ||||||
|                this->address_str().c_str(), char_status); |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     if (char_count == 0) { |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     service_resp.characteristics.emplace_back(); |     this->send_service_++; | ||||||
|     auto &characteristic_resp = service_resp.characteristics.back(); |     resp.services.emplace_back(); | ||||||
|     fill_128bit_uuid_array(characteristic_resp.uuid, char_result.uuid); |     auto &service_resp = resp.services.back(); | ||||||
|     characteristic_resp.handle = char_result.char_handle; |     fill_128bit_uuid_array(service_resp.uuid, service_result.uuid); | ||||||
|     characteristic_resp.properties = char_result.properties; |     service_resp.handle = service_result.start_handle; | ||||||
|     char_offset++; |  | ||||||
|  |  | ||||||
|     // Get the number of descriptors directly with one call |     // Get the number of characteristics directly with one call | ||||||
|     uint16_t total_desc_count = 0; |     uint16_t total_char_count = 0; | ||||||
|     esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count( |     esp_gatt_status_t char_count_status = | ||||||
|         this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count); |         esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, | ||||||
|  |                                      service_result.start_handle, service_result.end_handle, 0, &total_char_count); | ||||||
|  |  | ||||||
|     if (desc_count_status != ESP_GATT_OK) { |     if (char_count_status != ESP_GATT_OK) { | ||||||
|       ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_, |       ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_, | ||||||
|                this->address_str().c_str(), char_result.char_handle, desc_count_status); |                this->address_str().c_str(), char_count_status); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     if (total_desc_count == 0) { |  | ||||||
|       // No descriptors, continue to next characteristic |     if (total_char_count == 0) { | ||||||
|  |       // No characteristics, continue to next service | ||||||
|  |       services_processed++; | ||||||
|       continue; |       continue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Reserve space and process descriptors |     // Reserve space and process characteristics | ||||||
|     characteristic_resp.descriptors.reserve(total_desc_count); |     service_resp.characteristics.reserve(total_char_count); | ||||||
|     uint16_t desc_offset = 0; |     uint16_t char_offset = 0; | ||||||
|     esp_gattc_descr_elem_t desc_result; |     esp_gattc_char_elem_t char_result; | ||||||
|     while (true) {  // descriptors |     while (true) {  // characteristics | ||||||
|       uint16_t desc_count = 1; |       uint16_t char_count = 1; | ||||||
|       esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr( |       esp_gatt_status_t char_status = | ||||||
|           this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset); |           esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle, | ||||||
|       if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { |                                      service_result.end_handle, &char_result, &char_count, char_offset); | ||||||
|  |       if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       if (desc_status != ESP_GATT_OK) { |       if (char_status != ESP_GATT_OK) { | ||||||
|         ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_, |         ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, | ||||||
|                  this->address_str().c_str(), desc_status); |                  this->address_str().c_str(), char_status); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       if (desc_count == 0) { |       if (char_count == 0) { | ||||||
|         break;  // No more descriptors |         break; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       characteristic_resp.descriptors.emplace_back(); |       service_resp.characteristics.emplace_back(); | ||||||
|       auto &descriptor_resp = characteristic_resp.descriptors.back(); |       auto &characteristic_resp = service_resp.characteristics.back(); | ||||||
|       fill_128bit_uuid_array(descriptor_resp.uuid, desc_result.uuid); |       fill_128bit_uuid_array(characteristic_resp.uuid, char_result.uuid); | ||||||
|       descriptor_resp.handle = desc_result.handle; |       characteristic_resp.handle = char_result.char_handle; | ||||||
|       desc_offset++; |       characteristic_resp.properties = char_result.properties; | ||||||
|  |       char_offset++; | ||||||
|  |  | ||||||
|  |       // Get the number of descriptors directly with one call | ||||||
|  |       uint16_t total_desc_count = 0; | ||||||
|  |       esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count( | ||||||
|  |           this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count); | ||||||
|  |  | ||||||
|  |       if (desc_count_status != ESP_GATT_OK) { | ||||||
|  |         ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_, | ||||||
|  |                  this->address_str().c_str(), char_result.char_handle, desc_count_status); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (total_desc_count == 0) { | ||||||
|  |         // No descriptors, continue to next characteristic | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Reserve space and process descriptors | ||||||
|  |       characteristic_resp.descriptors.reserve(total_desc_count); | ||||||
|  |       uint16_t desc_offset = 0; | ||||||
|  |       esp_gattc_descr_elem_t desc_result; | ||||||
|  |       while (true) {  // descriptors | ||||||
|  |         uint16_t desc_count = 1; | ||||||
|  |         esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr( | ||||||
|  |             this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset); | ||||||
|  |         if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         if (desc_status != ESP_GATT_OK) { | ||||||
|  |           ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_, | ||||||
|  |                    this->address_str().c_str(), desc_status); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |         if (desc_count == 0) { | ||||||
|  |           break;  // No more descriptors | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         characteristic_resp.descriptors.emplace_back(); | ||||||
|  |         auto &descriptor_resp = characteristic_resp.descriptors.back(); | ||||||
|  |         fill_128bit_uuid_array(descriptor_resp.uuid, desc_result.uuid); | ||||||
|  |         descriptor_resp.handle = desc_result.handle; | ||||||
|  |         desc_offset++; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     services_processed++; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Send the message (we already checked api_conn is not null at the beginning) |   // Send the message with 1-3 services | ||||||
|   api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); |   api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ namespace esphome::bluetooth_proxy { | |||||||
|  |  | ||||||
| static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; | static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; | ||||||
| static const int DONE_SENDING_SERVICES = -2; | static const int DONE_SENDING_SERVICES = -2; | ||||||
|  | static const uint8_t MAX_SERVICES_PER_BATCH = 3; | ||||||
|  |  | ||||||
| using namespace esp32_ble_client; | using namespace esp32_ble_client; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ from esphome.const import ( | |||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_ON_IDLE, |     CONF_ON_IDLE, | ||||||
|     CONF_ON_STATE, |     CONF_ON_STATE, | ||||||
|  |     CONF_ON_TURN_OFF, | ||||||
|  |     CONF_ON_TURN_ON, | ||||||
|     CONF_TRIGGER_ID, |     CONF_TRIGGER_ID, | ||||||
|     CONF_VOLUME, |     CONF_VOLUME, | ||||||
| ) | ) | ||||||
| @@ -58,6 +60,12 @@ VolumeDownAction = media_player_ns.class_( | |||||||
| VolumeSetAction = media_player_ns.class_( | VolumeSetAction = media_player_ns.class_( | ||||||
|     "VolumeSetAction", automation.Action, cg.Parented.template(MediaPlayer) |     "VolumeSetAction", automation.Action, cg.Parented.template(MediaPlayer) | ||||||
| ) | ) | ||||||
|  | TurnOnAction = media_player_ns.class_( | ||||||
|  |     "TurnOnAction", automation.Action, cg.Parented.template(MediaPlayer) | ||||||
|  | ) | ||||||
|  | TurnOffAction = media_player_ns.class_( | ||||||
|  |     "TurnOffAction", automation.Action, cg.Parented.template(MediaPlayer) | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONF_ANNOUNCEMENT = "announcement" | CONF_ANNOUNCEMENT = "announcement" | ||||||
| CONF_ON_PLAY = "on_play" | CONF_ON_PLAY = "on_play" | ||||||
| @@ -72,12 +80,16 @@ PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.templat | |||||||
| AnnoucementTrigger = media_player_ns.class_( | AnnoucementTrigger = media_player_ns.class_( | ||||||
|     "AnnouncementTrigger", automation.Trigger.template() |     "AnnouncementTrigger", automation.Trigger.template() | ||||||
| ) | ) | ||||||
|  | OnTrigger = media_player_ns.class_("OnTrigger", automation.Trigger.template()) | ||||||
|  | OffTrigger = media_player_ns.class_("OffTrigger", automation.Trigger.template()) | ||||||
| IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition) | IsIdleCondition = media_player_ns.class_("IsIdleCondition", automation.Condition) | ||||||
| IsPausedCondition = media_player_ns.class_("IsPausedCondition", automation.Condition) | IsPausedCondition = media_player_ns.class_("IsPausedCondition", automation.Condition) | ||||||
| IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition) | IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition) | ||||||
| IsAnnouncingCondition = media_player_ns.class_( | IsAnnouncingCondition = media_player_ns.class_( | ||||||
|     "IsAnnouncingCondition", automation.Condition |     "IsAnnouncingCondition", automation.Condition | ||||||
| ) | ) | ||||||
|  | IsOnCondition = media_player_ns.class_("IsOnCondition", automation.Condition) | ||||||
|  | IsOffCondition = media_player_ns.class_("IsOffCondition", automation.Condition) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_media_player_core_(var, config): | async def setup_media_player_core_(var, config): | ||||||
| @@ -97,6 +109,12 @@ async def setup_media_player_core_(var, config): | |||||||
|     for conf in config.get(CONF_ON_ANNOUNCEMENT, []): |     for conf in config.get(CONF_ON_ANNOUNCEMENT, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [], conf) | ||||||
|  |     for conf in config.get(CONF_ON_TURN_ON, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |     for conf in config.get(CONF_ON_TURN_OFF, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def register_media_player(var, config): | async def register_media_player(var, config): | ||||||
| @@ -140,6 +158,16 @@ _MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | |||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(AnnoucementTrigger), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(AnnoucementTrigger), | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnTrigger), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OffTrigger), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -218,6 +246,12 @@ async def media_player_play_media_action(config, action_id, template_arg, args): | |||||||
| @automation.register_action( | @automation.register_action( | ||||||
|     "media_player.volume_down", VolumeDownAction, MEDIA_PLAYER_ACTION_SCHEMA |     "media_player.volume_down", VolumeDownAction, MEDIA_PLAYER_ACTION_SCHEMA | ||||||
| ) | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "media_player.turn_on", TurnOnAction, MEDIA_PLAYER_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "media_player.turn_off", TurnOffAction, MEDIA_PLAYER_ACTION_SCHEMA | ||||||
|  | ) | ||||||
| async def media_player_action(config, action_id, template_arg, args): | async def media_player_action(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     await cg.register_parented(var, config[CONF_ID]) |     await cg.register_parented(var, config[CONF_ID]) | ||||||
| @@ -238,6 +272,12 @@ async def media_player_action(config, action_id, template_arg, args): | |||||||
| @automation.register_condition( | @automation.register_condition( | ||||||
|     "media_player.is_announcing", IsAnnouncingCondition, MEDIA_PLAYER_CONDITION_SCHEMA |     "media_player.is_announcing", IsAnnouncingCondition, MEDIA_PLAYER_CONDITION_SCHEMA | ||||||
| ) | ) | ||||||
|  | @automation.register_condition( | ||||||
|  |     "media_player.is_on", IsOnCondition, MEDIA_PLAYER_CONDITION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_condition( | ||||||
|  |     "media_player.is_off", IsOffCondition, MEDIA_PLAYER_CONDITION_SCHEMA | ||||||
|  | ) | ||||||
| async def media_player_condition(config, action_id, template_arg, args): | async def media_player_condition(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     await cg.register_parented(var, config[CONF_ID]) |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|   | |||||||
| @@ -28,6 +28,10 @@ template<typename... Ts> | |||||||
| using VolumeUpAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_UP, Ts...>; | using VolumeUpAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_UP, Ts...>; | ||||||
| template<typename... Ts> | template<typename... Ts> | ||||||
| using VolumeDownAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_DOWN, Ts...>; | using VolumeDownAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_VOLUME_DOWN, Ts...>; | ||||||
|  | template<typename... Ts> | ||||||
|  | using TurnOnAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_TURN_ON, Ts...>; | ||||||
|  | template<typename... Ts> | ||||||
|  | using TurnOffAction = MediaPlayerCommandAction<MediaPlayerCommand::MEDIA_PLAYER_COMMAND_TURN_OFF, Ts...>; | ||||||
|  |  | ||||||
| template<typename... Ts> class PlayMediaAction : public Action<Ts...>, public Parented<MediaPlayer> { | template<typename... Ts> class PlayMediaAction : public Action<Ts...>, public Parented<MediaPlayer> { | ||||||
|   TEMPLATABLE_VALUE(std::string, media_url) |   TEMPLATABLE_VALUE(std::string, media_url) | ||||||
| @@ -66,6 +70,8 @@ using IdleTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE | |||||||
| using PlayTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING>; | using PlayTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING>; | ||||||
| using PauseTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED>; | using PauseTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED>; | ||||||
| using AnnouncementTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING>; | using AnnouncementTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING>; | ||||||
|  | using OnTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ON>; | ||||||
|  | using OffTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_OFF>; | ||||||
|  |  | ||||||
| template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> { | template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> { | ||||||
|  public: |  public: | ||||||
| @@ -87,5 +93,15 @@ template<typename... Ts> class IsAnnouncingCondition : public Condition<Ts...>, | |||||||
|   bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING; } |   bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING; } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class IsOnCondition : public Condition<Ts...>, public Parented<MediaPlayer> { | ||||||
|  |  public: | ||||||
|  |   bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ON; } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class IsOffCondition : public Condition<Ts...>, public Parented<MediaPlayer> { | ||||||
|  |  public: | ||||||
|  |   bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_OFF; } | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace media_player | }  // namespace media_player | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -9,6 +9,10 @@ static const char *const TAG = "media_player"; | |||||||
|  |  | ||||||
| const char *media_player_state_to_string(MediaPlayerState state) { | const char *media_player_state_to_string(MediaPlayerState state) { | ||||||
|   switch (state) { |   switch (state) { | ||||||
|  |     case MEDIA_PLAYER_STATE_ON: | ||||||
|  |       return "ON"; | ||||||
|  |     case MEDIA_PLAYER_STATE_OFF: | ||||||
|  |       return "OFF"; | ||||||
|     case MEDIA_PLAYER_STATE_IDLE: |     case MEDIA_PLAYER_STATE_IDLE: | ||||||
|       return "IDLE"; |       return "IDLE"; | ||||||
|     case MEDIA_PLAYER_STATE_PLAYING: |     case MEDIA_PLAYER_STATE_PLAYING: | ||||||
| @@ -18,6 +22,7 @@ const char *media_player_state_to_string(MediaPlayerState state) { | |||||||
|     case MEDIA_PLAYER_STATE_ANNOUNCING: |     case MEDIA_PLAYER_STATE_ANNOUNCING: | ||||||
|       return "ANNOUNCING"; |       return "ANNOUNCING"; | ||||||
|     case MEDIA_PLAYER_STATE_NONE: |     case MEDIA_PLAYER_STATE_NONE: | ||||||
|  |       return "NONE"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| @@ -49,6 +54,10 @@ const char *media_player_command_to_string(MediaPlayerCommand command) { | |||||||
|       return "REPEAT_OFF"; |       return "REPEAT_OFF"; | ||||||
|     case MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST: |     case MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST: | ||||||
|       return "CLEAR_PLAYLIST"; |       return "CLEAR_PLAYLIST"; | ||||||
|  |     case MEDIA_PLAYER_COMMAND_TURN_ON: | ||||||
|  |       return "TURN_ON"; | ||||||
|  |     case MEDIA_PLAYER_COMMAND_TURN_OFF: | ||||||
|  |       return "TURN_OFF"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| @@ -110,6 +119,10 @@ MediaPlayerCall &MediaPlayerCall::set_command(const std::string &command) { | |||||||
|     this->set_command(MEDIA_PLAYER_COMMAND_UNMUTE); |     this->set_command(MEDIA_PLAYER_COMMAND_UNMUTE); | ||||||
|   } else if (str_equals_case_insensitive(command, "TOGGLE")) { |   } else if (str_equals_case_insensitive(command, "TOGGLE")) { | ||||||
|     this->set_command(MEDIA_PLAYER_COMMAND_TOGGLE); |     this->set_command(MEDIA_PLAYER_COMMAND_TOGGLE); | ||||||
|  |   } else if (str_equals_case_insensitive(command, "TURN_ON")) { | ||||||
|  |     this->set_command(MEDIA_PLAYER_COMMAND_TURN_ON); | ||||||
|  |   } else if (str_equals_case_insensitive(command, "TURN_OFF")) { | ||||||
|  |     this->set_command(MEDIA_PLAYER_COMMAND_TURN_OFF); | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command.c_str()); |     ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command.c_str()); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -38,6 +38,8 @@ enum MediaPlayerState : uint8_t { | |||||||
|   MEDIA_PLAYER_STATE_PLAYING = 2, |   MEDIA_PLAYER_STATE_PLAYING = 2, | ||||||
|   MEDIA_PLAYER_STATE_PAUSED = 3, |   MEDIA_PLAYER_STATE_PAUSED = 3, | ||||||
|   MEDIA_PLAYER_STATE_ANNOUNCING = 4, |   MEDIA_PLAYER_STATE_ANNOUNCING = 4, | ||||||
|  |   MEDIA_PLAYER_STATE_OFF = 5, | ||||||
|  |   MEDIA_PLAYER_STATE_ON = 6, | ||||||
| }; | }; | ||||||
| const char *media_player_state_to_string(MediaPlayerState state); | const char *media_player_state_to_string(MediaPlayerState state); | ||||||
|  |  | ||||||
| @@ -54,6 +56,8 @@ enum MediaPlayerCommand : uint8_t { | |||||||
|   MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9, |   MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9, | ||||||
|   MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10, |   MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10, | ||||||
|   MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11, |   MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11, | ||||||
|  |   MEDIA_PLAYER_COMMAND_TURN_ON = 12, | ||||||
|  |   MEDIA_PLAYER_COMMAND_TURN_OFF = 13, | ||||||
| }; | }; | ||||||
| const char *media_player_command_to_string(MediaPlayerCommand command); | const char *media_player_command_to_string(MediaPlayerCommand command); | ||||||
|  |  | ||||||
| @@ -77,9 +81,11 @@ class MediaPlayerTraits { | |||||||
|   MediaPlayerTraits() = default; |   MediaPlayerTraits() = default; | ||||||
|  |  | ||||||
|   void set_supports_pause(bool supports_pause) { this->supports_pause_ = supports_pause; } |   void set_supports_pause(bool supports_pause) { this->supports_pause_ = supports_pause; } | ||||||
|  |  | ||||||
|   bool get_supports_pause() const { return this->supports_pause_; } |   bool get_supports_pause() const { return this->supports_pause_; } | ||||||
|  |  | ||||||
|  |   void set_supports_turn_off_on(bool supports_turn_off_on) { this->supports_turn_off_on_ = supports_turn_off_on; } | ||||||
|  |   bool get_supports_turn_off_on() const { return this->supports_turn_off_on_; } | ||||||
|  |  | ||||||
|   std::vector<MediaPlayerSupportedFormat> &get_supported_formats() { return this->supported_formats_; } |   std::vector<MediaPlayerSupportedFormat> &get_supported_formats() { return this->supported_formats_; } | ||||||
|  |  | ||||||
|   uint32_t get_feature_flags() const { |   uint32_t get_feature_flags() const { | ||||||
| @@ -90,12 +96,16 @@ class MediaPlayerTraits { | |||||||
|     if (this->get_supports_pause()) { |     if (this->get_supports_pause()) { | ||||||
|       flags |= MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY; |       flags |= MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY; | ||||||
|     } |     } | ||||||
|  |     if (this->get_supports_turn_off_on()) { | ||||||
|  |       flags |= MediaPlayerEntityFeature::TURN_OFF | MediaPlayerEntityFeature::TURN_ON; | ||||||
|  |     } | ||||||
|     return flags; |     return flags; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool supports_pause_{false}; |  | ||||||
|   std::vector<MediaPlayerSupportedFormat> supported_formats_{}; |   std::vector<MediaPlayerSupportedFormat> supported_formats_{}; | ||||||
|  |   bool supports_pause_{false}; | ||||||
|  |   bool supports_turn_off_on_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class MediaPlayerCall { | class MediaPlayerCall { | ||||||
|   | |||||||
| @@ -263,7 +263,7 @@ class TimePeriodMinutes(TimePeriod): | |||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| LAMBDA_PROG = re.compile(r"id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)") | LAMBDA_PROG = re.compile(r"\bid\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)") | ||||||
|  |  | ||||||
|  |  | ||||||
| class Lambda: | class Lambda: | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ platformio==6.1.18  # When updating platformio, also update /docker/Dockerfile | |||||||
| esptool==4.9.0 | esptool==4.9.0 | ||||||
| click==8.1.7 | click==8.1.7 | ||||||
| esphome-dashboard==20250514.0 | esphome-dashboard==20250514.0 | ||||||
| aioesphomeapi==37.1.5 | aioesphomeapi==37.1.6 | ||||||
| zeroconf==0.147.0 | zeroconf==0.147.0 | ||||||
| puremagic==1.30 | puremagic==1.30 | ||||||
| ruamel.yaml==0.18.14 # dashboard_import | ruamel.yaml==0.18.14 # dashboard_import | ||||||
|   | |||||||
| @@ -8,3 +8,10 @@ logger: | |||||||
|  |  | ||||||
| host: | host: | ||||||
|   mac_address: "62:23:45:AF:B3:DD" |   mac_address: "62:23:45:AF:B3:DD" | ||||||
|  |  | ||||||
|  | esphome: | ||||||
|  |   on_boot: | ||||||
|  |     - lambda: |- | ||||||
|  |         static const uint8_t my_addr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; | ||||||
|  |         if (!mac_address_is_valid(my_addr)) | ||||||
|  |           ESP_LOGD("test", "Invalid mac address %X", my_addr[0]); // etc. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user