async def test_setup_source(hass): """Check that we register sources correctly.""" platform = MockEntityPlatform(hass) entity_platform = MockEntity(name="Platform Config Source") await platform.async_add_entities([entity_platform]) platform.config_entry = MockConfigEntry() entity_entry = MockEntity(name="Config Entry Source") await platform.async_add_entities([entity_entry]) assert entity.entity_sources(hass) == { "test_domain.platform_config_source": { "custom_component": False, "domain": "test_platform", "source": entity.SOURCE_PLATFORM_CONFIG, }, "test_domain.config_entry_source": { "config_entry": platform.config_entry.entry_id, "custom_component": False, "domain": "test_platform", "source": entity.SOURCE_CONFIG_ENTRY, }, } await platform.async_reset() assert entity.entity_sources(hass) == {}
def handle_entity_source(hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]) -> None: """Handle entity source command.""" raw_sources = entity.entity_sources(hass) entity_perm = connection.user.permissions.check_entity if "entity_id" not in msg: if connection.user.permissions.access_all_entities("read"): sources = raw_sources else: sources = { entity_id: source for entity_id, source in raw_sources.items() if entity_perm(entity_id, "read") } connection.send_message(messages.result_message(msg["id"], sources)) return sources = {} for entity_id in msg["entity_id"]: if not entity_perm(entity_id, "read"): raise Unauthorized( context=connection.context(msg), permission=POLICY_READ, perm_category=CAT_ENTITIES, ) if (source := raw_sources.get(entity_id)) is None: connection.send_error(msg["id"], ERR_NOT_FOUND, "Entity not found") return sources[entity_id] = source
def warn_dip(hass: HomeAssistant, entity_id: str, state: State) -> None: """Log a warning once if a sensor with state_class_total has a decreasing value. The log will be suppressed until two dips have been seen to prevent warning due to rounding issues with databases storing the state as a single precision float, which was fixed in recorder DB version 20. """ if SEEN_DIP not in hass.data: hass.data[SEEN_DIP] = set() if entity_id not in hass.data[SEEN_DIP]: hass.data[SEEN_DIP].add(entity_id) return if WARN_DIP not in hass.data: hass.data[WARN_DIP] = set() if entity_id not in hass.data[WARN_DIP]: hass.data[WARN_DIP].add(entity_id) domain = entity_sources(hass).get(entity_id, {}).get("domain") if domain in ["energy", "growatt_server", "solaredge"]: return _LOGGER.warning( "Entity %s %shas state class total_increasing, but its state is " "not strictly increasing. Triggered by state %s with last_updated set to %s. " "Please %s", entity_id, f"from integration {domain} " if domain else "", state.state, state.last_updated.isoformat(), _suggest_report_issue(hass, entity_id), )
def _suggest_report_issue(hass: HomeAssistant, entity_id: str) -> str: """Suggest to report an issue.""" domain = entity_sources(hass).get(entity_id, {}).get("domain") custom_component = entity_sources(hass).get(entity_id, {}).get("custom_component") report_issue = "" if custom_component: report_issue = "report it to the custom component author." else: report_issue = ( "create a bug report at " "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue" ) if domain: report_issue += f"+label%3A%22integration%3A+{domain}%22" return report_issue
def warn_dip(hass: HomeAssistant, entity_id: str) -> None: """Log a warning once if a sensor with state_class_total has a decreasing value. The log will be suppressed until two dips have been seen to prevent warning due to rounding issues with databases storing the state as a single precision float, which was fixed in recorder DB version 20. """ if SEEN_DIP not in hass.data: hass.data[SEEN_DIP] = set() if entity_id not in hass.data[SEEN_DIP]: hass.data[SEEN_DIP].add(entity_id) return if WARN_DIP not in hass.data: hass.data[WARN_DIP] = set() if entity_id not in hass.data[WARN_DIP]: hass.data[WARN_DIP].add(entity_id) domain = entity_sources(hass).get(entity_id, {}).get("domain") if domain in ["energy", "growatt_server", "solaredge"]: return _LOGGER.warning( "Entity %s %shas state class total_increasing, but its state is " "not strictly increasing. Please create a bug report at %s", entity_id, f"from integration {domain} " if domain else "", "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue" "+label%3A%22integration%3A+recorder%22", )
async def async_handle_play_stream_service( camera: Camera, service_call: ServiceCall ) -> None: """Handle play stream services calls.""" fmt = service_call.data[ATTR_FORMAT] url = await _async_stream_endpoint_url(camera.hass, camera, fmt) hass = camera.hass data: Mapping[str, str] = { ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}", ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], } # It is required to send a different payload for cast media players entity_ids = service_call.data[ATTR_MEDIA_PLAYER] sources = entity_sources(hass) cast_entity_ids = [ entity for entity in entity_ids # All entities should be in sources. This extra guard is to # avoid people writing to the state machine and breaking it. if entity in sources and sources[entity]["domain"] == "cast" ] other_entity_ids = list(set(entity_ids) - set(cast_entity_ids)) if cast_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: cast_entity_ids, **data, ATTR_MEDIA_EXTRA: { "stream_type": "LIVE", "media_info": { "hlsVideoSegmentFormat": "fmp4", }, }, }, blocking=True, context=service_call.context, ) if other_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: other_entity_ids, **data, }, blocking=True, context=service_call.context, )
def warn_negative(hass: HomeAssistant, entity_id: str) -> None: """Log a warning once if a sensor with state_class_total has a negative value.""" if WARN_NEGATIVE not in hass.data: hass.data[WARN_NEGATIVE] = set() if entity_id not in hass.data[WARN_NEGATIVE]: hass.data[WARN_NEGATIVE].add(entity_id) domain = entity_sources(hass).get(entity_id, {}).get("domain") _LOGGER.warning( "Entity %s %shas state class total_increasing, but its state is " "negative. Please %s", entity_id, f"from integration {domain} " if domain else "", _suggest_report_issue(hass, entity_id), )
async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" fmt = service_call.data[ATTR_FORMAT] url = await _async_stream_endpoint_url(camera.hass, camera, fmt) hass = camera.hass data = { ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}", ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], } # It is required to send a different payload for cast media players entity_ids = service_call.data[ATTR_MEDIA_PLAYER] cast_entity_ids = [ entity for entity, source in entity_sources(hass).items() if entity in entity_ids and source["domain"] == "cast" ] other_entity_ids = list(set(entity_ids) - set(cast_entity_ids)) if cast_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: cast_entity_ids, **data, ATTR_MEDIA_EXTRA: { "stream_type": "LIVE", "media_info": { "hlsVideoSegmentFormat": "fmp4", }, }, }, blocking=True, context=service_call.context, ) if other_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: other_entity_ids, **data, }, blocking=True, context=service_call.context, )
def interfaces(self): """Yield the supported interfaces.""" yield AlexaPowerController(self.entity) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if supported & media_player.MediaPlayerEntityFeature.VOLUME_SET: yield AlexaSpeaker(self.entity) elif supported & media_player.MediaPlayerEntityFeature.VOLUME_STEP: yield AlexaStepSpeaker(self.entity) playback_features = ( media_player.MediaPlayerEntityFeature.PLAY | media_player.MediaPlayerEntityFeature.PAUSE | media_player.MediaPlayerEntityFeature.STOP | media_player.MediaPlayerEntityFeature.NEXT_TRACK | media_player.MediaPlayerEntityFeature.PREVIOUS_TRACK) if supported & playback_features: yield AlexaPlaybackController(self.entity) yield AlexaPlaybackStateReporter(self.entity) if supported & media_player.MediaPlayerEntityFeature.SEEK: yield AlexaSeekController(self.entity) if supported & media_player.MediaPlayerEntityFeature.SELECT_SOURCE: inputs = AlexaInputController.get_valid_inputs( self.entity.attributes.get( media_player.const.ATTR_INPUT_SOURCE_LIST, [])) if len(inputs) > 0: yield AlexaInputController(self.entity) if supported & media_player.MediaPlayerEntityFeature.PLAY_MEDIA: yield AlexaChannelController(self.entity) # AlexaEqualizerController is disabled for denonavr # since it blocks alexa from discovering any devices. domain = entity_sources(self.hass).get(self.entity_id, {}).get("domain") if (supported & media_player.MediaPlayerEntityFeature.SELECT_SOUND_MODE and domain != "denonavr"): inputs = AlexaEqualizerController.get_valid_inputs( self.entity.attributes.get( media_player.const.ATTR_SOUND_MODE_LIST, [])) if len(inputs) > 0: yield AlexaEqualizerController(self.entity) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass)
async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" async with async_timeout.timeout(10): source = await camera.stream_source() if not source: raise HomeAssistantError( f"{camera.entity_id} does not support play stream service") hass = camera.hass camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] url = request_stream( hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream, options=camera.stream_options, ) data = { ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}", ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], } # It is required to send a different payload for cast media players cast_entity_ids = [ entity for entity, source in entity_sources(hass).items() if entity in entity_ids and source["domain"] == "cast" ] other_entity_ids = list(set(entity_ids) - set(cast_entity_ids)) if cast_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: cast_entity_ids, **data, ATTR_MEDIA_EXTRA: { "stream_type": "LIVE", "media_info": { "hlsVideoSegmentFormat": "fmp4", }, }, }, blocking=True, context=service_call.context, ) if other_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: other_entity_ids, **data, }, blocking=True, context=service_call.context, )