async def clear_code(hass: HomeAssistant, entity_id: str, code_slot: int) -> None: """Clear the usercode from a code slot.""" _LOGGER.debug("Attempting to call clear_usercode...") if async_using_zwave_js(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): servicedata = { ATTR_ENTITY_ID: entity_id, ATTR_CODE_SLOT: code_slot, } await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_CLEAR_LOCK_USERCODE, servicedata) elif async_using_ozw(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): # Call dummy slot first as a workaround for curr_code_slot in (999, code_slot): servicedata = { ATTR_ENTITY_ID: entity_id, ATTR_CODE_SLOT: curr_code_slot, } await call_service(hass, OZW_DOMAIN, CLEAR_USERCODE, servicedata) elif async_using_zwave(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): node_id = get_node_id(hass, entity_id) if node_id is None: _LOGGER.error( "Problem retrieving node_id from entity %s", entity_id, ) return servicedata = { ATTR_NODE_ID: node_id, ATTR_CODE_SLOT: code_slot, } _LOGGER.debug( "Setting code slot value to random PIN as workaround in case clearing code " "doesn't work") await call_service( hass, LOCK_DOMAIN, SET_USERCODE, { **servicedata, ATTR_USER_CODE: str(random.randint(1000, 9999)) }, ) await call_service(hass, LOCK_DOMAIN, CLEAR_USERCODE, servicedata) else: raise ZWaveIntegrationNotConfiguredError
async def async_migrate_legacy_zwave( hass: HomeAssistant, zwave_config_entry: ConfigEntry, zwave_js_config_entry: ConfigEntry, migration_map: LegacyZWaveMappedData, ) -> None: """Perform Z-Wave to Z-Wave JS migration.""" dev_reg = async_get_device_registry(hass) for zwave_js_device_id, zwave_device_id in migration_map.device_entries.items( ): zwave_device_entry = dev_reg.async_get(zwave_device_id) if not zwave_device_entry: continue dev_reg.async_update_device( zwave_js_device_id, area_id=zwave_device_entry.area_id, name_by_user=zwave_device_entry.name_by_user, ) ent_reg = async_get_entity_registry(hass) for zwave_js_entity_id, zwave_entry in migration_map.entity_entries.items( ): zwave_entity_id = zwave_entry["entity_id"] if not (entity_entry := ent_reg.async_get(zwave_entity_id)): continue ent_reg.async_remove(zwave_entity_id) ent_reg.async_update_entity( zwave_js_entity_id, new_entity_id=entity_entry.entity_id, name=entity_entry.name, icon=entity_entry.icon, )
async def test_setup_partial_config_v31x(hass, mock_remote): """Test the setup with a v3.1.x server.""" config = {"platform": "uvc", "nvr": "foo", "key": "secret"} mock_remote.return_value.server_version = (3, 1, 3) assert await async_setup_component(hass, "camera", {"camera": config}) await hass.async_block_till_done() assert mock_remote.call_count == 1 assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False) camera_states = hass.states.async_all("camera") assert len(camera_states) == 2 state = hass.states.get("camera.front") assert state assert state.name == "Front" state = hass.states.get("camera.back") assert state assert state.name == "Back" entity_registry = async_get_entity_registry(hass) entity_entry = entity_registry.async_get("camera.front") assert entity_entry.unique_id == "one" entity_entry = entity_registry.async_get("camera.back") assert entity_entry.unique_id == "two"
async def test_alarm_create_delete( hass, config_entry, config, soco, alarm_clock, alarm_clock_extended, alarm_event ): """Test for correct creation and deletion of alarms during runtime.""" soco.alarmClock = alarm_clock_extended await setup_platform(hass, config_entry, config) subscription = alarm_clock_extended.subscribe.return_value sub_callback = subscription.callback sub_callback(event=alarm_event) await hass.async_block_till_done() entity_registry = async_get_entity_registry(hass) assert "switch.sonos_alarm_14" in entity_registry.entities assert "switch.sonos_alarm_15" in entity_registry.entities alarm_clock_extended.ListAlarms.return_value = alarm_clock.ListAlarms.return_value sub_callback(event=alarm_event) await hass.async_block_till_done() assert "switch.sonos_alarm_14" in entity_registry.entities assert "switch.sonos_alarm_15" not in entity_registry.entities
async def generate_keymaster_locks( hass: HomeAssistant, config_entry: ConfigEntry ) -> Tuple[KeymasterLock, List[KeymasterLock]]: """Generate primary and child keymaster locks from config entry.""" ent_reg = async_get_entity_registry(hass) primary_lock = KeymasterLock( config_entry.data[CONF_LOCK_NAME], config_entry.data[CONF_LOCK_ENTITY_ID], config_entry.data.get(CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID), config_entry.data.get(CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID), ent_reg, door_sensor_entity_id=config_entry.data[CONF_SENSOR_NAME], ) child_locks = [ KeymasterLock( lock_name, lock[CONF_LOCK_ENTITY_ID], lock.get(CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID), lock.get(CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID), ent_reg, ) for lock_name, lock in config_entry.data.get(CHILD_LOCKS, {}).items() ] return primary_lock, child_locks
async def test_alarm_create_delete(hass, config_entry, config, soco, alarm_clock, alarm_clock_extended, alarm_event): """Test for correct creation and deletion of alarms during runtime.""" entity_registry = async_get_entity_registry(hass) one_alarm = copy(alarm_clock.ListAlarms.return_value) two_alarms = copy(alarm_clock_extended.ListAlarms.return_value) await setup_platform(hass, config_entry, config) assert "switch.sonos_alarm_14" in entity_registry.entities assert "switch.sonos_alarm_15" not in entity_registry.entities subscription = alarm_clock.subscribe.return_value sub_callback = subscription.callback alarm_clock.ListAlarms.return_value = two_alarms sub_callback(event=alarm_event) await hass.async_block_till_done() assert "switch.sonos_alarm_14" in entity_registry.entities assert "switch.sonos_alarm_15" in entity_registry.entities alarm_event.increment_variable("alarm_list_version") alarm_clock.ListAlarms.return_value = one_alarm sub_callback(event=alarm_event) await hass.async_block_till_done() assert "switch.sonos_alarm_14" in entity_registry.entities assert "switch.sonos_alarm_15" not in entity_registry.entities
async def add_code(hass: HomeAssistant, entity_id: str, code_slot: int, usercode: str) -> None: """Set a user code.""" _LOGGER.debug("Attempting to call set_usercode...") servicedata = { ATTR_CODE_SLOT: code_slot, } if async_using_zwave_js(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): servicedata[ATTR_ENTITY_ID] = entity_id servicedata[ATTR_USER_CODE] = usercode await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_SET_LOCK_USERCODE, servicedata) elif async_using_zha(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): servicedata[ATTR_ENTITY_ID] = entity_id servicedata[ATTR_ZHA_USER_CODE] = usercode await call_service(hass, ZHA_DOMAIN, ZHA_SERVICE_SET_LOCK_USERCODE, servicedata) elif async_using_ozw(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): servicedata[ATTR_ENTITY_ID] = entity_id servicedata[ATTR_USER_CODE] = usercode await call_service(hass, OZW_DOMAIN, SET_USERCODE, servicedata) elif async_using_zwave(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): node_id = get_node_id(hass, entity_id) if node_id is None: _LOGGER.error( "Problem retrieving node_id from entity %s", entity_id, ) return servicedata[ATTR_NODE_ID] = node_id servicedata[ATTR_USER_CODE] = usercode await call_service(hass, LOCK_DOMAIN, SET_USERCODE, servicedata) else: raise ZWaveIntegrationNotConfiguredError
async def async_update(self) -> None: """Update sensor.""" if not self.ent_reg: self.ent_reg = async_get_entity_registry(self.hass) if ( not self.lock_config_entry_id or not self.hass.config_entries.async_get_entry(self.lock_config_entry_id) ): entity_id = self.primary_lock.lock_entity_id lock_ent_reg_entry = self.ent_reg.async_get(entity_id) if not lock_ent_reg_entry: if self._lock_found: self._lock_found = False _LOGGER.warning("Can't find your lock %s.", entity_id) return self.lock_config_entry_id = lock_ent_reg_entry.config_entry_id if not self._lock_found: _LOGGER.info("Found your lock %s", entity_id) self._lock_found = True try: client = self.hass.data[ZWAVE_JS_DOMAIN][self.lock_config_entry_id][ ZWAVE_JS_DATA_CLIENT ] except KeyError: _LOGGER.debug("Can't access Z-Wave JS data client.") self._attr_is_on = False return network_ready = bool( client.connected and client.driver and client.driver.controller ) # If network_ready and self._attr_is_on are both true or both false, we don't need # to do anything since there is nothing to update. if not network_ready ^ self.is_on: return self.async_set_is_on_property(network_ready, False) # If we just turned the sensor on, we need to get the latest lock # nodes and devices if self.is_on: await async_update_zwave_js_nodes_and_devices( self.hass, self.lock_config_entry_id, self.primary_lock, self.child_locks, )
def _handle_event(self, event_type: EventType, resource: CLIPResource) -> None: """Handle status event for this resource (or it's parent).""" if event_type == EventType.RESOURCE_DELETED and resource.id == self.resource.id: self.logger.debug("Received delete for %s", self.entity_id) # non-device bound entities like groups and scenes need to be removed here # all others will be be removed by device setup in case of device removal ent_reg = async_get_entity_registry(self.hass) ent_reg.async_remove(self.entity_id) else: self.logger.debug("Received status update for %s", self.entity_id) self.on_update() self.async_write_ha_state()
async def test_setup_full_config(hass, mock_remote, camera_info): """Test the setup with full configuration.""" config = { "platform": "uvc", "nvr": "foo", "password": "******", "port": 123, "key": "secret", } def mock_get_camera(uuid): """Create a mock camera.""" if uuid == "id3": camera_info["model"] = "airCam" return camera_info mock_remote.return_value.index.return_value.append({ "uuid": "three", "name": "Old AirCam", "id": "id3" }) mock_remote.return_value.get_camera.side_effect = mock_get_camera assert await async_setup_component(hass, "camera", {"camera": config}) await hass.async_block_till_done() assert mock_remote.call_count == 1 assert mock_remote.call_args == call("foo", 123, "secret", ssl=False) camera_states = hass.states.async_all("camera") assert len(camera_states) == 2 state = hass.states.get("camera.front") assert state assert state.name == "Front" state = hass.states.get("camera.back") assert state assert state.name == "Back" entity_registry = async_get_entity_registry(hass) entity_entry = entity_registry.async_get("camera.front") assert entity_entry.unique_id == "id1" entity_entry = entity_registry.async_get("camera.back") assert entity_entry.unique_id == "id2"
async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities ): """Setup config entry.""" # Add entities for all defined slots start_from = entry.data[CONF_START] code_slots = entry.data[CONF_SLOTS] async_add_entities( [ CodesSensor(hass, entry, x) for x in range(start_from, start_from + code_slots) ], True, ) async def code_slots_changed( ent_reg: EntityRegistry, platform: entity_platform.EntityPlatform, config_entry: ConfigEntry, old_slots: List[int], new_slots: List[int], ): """Handle code slots changed.""" slots_to_add = list(set(new_slots) - set(old_slots)) slots_to_remove = list(set(old_slots) - set(new_slots)) for slot in slots_to_remove: sensor_name = slugify( f"{config_entry.data[CONF_LOCK_NAME]}_code_slot_{slot}" ) entity_id = f"sensor.{sensor_name}" if ent_reg.async_get(entity_id): await platform.async_remove_entity(entity_id) ent_reg.async_remove(entity_id) async_add_entities( [CodesSensor(hass, entry, x) for x in slots_to_add], True, ) async_dispatcher_connect( hass, f"{DOMAIN}_{entry.entry_id}_code_slots_changed", partial( code_slots_changed, async_get_entity_registry(hass), entity_platform.current_platform.get(), entry, ), ) return True
def _handle_event(self, event_type: EventType, resource: HueResource) -> None: """Handle status event for this resource (or it's parent).""" if event_type == EventType.RESOURCE_DELETED: # remove any services created for zones/rooms # regular devices are removed automatically by the logic in device.py. if resource.type in [ResourceTypes.ROOM, ResourceTypes.ZONE]: dev_reg = async_get_device_registry(self.hass) if device := dev_reg.async_get_device({(DOMAIN, resource.id)}): dev_reg.async_remove_device(device.id) if resource.type in [ ResourceTypes.GROUPED_LIGHT, ResourceTypes.SCENE ]: ent_reg = async_get_entity_registry(self.hass) ent_reg.async_remove(self.entity_id) return
async def set_unique_id(self): """Set unique id for projector config entry.""" _LOGGER.debug("Setting unique_id for projector") if self._unique_id: return False if uid := await self._projector.get_serial_number(): self.hass.config_entries.async_update_entry(self._entry, unique_id=uid) registry = async_get_entity_registry(self.hass) old_entity_id = registry.async_get_entity_id( "media_player", DOMAIN, self._entry.entry_id) if old_entity_id is not None: registry.async_update_entity(old_entity_id, new_unique_id=uid) self.hass.async_create_task( self.hass.config_entries.async_reload(self._entry.entry_id)) return True
async def clear_code(hass: HomeAssistant, entity_id: str, code_slot: int) -> None: """Clear the usercode from a code slot.""" _LOGGER.debug("Attempting to call clear_usercode...") if async_using_zwave_js(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): servicedata = { ATTR_ENTITY_ID: entity_id, ATTR_CODE_SLOT: code_slot, } await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_CLEAR_LOCK_USERCODE, servicedata) else: raise ZWaveIntegrationNotConfiguredError
async def test_enable_entity_disabled_device(hass, client, device_registry): """Test enabling entity of disabled device.""" entity_id = "test_domain.test_platform_1234" config_entry = MockConfigEntry(domain="test_platform") config_entry.add_to_hass(hass) device = device_registry.async_get_or_create( config_entry_id="1234", connections={("ethernet", "12:34:56:78:90:AB:CD:EF")}, identifiers={("bridgeid", "0123")}, manufacturer="manufacturer", model="model", disabled_by=DeviceEntryDisabler.USER, ) device_info = { "connections": {("ethernet", "12:34:56:78:90:AB:CD:EF")}, } platform = MockEntityPlatform(hass) platform.config_entry = config_entry entity = MockEntity(unique_id="1234", device_info=device_info) await platform.async_add_entities([entity]) state = hass.states.get(entity_id) assert state is None entity_reg = async_get_entity_registry(hass) entity_entry = entity_reg.async_get(entity_id) assert entity_entry.config_entry_id == config_entry.entry_id assert entity_entry.device_id == device.id assert entity_entry.disabled_by == RegistryEntryDisabler.DEVICE # UPDATE DISABLED_BY TO NONE await client.send_json( { "id": 8, "type": "config/entity_registry/update", "entity_id": entity_id, "disabled_by": None, } ) msg = await client.receive_json() assert not msg["success"]
async def add_code(hass: HomeAssistant, entity_id: str, code_slot: int, usercode: str) -> None: """Set a user code.""" _LOGGER.debug("Attempting to call set_usercode...") servicedata = { ATTR_CODE_SLOT: code_slot, ATTR_USER_CODE: usercode, } if async_using_zwave_js(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): servicedata[ATTR_ENTITY_ID] = entity_id await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_SET_LOCK_USERCODE, servicedata) else: raise ZWaveIntegrationNotConfiguredError
async def async_update_zha_devices( hass: HomeAssistant, entry_id: str, primary_lock: KeymasterLock, child_locks: List[KeymasterLock], ) -> None: """Update ZHA devices.""" ent_reg = async_get_entity_registry(hass) dev_reg = async_get_device_registry(hass) for lock in [primary_lock, *child_locks]: lock_ent_reg_entry = ent_reg.async_get(lock.lock_entity_id) if not lock_ent_reg_entry: continue lock_dev_reg_entry = dev_reg.async_get(lock_ent_reg_entry.device_id) if not lock_dev_reg_entry: continue lock.zha_lock_entity = lock_ent_reg_entry lock.zha_lock_device = lock_dev_reg_entry
def add_entity_value( self, entity_id: str, entity_values: ZWaveDeviceEntityValues, ) -> None: """Add info for one entity and Z-Wave value.""" ent_reg = async_get_entity_registry(self._hass) dev_reg = async_get_device_registry(self._hass) node = entity_values.primary.node entity_entry = ent_reg.async_get(entity_id) assert entity_entry device_identifier, _ = node_device_id_and_name( node, entity_values.primary.instance) device_entry = dev_reg.async_get_device({device_identifier}, set()) assert device_entry # Normalize unit of measurement. if unit := entity_entry.unit_of_measurement: unit = unit.lower()
async def refresh_codes(hass: HomeAssistant, entity_id: str, instance_id: int = 1) -> None: """Refresh lock codes.""" try: config_entry = next( config_entry for config_entry in hass.config_entries.async_entries(DOMAIN) if config_entry.data[CONF_LOCK_ENTITY_ID] == entity_id) except StopIteration: _LOGGER.error("Entity ID %s not set up in keymaster", entity_id) return ent_reg = async_get_entity_registry(hass) if async_using_zwave_js(entity_id=entity_id, ent_reg=ent_reg): code_slots = get_code_slots_list(config_entry.data) node = async_get_node_from_entity_id(hass, entity_id, ent_reg=ent_reg) for code_slot in code_slots: await get_usercode_from_node(node, code_slot) return
def add_entity_value( self, config_entry: ConfigEntry, entity_id: str, discovery_info: ZwaveDiscoveryInfo, ) -> None: """Add info for one entity and Z-Wave JS value.""" ent_reg = async_get_entity_registry(self._hass) dev_reg = async_get_device_registry(self._hass) node = discovery_info.node primary_value = discovery_info.primary_value entity_entry = ent_reg.async_get(entity_id) assert entity_entry device_identifier = get_device_id(node.client, node) device_entry = dev_reg.async_get_device({device_identifier}, set()) assert device_entry # Normalize unit of measurement. if unit := entity_entry.unit_of_measurement: unit = unit.lower()
async def refresh_codes(hass: HomeAssistant, entity_id: str, instance_id: int = 1) -> None: """Refresh lock codes.""" try: config_entry = next( config_entry for config_entry in hass.config_entries.async_entries(DOMAIN) if config_entry.data[CONF_LOCK_ENTITY_ID] == entity_id) except StopIteration: _LOGGER.error("Entity ID %s not set up in keymaster", entity_id) return ent_reg = async_get_entity_registry(hass) if async_using_zwave_js(entity_id=entity_id, ent_reg=ent_reg): code_slots = get_code_slots_list(config_entry.data) node = async_get_node_from_entity_id(hass, entity_id, ent_reg=ent_reg) for code_slot in code_slots: await get_usercode_from_node(node, code_slot) return # OZW Button press (experimental) if async_using_ozw(entity_id=entity_id, ent_reg=ent_reg): node_id = get_node_id(hass, entity_id) if node_id is None: _LOGGER.error( "Problem retrieving node_id from entity %s", entity_id, ) return manager = hass.data[OZW_DOMAIN][MANAGER] lock_values = manager.get_instance(instance_id).get_node( node_id).values() for value in lock_values: if value.command_class == CommandClass.USER_CODE and value.index == 255: _LOGGER.debug("DEBUG: Index found valueIDKey: %s", int(value.value_id_key)) value.send_value(True) value.send_value(False)
async def refresh_codes(hass: HomeAssistant, entity_id: str, instance_id: int = 1) -> None: """Refresh lock codes.""" node_id = get_node_id(hass, entity_id) if node_id is None: _LOGGER.error( "Problem retrieving node_id from entity %s", entity_id, ) return # OZW Button press (experimental) if async_using_ozw(entity_id=entity_id, ent_reg=async_get_entity_registry(hass)): manager = hass.data[OZW_DOMAIN][MANAGER] lock_values = manager.get_instance(instance_id).get_node( node_id).values() for value in lock_values: if value.command_class == CommandClass.USER_CODE and value.index == 255: _LOGGER.debug("DEBUG: Index found valueIDKey: %s", int(value.value_id_key)) value.send_value(True) value.send_value(False)
async def async_update_zwave_js_nodes_and_devices( hass: HomeAssistant, entry_id: str, primary_lock: KeymasterLock, child_locks: List[KeymasterLock], ) -> None: """Update Z-Wave JS nodes and devices.""" client = hass.data[ZWAVE_JS_DOMAIN][entry_id][ZWAVE_JS_DATA_CLIENT] ent_reg = async_get_entity_registry(hass) dev_reg = async_get_device_registry(hass) for lock in [primary_lock, *child_locks]: lock_ent_reg_entry = ent_reg.async_get(lock.lock_entity_id) if not lock_ent_reg_entry: continue lock_dev_reg_entry = dev_reg.async_get(lock_ent_reg_entry.device_id) if not lock_dev_reg_entry: continue node_id: int = 0 for identifier in lock_dev_reg_entry.identifiers: if identifier[0] == ZWAVE_JS_DOMAIN: node_id = int(identifier[1].split("-")[1]) lock.zwave_js_lock_node = client.driver.controller.nodes[node_id] lock.zwave_js_lock_device = lock_dev_reg_entry
async def handle_v2_migration(hass: core.HomeAssistant, entry: ConfigEntry) -> None: """Perform migration of devices and entities to V2 Id's.""" host = entry.data[CONF_HOST] api_key = entry.data[CONF_API_KEY] websession = aiohttp_client.async_get_clientsession(hass) dev_reg = async_get_device_registry(hass) ent_reg = async_get_entity_registry(hass) LOGGER.info( "Start of migration of devices and entities to support API schema 2") # Create mapping of mac address to HA device id's. # Identifier in dev reg should be mac-address, # but in some cases it has a postfix like `-0b` or `-01`. dev_ids = {} for hass_dev in devices_for_config_entries(dev_reg, entry.entry_id): for domain, mac in hass_dev.identifiers: if domain != DOMAIN: continue normalized_mac = mac.split("-")[0] dev_ids[normalized_mac] = hass_dev.id # initialize bridge connection just for the migration async with HueBridgeV2(host, api_key, websession) as api: sensor_class_mapping = { SensorDeviceClass.BATTERY.value: ResourceTypes.DEVICE_POWER, BinarySensorDeviceClass.MOTION.value: ResourceTypes.MOTION, SensorDeviceClass.ILLUMINANCE.value: ResourceTypes.LIGHT_LEVEL, SensorDeviceClass.TEMPERATURE.value: ResourceTypes.TEMPERATURE, } # migrate entities attached to a device for hue_dev in api.devices: zigbee = api.devices.get_zigbee_connectivity(hue_dev.id) if not zigbee or not zigbee.mac_address: # not a zigbee device or invalid mac continue # get existing device by V1 identifier (mac address) if hue_dev.product_data.product_archetype == DeviceArchetypes.BRIDGE_V2: hass_dev_id = dev_ids.get(api.config.bridge_id.upper()) else: hass_dev_id = dev_ids.get(zigbee.mac_address) if hass_dev_id is None: # can be safely ignored, this device does not exist in current config LOGGER.debug( "Ignoring device %s (%s) as it does not (yet) exist in the device registry", hue_dev.metadata.name, hue_dev.id, ) continue dev_reg.async_update_device(hass_dev_id, new_identifiers={(DOMAIN, hue_dev.id)}) LOGGER.info("Migrated device %s (%s)", hue_dev.metadata.name, hass_dev_id) # loop through all entities for device and find match for ent in async_entries_for_device(ent_reg, hass_dev_id, True): if ent.entity_id.startswith("light"): # migrate light # should always return one lightid here new_unique_id = next(iter(hue_dev.lights), None) else: # migrate sensors matched_dev_class = sensor_class_mapping.get( ent.original_device_class or "unknown") new_unique_id = next( (sensor.id for sensor in api.devices.get_sensors(hue_dev.id) if sensor.type == matched_dev_class), None, ) if new_unique_id is None: # this may happen if we're looking at orphaned or unsupported entity LOGGER.warning( "Skip migration of %s because it no longer exists on the bridge", ent.entity_id, ) continue try: ent_reg.async_update_entity(ent.entity_id, new_unique_id=new_unique_id) except ValueError: # assume edge case where the entity was already migrated in a previous run # which got aborted somehow and we do not want # to crash the entire integration init LOGGER.warning( "Skip migration of %s because it already exists", ent.entity_id, ) else: LOGGER.info( "Migrated entity %s from unique id %s to %s", ent.entity_id, ent.unique_id, new_unique_id, ) # migrate entities that are not connected to a device (groups) for ent in entities_for_config_entry(ent_reg, entry.entry_id): if ent.device_id is not None: continue if "-" in ent.unique_id: # handle case where unique id is v2-id of group/zone hue_group = api.groups.get(ent.unique_id) else: # handle case where the unique id is just the v1 id v1_id = f"/groups/{ent.unique_id}" hue_group = api.groups.room.get_by_v1_id( v1_id) or api.groups.zone.get_by_v1_id(v1_id) if hue_group is None or hue_group.grouped_light is None: # this may happen if we're looking at some orphaned entity LOGGER.warning( "Skip migration of %s because it no longer exist on the bridge", ent.entity_id, ) continue new_unique_id = hue_group.grouped_light LOGGER.info( "Migrating %s from unique id %s to %s ", ent.entity_id, ent.unique_id, new_unique_id, ) try: ent_reg.async_update_entity(ent.entity_id, new_unique_id=new_unique_id) except ValueError: # assume edge case where the entity was already migrated in a previous run # which got aborted somehow and we do not want # to crash the entire integration init LOGGER.warning( "Skip migration of %s because it already exists", ent.entity_id, ) LOGGER.info( "Migration of devices and entities to support API schema 2 finished")
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Set up is called when Home Assistant is loading our component.""" hass.data.setdefault(DOMAIN, {}) _LOGGER.info( "Version %s is starting, if you have any issues please report" " them here: %s", VERSION, ISSUE_URL, ) should_generate_package = config_entry.data.get(CONF_GENERATE) updated_config = config_entry.data.copy() # pop CONF_GENERATE if it is in data updated_config.pop(CONF_GENERATE, None) # If CONF_PATH is absolute, make it relative. This can be removed in the future, # it is only needed for entries that are being migrated from using the old absolute # path config_path = hass.config.path() if config_entry.data[CONF_PATH].startswith(config_path): num_chars_config_path = len(config_path) updated_config[CONF_PATH] = updated_config[CONF_PATH][ num_chars_config_path:] # Remove leading slashes updated_config[CONF_PATH] = updated_config[CONF_PATH].lstrip( "/").lstrip("\\") if "parent" not in config_entry.data.keys(): updated_config[CONF_PARENT] = None elif config_entry.data[CONF_PARENT] == "(none)": updated_config[CONF_PARENT] = None if updated_config != config_entry.data: hass.config_entries.async_update_entry(config_entry, data=updated_config) config_entry.add_update_listener(update_listener) primary_lock, child_locks = await generate_keymaster_locks( hass, config_entry) hass.data[DOMAIN][config_entry.entry_id] = { PRIMARY_LOCK: primary_lock, CHILD_LOCKS: child_locks, UNSUB_LISTENERS: [], } coordinator = LockUsercodeUpdateCoordinator( hass, config_entry, async_get_entity_registry(hass)) hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] = coordinator # Button Press async def _refresh_codes(service: ServiceCall) -> None: """Refresh lock codes.""" _LOGGER.debug("Refresh Codes service: %s", service) entity_id = service.data[ATTR_ENTITY_ID] instance_id = 1 await refresh_codes(hass, entity_id, instance_id) hass.services.async_register( DOMAIN, SERVICE_REFRESH_CODES, _refresh_codes, schema=vol.Schema({ vol.Required(ATTR_ENTITY_ID): vol.Coerce(str), }), ) # Add code async def _add_code(service: ServiceCall) -> None: """Set a user code.""" _LOGGER.debug("Add Code service: %s", service) entity_id = service.data[ATTR_ENTITY_ID] code_slot = service.data[ATTR_CODE_SLOT] usercode = service.data[ATTR_USER_CODE] await add_code(hass, entity_id, code_slot, usercode) hass.services.async_register( DOMAIN, SERVICE_ADD_CODE, _add_code, schema=vol.Schema({ vol.Required(ATTR_ENTITY_ID): vol.Coerce(str), vol.Required(ATTR_CODE_SLOT): vol.Coerce(int), vol.Required(ATTR_USER_CODE): vol.Coerce(str), }), ) # Clear code async def _clear_code(service: ServiceCall) -> None: """Clear a user code.""" _LOGGER.debug("Clear Code service: %s", service) entity_id = service.data[ATTR_ENTITY_ID] code_slot = service.data[ATTR_CODE_SLOT] await clear_code(hass, entity_id, code_slot) hass.services.async_register( DOMAIN, SERVICE_CLEAR_CODE, _clear_code, schema=vol.Schema({ vol.Required(ATTR_ENTITY_ID): vol.Coerce(str), vol.Required(ATTR_CODE_SLOT): vol.Coerce(int), }), ) # Generate package files def _generate_package(service: ServiceCall) -> None: """Generate the package files.""" _LOGGER.debug("DEBUG: %s", service) name = service.data[ATTR_NAME] generate_package_files(hass, name) hass.services.async_register( DOMAIN, SERVICE_GENERATE_PACKAGE, _generate_package, schema=vol.Schema({vol.Optional(ATTR_NAME): vol.Coerce(str)}), ) await async_reset_code_slot_if_pin_unknown( hass, primary_lock.lock_name, config_entry.data[CONF_SLOTS], config_entry.data[CONF_START], ) for platform in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup( config_entry, platform)) # if the use turned on the bool generate the files if should_generate_package: servicedata = {"lockname": primary_lock.lock_name} await hass.services.async_call(DOMAIN, SERVICE_GENERATE_PACKAGE, servicedata, blocking=True) if async_using_zwave_js(lock=primary_lock): # Listen to Z-Wave JS events so we can fire our own events hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append( hass.bus.async_listen( ZWAVE_JS_NOTIFICATION_EVENT, functools.partial(handle_zwave_js_event, hass, config_entry), )) await system_health_check(hass, config_entry) return True # We only get here if we are not using zwave_js # Check if we need to check alarm type/alarm level sensors, in which case # we need to listen for lock state changes locks_to_watch = [] for lock in [primary_lock, *child_locks]: if (lock.alarm_level_or_user_code_entity_id not in ( None, "sensor.fake", ) and lock.alarm_type_or_access_control_entity_id not in (None, "sensor.fake")): locks_to_watch.append(lock) if locks_to_watch: if hass.state == CoreState.running: await homeassistant_started_listener(hass, config_entry, locks_to_watch) else: hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STARTED, functools.partial(homeassistant_started_listener, hass, config_entry, locks_to_watch), ) if primary_lock.parent is not None: await init_child_locks( hass, config_entry.data[CONF_START], config_entry.data[CONF_SLOTS], config_entry.data[CONF_LOCK_NAME], ) await system_health_check(hass, config_entry) return True
async def handle_v2_migration(hass: core.HomeAssistant, entry: ConfigEntry) -> None: """Perform migration of devices and entities to V2 Id's.""" host = entry.data[CONF_HOST] api_key = entry.data[CONF_API_KEY] websession = aiohttp_client.async_get_clientsession(hass) dev_reg = async_get_device_registry(hass) ent_reg = async_get_entity_registry(hass) LOGGER.info( "Start of migration of devices and entities to support API schema 2") # initialize bridge connection just for the migration async with HueBridgeV2(host, api_key, websession) as api: sensor_class_mapping = { DEVICE_CLASS_BATTERY: ResourceTypes.DEVICE_POWER, DEVICE_CLASS_MOTION: ResourceTypes.MOTION, DEVICE_CLASS_ILLUMINANCE: ResourceTypes.LIGHT_LEVEL, DEVICE_CLASS_TEMPERATURE: ResourceTypes.TEMPERATURE, } # handle entities attached to device for hue_dev in api.devices: zigbee = api.devices.get_zigbee_connectivity(hue_dev.id) if not zigbee or not zigbee.mac_address: # not a zigbee device or invalid mac continue # get/update existing device by V1 identifier (mac address) # the device will now have both the old and the new identifier identifiers = {(DOMAIN, hue_dev.id), (DOMAIN, zigbee.mac_address)} hass_dev = dev_reg.async_get_or_create( config_entry_id=entry.entry_id, identifiers=identifiers) LOGGER.info("Migrated device %s (%s)", hass_dev.name, hass_dev.id) # loop through al entities for device and find match for ent in async_entries_for_device(ent_reg, hass_dev.id, True): # migrate light if ent.entity_id.startswith("light"): # should always return one lightid here new_unique_id = next(iter(hue_dev.lights)) if ent.unique_id == new_unique_id: continue # just in case LOGGER.info( "Migrating %s from unique id %s to %s", ent.entity_id, ent.unique_id, new_unique_id, ) ent_reg.async_update_entity(ent.entity_id, new_unique_id=new_unique_id) continue # migrate sensors matched_dev_class = sensor_class_mapping.get( ent.original_device_class or "unknown") if matched_dev_class is None: # this may happen if we're looking at orphaned or unsupported entity LOGGER.warning( "Skip migration of %s because it no longer exists on the bridge", ent.entity_id, ) continue for sensor in api.devices.get_sensors(hue_dev.id): if sensor.type != matched_dev_class: continue new_unique_id = sensor.id if ent.unique_id == new_unique_id: break # just in case LOGGER.info( "Migrating %s from unique id %s to %s", ent.entity_id, ent.unique_id, new_unique_id, ) try: ent_reg.async_update_entity(ent.entity_id, new_unique_id=sensor.id) except ValueError: # assume edge case where the entity was already migrated in a previous run # which got aborted somehow and we do not want # to crash the entire integration init LOGGER.warning( "Skip migration of %s because it already exists", ent.entity_id, ) break # migrate entities that are not connected to a device (groups) for ent in entities_for_config_entry(ent_reg, entry.entry_id): if ent.device_id is not None: continue v1_id = f"/groups/{ent.unique_id}" hue_group = api.groups.room.get_by_v1_id(v1_id) if hue_group is None or hue_group.grouped_light is None: # try again with zone hue_group = api.groups.zone.get_by_v1_id(v1_id) if hue_group is None or hue_group.grouped_light is None: # this may happen if we're looking at some orphaned entity LOGGER.warning( "Skip migration of %s because it no longer exist on the bridge", ent.entity_id, ) continue new_unique_id = hue_group.grouped_light LOGGER.info( "Migrating %s from unique id %s to %s ", ent.entity_id, ent.unique_id, new_unique_id, ) try: ent_reg.async_update_entity(ent.entity_id, new_unique_id=new_unique_id) except ValueError: # assume edge case where the entity was already migrated in a previous run # which got aborted somehow and we do not want # to crash the entire integration init LOGGER.warning( "Skip migration of %s because it already exists", ent.entity_id, ) LOGGER.info( "Migration of devices and entities to support API schema 2 finished")