async def async_get_triggers(opp, device_id): """List device triggers.""" triggers = [] entity_registry = await opp.helpers.entity_registry.async_get_registry() entries = [ entry for entry in async_entries_for_device(entity_registry, device_id) if entry.domain == DOMAIN ] for entry in entries: device_class = DEVICE_CLASS_NONE state = opp.states.get(entry.entity_id) unit_of_measurement = ( state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) if state else None ) if not state or not unit_of_measurement: continue if ATTR_DEVICE_CLASS in state.attributes: device_class = state.attributes[ATTR_DEVICE_CLASS] templates = ENTITY_TRIGGERS.get( device_class, ENTITY_TRIGGERS[DEVICE_CLASS_NONE] ) triggers.extend( ( { **automation, "platform": "device", "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, } for automation in templates ) ) return triggers
async def async_get_triggers(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device triggers for Media player entities.""" registry = await entity_registry.async_get_registry(opp) triggers = [] # Get all the integration entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue # Add triggers for each entity that belongs to this integration triggers += [{ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: trigger, } for trigger in TRIGGER_TYPES] return triggers
async def test_device_update_listener(opp): """Test we update device and entity registry when the entry is renamed.""" device = get_device("Office") device_registry = mock_device_registry(opp) entity_registry = mock_registry(opp) mock_api, mock_entry = await device.setup_entry(opp) await opp.async_block_till_done() with patch( "openpeerpower.components.broadlink.device.blk.gendevice", return_value=mock_api ): opp.config_entries.async_update_entry(mock_entry, title="New Name") await opp.async_block_till_done() device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)}) assert device_entry.name == "New Name" for entry in async_entries_for_device(entity_registry, device_entry.id): assert entry.original_name.startswith("New Name")
def async_migrate_old_entity( opp: OpenPeerPower, ent_reg: EntityRegistry, registered_unique_ids: set[str], platform: str, device: DeviceEntry, unique_id: str, ) -> None: """Migrate existing entity if current one can't be found and an old one exists.""" # If we can find an existing entity with this unique ID, there's nothing to migrate if ent_reg.async_get_entity_id(platform, DOMAIN, unique_id): return value_id = ValueID.from_unique_id(unique_id) # Look for existing entities in the registry that could be the same value but on # a different endpoint existing_entity_entries: list[RegistryEntry] = [] for entry in async_entries_for_device(ent_reg, device.id): # If entity is not in the domain for this discovery info or entity has already # been processed, skip it if entry.domain != platform or entry.unique_id in registered_unique_ids: continue old_ent_value_id = ValueID.from_unique_id(entry.unique_id) if value_id.is_same_value_different_endpoints(old_ent_value_id): existing_entity_entries.append(entry) # We can return early if we get more than one result if len(existing_entity_entries) > 1: return # If we couldn't find any results, return early if not existing_entity_entries: return entry = existing_entity_entries[0] state = opp.states.get(entry.entity_id) if not state or state.state == STATE_UNAVAILABLE: async_migrate_unique_id(ent_reg, platform, entry.unique_id, unique_id)
async def async_get_conditions( opp: OpenPeerPower, device_id: str ) -> list[dict[str, str]]: """List device conditions.""" conditions: list[dict[str, str]] = [] entity_registry = await async_get_registry(opp) entries = [ entry for entry in async_entries_for_device(entity_registry, device_id) if entry.domain == DOMAIN ] for entry in entries: device_class = DEVICE_CLASS_NONE state = opp.states.get(entry.entity_id) unit_of_measurement = ( state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) if state else None ) if not state or not unit_of_measurement: continue if ATTR_DEVICE_CLASS in state.attributes: device_class = state.attributes[ATTR_DEVICE_CLASS] templates = ENTITY_CONDITIONS.get( device_class, ENTITY_CONDITIONS[DEVICE_CLASS_NONE] ) conditions.extend( { **template, "condition": "device", "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, } for template in templates ) return conditions
async def _async_get_automations(opp: OpenPeerPower, device_id: str, automation_templates: List[dict], domain: str) -> List[dict]: """List device automations.""" automations: List[Dict[str, Any]] = [] entity_registry = await opp.helpers.entity_registry.async_get_registry() entries = [ entry for entry in async_entries_for_device(entity_registry, device_id) if entry.domain == domain ] for entry in entries: automations.extend(({ **template, "device_id": device_id, "entity_id": entry.entity_id, "domain": domain, } for template in automation_templates)) return automations
async def test_device_setup_registry(opp): """Test we register the device and the entries correctly.""" device = get_device("Office") device_registry = mock_device_registry(opp) entity_registry = mock_registry(opp) _, mock_entry = await device.setup_entry(opp) await opp.async_block_till_done() assert len(device_registry.devices) == 1 device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)}) assert device_entry.identifiers == {(DOMAIN, device.mac)} assert device_entry.name == device.name assert device_entry.model == device.model assert device_entry.manufacturer == device.manufacturer assert device_entry.sw_version == device.fwversion for entry in async_entries_for_device(entity_registry, device_entry.id): assert entry.original_name.startswith(device.name)
async def async_get_triggers(opp: OpenPeerPower, device_id: str) -> List[dict]: """List device triggers for Climate devices.""" registry = await entity_registry.async_get_registry(opp) triggers = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue state = opp.states.get(entry.entity_id) # Add triggers for each entity that belongs to this integration triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "hvac_mode_changed", }) if state and const.ATTR_CURRENT_TEMPERATURE in state.attributes: triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "current_temperature_changed", }) if state and const.ATTR_CURRENT_HUMIDITY in state.attributes: triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "current_humidity_changed", }) return triggers
async def test_rm_pro_sensor_setup(opp): """Test a successful RM pro sensor setup.""" device = get_device("Office") mock_api = device.get_mock_api() mock_api.check_sensors.return_value = {"temperature": 18.2} device_registry = mock_device_registry(opp) entity_registry = mock_registry(opp) mock_api, mock_entry = await device.setup_entry(opp, mock_api=mock_api) assert mock_api.check_sensors.call_count == 1 device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)}) entries = async_entries_for_device(entity_registry, device_entry.id) sensors = [entry for entry in entries if entry.domain == SENSOR_DOMAIN] assert len(sensors) == 1 sensors_and_states = {(sensor.original_name, opp.states.get(sensor.entity_id).state) for sensor in sensors} assert sensors_and_states == {(f"{device.name} Temperature", "18.2")}
async def test_device_info(opp: OpenPeerPower) -> None: """Verify device information includes expected details.""" client = create_mock_client() client.components = TEST_COMPONENTS for component in TEST_COMPONENTS: name = slugify(KEY_COMPONENTID_TO_NAME[str(component["name"])]) register_test_entity( opp, SWITCH_DOMAIN, f"{TYPE_HYPERION_COMPONENT_SWITCH_BASE}_{name}", f"{TEST_SWITCH_COMPONENT_BASE_ENTITY_ID}_{name}", ) await setup_test_config_entry(opp, hyperion_client=client) assert opp.states.get(TEST_SWITCH_COMPONENT_ALL_ENTITY_ID) is not None device_identifer = get_hyperion_device_id(TEST_SYSINFO_ID, TEST_INSTANCE) device_registry = dr.async_get(opp) device = device_registry.async_get_device({(DOMAIN, device_identifer)}) assert device assert device.config_entries == {TEST_CONFIG_ENTRY_ID} assert device.identifiers == {(DOMAIN, device_identifer)} assert device.manufacturer == HYPERION_MANUFACTURER_NAME assert device.model == HYPERION_MODEL_NAME assert device.name == TEST_INSTANCE_1["friendly_name"] entity_registry = await er.async_get_registry(opp) entities_from_device = [ entry.entity_id for entry in er.async_entries_for_device(entity_registry, device.id) ] for component in TEST_COMPONENTS: name = slugify(KEY_COMPONENTID_TO_NAME[str(component["name"])]) entity_id = TEST_SWITCH_COMPONENT_BASE_ENTITY_ID + "_" + name assert entity_id in entities_from_device
async def async_get_actions(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device actions.""" actions = await toggle_entity.async_get_actions(opp, device_id, DOMAIN) entity_registry = er.async_get(opp) for entry in er.async_entries_for_device(entity_registry, device_id): if entry.domain != DOMAIN: continue supported_color_modes = get_supported_color_modes(opp, entry.entity_id) supported_features = get_supported_features(opp, entry.entity_id) if brightness_supported(supported_color_modes): actions.extend(( { CONF_TYPE: TYPE_BRIGHTNESS_INCREASE, "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, }, { CONF_TYPE: TYPE_BRIGHTNESS_DECREASE, "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, }, )) if supported_features & SUPPORT_FLASH: actions.extend(({ CONF_TYPE: TYPE_FLASH, "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, }, )) return actions
async def async_get_actions(opp: OpenPeerPower, device_id: str) -> List[dict]: """List device actions for NEW_NAME devices.""" registry = await entity_registry.async_get_registry(opp) actions = [] # TODO Read this comment and remove it. # This example shows how to iterate over the entities of this device # that match this integration. If your actions instead rely on # calling services, do something like: # zha_device = await _async_get_zha_device(opp, device_id) # return zha_device.device_actions # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue # Add actions for each entity that belongs to this integration # TODO add your own actions. actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "turn_on", } ) actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "turn_off", } ) return actions
async def async_get_actions(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device actions for Humidifier devices.""" registry = await entity_registry.async_get_registry(opp) actions = await toggle_entity.async_get_actions(opp, device_id, DOMAIN) # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue state = opp.states.get(entry.entity_id) actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "set_humidity", } ) # We need a state or else we can't populate the available modes. if state is None: continue if state.attributes[ATTR_SUPPORTED_FEATURES] & const.SUPPORT_MODES: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "set_mode", } ) return actions
async def async_get_actions(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device actions for Fan devices.""" registry = await entity_registry.async_get_registry(opp) actions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue actions.append({ CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "turn_on", }) actions.append({ CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "turn_off", }) return actions
async def async_get_triggers(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device triggers for Kodi devices.""" registry = await entity_registry.async_get_registry(opp) triggers = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain == "media_player": triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "turn_on", }) triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "turn_off", }) return triggers
async def async_get_actions(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device actions for Lock devices.""" registry = await entity_registry.async_get_registry(opp) actions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue # Add actions for each entity that belongs to this integration actions.append({ CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "lock", }) actions.append({ CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "unlock", }) state = opp.states.get(entry.entity_id) if state: features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if features & (SUPPORT_OPEN): actions.append({ CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "open", }) return actions
def _resolve_device(self, device_id) -> None: """Resolve a device.""" device_entry = self._device_reg.async_get(device_id) # Unlikely entry doesn't exist, but let's guard for bad data. if device_entry is not None: if device_entry.area_id: self._add_or_resolve("area", device_entry.area_id) for config_entry_id in device_entry.config_entries: self._add_or_resolve("config_entry", config_entry_id) # We do not resolve device_entry.via_device_id because that # device is not related data-wise inside HA. for entity_entry in entity_registry.async_entries_for_device( self._entity_reg, device_id ): self._add_or_resolve("entity", entity_entry.entity_id) for entity_id in script.scripts_with_device(self.opp, device_id): self._add_or_resolve("entity", entity_id) for entity_id in automation.automations_with_device(self.opp, device_id): self._add_or_resolve("entity", entity_id)
async def async_get_conditions(opp: OpenPeerPower, device_id: str) -> list[dict[str, str]]: """List device conditions for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(opp) conditions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue state = opp.states.get(entry.entity_id) # We need a state or else we can't populate the different armed conditions if state is None: continue supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] # Add conditions for each entity that belongs to this integration conditions += [ { CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: CONDITION_DISARMED, }, { CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: CONDITION_TRIGGERED, }, ] if supported_features & SUPPORT_ALARM_ARM_HOME: conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: CONDITION_ARMED_HOME, }) if supported_features & SUPPORT_ALARM_ARM_AWAY: conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: CONDITION_ARMED_AWAY, }) if supported_features & SUPPORT_ALARM_ARM_NIGHT: conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: CONDITION_ARMED_NIGHT, }) if supported_features & SUPPORT_ALARM_ARM_CUSTOM_BYPASS: conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: CONDITION_ARMED_CUSTOM_BYPASS, }) return conditions
async def async_get_conditions(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device conditions for Cover devices.""" registry = await entity_registry.async_get_registry(opp) conditions: list[dict[str, Any]] = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue state = opp.states.get(entry.entity_id) if not state or ATTR_SUPPORTED_FEATURES not in state.attributes: continue supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] supports_open_close = supported_features & (SUPPORT_OPEN | SUPPORT_CLOSE) # Add conditions for each entity that belongs to this integration if supports_open_close: conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "is_open", }) conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "is_closed", }) conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "is_opening", }) conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "is_closing", }) if supported_features & SUPPORT_SET_POSITION: conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "is_position", }) if supported_features & SUPPORT_SET_TILT_POSITION: conditions.append({ CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "is_tilt_position", }) return conditions
def device_entities(opp: OpenPeerPower, device_id: str) -> Iterable[str]: """Get entity ids for entities tied to a device.""" entity_reg = entity_registry.async_get(opp) entries = entity_registry.async_entries_for_device(entity_reg, device_id) return [entry.entity_id for entry in entries]
async def async_get_actions(opp: OpenPeerPower, device_id: str) -> list[dict]: """List device actions for Cover devices.""" registry = await entity_registry.async_get_registry(opp) actions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue state = opp.states.get(entry.entity_id) if not state or ATTR_SUPPORTED_FEATURES not in state.attributes: continue supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] # Add actions for each entity that belongs to this integration if supported_features & SUPPORT_SET_POSITION: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "set_position", } ) else: if supported_features & SUPPORT_OPEN: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "open", } ) if supported_features & SUPPORT_CLOSE: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "close", } ) if supported_features & SUPPORT_STOP: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "stop", } ) if supported_features & SUPPORT_SET_TILT_POSITION: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "set_tilt_position", } ) else: if supported_features & SUPPORT_OPEN_TILT: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "open_tilt", } ) if supported_features & SUPPORT_CLOSE_TILT: actions.append( { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "close_tilt", } ) return actions
async def async_get_triggers(opp: OpenPeerPower, device_id: str) -> List[dict]: """List device triggers for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(opp) triggers = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue entity_state = opp.states.get(entry.entity_id) # We need a state or else we can't populate the HVAC and preset modes. if entity_state is None: continue supported_features = entity_state.attributes["supported_features"] # Add triggers for each entity that belongs to this integration triggers += [ { CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "disarmed", }, { CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "triggered", }, ] if supported_features & SUPPORT_ALARM_ARM_HOME: triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "armed_home", }) if supported_features & SUPPORT_ALARM_ARM_AWAY: triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "armed_away", }) if supported_features & SUPPORT_ALARM_ARM_NIGHT: triggers.append({ CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "armed_night", }) return triggers