async def async_initialise_vehicle( self, vehicle_link: KamereonVehiclesLink, renault_account: RenaultAccount, scan_interval: timedelta, config_entry: ConfigEntry, device_registry: dr.DeviceRegistry, ) -> None: """Set up proxy.""" assert vehicle_link.vin is not None assert vehicle_link.vehicleDetails is not None # Generate vehicle proxy vehicle = RenaultVehicleProxy( hass=self._hass, vehicle=await renault_account.get_api_vehicle(vehicle_link.vin), details=vehicle_link.vehicleDetails, scan_interval=scan_interval, ) await vehicle.async_initialise() device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers=vehicle.device_info[ATTR_IDENTIFIERS], manufacturer=vehicle.device_info[ATTR_MANUFACTURER], name=vehicle.device_info[ATTR_NAME], model=vehicle.device_info[ATTR_MODEL], sw_version=vehicle.device_info[ATTR_SW_VERSION], ) self._vehicles[vehicle_link.vin] = vehicle
def async_register_os_in_dev_reg(entry_id: str, dev_reg: DeviceRegistry, os_dict: dict[str, Any]) -> None: """Register OS in the device registry.""" params = DeviceInfo( identifiers={(DOMAIN, "OS")}, manufacturer="Home Assistant", model=SupervisorEntityModel.OS, sw_version=os_dict[ATTR_VERSION], name="Home Assistant Operating System", entry_type=DeviceEntryType.SERVICE, ) dev_reg.async_get_or_create(config_entry_id=entry_id, **params)
def async_register_os_in_dev_reg(entry_id: str, dev_reg: DeviceRegistry, os_dict: Dict[str, Any]) -> None: """Register OS in the device registry.""" params = { "config_entry_id": entry_id, "identifiers": {(DOMAIN, "OS")}, "manufacturer": "Home Assistant", "model": "Home Assistant Operating System", "sw_version": os_dict[ATTR_VERSION], "name": "Home Assistant Operating System", "entry_type": ATTR_SERVICE, } dev_reg.async_get_or_create(**params)
def async_register_addons_in_dev_reg(entry_id: str, dev_reg: DeviceRegistry, addons: List[Dict[str, Any]]) -> None: """Register addons in the device registry.""" for addon in addons: params = { "config_entry_id": entry_id, "identifiers": {(DOMAIN, addon[ATTR_SLUG])}, "model": "Home Assistant Add-on", "sw_version": addon[ATTR_VERSION], "name": addon[ATTR_NAME], "entry_type": ATTR_SERVICE, } if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL): params["manufacturer"] = manufacturer dev_reg.async_get_or_create(**params)
def async_register_supervisor_in_dev_reg( entry_id: str, dev_reg: dr.DeviceRegistry, supervisor_dict: dict[str, Any], ) -> None: """Register OS in the device registry.""" params = DeviceInfo( identifiers={(DOMAIN, "supervisor")}, manufacturer="Home Assistant", model=SupervisorEntityModel.SUPERVIOSR, sw_version=supervisor_dict[ATTR_VERSION], name="Home Assistant Supervisor", entry_type=dr.DeviceEntryType.SERVICE, ) dev_reg.async_get_or_create(config_entry_id=entry_id, **params)
async def test_get_conditions( hass: HomeAssistant, device_reg: device_registry.DeviceRegistry, entity_reg: entity_registry.EntityRegistry, ) -> None: """Test we get the expected conditions from a NEW_DOMAIN.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) expected_conditions = [ { "condition": "device", "domain": DOMAIN, "type": "is_off", "device_id": device_entry.id, "entity_id": f"{DOMAIN}.test_5678", }, { "condition": "device", "domain": DOMAIN, "type": "is_on", "device_id": device_entry.id, "entity_id": f"{DOMAIN}.test_5678", }, ] conditions = await async_get_device_automations(hass, "condition", device_entry.id) assert_lists_same(conditions, expected_conditions)
def async_register_addons_in_dev_reg( entry_id: str, dev_reg: DeviceRegistry, addons: list[dict[str, Any]] ) -> None: """Register addons in the device registry.""" for addon in addons: params = DeviceInfo( identifiers={(DOMAIN, addon[ATTR_SLUG])}, model=SupervisorEntityModel.ADDON, sw_version=addon[ATTR_VERSION], name=addon[ATTR_NAME], entry_type=DeviceEntryType.SERVICE, configuration_url=f"homeassistant://hassio/addon/{addon[ATTR_SLUG]}", ) if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL): params[ATTR_MANUFACTURER] = manufacturer dev_reg.async_get_or_create(config_entry_id=entry_id, **params)
def _update_device( hass: HomeAssistant, config_entry: ConfigEntry, config: TasmotaDeviceConfig, device_registry: DeviceRegistry, ) -> None: """Add or update device registry.""" _LOGGER.debug("Adding or updating tasmota device %s", config[CONF_MAC]) device_registry.async_get_or_create( connections={(CONNECTION_NETWORK_MAC, config[CONF_MAC])}, manufacturer=config[CONF_MANUFACTURER], model=config[CONF_MODEL], name=config[CONF_NAME], sw_version=config[CONF_SW_VERSION], config_entry_id=config_entry.entry_id, )
async def test_get_actions( hass: HomeAssistant, device_reg: device_registry.DeviceRegistry, entity_reg: entity_registry.EntityRegistry, ) -> None: """Test we get the expected actions from a button.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) expected_actions = [{ "domain": DOMAIN, "type": "press", "device_id": device_entry.id, "entity_id": "button.test_5678", }] actions = await async_get_device_automations(hass, DeviceAutomationType.ACTION, device_entry.id) assert_lists_same(actions, expected_actions)
async def test_get_trigger_capabilities(hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry) -> None: """Test we get the expected capabilities from a update trigger.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) expected_capabilities = { "extra_fields": [{ "name": "for", "optional": True, "type": "positive_time_period_dict" }] } triggers = await async_get_device_automations(hass, DeviceAutomationType.TRIGGER, device_entry.id) for trigger in triggers: capabilities = await async_get_device_automation_capabilities( hass, DeviceAutomationType.TRIGGER, trigger) assert capabilities == expected_capabilities
async def test_get_triggers( hass: HomeAssistant, device_reg: device_registry.DeviceRegistry, entity_reg: EntityRegistry, ) -> None: """Test we get the expected triggers from a button.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) expected_triggers = [{ "platform": "device", "domain": DOMAIN, "type": "pressed", "device_id": device_entry.id, "entity_id": f"{DOMAIN}.test_5678", "metadata": { "secondary": False }, }] triggers = await async_get_device_automations(hass, DeviceAutomationType.TRIGGER, device_entry.id) assert_lists_same(triggers, expected_triggers)
async def test_migration_device_online_end_to_end(hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry): """Test migration from single config entry.""" config_entry = MockConfigEntry(domain=DOMAIN, title="LEGACY", data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) device = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, SERIAL)}, connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)}, name=LABEL, ) light_entity_reg = entity_reg.async_get_or_create( config_entry=config_entry, platform=DOMAIN, domain="light", unique_id=dr.format_mac(SERIAL), original_name=LABEL, device_id=device.id, ) with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device(): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() migrated_entry = None for entry in hass.config_entries.async_entries(DOMAIN): if entry.unique_id == DOMAIN: migrated_entry = entry break assert migrated_entry is not None assert device.config_entries == {migrated_entry.entry_id} assert light_entity_reg.config_entry_id == migrated_entry.entry_id assert er.async_entries_for_config_entry(entity_reg, config_entry) == [] hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20)) await hass.async_block_till_done() legacy_entry = None for entry in hass.config_entries.async_entries(DOMAIN): if entry.unique_id == DOMAIN: legacy_entry = entry break assert legacy_entry is None
async def test_migration_device_online_end_to_end_after_downgrade( hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry): """Test migration from single config entry can happen again after a downgrade.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) already_migrated_config_entry = MockConfigEntry( domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=MAC_ADDRESS) already_migrated_config_entry.add_to_hass(hass) device = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)}, name=ALIAS, ) light_entity_reg = entity_reg.async_get_or_create( config_entry=config_entry, platform=DOMAIN, domain="light", unique_id=MAC_ADDRESS, original_name=ALIAS, device_id=device.id, ) power_sensor_entity_reg = entity_reg.async_get_or_create( config_entry=config_entry, platform=DOMAIN, domain="sensor", unique_id=f"{MAC_ADDRESS}_sensor", original_name=ALIAS, device_id=device.id, ) with _patch_discovery(), _patch_single_discovery(): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() assert device.config_entries == {config_entry.entry_id} assert light_entity_reg.config_entry_id == config_entry.entry_id assert power_sensor_entity_reg.config_entry_id == config_entry.entry_id assert er.async_entries_for_config_entry(entity_reg, config_entry) == [] hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() legacy_entry = None for entry in hass.config_entries.async_entries(DOMAIN): if entry.unique_id == DOMAIN: legacy_entry = entry break assert legacy_entry is None
def _add_camera( hass: HomeAssistant, device_registry: dr.DeviceRegistry, client: MotionEyeClient, entry: ConfigEntry, camera_id: int, camera: dict[str, Any], device_identifier: tuple[str, str], ) -> None: """Add a motionEye camera to hass.""" device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={device_identifier}, manufacturer=MOTIONEYE_MANUFACTURER, model=MOTIONEYE_MANUFACTURER, name=camera[KEY_NAME], ) async_dispatcher_send( hass, SIGNAL_CAMERA_ADD.format(entry.entry_id), camera, )
def register_node_in_dev_reg( hass: HomeAssistant, entry: ConfigEntry, dev_reg: device_registry.DeviceRegistry, client: ZwaveClient, node: ZwaveNode, remove_device_func: Callable[[device_registry.DeviceEntry], None], ) -> device_registry.DeviceEntry: """Register node in dev reg.""" device_id = get_device_id(client, node) device_id_ext = get_device_id_ext(client, node) device = dev_reg.async_get_device({device_id}) # Replace the device if it can be determined that this node is not the # same product as it was previously. if ( device_id_ext and device and len(device.identifiers) == 2 and device_id_ext not in device.identifiers ): remove_device_func(device) device = None if device_id_ext: ids = {device_id, device_id_ext} else: ids = {device_id} params = { ATTR_IDENTIFIERS: ids, ATTR_SW_VERSION: node.firmware_version, ATTR_NAME: node.name or node.device_config.description or f"Node {node.node_id}", ATTR_MODEL: node.device_config.label, ATTR_MANUFACTURER: node.device_config.manufacturer, } if node.location: params[ATTR_SUGGESTED_AREA] = node.location device = dev_reg.async_get_or_create(config_entry_id=entry.entry_id, **params) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device) return device
def register_device( hass: HomeAssistant, entry: ConfigEntry, dev_reg: device_registry.DeviceRegistry, device: Device, ) -> None: """Register device in device registry.""" params = DeviceInfo( identifiers={(DOMAIN, device.address)}, name=device.name, model=device.model, manufacturer="Govee", ) device = dev_reg.async_get_or_create(config_entry_id=entry.entry_id, **params) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)
def register_node_in_dev_reg( hass: HomeAssistant, entry: ConfigEntry, dev_reg: device_registry.DeviceRegistry, client: ZwaveClient, node: ZwaveNode, ) -> None: """Register node in dev reg.""" device = dev_reg.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}")}, sw_version=node.firmware_version, name=node.name or node.device_config.description or f"Node {node.node_id}", model=node.device_config.label, manufacturer=node.device_config.manufacturer, ) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)
def register_node_in_dev_reg( hass: HomeAssistant, entry: ConfigEntry, dev_reg: device_registry.DeviceRegistry, driver: Driver, node: ZwaveNode, remove_device_func: Callable[[device_registry.DeviceEntry], None], ) -> device_registry.DeviceEntry: """Register node in dev reg.""" device_id = get_device_id(driver, node) device_id_ext = get_device_id_ext(driver, node) device = dev_reg.async_get_device({device_id}) # Replace the device if it can be determined that this node is not the # same product as it was previously. if ( device_id_ext and device and len(device.identifiers) == 2 and device_id_ext not in device.identifiers ): remove_device_func(device) device = None if device_id_ext: ids = {device_id, device_id_ext} else: ids = {device_id} device = dev_reg.async_get_or_create( config_entry_id=entry.entry_id, identifiers=ids, sw_version=node.firmware_version, name=node.name or node.device_config.description or f"Node {node.node_id}", model=node.device_config.label, manufacturer=node.device_config.manufacturer, suggested_area=node.location if node.location else UNDEFINED, ) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device) return device
async def test_get_triggers(hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry) -> None: """Test we get the expected triggers from a update entity.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) device_entry = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id) expected_triggers = [ { "platform": "device", "domain": DOMAIN, "type": "changed_states", "device_id": device_entry.id, "entity_id": f"{DOMAIN}.test_5678", }, { "platform": "device", "domain": DOMAIN, "type": "turned_off", "device_id": device_entry.id, "entity_id": f"{DOMAIN}.test_5678", }, { "platform": "device", "domain": DOMAIN, "type": "turned_on", "device_id": device_entry.id, "entity_id": f"{DOMAIN}.test_5678", }, ] triggers = await async_get_device_automations(hass, DeviceAutomationType.TRIGGER, device_entry.id) assert triggers == expected_triggers
def register_node_in_dev_reg( hass: HomeAssistant, entry: ConfigEntry, dev_reg: device_registry.DeviceRegistry, client: ZwaveClient, node: ZwaveNode, ) -> None: """Register node in dev reg.""" params = { "config_entry_id": entry.entry_id, "identifiers": {get_device_id(client, node)}, "sw_version": node.firmware_version, "name": node.name or node.device_config.description or f"Node {node.node_id}", "model": node.device_config.label, "manufacturer": node.device_config.manufacturer, } if node.location: params["suggested_area"] = node.location device = dev_reg.async_get_or_create(**params) async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)
async def test_migration_device_online_end_to_end_ignores_other_devices( hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry): """Test migration from single config entry.""" legacy_config_entry = MockConfigEntry(domain=DOMAIN, title="LEGACY", data={}, unique_id=DOMAIN) legacy_config_entry.add_to_hass(hass) other_domain_config_entry = MockConfigEntry(domain="other_domain", data={}, unique_id="other_domain") other_domain_config_entry.add_to_hass(hass) device = device_reg.async_get_or_create( config_entry_id=legacy_config_entry.entry_id, identifiers={(DOMAIN, SERIAL)}, connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)}, name=LABEL, ) other_device = device_reg.async_get_or_create( config_entry_id=other_domain_config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "556655665566")}, name=LABEL, ) light_entity_reg = entity_reg.async_get_or_create( config_entry=legacy_config_entry, platform=DOMAIN, domain="light", unique_id=SERIAL, original_name=LABEL, device_id=device.id, ) ignored_entity_reg = entity_reg.async_get_or_create( config_entry=other_domain_config_entry, platform=DOMAIN, domain="sensor", unique_id="00:00:00:00:00:00_sensor", original_name=LABEL, device_id=device.id, ) garbage_entity_reg = entity_reg.async_get_or_create( config_entry=legacy_config_entry, platform=DOMAIN, domain="sensor", unique_id="garbage", original_name=LABEL, device_id=other_device.id, ) with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device(): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20)) await hass.async_block_till_done() new_entry = None legacy_entry = None for entry in hass.config_entries.async_entries(DOMAIN): if entry.unique_id == DOMAIN: legacy_entry = entry else: new_entry = entry assert new_entry is not None assert legacy_entry is None assert device.config_entries == {legacy_config_entry.entry_id} assert light_entity_reg.config_entry_id == legacy_config_entry.entry_id assert ignored_entity_reg.config_entry_id == other_domain_config_entry.entry_id assert garbage_entity_reg.config_entry_id == legacy_config_entry.entry_id assert er.async_entries_for_config_entry(entity_reg, legacy_config_entry) == [] assert dr.async_entries_for_config_entry(device_reg, legacy_config_entry) == []
async def test_migration_device_online_end_to_end_ignores_other_devices( hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry): """Test migration from single config entry.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) other_domain_config_entry = MockConfigEntry(domain="other_domain", data={}, unique_id="other_domain") other_domain_config_entry.add_to_hass(hass) device = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)}, name=ALIAS, ) other_device = device_reg.async_get_or_create( config_entry_id=other_domain_config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "556655665566")}, name=ALIAS, ) light_entity_reg = entity_reg.async_get_or_create( config_entry=config_entry, platform=DOMAIN, domain="light", unique_id=MAC_ADDRESS, original_name=ALIAS, device_id=device.id, ) power_sensor_entity_reg = entity_reg.async_get_or_create( config_entry=config_entry, platform=DOMAIN, domain="sensor", unique_id=f"{MAC_ADDRESS}_sensor", original_name=ALIAS, device_id=device.id, ) ignored_entity_reg = entity_reg.async_get_or_create( config_entry=other_domain_config_entry, platform=DOMAIN, domain="sensor", unique_id="00:00:00:00:00:00_sensor", original_name=ALIAS, device_id=device.id, ) garbage_entity_reg = entity_reg.async_get_or_create( config_entry=config_entry, platform=DOMAIN, domain="sensor", unique_id="garbage", original_name=ALIAS, device_id=other_device.id, ) with _patch_discovery(), _patch_single_discovery(): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() migrated_entry = None for entry in hass.config_entries.async_entries(DOMAIN): if entry.unique_id == DOMAIN: migrated_entry = entry break assert migrated_entry is not None assert device.config_entries == {migrated_entry.entry_id} assert light_entity_reg.config_entry_id == migrated_entry.entry_id assert power_sensor_entity_reg.config_entry_id == migrated_entry.entry_id assert ignored_entity_reg.config_entry_id == other_domain_config_entry.entry_id assert garbage_entity_reg.config_entry_id == config_entry.entry_id assert er.async_entries_for_config_entry(entity_reg, config_entry) == [] hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() legacy_entry = None for entry in hass.config_entries.async_entries(DOMAIN): if entry.unique_id == DOMAIN: legacy_entry = entry break assert legacy_entry is not None
def _add_camera( hass: HomeAssistant, device_registry: dr.DeviceRegistry, client: MotionEyeClient, entry: ConfigEntry, camera_id: int, camera: dict[str, Any], device_identifier: tuple[str, str], ) -> None: """Add a motionEye camera to hass.""" def _is_recognized_web_hook(url: str) -> bool: """Determine whether this integration set a web hook.""" return f"{WEB_HOOK_SENTINEL_KEY}={WEB_HOOK_SENTINEL_VALUE}" in url def _set_webhook( url: str, key_url: str, key_method: str, key_enabled: str, camera: dict[str, Any], ) -> bool: """Set a web hook.""" if (entry.options.get( CONF_WEBHOOK_SET_OVERWRITE, DEFAULT_WEBHOOK_SET_OVERWRITE, ) or not camera.get(key_url) or _is_recognized_web_hook(camera[key_url])) and ( not camera.get(key_enabled, False) or camera.get(key_method) != KEY_HTTP_METHOD_GET or camera.get(key_url) != url): camera[key_enabled] = True camera[key_method] = KEY_HTTP_METHOD_GET camera[key_url] = url return True return False def _build_url(base: str, keys: list[str]) -> str: """Build a motionEye webhook URL.""" return (base + "?" + urlencode( { **{ k: KEY_WEB_HOOK_CONVERSION_SPECIFIERS[k] for k in sorted(keys) }, WEB_HOOK_SENTINEL_KEY: WEB_HOOK_SENTINEL_VALUE, }, safe="%{}", )) device = device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={device_identifier}, manufacturer=MOTIONEYE_MANUFACTURER, model=MOTIONEYE_MANUFACTURER, name=camera[KEY_NAME], ) if entry.options.get(CONF_WEBHOOK_SET, DEFAULT_WEBHOOK_SET): url = None try: url = get_url(hass) except NoURLAvailableError: pass if url: if _set_webhook( _build_url( f"{url}{API_PATH_DEVICE_ROOT}{device.id}/{EVENT_MOTION_DETECTED}", EVENT_MOTION_DETECTED_KEYS, ), KEY_WEB_HOOK_NOTIFICATIONS_URL, KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD, KEY_WEB_HOOK_NOTIFICATIONS_ENABLED, camera, ) | _set_webhook( _build_url( f"{url}{API_PATH_DEVICE_ROOT}{device.id}/{EVENT_FILE_STORED}", EVENT_FILE_STORED_KEYS, ), KEY_WEB_HOOK_STORAGE_URL, KEY_WEB_HOOK_STORAGE_HTTP_METHOD, KEY_WEB_HOOK_STORAGE_ENABLED, camera, ): hass.async_create_task( client.async_set_camera(camera_id, camera)) async_dispatcher_send( hass, SIGNAL_CAMERA_ADD.format(entry.entry_id), camera, )
async def test_discovery_is_more_frequent_during_migration( hass: HomeAssistant, device_reg: DeviceRegistry, entity_reg: EntityRegistry): """Test that discovery is more frequent during migration.""" config_entry = MockConfigEntry(domain=DOMAIN, title="LEGACY", data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) device = device_reg.async_get_or_create( config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, SERIAL)}, connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)}, name=LABEL, ) entity_reg.async_get_or_create( config_entry=config_entry, platform=DOMAIN, domain="light", unique_id=dr.format_mac(SERIAL), original_name=LABEL, device_id=device.id, ) bulb = _mocked_bulb() start_calls = 0 class MockLifxDiscovery: """Mock lifx discovery.""" def __init__(self, *args, **kwargs): """Init discovery.""" self.bulb = bulb self.lights = {} def start(self): """Mock start.""" nonlocal start_calls start_calls += 1 # Discover the bulb so we can complete migration # and verify we switch back to normal discovery # interval if start_calls == 4: self.lights = {self.bulb.mac_addr: self.bulb} def cleanup(self): """Mock cleanup.""" with _patch_device(device=bulb), _patch_config_flow_try_connect( device=bulb), patch.object(discovery, "DEFAULT_TIMEOUT", 0), patch( "homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert start_calls == 0 hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert start_calls == 1 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) await hass.async_block_till_done() assert start_calls == 3 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10)) await hass.async_block_till_done() assert start_calls == 4 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15)) await hass.async_block_till_done() assert start_calls == 5
def _add_camera( hass: HomeAssistant, device_registry: dr.DeviceRegistry, client: MotionEyeClient, entry: ConfigEntry, camera_id: int, camera: dict[str, Any], device_identifier: tuple[str, str], ) -> None: """Add a motionEye camera to hass.""" def _is_recognized_web_hook(url: str) -> bool: """Determine whether this integration set a web hook.""" return f"{WEB_HOOK_SENTINEL_KEY}={WEB_HOOK_SENTINEL_VALUE}" in url def _set_webhook( url: str, key_url: str, key_method: str, key_enabled: str, camera: dict[str, Any], ) -> bool: """Set a web hook.""" if (entry.options.get( CONF_WEBHOOK_SET_OVERWRITE, DEFAULT_WEBHOOK_SET_OVERWRITE, ) or not camera.get(key_url) or _is_recognized_web_hook(camera[key_url])) and ( not camera.get(key_enabled, False) or camera.get(key_method) != KEY_HTTP_METHOD_POST_JSON or camera.get(key_url) != url): camera[key_enabled] = True camera[key_method] = KEY_HTTP_METHOD_POST_JSON camera[key_url] = url return True return False def _build_url(device: dr.DeviceEntry, base: str, event_type: str, keys: list[str]) -> str: """Build a motionEye webhook URL.""" # This URL-surgery cannot use YARL because the output must NOT be # url-encoded. This is because motionEye will do further string # manipulation/substitution on this value before ultimately fetching it, # and it cannot deal with URL-encoded input to that string manipulation. return urljoin( base, "?" + urlencode( { **{ k: KEY_WEB_HOOK_CONVERSION_SPECIFIERS[k] for k in sorted(keys) }, WEB_HOOK_SENTINEL_KEY: WEB_HOOK_SENTINEL_VALUE, ATTR_EVENT_TYPE: event_type, ATTR_DEVICE_ID: device.id, }, safe="%{}", ), ) device = device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={device_identifier}, manufacturer=MOTIONEYE_MANUFACTURER, model=MOTIONEYE_MANUFACTURER, name=camera[KEY_NAME], ) if entry.options.get(CONF_WEBHOOK_SET, DEFAULT_WEBHOOK_SET): url = async_generate_motioneye_webhook(hass, entry.data[CONF_WEBHOOK_ID]) if url: set_motion_event = _set_webhook( _build_url( device, url, EVENT_MOTION_DETECTED, EVENT_MOTION_DETECTED_KEYS, ), KEY_WEB_HOOK_NOTIFICATIONS_URL, KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD, KEY_WEB_HOOK_NOTIFICATIONS_ENABLED, camera, ) set_storage_event = _set_webhook( _build_url( device, url, EVENT_FILE_STORED, EVENT_FILE_STORED_KEYS, ), KEY_WEB_HOOK_STORAGE_URL, KEY_WEB_HOOK_STORAGE_HTTP_METHOD, KEY_WEB_HOOK_STORAGE_ENABLED, camera, ) if set_motion_event or set_storage_event: hass.async_create_task( client.async_set_camera(camera_id, camera)) async_dispatcher_send( hass, SIGNAL_CAMERA_ADD.format(entry.entry_id), camera, )