From e1e6d60def2053a3bd66f0e50db3c39a9f125bd2 Mon Sep 17 00:00:00 2001 From: olicooper <22296715+olicooper@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:06:26 +0100 Subject: [PATCH] Add basic cardMedia support --- components/nspanel_lovelace/__init__.py | 30 +++-- components/nspanel_lovelace/card_items.cpp | 3 + components/nspanel_lovelace/cards.cpp | 105 ++++++++++++++++++ components/nspanel_lovelace/cards.h | 25 +++++ components/nspanel_lovelace/entity.cpp | 5 +- .../nspanel_lovelace/nspanel_lovelace.cpp | 105 ++++++++++++++++-- .../nspanel_lovelace/page_item_base.cpp | 22 ++++ components/nspanel_lovelace/page_item_base.h | 1 + components/nspanel_lovelace/page_visitor.cpp | 4 + components/nspanel_lovelace/page_visitor.h | 3 + components/nspanel_lovelace/types.h | 49 ++++++++ test.yaml | 8 +- 12 files changed, 338 insertions(+), 22 deletions(-) diff --git a/components/nspanel_lovelace/__init__.py b/components/nspanel_lovelace/__init__.py index bccfeef..1935307 100644 --- a/components/nspanel_lovelace/__init__.py +++ b/components/nspanel_lovelace/__init__.py @@ -66,7 +66,7 @@ def AUTO_LOAD(): ENTITY_TYPES = [ 'sensor','binary_sensor','light','switch','scene','timer','weather','navigate', 'alarm_control_panel','input_boolean','input_button','cover','fan','automation', - 'script', 'climate' + 'script', 'climate', 'media_player' ] CONF_INCOMING_MSG = "on_incoming_msg" @@ -110,12 +110,14 @@ def AUTO_LOAD(): CARD_QR="cardQR" CARD_ALARM="cardAlarm" CARD_THERMO="cardThermo" -CARD_TYPE_OPTIONS = [CARD_ENTITIES, CARD_GRID, CARD_GRID2, CARD_QR, CARD_ALARM, CARD_THERMO] +CARD_MEDIA="cardMedia" +CARD_TYPE_OPTIONS = [CARD_ENTITIES, CARD_GRID, CARD_GRID2, CARD_QR, CARD_ALARM, CARD_THERMO, CARD_MEDIA] CONF_CARD_QR_TEXT = "qr_text" CONF_CARD_ALARM_ENTITY_ID = "alarm_entity_id" CONF_CARD_ALARM_SUPPORTED_MODES = "supported_modes" CONF_CARD_THERMO_ENTITY_ID = "thermo_entity_id" +CONF_CARD_MEDIA_ENTITY_ID = "media_entity_id" def load_icons(): global iconJson @@ -311,6 +313,8 @@ def get_card_entities_length_limits(card_type: str, model: str = 'eu') -> list[i return [1,8] if (card_type == CARD_QR): return [1,2] + if (card_type == CARD_MEDIA): + return [0,8] return [0,0] def validate_config(config): @@ -351,6 +355,8 @@ def validate_config(config): add_entity_id(card_config.get(CONF_CARD_ALARM_ENTITY_ID)) if CONF_CARD_THERMO_ENTITY_ID in card_config: add_entity_id(card_config.get(CONF_CARD_THERMO_ENTITY_ID)) + if CONF_CARD_MEDIA_ENTITY_ID in card_config: + add_entity_id(card_config.get(CONF_CARD_MEDIA_ENTITY_ID)) if CONF_SCREENSAVER in config: screensaver_config = config.get(CONF_SCREENSAVER) @@ -403,6 +409,10 @@ def validate_config(config): CARD_THERMO: SCHEMA_CARD_BASE.extend({ cv.Required(CONF_CARD_THERMO_ENTITY_ID): valid_entity_id(['climate']) }), + CARD_MEDIA: SCHEMA_CARD_BASE.extend({ + cv.Optional(CONF_CARD_ENTITIES): cv.ensure_list(SCHEMA_CARD_ENTITY), + cv.Required(CONF_CARD_MEDIA_ENTITY_ID): valid_entity_id(['media_player']) + }), }, default_type=CARD_GRID)) ), @@ -423,6 +433,7 @@ def validate_config(config): QRCard = nspanel_lovelace_ns.class_("QRCard") AlarmCard = nspanel_lovelace_ns.class_("AlarmCard") ThermoCard = nspanel_lovelace_ns.class_("ThermoCard") +MediaCard = nspanel_lovelace_ns.class_("MediaCard") DeleteItem = nspanel_lovelace_ns.class_("DeleteItem") NavigationItem = nspanel_lovelace_ns.class_("NavigationItem") @@ -443,6 +454,7 @@ def validate_config(config): CARD_QR: ["nspanel_card_", QRCard, PageType.cardQR, EntitiesCardEntityItem], CARD_ALARM: ["nspanel_card_", AlarmCard, PageType.cardAlarm, AlarmButtonItem], CARD_THERMO: ["nspanel_card_", ThermoCard, PageType.cardThermo, None], + CARD_MEDIA: ["nspanel_card_", MediaCard, PageType.cardMedia, GridCardEntityItem], } def get_new_uuid(prefix: str = ""): @@ -707,14 +719,16 @@ async def to_code(config): # cg.add(card_class.set_sleep_timeout(sleep_timeout)) # cg.add(cg.RawExpression(f"{card_variable}->set_sleep_timeout({sleep_timeout})")) - if card_config[CONF_CARD_TYPE] == CARD_ALARM: - cg.add(cg.RawExpression( - f"auto {card_variable} = " - f"{nspanel.create_page.template(page_info[1]).__call__(card_uuids[i], get_entity_id(card_config[CONF_CARD_ALARM_ENTITY_ID]), title, sleep_timeout)}")) - elif card_config[CONF_CARD_TYPE] == CARD_THERMO: + if card_config[CONF_CARD_TYPE] in [CARD_ALARM, CARD_THERMO, CARD_MEDIA]: + if (card_config[CONF_CARD_TYPE] == CARD_ALARM): + entity_id_key = CONF_CARD_ALARM_ENTITY_ID + elif (card_config[CONF_CARD_TYPE] == CARD_THERMO): + entity_id_key = CONF_CARD_THERMO_ENTITY_ID + else: + entity_id_key = CONF_CARD_MEDIA_ENTITY_ID cg.add(cg.RawExpression( f"auto {card_variable} = " - f"{nspanel.create_page.template(page_info[1]).__call__(card_uuids[i], get_entity_id(card_config[CONF_CARD_THERMO_ENTITY_ID]), title, sleep_timeout)}")) + f"{nspanel.create_page.template(page_info[1]).__call__(card_uuids[i], get_entity_id(card_config[entity_id_key]), title, sleep_timeout)}")) else: cg.add(cg.RawExpression( f"auto {card_variable} = " diff --git a/components/nspanel_lovelace/card_items.cpp b/components/nspanel_lovelace/card_items.cpp index 0536f5a..1147596 100644 --- a/components/nspanel_lovelace/card_items.cpp +++ b/components/nspanel_lovelace/card_items.cpp @@ -80,6 +80,9 @@ void EntitiesCardEntityItem::on_entity_attribute_change( attr != ha_attr_type::current_temperature) { return; } + } else if (!this->is_type(entity_type::media_player)) { + // Any entity type not mentioned above doesn't need re-rendering + return; } this->on_state_callback_(this); diff --git a/components/nspanel_lovelace/cards.cpp b/components/nspanel_lovelace/cards.cpp index 526f59d..77b146a 100644 --- a/components/nspanel_lovelace/cards.cpp +++ b/components/nspanel_lovelace/cards.cpp @@ -383,5 +383,110 @@ std::string &ThermoCard::render(std::string &buffer) { return buffer; } +/* + * =============== MediaCard =============== + */ + +MediaCard::MediaCard(const std::string &uuid, + const std::shared_ptr &media_entity) : + Card(page_type::cardMedia, uuid), + media_entity_(media_entity) { + media_entity->add_subscriber(this); +} + +MediaCard::MediaCard(const std::string &uuid, + const std::shared_ptr &media_entity, + const std::string &title) : + Card(page_type::cardMedia, uuid, title), + media_entity_(media_entity) { + media_entity->add_subscriber(this); +} + +MediaCard::MediaCard(const std::string &uuid, + const std::shared_ptr &media_entity, + const std::string &title, const uint16_t sleep_timeout) : + Card(page_type::cardMedia, uuid, title, sleep_timeout), + media_entity_(media_entity) { + media_entity->add_subscriber(this); +} + +MediaCard::~MediaCard() { + media_entity_->remove_subscriber(this); +} + +void MediaCard::accept(PageVisitor& visitor) { visitor.visit(*this); } + +// entityUpd~{heading}~{navigation}~{entityId}~{title}~~{author}~~{volume}~{iconplaypause}~{onoffbutton}~{shuffleBtn}{media_icon}{item_str} +std::string &MediaCard::render(std::string &buffer) { + buffer.assign(this->get_render_instruction()) + .append(1, SEPARATOR) + .append(this->get_title()) + .append(1, SEPARATOR); + + this->render_nav(buffer).append(1, SEPARATOR); + + buffer.append(this->media_entity_->get_entity_id()); + buffer.append(1, SEPARATOR); + + buffer.append(this->media_entity_->get_attribute( + ha_attr_type::media_title)); + buffer.append(2, SEPARATOR); + + buffer.append(this->media_entity_->get_attribute( + ha_attr_type::media_artist)); + buffer.append(2, SEPARATOR); + + buffer.append(std::to_string( + std::stoi(this->media_entity_->get_attribute( + ha_attr_type::volume_level, "0")) * 100)); + buffer.append(1, SEPARATOR); + + auto icon = this->media_entity_->is_state("playing") + ? icon_t::pause : icon_t::play; + buffer.append(icon).append(1, SEPARATOR); + + uint32_t supported_features = value_or_default(this->media_entity_-> + get_attribute(ha_attr_type::supported_features), 0UL); + + // on/off button colour + if (supported_features & 0b10000000) { + if (this->media_entity_->is_state(generic_type::off)) + buffer.append(std::to_string(1374)); // light blue + else + buffer.append(std::to_string(64704)); // orange + } else { + buffer.append(generic_type::disable); + } + buffer.append(1, SEPARATOR); + + // shuffle button icon + if (supported_features & 0b100000000000000) { + if (this->media_entity_->get_attribute(ha_attr_type::shuffle) == generic_type::on) + buffer.append(icon_t::shuffle); + else + buffer.append(icon_t::shuffle_disable); + } else { + buffer.append(generic_type::disable); + } + buffer.append(1, SEPARATOR); + + // todo: create PageItem for this? + // media icon/button: type~internalName~icon~iconColor~displayName~ + // on btn press: event,buttonPress2,{entity_id},button + buffer.append(entity_render_type::media_pl).append(1, SEPARATOR); + buffer.append(this->media_entity_->get_entity_id()).append(1, SEPARATOR); + auto media_icon = get_icon_by_name(MEDIA_TYPE_MAP, + this->media_entity_->get_attribute(ha_attr_type::media_content_type)); + buffer.append(media_icon).append(1, SEPARATOR); + buffer.append(std::to_string(17299)).append(2, SEPARATOR); + + for (auto& item : this->items_) { + buffer.append(1, SEPARATOR).append(item->render()); + } + if (this->items_.size() > 0) buffer.append(1, SEPARATOR); + + return buffer; +} + } // namespace nspanel_lovelace } // namespace esphome \ No newline at end of file diff --git a/components/nspanel_lovelace/cards.h b/components/nspanel_lovelace/cards.h index d56a43c..a20d3c5 100644 --- a/components/nspanel_lovelace/cards.h +++ b/components/nspanel_lovelace/cards.h @@ -137,5 +137,30 @@ class ThermoCard : public Card, public IEntitySubscriber { const char* temperature_unit_icon_; }; +/* + * =============== MediaCard =============== + */ + +class MediaCard : public Card, public IEntitySubscriber { +public: + MediaCard(const std::string &uuid, + const std::shared_ptr &media_entity); + MediaCard(const std::string &uuid, + const std::shared_ptr &media_entity, + const std::string &title); + MediaCard(const std::string &uuid, + const std::shared_ptr &media_entity, + const std::string &title, + const uint16_t sleep_timeout); + virtual ~MediaCard(); + + void accept(PageVisitor& visitor) override; + + std::string &render(std::string &buffer) override; + +protected: + std::shared_ptr media_entity_; +}; + } // namespace nspanel_lovelace } // namespace esphome \ No newline at end of file diff --git a/components/nspanel_lovelace/entity.cpp b/components/nspanel_lovelace/entity.cpp index ebfe702..53968ec 100644 --- a/components/nspanel_lovelace/entity.cpp +++ b/components/nspanel_lovelace/entity.cpp @@ -126,7 +126,10 @@ void Entity::set_attribute(ha_attr_type attr, const std::string &value) { attr == ha_attr_type::preset_modes || attr == ha_attr_type::swing_modes || attr == ha_attr_type::fan_modes || - attr == ha_attr_type::hvac_modes) { + attr == ha_attr_type::hvac_modes || + // todo: this list can contain any value (including ones with commas), + // convert_python_arr_str does not support this scenario! + attr == ha_attr_type::source_list) { this->attributes_[attr] = convert_python_arr_str(value); } else { this->attributes_[attr] = value; diff --git a/components/nspanel_lovelace/nspanel_lovelace.cpp b/components/nspanel_lovelace/nspanel_lovelace.cpp index aba021f..15921db 100644 --- a/components/nspanel_lovelace/nspanel_lovelace.cpp +++ b/components/nspanel_lovelace/nspanel_lovelace.cpp @@ -239,6 +239,30 @@ void NSPanelLovelace::setup() { &NSPanelLovelace::on_entity_attribute_update_, entity_id, to_string(ha_attr_type::hvac_modes)); } + else if (entity->is_type(entity_type::media_player)) { + add_state_subscription = true; + this->subscribe_homeassistant_state_attr( + &NSPanelLovelace::on_entity_attribute_update_, + entity_id, to_string(ha_attr_type::supported_features)); + this->subscribe_homeassistant_state_attr( + &NSPanelLovelace::on_entity_attribute_update_, + entity_id, to_string(ha_attr_type::media_content_type)); + this->subscribe_homeassistant_state_attr( + &NSPanelLovelace::on_entity_attribute_update_, + entity_id, to_string(ha_attr_type::media_title)); + this->subscribe_homeassistant_state_attr( + &NSPanelLovelace::on_entity_attribute_update_, + entity_id, to_string(ha_attr_type::media_artist)); + this->subscribe_homeassistant_state_attr( + &NSPanelLovelace::on_entity_attribute_update_, + entity_id, to_string(ha_attr_type::volume_level)); + this->subscribe_homeassistant_state_attr( + &NSPanelLovelace::on_entity_attribute_update_, + entity_id, to_string(ha_attr_type::shuffle)); + this->subscribe_homeassistant_state_attr( + &NSPanelLovelace::on_entity_attribute_update_, + entity_id, to_string(ha_attr_type::source_list)); + } if (add_state_subscription) { this->subscribe_homeassistant_state( @@ -1375,19 +1399,70 @@ void NSPanelLovelace::process_button_press_( } // media cards else if (button_type == button_type::mediaNext) { - // todo + this->call_ha_service_( + entity_type, ha_action_type::media_next_track, entity_id); } else if (button_type == button_type::mediaBack) { - // todo + this->call_ha_service_( + entity_type, ha_action_type::media_previous_track, entity_id); } else if (button_type == button_type::mediaPause) { - // todo + this->call_ha_service_( + entity_type, ha_action_type::media_play_pause, entity_id); } else if (button_type == button_type::mediaOnOff) { - // todo + auto entity = this->get_entity_(entity_id); + if (entity == nullptr) return; + this->call_ha_service_( + entity_type, + entity->is_state(generic_type::on) + ? ha_action_type::turn_off + : ha_action_type::turn_on, + entity_id); } else if (button_type == button_type::mediaShuffle) { - // todo + auto entity = this->get_entity_(entity_id); + if (entity == nullptr) return; + auto shuffle = entity->get_attribute(ha_attr_type::shuffle); + if (shuffle.empty()) return; + shuffle = shuffle == generic_type::off + ? generic_type::on : generic_type::off; + this->call_ha_service_( + entity_type, + ha_action_type::shuffle_set, + {{ + {to_string(ha_attr_type::entity_id), entity_id}, + {to_string(ha_attr_type::shuffle), shuffle} + }}); } else if (button_type == button_type::volumeSlider) { - // todo + auto volume = esphome::str_snprintf("%.2f", 6, std::stoi(value) * 0.01f); + this->call_ha_service_( + entity_type, + ha_action_type::volume_set, + {{ + {to_string(ha_attr_type::entity_id), entity_id}, + {to_string(ha_attr_type::volume_level), volume} + }}); } else if (button_type == button_type::speakerSel) { - // todo + this->call_ha_service_( + entity_type, + ha_action_type::select_source, + {{ + {to_string(ha_attr_type::entity_id), entity_id}, + {to_string(ha_attr_type::source), value} + }}); + } else if (button_type == button_type::modeMediaPlayer) { + auto entity = this->get_entity_(entity_id); + if (entity == nullptr) return; + auto source_list_str = entity->get_attribute(ha_attr_type::source_list); + if (source_list_str.empty()) return; + std::vector source_list; + split_str(',', source_list_str, source_list); + uint8_t index = stoi(value); + if (source_list.size() <= index) return; + this->call_ha_service_( + entity_type, + ha_action_type::select_source, + {{ + {to_string(ha_attr_type::entity_id), entity_id}, + {to_string(ha_attr_type::source), source_list.at(index)} + }}); } // light cards else if (button_type == button_type::brightnessSlider) { @@ -1405,7 +1480,7 @@ void NSPanelLovelace::process_button_press_( }}); } else if (button_type == button_type::colorTempSlider) { if (value.empty()) return; - auto entity = get_entity_(entity_id); + auto entity = this->get_entity_(entity_id); if (entity == nullptr) return; auto &minstr = entity->get_attribute(ha_attr_type::min_mireds); auto &maxstr = entity->get_attribute(ha_attr_type::max_mireds); @@ -1487,7 +1562,7 @@ void NSPanelLovelace::process_button_press_( {to_string(ha_attr_type::hvac_mode), value} }}); } else if (button_type == button_type::modePresetModes) { - auto entity = get_entity_(entity_id); + auto entity = this->get_entity_(entity_id); if (entity == nullptr) return; auto &modes_str = entity->get_attribute(ha_attr_type::preset_modes); if (modes_str.empty()) return; @@ -1502,7 +1577,7 @@ void NSPanelLovelace::process_button_press_( {to_string(ha_attr_type::preset_mode), selected_mode} }}); } else if (button_type == button_type::modeSwingModes) { - auto entity = get_entity_(entity_id); + auto entity = this->get_entity_(entity_id); if (entity == nullptr) return; auto &modes_str = entity->get_attribute(ha_attr_type::swing_modes); if (modes_str.empty()) return; @@ -1517,7 +1592,7 @@ void NSPanelLovelace::process_button_press_( {to_string(ha_attr_type::swing_mode), selected_mode} }}); } else if (button_type == button_type::modeFanModes) { - auto entity = get_entity_(entity_id); + auto entity = this->get_entity_(entity_id); if (entity == nullptr) return; auto &modes_str = entity->get_attribute(ha_attr_type::fan_modes); if (modes_str.empty()) return; @@ -1686,13 +1761,19 @@ void NSPanelLovelace::on_entity_attribute_update_(std::string entity_id, std::st } } + auto entity_type = get_entity_type(entity_id); // Thermo cards don't have items to check, only a single thermo entity // render updates when climate entitites are updated - if (get_entity_type(entity_id) == entity_type::climate && + if (entity_type == entity_type::climate && this->current_page_->is_type(page_type::cardThermo)) { force_current_page_update_ = true; return; } + else if (entity_type == entity_type::media_player && + this->current_page_->is_type(page_type::cardMedia)) { + force_current_page_update_ = true; + return; + } // todo: implement popup page checks too // if (this->popup_page_current_uuid_ == item->get_uuid()) { diff --git a/components/nspanel_lovelace/page_item_base.cpp b/components/nspanel_lovelace/page_item_base.cpp index 2e62d9d..d2f960b 100644 --- a/components/nspanel_lovelace/page_item_base.cpp +++ b/components/nspanel_lovelace/page_item_base.cpp @@ -245,6 +245,12 @@ void StatefulPageItem::on_entity_attribute_change(ha_attr_type attr, const std:: this->icon_value_ = (icon == nullptr ? this->icon_default_value_ : icon); } } + } else if (attr == ha_attr_type::media_content_type) { + if (this->icon_value_overridden_) return; + this->icon_value_ = get_icon_by_name( + MEDIA_TYPE_MAP, + this->entity_->get_attribute(ha_attr_type::media_content_type), + generic_type::off); } else { return; } @@ -269,6 +275,8 @@ void StatefulPageItem::set_on_state_callback_(const char *type) { this->on_state_callback_ = StatefulPageItem::state_cover_fn; } else if (type == entity_type::climate) { this->on_state_callback_ = StatefulPageItem::state_climate_fn; + } else if (type == entity_type::media_player) { + this->on_state_callback_ = StatefulPageItem::state_media_fn; } /* else if ( type == entity_type::button || type == entity_type::input_button) { @@ -387,6 +395,20 @@ void StatefulPageItem::state_climate_fn(StatefulPageItem *me) { } } +void StatefulPageItem::state_media_fn(StatefulPageItem *me) { + if (me->icon_color_overridden_) { + return; + } + + if (me->entity_->is_state(generic_type::off)) { + me->icon_color_ = 17299u; // blue + } else if (!me->entity_->is_state(generic_type::unavailable)) { + me->icon_color_ = 64909u; // yellow + } else { + me->icon_color_ = 38066u; // grey + } +} + // clang-format off // void StatefulPageItem::state_button_fn(StatefulPageItem *me) {} // void StatefulPageItem::state_scene_fn(StatefulPageItem *me) {} diff --git a/components/nspanel_lovelace/page_item_base.h b/components/nspanel_lovelace/page_item_base.h index 33e3387..bdd9a2e 100644 --- a/components/nspanel_lovelace/page_item_base.h +++ b/components/nspanel_lovelace/page_item_base.h @@ -214,6 +214,7 @@ class StatefulPageItem : static void state_binary_sensor_fn(StatefulPageItem *me); static void state_cover_fn(StatefulPageItem *me); static void state_climate_fn(StatefulPageItem *me); + static void state_media_fn(StatefulPageItem *me); // static void state_button_fn(StatefulPageItem *me); // static void state_scene_fn(StatefulPageItem *me); // static void state_script_fn(StatefulPageItem *me); diff --git a/components/nspanel_lovelace/page_visitor.cpp b/components/nspanel_lovelace/page_visitor.cpp index cd66693..534d568 100644 --- a/components/nspanel_lovelace/page_visitor.cpp +++ b/components/nspanel_lovelace/page_visitor.cpp @@ -32,6 +32,10 @@ bool InheritancePageVisitor::visit(ThermoCard &page) { return visit(static_cast(page)) || visit(static_cast(page)); } +bool InheritancePageVisitor::visit(MediaCard &page) { + return visit(static_cast(page)) || + visit(static_cast(page)); +} } // namespace nspanel_lovelace } // namespace esphome \ No newline at end of file diff --git a/components/nspanel_lovelace/page_visitor.h b/components/nspanel_lovelace/page_visitor.h index 5095d6a..e81f328 100644 --- a/components/nspanel_lovelace/page_visitor.h +++ b/components/nspanel_lovelace/page_visitor.h @@ -17,6 +17,7 @@ class EntitiesCard; class QRCard; class AlarmCard; class ThermoCard; +class MediaCard; class PageVisitor { public: @@ -28,6 +29,7 @@ class PageVisitor { virtual bool visit(QRCard &) = 0; virtual bool visit(AlarmCard &) = 0; virtual bool visit(ThermoCard &) = 0; + virtual bool visit(MediaCard &) = 0; }; class InheritancePageVisitor : public PageVisitor { @@ -40,6 +42,7 @@ class InheritancePageVisitor : public PageVisitor { virtual bool visit(QRCard &); virtual bool visit(AlarmCard &); virtual bool visit(ThermoCard &); + virtual bool visit(MediaCard &); }; template diff --git a/components/nspanel_lovelace/types.h b/components/nspanel_lovelace/types.h index c50bf12..017292c 100644 --- a/components/nspanel_lovelace/types.h +++ b/components/nspanel_lovelace/types.h @@ -174,6 +174,10 @@ class DayOfWeekMap { // todo: populate a list of all used icons struct icon_t { static constexpr const char* alert_circle_outline = u8"\uE5D5"; + static constexpr const char* pause = u8"\uE3E3"; + static constexpr const char* play = u8"\uE409"; + static constexpr const char* shuffle = u8"\uE49C"; + static constexpr const char* shuffle_disable = u8"\uE49D"; }; struct Icon { @@ -465,6 +469,12 @@ struct ha_action_type { static constexpr const char* set_preset_mode = "set_preset_mode"; static constexpr const char* set_swing_mode = "set_swing_mode"; static constexpr const char* set_fan_mode = "set_fan_mode"; + static constexpr const char* media_next_track = "media_next_track"; + static constexpr const char* media_previous_track = "media_previous_track"; + static constexpr const char* media_play_pause = "media_play_pause"; + static constexpr const char* shuffle_set = "shuffle_set"; + static constexpr const char* volume_set = "volume_set"; + static constexpr const char* select_source = "select_source"; }; enum class ha_attr_type : uint8_t { @@ -515,6 +525,14 @@ enum class ha_attr_type : uint8_t { fan_modes, hvac_mode, hvac_modes, + // media + media_title, + media_artist, + volume_level, + shuffle, + media_content_type, + source, + source_list, }; static constexpr const char* ha_attr_names [] = { @@ -565,6 +583,14 @@ static constexpr const char* ha_attr_names [] = { "fan_modes", "hvac_mode", "hvac_modes", + // media + "media_title", + "media_artist", + "volume_level", + "shuffle", + "media_content_type", + "source", + "source_list", }; inline const char *to_string(ha_attr_type attr) { @@ -600,6 +626,17 @@ struct ha_attr_hvac_mode { static constexpr const char* fan_only = "fan_only"; }; +struct ha_attr_media_content_type { + static constexpr const char* music = "music"; + static constexpr const char* tvshow = "tvshow"; + static constexpr const char* video = "video"; + static constexpr const char* episode = "episode"; + static constexpr const char* channel = "channel"; + static constexpr const char* playlist = "playlist"; + static constexpr const char* app = "app"; + static constexpr const char* url = "url"; +}; + enum class datetime_mode : uint8_t { date = 1<<0, time = 1<<1, @@ -807,6 +844,18 @@ const char_map CLIMATE_ICON_MAP { {ha_attr_hvac_mode::fan_only, u8"\uE20F"}, // fan }; +const char_map MEDIA_TYPE_MAP { + {generic_type::off, u8"\uE4C3"}, // speaker-off + {ha_attr_media_content_type::music, u8"\uE759"}, // music + {ha_attr_media_content_type::tvshow, u8"\uE380"}, // movie + {ha_attr_media_content_type::video, u8"\uE566"}, // video + {ha_attr_media_content_type::episode, u8"\uE410"}, // playlist-play (originally: icon_t::alert_circle_outline) + {ha_attr_media_content_type::channel, u8"\uEDF1"}, // playlist-star (OR radio-tower E43A / broadcast F71F?) (originally: icon_t::alert_circle_outline) + {ha_attr_media_content_type::playlist, u8"\uECB7"}, // playlist-music (originally: icon_t::alert_circle_outline) + {ha_attr_media_content_type::app, u8"\uE3CA"}, // newly added! open-in-app + {ha_attr_media_content_type::url, u8"\uED1A"}, // newly added! link-box-outline (OR cast E117?) +}; + // cover_mapping const char_list_map COVER_MAP { // "device_class": ("icon-open", "icon-closed", "icon-cover-open", "icon-cover-close") diff --git a/test.yaml b/test.yaml index 96bc864..766b7a5 100644 --- a/test.yaml +++ b/test.yaml @@ -103,7 +103,6 @@ nspanel_lovelace: name: Climate Ecobee - entity_id: climate.hvac name: Climate HVAC - - type: cardQR id: qr_card @@ -140,6 +139,13 @@ nspanel_lovelace: title: Thermo heatpump thermo_entity_id: climate.heatpump + - type: cardMedia + sleep_timeout: 60 + title: Media Lounge + media_entity_id: media_player.lounge_room + entities: + - entity_id: sensor.carbon_dioxide + logger: baud_rate: 115200 level: DEBUG #[NONE,ERROR,WARN,DEBUG,VERBOSE,VERY_VERBOSE]