diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 8eb2d26e54..27edf4680f 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1298,6 +1298,8 @@ enum MediaPlayerState { MEDIA_PLAYER_STATE_PLAYING = 2; MEDIA_PLAYER_STATE_PAUSED = 3; MEDIA_PLAYER_STATE_ANNOUNCING = 4; + MEDIA_PLAYER_STATE_OFF = 5; + MEDIA_PLAYER_STATE_ON = 6; } enum MediaPlayerCommand { MEDIA_PLAYER_COMMAND_PLAY = 0; @@ -1312,6 +1314,8 @@ enum MediaPlayerCommand { MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9; MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10; MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11; + MEDIA_PLAYER_COMMAND_TURN_ON = 12; + MEDIA_PLAYER_COMMAND_TURN_OFF = 13; } enum MediaPlayerFormatPurpose { MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0; @@ -1501,7 +1505,7 @@ message BluetoothGATTGetServicesResponse { option (ifdef) = "USE_BLUETOOTH_PROXY"; uint64 address = 1; - repeated BluetoothGATTService services = 2 [(fixed_array_size) = 1]; + repeated BluetoothGATTService services = 2; } message BluetoothGATTGetServicesDoneResponse { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 2e8adeaf5c..ef02a5a774 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1929,11 +1929,13 @@ void BluetoothGATTService::calculate_size(ProtoSize &size) const { } void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const { 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 { 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 { buffer.encode_uint64(1, this->address); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index c190c8e191..6c2ca60e00 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -151,6 +151,8 @@ enum MediaPlayerState : uint32_t { MEDIA_PLAYER_STATE_PLAYING = 2, MEDIA_PLAYER_STATE_PAUSED = 3, MEDIA_PLAYER_STATE_ANNOUNCING = 4, + MEDIA_PLAYER_STATE_OFF = 5, + MEDIA_PLAYER_STATE_ON = 6, }; enum MediaPlayerCommand : uint32_t { MEDIA_PLAYER_COMMAND_PLAY = 0, @@ -165,6 +167,8 @@ enum MediaPlayerCommand : uint32_t { MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9, MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10, MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11, + MEDIA_PLAYER_COMMAND_TURN_ON = 12, + MEDIA_PLAYER_COMMAND_TURN_OFF = 13, }; enum MediaPlayerFormatPurpose : uint32_t { MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, @@ -1891,12 +1895,12 @@ class BluetoothGATTService : public ProtoMessage { class BluetoothGATTGetServicesResponse : public ProtoMessage { public: 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 const char *message_name() const override { return "bluetooth_gatt_get_services_response"; } #endif uint64_t address{0}; - std::array services{}; + std::vector services{}; void encode(ProtoWriteBuffer buffer) const override; void calculate_size(ProtoSize &size) const override; #ifdef HAS_PROTO_MESSAGE_DUMP diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 032cfbc044..b934aead32 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -385,6 +385,10 @@ template<> const char *proto_enum_to_string(enums::Medi return "MEDIA_PLAYER_STATE_PAUSED"; case enums::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: return "UNKNOWN"; } @@ -415,6 +419,10 @@ template<> const char *proto_enum_to_string(enums::Me return "MEDIA_PLAYER_COMMAND_REPEAT_OFF"; case enums::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: return "UNKNOWN"; } diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index b3484032b2..e4b1c80619 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -57,7 +57,7 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) { } 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->proxy_->send_gatt_services_done(this->address_); if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || @@ -73,116 +73,128 @@ void BluetoothConnection::send_service_for_discovery_() { return; } - // Send next service - 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; - } - + // Prepare response for up to 3 services api::BluetoothGATTGetServicesResponse resp; 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 - uint16_t total_char_count = 0; - esp_gatt_status_t char_count_status = - 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); + // Process up to 3 services in this iteration + uint8_t services_to_process = + std::min(MAX_SERVICES_PER_BATCH, static_cast(this->service_count_ - this->send_service_)); + uint8_t services_processed = 0; + resp.services.reserve(services_to_process); - if (char_count_status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_, - this->address_str().c_str(), char_count_status); - return; - } + for (int service_idx = 0; service_idx < services_to_process; service_idx++) { + 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_); - if (total_char_count == 0) { - // No characteristics, just send the service response - api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); - return; - } - - // 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); + 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_); return; } - if (char_count == 0) { - break; - } - service_resp.characteristics.emplace_back(); - auto &characteristic_resp = service_resp.characteristics.back(); - fill_128bit_uuid_array(characteristic_resp.uuid, char_result.uuid); - characteristic_resp.handle = char_result.char_handle; - characteristic_resp.properties = char_result.properties; - char_offset++; + this->send_service_++; + resp.services.emplace_back(); + auto &service_resp = resp.services.back(); + fill_128bit_uuid_array(service_resp.uuid, service_result.uuid); + service_resp.handle = service_result.start_handle; - // 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); + // Get the number of characteristics directly with one call + uint16_t total_char_count = 0; + esp_gatt_status_t char_count_status = + 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) { - 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); + if (char_count_status != ESP_GATT_OK) { + ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_, + this->address_str().c_str(), char_count_status); 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; } - // 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) { + // 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 (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); + 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; } - if (desc_count == 0) { - break; // No more descriptors + if (char_count == 0) { + break; } - 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++; + service_resp.characteristics.emplace_back(); + auto &characteristic_resp = service_resp.characteristics.back(); + fill_128bit_uuid_array(characteristic_resp.uuid, char_result.uuid); + characteristic_resp.handle = char_result.char_handle; + 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); } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index d249515fdf..b33460339b 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -22,6 +22,7 @@ namespace esphome::bluetooth_proxy { static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; static const int DONE_SENDING_SERVICES = -2; +static const uint8_t MAX_SERVICES_PER_BATCH = 3; using namespace esp32_ble_client; diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index ccded1deb2..d288e70cba 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -7,6 +7,8 @@ from esphome.const import ( CONF_ID, CONF_ON_IDLE, CONF_ON_STATE, + CONF_ON_TURN_OFF, + CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_VOLUME, ) @@ -58,6 +60,12 @@ VolumeDownAction = media_player_ns.class_( VolumeSetAction = media_player_ns.class_( "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_ON_PLAY = "on_play" @@ -72,12 +80,16 @@ PauseTrigger = media_player_ns.class_("PauseTrigger", automation.Trigger.templat AnnoucementTrigger = media_player_ns.class_( "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) IsPausedCondition = media_player_ns.class_("IsPausedCondition", automation.Condition) IsPlayingCondition = media_player_ns.class_("IsPlayingCondition", automation.Condition) IsAnnouncingCondition = media_player_ns.class_( "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): @@ -97,6 +109,12 @@ async def setup_media_player_core_(var, config): for conf in config.get(CONF_ON_ANNOUNCEMENT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) 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): @@ -140,6 +158,16 @@ _MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( 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( "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): var = cg.new_Pvariable(action_id, template_arg) 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( "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): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) diff --git a/esphome/components/media_player/automation.h b/esphome/components/media_player/automation.h index 422c224a85..3af5959f32 100644 --- a/esphome/components/media_player/automation.h +++ b/esphome/components/media_player/automation.h @@ -28,6 +28,10 @@ template using VolumeUpAction = MediaPlayerCommandAction; template using VolumeDownAction = MediaPlayerCommandAction; +template +using TurnOnAction = MediaPlayerCommandAction; +template +using TurnOffAction = MediaPlayerCommandAction; template class PlayMediaAction : public Action, public Parented { TEMPLATABLE_VALUE(std::string, media_url) @@ -66,6 +70,8 @@ using IdleTrigger = MediaPlayerStateTrigger; using PauseTrigger = MediaPlayerStateTrigger; using AnnouncementTrigger = MediaPlayerStateTrigger; +using OnTrigger = MediaPlayerStateTrigger; +using OffTrigger = MediaPlayerStateTrigger; template class IsIdleCondition : public Condition, public Parented { public: @@ -87,5 +93,15 @@ template class IsAnnouncingCondition : public Condition, bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING; } }; +template class IsOnCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_ON; } +}; + +template class IsOffCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->state == MediaPlayerState::MEDIA_PLAYER_STATE_OFF; } +}; + } // namespace media_player } // namespace esphome diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 32da7ee265..3f274bf73b 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -9,6 +9,10 @@ static const char *const TAG = "media_player"; const char *media_player_state_to_string(MediaPlayerState state) { switch (state) { + case MEDIA_PLAYER_STATE_ON: + return "ON"; + case MEDIA_PLAYER_STATE_OFF: + return "OFF"; case MEDIA_PLAYER_STATE_IDLE: return "IDLE"; case MEDIA_PLAYER_STATE_PLAYING: @@ -18,6 +22,7 @@ const char *media_player_state_to_string(MediaPlayerState state) { case MEDIA_PLAYER_STATE_ANNOUNCING: return "ANNOUNCING"; case MEDIA_PLAYER_STATE_NONE: + return "NONE"; default: return "UNKNOWN"; } @@ -49,6 +54,10 @@ const char *media_player_command_to_string(MediaPlayerCommand command) { return "REPEAT_OFF"; case MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST: return "CLEAR_PLAYLIST"; + case MEDIA_PLAYER_COMMAND_TURN_ON: + return "TURN_ON"; + case MEDIA_PLAYER_COMMAND_TURN_OFF: + return "TURN_OFF"; default: return "UNKNOWN"; } @@ -110,6 +119,10 @@ MediaPlayerCall &MediaPlayerCall::set_command(const std::string &command) { this->set_command(MEDIA_PLAYER_COMMAND_UNMUTE); } else if (str_equals_case_insensitive(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 { ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command.c_str()); } diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 98aa8f609d..2f1c99115f 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -38,6 +38,8 @@ enum MediaPlayerState : uint8_t { MEDIA_PLAYER_STATE_PLAYING = 2, MEDIA_PLAYER_STATE_PAUSED = 3, MEDIA_PLAYER_STATE_ANNOUNCING = 4, + MEDIA_PLAYER_STATE_OFF = 5, + MEDIA_PLAYER_STATE_ON = 6, }; 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_OFF = 10, 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); @@ -77,9 +81,11 @@ class MediaPlayerTraits { MediaPlayerTraits() = default; void set_supports_pause(bool supports_pause) { this->supports_pause_ = 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 &get_supported_formats() { return this->supported_formats_; } uint32_t get_feature_flags() const { @@ -90,12 +96,16 @@ class MediaPlayerTraits { if (this->get_supports_pause()) { flags |= MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY; } + if (this->get_supports_turn_off_on()) { + flags |= MediaPlayerEntityFeature::TURN_OFF | MediaPlayerEntityFeature::TURN_ON; + } return flags; } protected: - bool supports_pause_{false}; std::vector supported_formats_{}; + bool supports_pause_{false}; + bool supports_turn_off_on_{false}; }; class MediaPlayerCall { diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 472067797e..39c6c3def1 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -263,7 +263,7 @@ class TimePeriodMinutes(TimePeriod): 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: diff --git a/requirements.txt b/requirements.txt index 3b3ee8142e..d261f6d896 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==4.9.0 click==8.1.7 esphome-dashboard==20250514.0 -aioesphomeapi==37.1.5 +aioesphomeapi==37.1.6 zeroconf==0.147.0 puremagic==1.30 ruamel.yaml==0.18.14 # dashboard_import diff --git a/tests/components/host/common.yaml b/tests/components/host/common.yaml index fca0c5d597..5c329c8245 100644 --- a/tests/components/host/common.yaml +++ b/tests/components/host/common.yaml @@ -8,3 +8,10 @@ logger: host: 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.