diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 8eb2d26e54..32bbc5ec0d 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; diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index c190c8e191..7f2299f77c 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, 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/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 {