from ad_toolbox.smartobject import SmartObject EMPTY_VALUES = {None, "", "unknown", "unavailable", "none", "null"} def useful(value): """Return a displayable Home Assistant value, or None.""" if isinstance(value, str): value = value.strip() if value.lower() in EMPTY_VALUES: return None elif value in EMPTY_VALUES: return None return value class VertexContent(SmartObject): """Expose normalized metadata for the source currently selected on a Vertex2.""" def on_initialize_smart_object(self): super().on_initialize_smart_object() self.output = self.args.get("output", "sensor.vertex2_current_content") self.input_selected = self.args.get( "input_selected", "sensor.vertex2_input_selected" ) self.has_signal = self.args.get( "has_signal", "binary_sensor.vertex2_has_input_signal" ) self.sources = self.args.get("sources", {}) state, attributes = self.build_content() self.output_entity = self.create_entity( self.output, state=state, attributes=attributes ) self.listen_state(self._state_changed, self.input_selected) self.listen_state(self._state_changed, self.has_signal) # attribute="all" is important: a media title can change while the # media_player state remains "playing". listened = set() for config in self.sources.values(): for entity in (config.get("entity"), config.get("artwork_entity")): if entity and entity not in listened: self.listen_state(self._state_changed, entity, attribute="all") listened.add(entity) def _state_changed(self, entity, attribute, old, new, kwargs): self.update_content() def _entity_data(self, entity): if not entity: return None, {} data = self.get_state(entity, attribute="all") or {} return data.get("state"), data.get("attributes", {}) or {} @staticmethod def _first_attribute(attributes, names): for name in names: value = useful(attributes.get(name)) if value is not None: return value return None def _artwork(self, config, attributes): artwork = self._first_attribute( attributes, config.get("artwork_attributes", ["entity_picture"]) ) if artwork is not None: return artwork _, artwork_attributes = self._entity_data(config.get("artwork_entity")) return useful(artwork_attributes.get("entity_picture")) @staticmethod def _pause_supported(config, application, source_state): pause_states = config.get("pause_states", []) if not pause_states or useful(source_state) is None: return False supported_apps = config.get("pause_supported_apps") if supported_apps is None: return True return application in supported_apps def build_content(self): selected = useful(self.get_state(self.input_selected)) has_signal = self.get_state(self.has_signal) == "on" base = { "friendly_name": "Contenu Vertex2", "icon": "mdi:television-play", "source": selected, "platform": None, "content_type": None, "title": None, "series_title": None, "application": None, "status": "idle", "artwork": None, "entity_picture": None, "available": False, "is_paused": False, "is_pause_supported": False, } if not has_signal or selected is None: base["source"] = selected base["icon"] = "mdi:television-off" return "idle", base config = self.sources.get(str(selected), {}) label = config.get("label", selected) source_state, attributes = self._entity_data(config.get("entity")) application = self._first_attribute( attributes, config.get("application_attributes", ["app_name"]) ) title = self._first_attribute( attributes, config.get("title_attributes", ["media_title"]) ) series_title = self._first_attribute( attributes, config.get("series_title_attributes", ["media_series_title"]) ) if title is None and config.get("state_as_title"): title = useful(source_state) artwork = self._artwork(config, attributes) pause_supported = self._pause_supported(config, application, source_state) pause_states = config.get("pause_states", []) is_paused = pause_supported and source_state in pause_states content_type = self._first_attribute( attributes, config.get("content_type_attributes", ["media_content_type"]) ) if content_type is None: content_type = config.get("content_type") if content_type is None and application and title is None: content_type = "application" display = title or application or label base.update({ "icon": config.get("icon", base["icon"]), "platform": config.get("platform", label), "content_type": content_type, "title": title, "series_title": series_title, "application": application, "status": useful(source_state) or "active", "artwork": artwork, "entity_picture": artwork, "available": True, "is_paused": is_paused, "is_pause_supported": pause_supported, }) return str(display)[:255], base def update_content(self): state, attributes = self.build_content() current_state = self.output_entity.get_state() current_attributes = self.output_entity.get_state(attribute="all") or {} if isinstance(current_attributes, dict) and "attributes" in current_attributes: current_attributes = current_attributes["attributes"] expected_attributes = dict(attributes) expected_attributes["ad_app"] = self.name if current_state != state or current_attributes != expected_attributes: self.output_entity.set_state(state=state, attributes=expected_attributes)