async def test_options_replace_sensor_device(hass): """Test we can replace a sensor device.""" await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={ "host": None, "port": None, "device": "/dev/tty123", "automatic_add": False, "devices": { "0a520101f00400e22d0189": { "device_id": ["52", "1", "f0:04"] }, "0a520105230400c3260279": { "device_id": ["52", "1", "23:04"] }, }, }, unique_id=DOMAIN, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_rssi_numeric") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_battery_numeric") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity_status") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_temperature") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_rssi_numeric") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_battery_numeric") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity_status") assert state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_temperature") assert state device_registry = dr.async_get(hass) device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) old_device = next( (elem.id for elem in device_entries if next(iter(elem.identifiers))[1:] == ("52", "1", "f0:04")), None, ) new_device = next( (elem.id for elem in device_entries if next(iter(elem.identifiers))[1:] == ("52", "1", "23:04")), None, ) result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "automatic_add": False, "device": old_device, }, ) assert result["type"] == "form" assert result["step_id"] == "set_device_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "replace_device": new_device, }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() entity_registry = er.async_get(hass) entry = entity_registry.async_get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_rssi_numeric") assert entry assert entry.device_id == new_device entry = entity_registry.async_get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity") assert entry assert entry.device_id == new_device entry = entity_registry.async_get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity_status") assert entry assert entry.device_id == new_device entry = entity_registry.async_get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_battery_numeric") assert entry assert entry.device_id == new_device entry = entity_registry.async_get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_temperature") assert entry assert entry.device_id == new_device state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_rssi_numeric") assert not state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_battery_numeric") assert not state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity") assert not state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity_status") assert not state state = hass.states.get( "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_temperature") assert not state
async def test_device_registry_calls(hass): """Test device registry entries for hassio.""" dev_reg = async_get(hass) supervisor_mock_data = { "addons": [ { "name": "test", "slug": "test", "installed": True, "update_available": False, "version": "1.0.0", "version_latest": "1.0.0", "repository": "test", "url": "https://github.com/home-assistant/addons/test", }, { "name": "test2", "slug": "test2", "installed": True, "update_available": False, "version": "1.0.0", "version_latest": "1.0.0", "url": "https://github.com", }, ] } os_mock_data = { "board": "odroid-n2", "boot": "A", "update_available": False, "version": "5.12", "version_latest": "5.12", } with patch.dict(os.environ, MOCK_ENVIRON), patch( "homeassistant.components.hassio.HassIO.get_supervisor_info", return_value=supervisor_mock_data, ), patch( "homeassistant.components.hassio.HassIO.get_os_info", return_value=os_mock_data, ): config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert len(dev_reg.devices) == 3 supervisor_mock_data = { "addons": [ { "name": "test2", "slug": "test2", "installed": True, "update_available": False, "version": "1.0.0", "version_latest": "1.0.0", "url": "https://github.com", }, ] } # Test that when addon is removed, next update will remove the add-on and subsequent updates won't with patch( "homeassistant.components.hassio.HassIO.get_supervisor_info", return_value=supervisor_mock_data, ), patch( "homeassistant.components.hassio.HassIO.get_os_info", return_value=os_mock_data, ): async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1)) await hass.async_block_till_done() assert len(dev_reg.devices) == 2 async_fire_time_changed(hass, dt_util.now() + timedelta(hours=2)) await hass.async_block_till_done() assert len(dev_reg.devices) == 2 supervisor_mock_data = { "addons": [ { "name": "test2", "slug": "test2", "installed": True, "update_available": False, "version": "1.0.0", "version_latest": "1.0.0", "url": "https://github.com", }, { "name": "test3", "slug": "test3", "installed": True, "update_available": False, "version": "1.0.0", "version_latest": "1.0.0", "url": "https://github.com", }, ] } # Test that when addon is added, next update will reload the entry so we register # a new device with patch( "homeassistant.components.hassio.HassIO.get_supervisor_info", return_value=supervisor_mock_data, ), patch( "homeassistant.components.hassio.HassIO.get_os_info", return_value=os_mock_data, ): async_fire_time_changed(hass, dt_util.now() + timedelta(hours=3)) await hass.async_block_till_done() assert len(dev_reg.devices) == 3
async def test_get_action_capabilities( hass: HomeAssistant, client: Client, climate_radio_thermostat_ct100_plus: Node, integration: ConfigEntry, ): """Test we get the expected action capabilities.""" node = climate_radio_thermostat_ct100_plus dev_reg = device_registry.async_get(hass) device = device_registry.async_entries_for_config_entry( dev_reg, integration.entry_id )[0] # Test refresh_value capabilities = await device_action.async_get_action_capabilities( hass, { "platform": "device", "domain": DOMAIN, "device_id": device.id, "type": "refresh_value", }, ) assert capabilities and "extra_fields" in capabilities assert voluptuous_serialize.convert( capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [{"type": "boolean", "name": "refresh_all_values", "optional": True}] # Test ping capabilities = await device_action.async_get_action_capabilities( hass, { "platform": "device", "domain": DOMAIN, "device_id": device.id, "type": "ping", }, ) assert not capabilities # Test set_value capabilities = await device_action.async_get_action_capabilities( hass, { "platform": "device", "domain": DOMAIN, "device_id": device.id, "type": "set_value", }, ) assert capabilities and "extra_fields" in capabilities cc_options = [(cc.value, cc.name) for cc in CommandClass] assert voluptuous_serialize.convert( capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { "name": "command_class", "required": True, "options": cc_options, "type": "select", }, {"name": "property", "required": True, "type": "string"}, {"name": "property_key", "optional": True, "type": "string"}, {"name": "endpoint", "optional": True, "type": "string"}, {"name": "value", "required": True, "type": "string"}, {"type": "boolean", "name": "wait_for_result", "optional": True}, ] # Test enumerated type param capabilities = await device_action.async_get_action_capabilities( hass, { "platform": "device", "domain": DOMAIN, "device_id": device.id, "type": "set_config_parameter", "parameter": 1, "bitmask": None, "subtype": f"{node.node_id}-112-0-1 (Temperature Reporting Threshold)", }, ) assert capabilities and "extra_fields" in capabilities assert voluptuous_serialize.convert( capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { "name": "value", "required": True, "options": [ (0, "Disabled"), (1, "0.5° F"), (2, "1.0° F"), (3, "1.5° F"), (4, "2.0° F"), ], "type": "select", } ] # Test range type param capabilities = await device_action.async_get_action_capabilities( hass, { "platform": "device", "domain": DOMAIN, "device_id": device.id, "type": "set_config_parameter", "parameter": 10, "bitmask": None, "subtype": f"{node.node_id}-112-0-10 (Temperature Reporting Filter)", }, ) assert capabilities and "extra_fields" in capabilities assert voluptuous_serialize.convert( capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { "name": "value", "required": True, "type": "integer", "valueMin": 0, "valueMax": 124, } ] # Test undefined type param capabilities = await device_action.async_get_action_capabilities( hass, { "platform": "device", "domain": DOMAIN, "device_id": device.id, "type": "set_config_parameter", "parameter": 2, "bitmask": None, "subtype": f"{node.node_id}-112-0-2 (HVAC Settings)", }, ) assert not capabilities
async def test_migrate_zwave( hass, zwave_integration, aeon_smart_switch_6, multisensor_6, integration, hass_ws_client, ): """Test the Z-Wave to Z-Wave JS migration websocket api.""" entry = integration client = await hass_ws_client(hass) assert hass.config_entries.async_entries("zwave") await client.send_json({ ID: 5, TYPE: "zwave_js/migrate_zwave", ENTRY_ID: entry.entry_id, "dry_run": False, }) msg = await client.receive_json() result = msg["result"] migration_entity_map = { ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6", ZWAVE_LUMINANCE_ENTITY: "sensor.multisensor_6_illuminance", ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level", } assert result["zwave_entity_ids"] == [ ZWAVE_SWITCH_ENTITY, ZWAVE_POWER_ENTITY, ZWAVE_SOURCE_NODE_ENTITY, ZWAVE_LUMINANCE_ENTITY, ZWAVE_BATTERY_ENTITY, ZWAVE_TAMPERING_ENTITY, ] expected_zwave_js_entities = [ "switch.smart_switch_6", "sensor.multisensor_6_air_temperature", "sensor.multisensor_6_illuminance", "sensor.multisensor_6_humidity", "sensor.multisensor_6_ultraviolet", "binary_sensor.multisensor_6_home_security_tampering_product_cover_removed", "binary_sensor.multisensor_6_home_security_motion_detection", "sensor.multisensor_6_battery_level", "binary_sensor.multisensor_6_low_battery_level", "light.smart_switch_6", "sensor.smart_switch_6_electric_consumed_kwh", "sensor.smart_switch_6_electric_consumed_w", "sensor.smart_switch_6_electric_consumed_v", "sensor.smart_switch_6_electric_consumed_a", ] # Assert that both lists have the same items without checking order assert not set( result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities) assert result["migration_entity_map"] == migration_entity_map assert result["migrated"] is True dev_reg = dr.async_get(hass) ent_reg = er.async_get(hass) # check the device registry migration # check that the migrated entries have correct attributes multisensor_device_entry = dev_reg.async_get_device(identifiers={ ("zwave_js", "3245146787-52") }, connections=set()) assert multisensor_device_entry assert multisensor_device_entry.name_by_user == ZWAVE_MULTISENSOR_DEVICE_NAME assert multisensor_device_entry.area_id == ZWAVE_MULTISENSOR_DEVICE_AREA switch_device_entry = dev_reg.async_get_device(identifiers={ ("zwave_js", "3245146787-102") }, connections=set()) assert switch_device_entry assert switch_device_entry.name_by_user == ZWAVE_SWITCH_DEVICE_NAME assert switch_device_entry.area_id == ZWAVE_SWITCH_DEVICE_AREA migration_device_map = { ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id, ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_entry.id, } assert result["migration_device_map"] == migration_device_map # check the entity registry migration # this should have been migrated and no longer present under that id assert not ent_reg.async_is_registered( "sensor.multisensor_6_battery_level") assert not ent_reg.async_is_registered("sensor.multisensor_6_illuminance") # these should not have been migrated and is still in the registry assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY) source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_ENTITY) assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY) source_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY) assert source_entry.unique_id == ZWAVE_POWER_UNIQUE_ID assert ent_reg.async_is_registered(ZWAVE_TAMPERING_ENTITY) tampering_entry = ent_reg.async_get(ZWAVE_TAMPERING_ENTITY) assert tampering_entry.unique_id == ZWAVE_TAMPERING_UNIQUE_ID assert ent_reg.async_is_registered( "sensor.smart_switch_6_electric_consumed_w") # this is the new entity_ids of the zwave_js entities assert ent_reg.async_is_registered(ZWAVE_SWITCH_ENTITY) assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY) assert ent_reg.async_is_registered(ZWAVE_LUMINANCE_ENTITY) # check that the migrated entries have correct attributes switch_entry = ent_reg.async_get(ZWAVE_SWITCH_ENTITY) assert switch_entry assert switch_entry.unique_id == "3245146787.102-37-0-currentValue" assert switch_entry.name == ZWAVE_SWITCH_NAME assert switch_entry.icon == ZWAVE_SWITCH_ICON battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY) assert battery_entry assert battery_entry.unique_id == "3245146787.52-128-0-level" assert battery_entry.name == ZWAVE_BATTERY_NAME assert battery_entry.icon == ZWAVE_BATTERY_ICON luminance_entry = ent_reg.async_get(ZWAVE_LUMINANCE_ENTITY) assert luminance_entry assert luminance_entry.unique_id == "3245146787.52-49-0-Illuminance" assert luminance_entry.name == ZWAVE_LUMINANCE_NAME assert luminance_entry.icon == ZWAVE_LUMINANCE_ICON assert luminance_entry.unit_of_measurement == LIGHT_LUX # check that the zwave config entry has been removed assert not hass.config_entries.async_entries("zwave") # Check that the zwave integration fails entry setup after migration zwave_config_entry = MockConfigEntry(domain="zwave") zwave_config_entry.add_to_hass(hass) assert not await hass.config_entries.async_setup( zwave_config_entry.entry_id)
async def async_attach_trigger( hass: HomeAssistant, config: ConfigType, action: AutomationActionType, automation_info: AutomationTriggerInfo, *, platform_type: str = PLATFORM_TYPE, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" nodes: set[Node] = set() if ATTR_DEVICE_ID in config: nodes.update({ async_get_node_from_device_id(hass, device_id) for device_id in config.get(ATTR_DEVICE_ID, []) }) if ATTR_ENTITY_ID in config: nodes.update({ async_get_node_from_entity_id(hass, entity_id) for entity_id in config.get(ATTR_ENTITY_ID, []) }) from_value = config[ATTR_FROM] to_value = config[ATTR_TO] command_class = config[ATTR_COMMAND_CLASS] property_ = config[ATTR_PROPERTY] endpoint = config.get(ATTR_ENDPOINT) property_key = config.get(ATTR_PROPERTY_KEY) unsubs = [] job = HassJob(action) trigger_data = automation_info["trigger_data"] @callback def async_on_value_updated(value: Value, device: dr.DeviceEntry, event: Event) -> None: """Handle value update.""" event_value: Value = event["value"] if event_value != value: return # Get previous value and its state value if it exists prev_value_raw = event["args"]["prevValue"] prev_value = value.metadata.states.get(str(prev_value_raw), prev_value_raw) # Get current value and its state value if it exists curr_value_raw = event["args"]["newValue"] curr_value = value.metadata.states.get(str(curr_value_raw), curr_value_raw) # Check from and to values against previous and current values respectively for value_to_eval, raw_value_to_eval, match in ( (prev_value, prev_value_raw, from_value), (curr_value, curr_value_raw, to_value), ): if (match != MATCH_ALL and value_to_eval != match and not (isinstance(match, list) and (value_to_eval in match or raw_value_to_eval in match)) and raw_value_to_eval != match): return device_name = device.name_by_user or device.name payload = { **trigger_data, CONF_PLATFORM: platform_type, ATTR_DEVICE_ID: device.id, ATTR_NODE_ID: value.node.node_id, ATTR_COMMAND_CLASS: value.command_class, ATTR_COMMAND_CLASS_NAME: value.command_class_name, ATTR_PROPERTY: value.property_, ATTR_PROPERTY_NAME: value.property_name, ATTR_ENDPOINT: endpoint, ATTR_PROPERTY_KEY: value.property_key, ATTR_PROPERTY_KEY_NAME: value.property_key_name, ATTR_PREVIOUS_VALUE: prev_value, ATTR_PREVIOUS_VALUE_RAW: prev_value_raw, ATTR_CURRENT_VALUE: curr_value, ATTR_CURRENT_VALUE_RAW: curr_value_raw, "description": f"Z-Wave value {value_id} updated on {device_name}", } hass.async_run_hass_job(job, {"trigger": payload}) dev_reg = dr.async_get(hass) for node in nodes: device_identifier = get_device_id(node.client, node) device = dev_reg.async_get_device({device_identifier}) assert device value_id = get_value_id(node, command_class, property_, endpoint, property_key) value = node.values[value_id] # We need to store the current value and device for the callback unsubs.append( node.on( "value updated", functools.partial(async_on_value_updated, value, device), )) @callback def async_remove() -> None: """Remove state listeners async.""" for unsub in unsubs: unsub() unsubs.clear() return async_remove
) hw_version = None sw_version = None if router_info: hw_version = router_info.get("HardwareVersion") sw_version = router_info.get("SoftwareVersion") if router_info.get("DeviceName"): device_info[ATTR_MODEL] = router_info["DeviceName"] if not sw_version and router.data.get(KEY_DEVICE_BASIC_INFORMATION): sw_version = router.data[KEY_DEVICE_BASIC_INFORMATION].get( "SoftwareVersion") if hw_version: device_info[ATTR_HW_VERSION] = hw_version if sw_version: device_info[ATTR_SW_VERSION] = sw_version device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, **device_info, ) # Forward config entry setup to platforms hass.config_entries.async_setup_platforms(entry, PLATFORMS) # Notify doesn't support config entry setup yet, load with discovery for now await discovery.async_load_platform( hass, Platform.NOTIFY, DOMAIN, { ATTR_UNIQUE_ID: entry.unique_id,
async def async_setup_gateway_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Set up the Xiaomi Gateway component from a config entry.""" host = entry.data[CONF_HOST] token = entry.data[CONF_TOKEN] name = entry.title gateway_id = entry.unique_id # For backwards compat if entry.unique_id.endswith("-gateway"): hass.config_entries.async_update_entry(entry, unique_id=entry.data["mac"]) entry.async_on_unload(entry.add_update_listener(update_listener)) # Connect to gateway gateway = ConnectXiaomiGateway(hass, entry) try: await gateway.async_connect_gateway(host, token) except AuthException as error: raise ConfigEntryAuthFailed() from error except SetupException as error: raise ConfigEntryNotReady() from error gateway_info = gateway.gateway_info device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, gateway_info.mac_address)}, identifiers={(DOMAIN, gateway_id)}, manufacturer="Xiaomi", name=name, model=gateway_info.model, sw_version=gateway_info.firmware_version, hw_version=gateway_info.hardware_version, ) def update_data(): """Fetch data from the subdevice.""" data = {} for sub_device in gateway.gateway_device.devices.values(): try: sub_device.update() except GatewayException as ex: _LOGGER.error("Got exception while fetching the state: %s", ex) data[sub_device.sid] = {ATTR_AVAILABLE: False} else: data[sub_device.sid] = {ATTR_AVAILABLE: True} return data async def async_update_data(): """Fetch data from the subdevice using async_add_executor_job.""" return await hass.async_add_executor_job(update_data) # Create update coordinator coordinator = DataUpdateCoordinator( hass, _LOGGER, name=name, update_method=async_update_data, # Polling interval. Will only be polled if there are subscribers. update_interval=UPDATE_INTERVAL, ) hass.data[DOMAIN][entry.entry_id] = { CONF_GATEWAY: gateway.gateway_device, KEY_COORDINATOR: coordinator, } for platform in GATEWAY_PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, platform))
async def async_setup_entry( # noqa: C901 hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Z-Wave JS from a config entry.""" use_addon = entry.data.get(CONF_USE_ADDON) if use_addon: await async_ensure_addon_running(hass, entry) client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) dev_reg = device_registry.async_get(hass) ent_reg = entity_registry.async_get(hass) entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) entry_hass_data[DATA_CLIENT] = client entry_hass_data[DATA_PLATFORM_SETUP] = {} registered_unique_ids: dict[str, dict[str, set[str]]] = defaultdict(dict) async def async_on_node_ready(node: ZwaveNode) -> None: """Handle node ready event.""" LOGGER.debug("Processing node %s", node) platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP] # register (or update) node in device registry device = register_node_in_dev_reg(hass, entry, dev_reg, client, node) # We only want to create the defaultdict once, even on reinterviews if device.id not in registered_unique_ids: registered_unique_ids[device.id] = defaultdict(set) value_updates_disc_info: dict[str, ZwaveDiscoveryInfo] = {} # run discovery on all node values and create/update entities for disc_info in async_discover_values(node, device): platform = disc_info.platform # This migration logic was added in 2021.3 to handle a breaking change to # the value_id format. Some time in the future, this call (as well as the # helper functions) can be removed. async_migrate_discovered_value( hass, ent_reg, registered_unique_ids[device.id][platform], device, client, disc_info, ) if platform not in platform_setup_tasks: platform_setup_tasks[platform] = hass.async_create_task( hass.config_entries.async_forward_entry_setup( entry, platform)) await platform_setup_tasks[platform] LOGGER.debug("Discovered entity: %s", disc_info) async_dispatcher_send(hass, f"{DOMAIN}_{entry.entry_id}_add_{platform}", disc_info) # Capture discovery info for values we want to watch for updates if disc_info.assumed_state: value_updates_disc_info[ disc_info.primary_value.value_id] = disc_info # add listener for value updated events if necessary if value_updates_disc_info: entry.async_on_unload( node.on( "value updated", lambda event: async_on_value_updated( value_updates_disc_info, event["value"]), )) # add listener for stateless node value notification events entry.async_on_unload( node.on( "value notification", lambda event: async_on_value_notification(event[ "value_notification"]), )) # add listener for stateless node notification events entry.async_on_unload( node.on( "notification", lambda event: async_on_notification(event["notification"]), )) async def async_on_node_added(node: ZwaveNode) -> None: """Handle node added event.""" platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP] # We need to set up the sensor platform if it hasn't already been setup in # order to create the node status sensor if SENSOR_DOMAIN not in platform_setup_tasks: platform_setup_tasks[SENSOR_DOMAIN] = hass.async_create_task( hass.config_entries.async_forward_entry_setup( entry, SENSOR_DOMAIN)) # This guard ensures that concurrent runs of this function all await the # platform setup task if not platform_setup_tasks[SENSOR_DOMAIN].done(): await platform_setup_tasks[SENSOR_DOMAIN] # Create a node status sensor for each device async_dispatcher_send( hass, f"{DOMAIN}_{entry.entry_id}_add_node_status_sensor", node) # we only want to run discovery when the node has reached ready state, # otherwise we'll have all kinds of missing info issues. if node.ready: await async_on_node_ready(node) return # if node is not yet ready, register one-time callback for ready state LOGGER.debug("Node added: %s - waiting for it to become ready", node.node_id) node.once( "ready", lambda event: hass.async_create_task( async_on_node_ready(event["node"])), ) # we do submit the node to device registry so user has # some visual feedback that something is (in the process of) being added register_node_in_dev_reg(hass, entry, dev_reg, client, node) @callback def async_on_node_removed(node: ZwaveNode) -> None: """Handle node removed event.""" # grab device in device registry attached to this node dev_id = get_device_id(client, node) device = dev_reg.async_get_device({dev_id}) # note: removal of entity registry entry is handled by core dev_reg.async_remove_device(device.id) # type: ignore registered_unique_ids.pop(device.id, None) # type: ignore @callback def async_on_value_notification(notification: ValueNotification) -> None: """Relay stateless value notification events from Z-Wave nodes to hass.""" device = dev_reg.async_get_device( {get_device_id(client, notification.node)}) raw_value = value = notification.value if notification.metadata.states: value = notification.metadata.states.get(str(value), value) hass.bus.async_fire( ZWAVE_JS_VALUE_NOTIFICATION_EVENT, { ATTR_DOMAIN: DOMAIN, ATTR_NODE_ID: notification.node.node_id, ATTR_HOME_ID: client.driver.controller.home_id, ATTR_ENDPOINT: notification.endpoint, ATTR_DEVICE_ID: device.id, # type: ignore ATTR_COMMAND_CLASS: notification.command_class, ATTR_COMMAND_CLASS_NAME: notification.command_class_name, ATTR_LABEL: notification.metadata.label, ATTR_PROPERTY: notification.property_, ATTR_PROPERTY_NAME: notification.property_name, ATTR_PROPERTY_KEY: notification.property_key, ATTR_PROPERTY_KEY_NAME: notification.property_key_name, ATTR_VALUE: value, ATTR_VALUE_RAW: raw_value, }, ) @callback def async_on_notification( notification: EntryControlNotification | NotificationNotification, ) -> None: """Relay stateless notification events from Z-Wave nodes to hass.""" device = dev_reg.async_get_device( {get_device_id(client, notification.node)}) event_data = { ATTR_DOMAIN: DOMAIN, ATTR_NODE_ID: notification.node.node_id, ATTR_HOME_ID: client.driver.controller.home_id, ATTR_DEVICE_ID: device.id, # type: ignore ATTR_COMMAND_CLASS: notification.command_class, } if isinstance(notification, EntryControlNotification): event_data.update({ ATTR_COMMAND_CLASS_NAME: "Entry Control", ATTR_EVENT_TYPE: notification.event_type, ATTR_DATA_TYPE: notification.data_type, ATTR_EVENT_DATA: notification.event_data, }) else: event_data.update({ ATTR_COMMAND_CLASS_NAME: "Notification", ATTR_LABEL: notification.label, ATTR_TYPE: notification.type_, ATTR_EVENT: notification.event, ATTR_EVENT_LABEL: notification.event_label, ATTR_PARAMETERS: notification.parameters, }) hass.bus.async_fire(ZWAVE_JS_NOTIFICATION_EVENT, event_data) @callback def async_on_value_updated(value_updates_disc_info: dict[ str, ZwaveDiscoveryInfo], value: Value) -> None: """Fire value updated event.""" # Get the discovery info for the value that was updated. If there is # no discovery info for this value, we don't need to fire an event if value.value_id not in value_updates_disc_info: return disc_info = value_updates_disc_info[value.value_id] device = dev_reg.async_get_device({get_device_id(client, value.node)}) unique_id = get_unique_id(client.driver.controller.home_id, disc_info.primary_value.value_id) entity_id = ent_reg.async_get_entity_id(disc_info.platform, DOMAIN, unique_id) raw_value = value_ = value.value if value.metadata.states: value_ = value.metadata.states.get(str(value), value_) hass.bus.async_fire( ZWAVE_JS_VALUE_UPDATED_EVENT, { ATTR_NODE_ID: value.node.node_id, ATTR_HOME_ID: client.driver.controller.home_id, ATTR_DEVICE_ID: device.id, # type: ignore ATTR_ENTITY_ID: entity_id, ATTR_COMMAND_CLASS: value.command_class, ATTR_COMMAND_CLASS_NAME: value.command_class_name, ATTR_ENDPOINT: value.endpoint, ATTR_PROPERTY: value.property_, ATTR_PROPERTY_NAME: value.property_name, ATTR_PROPERTY_KEY: value.property_key, ATTR_PROPERTY_KEY_NAME: value.property_key_name, ATTR_VALUE: value_, ATTR_VALUE_RAW: raw_value, }, ) # connect and throw error if connection failed try: async with timeout(CONNECT_TIMEOUT): await client.connect() except InvalidServerVersion as err: if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED): LOGGER.error("Invalid server version: %s", err) entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True if use_addon: async_ensure_addon_updated(hass) raise ConfigEntryNotReady from err except (asyncio.TimeoutError, BaseZwaveJSServerError) as err: if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED): LOGGER.error("Failed to connect: %s", err) entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True raise ConfigEntryNotReady from err else: LOGGER.info("Connected to Zwave JS Server") entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False services = ZWaveServices(hass, ent_reg, dev_reg) services.async_register() # Set up websocket API async_register_api(hass) async def start_platforms() -> None: """Start platforms and perform discovery.""" driver_ready = asyncio.Event() async def handle_ha_shutdown(event: Event) -> None: """Handle HA shutdown.""" await disconnect_client(hass, entry) listen_task = asyncio.create_task( client_listen(hass, entry, client, driver_ready)) entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task entry.async_on_unload( hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, handle_ha_shutdown)) try: await driver_ready.wait() except asyncio.CancelledError: LOGGER.debug("Cancelling start platforms") return LOGGER.info("Connection to Zwave JS Server initialized") # If opt in preference hasn't been specified yet, we do nothing, otherwise # we apply the preference if opted_in := entry.data.get(CONF_DATA_COLLECTION_OPTED_IN): await async_enable_statistics(client) elif opted_in is False: await client.driver.async_disable_statistics()
def _async_get_ufp_instances( hass: HomeAssistant, device_id: str) -> tuple[dr.DeviceEntry, ProtectApiClient]: device_registry = dr.async_get(hass) if not (device_entry := device_registry.async_get(device_id)): raise HomeAssistantError(f"No device found for device id: {device_id}")
async def assert_devices_and_entities_created(hass: HomeAssistant, expected: DeviceTestInfo): """Check that all expected devices and entities are loaded and enumerated as expected.""" entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) async def _do_assertions(expected: DeviceTestInfo) -> dr.DeviceEntry: # Note: homekit_controller currently uses a 3-tuple for device identifiers # The current standard is a 2-tuple (hkc was not migrated when this change was brought in) # There are currently really 3 cases here: # - We can match exactly one device by serial number. This won't work for devices like the Ryse. # These have nlank or broken serial numbers. # - The device unique id is "00:00:00:00:00:00" - this is the pairing id. This is only set for # the root (bridge) device. # - The device unique id is "00:00:00:00:00:00-X", where X is a HAP aid. This is only set when # we have detected broken serial numbers (and serial number is not used as an identifier). device = device_registry.async_get_device({ (DOMAIN, IDENTIFIER_SERIAL_NUMBER, expected.serial_number), (DOMAIN, IDENTIFIER_ACCESSORY_ID, expected.unique_id), }) logger.debug("Comparing device %r to %r", device, expected) assert device assert device.name == expected.name assert device.model == expected.model assert device.manufacturer == expected.manufacturer assert device.hw_version == expected.hw_version assert device.sw_version == expected.sw_version # We might have matched the device by one identifier only # Lets check that the other one is correct. Otherwise the test might silently be wrong. serial_number_set = False accessory_id_set = False for _, key, value in device.identifiers: if key == IDENTIFIER_SERIAL_NUMBER: assert value == expected.serial_number serial_number_set = True elif key == IDENTIFIER_ACCESSORY_ID: assert value == expected.unique_id accessory_id_set = True # If unique_id or serial is provided it MUST actually appear in the device registry entry. assert (not expected.unique_id) ^ accessory_id_set assert (not expected.serial_number) ^ serial_number_set for entity_info in expected.entities: entity = entity_registry.async_get(entity_info.entity_id) logger.debug("Comparing entity %r to %r", entity, entity_info) assert entity assert entity.device_id == device.id assert entity.unique_id == entity_info.unique_id assert entity.supported_features == entity_info.supported_features assert entity.entity_category == entity_info.entity_category assert entity.unit_of_measurement == entity_info.unit_of_measurement assert entity.capabilities == entity_info.capabilities state = hass.states.get(entity_info.entity_id) logger.debug("Comparing state %r to %r", state, entity_info) assert state is not None assert state.state == entity_info.state assert state.attributes[ "friendly_name"] == entity_info.friendly_name all_triggers = await async_get_device_automations( hass, DeviceAutomationType.TRIGGER, device.id) stateless_triggers = [] for trigger in all_triggers: if trigger.get("entity_id"): continue stateless_triggers.append( DeviceTriggerInfo(type=trigger.get("type"), subtype=trigger.get("subtype"))) assert stateless_triggers == (expected.stateless_triggers or []) for child in expected.devices: child_device = await _do_assertions(child) assert child_device.via_device_id == device.id assert child_device.id != device.id return device root_device = await _do_assertions(expected) # Root device must not have a via, otherwise its not the device assert root_device.via_device_id is None
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback): session = SequentialWriterSession(entry.data["ip"], PORT) state = State() try: cmd = PingCommand(state).raw await asyncio.wait_for(session.write(cmd), timeout=5) state.increment_frame_number() _ = await session.read(64) except (asyncio.TimeoutError, IOError) as e: raise ConfigEntryNotReady from e async def async_update(): try: _LOGGER.info("Getting number of zones") cmd = GetNumberOfZonesCommand(state).raw await session.write(cmd) state.increment_frame_number() res = await session.read(64) number_of_zones = GetNumberOfZonesResponse(res).number zones = [] for zone_num in range(1, number_of_zones + 1): _LOGGER.info("Getting info about zone=%d", zone_num) cmd = GetZoneInfoCommand(state, zone=zone_num).raw await session.write(cmd) state.increment_frame_number() res = await session.read(64) zone_info = GetZoneInfoResponse(res) _LOGGER.debug("Zone=%d has type=%s, name=%s", zone_num, zone_info.type, zone_info.name) zones.append({"num": zone_num, "name": zone_info.name, "type": zone_info.type}) return zones except (IOError, ValueError) as e: # IOError can hardly happen when Session is too resilient... # but ValueError happens quite often because of malformed (out-of-order) zone_info responses raise UpdateFailed(e) from e coordinator = DataUpdateCoordinator( hass, _LOGGER, name=DOMAIN, update_method=async_update, # update_interval=timedelta(hours=1), # do NOT update data at all - only after HA restart ) await coordinator.async_config_entry_first_refresh() dr = device_registry.async_get(hass) dr.async_get_or_create( config_entry_id=entry.entry_id, connections={(CONNECTION_NETWORK_MAC, entry.data["mac"])}, name=f"{MANUFACTURER} Wi-Fi relay", # TODO localize? manufacturer=MANUFACTURER, ) new_entities = [] for zone_info in coordinator.data: zone = Zone(entry, coordinator, session, state, zone_info["num"], zone_info["type"], zone_info["name"]) new_entities.append(zone) if new_entities: async_add_entities(new_entities) hass.data[DOMAIN][entry.entry_id] = { "session": session, }
async def test_options_add_and_configure_device(hass): """Test we can add a device.""" await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={ "host": None, "port": None, "device": "/dev/tty123", "automatic_add": False, "devices": {}, }, unique_id=DOMAIN, ) entry.add_to_hass(hass) result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "automatic_add": True, "event_code": "0913000022670e013970", }, ) assert result["type"] == "form" assert result["step_id"] == "set_device_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "fire_event": False, "signal_repetitions": 5, "data_bits": 4, "off_delay": "abcdef", "command_on": "xyz", "command_off": "xyz", }, ) assert result["type"] == "form" assert result["step_id"] == "set_device_options" assert result["errors"] assert result["errors"]["off_delay"] == "invalid_input_off_delay" assert result["errors"]["command_on"] == "invalid_input_2262_on" assert result["errors"]["command_off"] == "invalid_input_2262_off" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "fire_event": False, "signal_repetitions": 5, "data_bits": 4, "command_on": "0xE", "command_off": "0x7", "off_delay": "9", }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() assert entry.data["automatic_add"] assert entry.data["devices"]["0913000022670e013970"] assert not entry.data["devices"]["0913000022670e013970"]["fire_event"] assert entry.data["devices"]["0913000022670e013970"][ "signal_repetitions"] == 5 assert entry.data["devices"]["0913000022670e013970"]["off_delay"] == 9 state = hass.states.get("binary_sensor.pt2262_22670e") assert state assert state.state == "off" assert state.attributes.get("friendly_name") == "PT2262 22670e" device_registry = dr.async_get(hass) device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "automatic_add": False, "device": device_entries[0].id, }, ) assert result["type"] == "form" assert result["step_id"] == "set_device_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "fire_event": True, "signal_repetitions": 5, "data_bits": 4, "command_on": "0xE", "command_off": "0x7", }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() assert entry.data["devices"]["0913000022670e013970"] assert entry.data["devices"]["0913000022670e013970"]["fire_event"] assert entry.data["devices"]["0913000022670e013970"][ "signal_repetitions"] == 5 assert "delay_off" not in entry.data["devices"]["0913000022670e013970"]
async def test_options_remove_multiple_devices(hass): """Test we can add a device.""" await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={ "host": None, "port": None, "device": "/dev/tty123", "automatic_add": False, "devices": { "0b1100cd0213c7f230010f71": { "device_id": ["11", "0", "213c7f2:48"] }, "0b1100100118cdea02010f70": { "device_id": ["11", "0", "118cdea:2"] }, "0b1100101118cdea02010f70": { "device_id": ["11", "0", "1118cdea:2"] }, }, }, unique_id=DOMAIN, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() state = hass.states.get("binary_sensor.ac_213c7f2_48") assert state state = hass.states.get("binary_sensor.ac_118cdea_2") assert state state = hass.states.get("binary_sensor.ac_1118cdea_2") assert state device_registry = dr.async_get(hass) device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert len(device_entries) == 3 def match_device_id(entry): device_id = next(iter(entry.identifiers))[1:] if device_id == ("11", "0", "213c7f2:48"): return True if device_id == ("11", "0", "118cdea:2"): return True return False remove_devices = [ elem.id for elem in device_entries if match_device_id(elem) ] result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "automatic_add": False, "remove_device": remove_devices, }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() state = hass.states.get("binary_sensor.ac_213c7f2_48") assert not state state = hass.states.get("binary_sensor.ac_118cdea_2") assert not state state = hass.states.get("binary_sensor.ac_1118cdea_2") assert state
async def test_options_replace_control_device(hass): """Test we can replace a control device.""" await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={ "host": None, "port": None, "device": "/dev/tty123", "automatic_add": False, "devices": { "0b1100100118cdea02010f70": { "device_id": ["11", "0", "118cdea:2"], "signal_repetitions": 1, }, "0b1100101118cdea02010f70": { "device_id": ["11", "0", "1118cdea:2"], "signal_repetitions": 1, }, }, }, unique_id=DOMAIN, ) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() state = hass.states.get("binary_sensor.ac_118cdea_2") assert state state = hass.states.get("sensor.ac_118cdea_2_rssi_numeric") assert state state = hass.states.get("switch.ac_118cdea_2") assert state state = hass.states.get("binary_sensor.ac_1118cdea_2") assert state state = hass.states.get("sensor.ac_1118cdea_2_rssi_numeric") assert state state = hass.states.get("switch.ac_1118cdea_2") assert state device_registry = dr.async_get(hass) device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) old_device = next( (elem.id for elem in device_entries if next(iter(elem.identifiers))[1:] == ("11", "0", "118cdea:2")), None, ) new_device = next( (elem.id for elem in device_entries if next(iter(elem.identifiers))[1:] == ("11", "0", "1118cdea:2")), None, ) result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "automatic_add": False, "device": old_device, }, ) assert result["type"] == "form" assert result["step_id"] == "set_device_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "replace_device": new_device, }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() entity_registry = er.async_get(hass) entry = entity_registry.async_get("binary_sensor.ac_118cdea_2") assert entry assert entry.device_id == new_device entry = entity_registry.async_get("sensor.ac_118cdea_2_rssi_numeric") assert entry assert entry.device_id == new_device entry = entity_registry.async_get("switch.ac_118cdea_2") assert entry assert entry.device_id == new_device state = hass.states.get("binary_sensor.ac_1118cdea_2") assert not state state = hass.states.get("sensor.ac_1118cdea_2_rssi_numeric") assert not state state = hass.states.get("switch.ac_1118cdea_2") assert not state
class ScannerEntity(BaseTrackerEntity): """Base class for a tracked device that is on a scanned network.""" @property def ip_address(self) -> str | None: """Return the primary ip address of the device.""" return None @property def mac_address(self) -> str | None: """Return the mac address of the device.""" return None @property def hostname(self) -> str | None: """Return hostname of the device.""" return None @property def state(self) -> str: """Return the state of the device.""" if self.is_connected: return STATE_HOME return STATE_NOT_HOME @property def is_connected(self) -> bool: """Return true if the device is connected to the network.""" raise NotImplementedError @property def unique_id(self) -> str | None: """Return unique ID of the entity.""" return self.mac_address @final @property def device_info(self) -> DeviceInfo | None: """Device tracker entities should not create device registry entries.""" return None @property def entity_registry_enabled_default(self) -> bool: """Return if entity is enabled by default.""" # If mac_address is None, we can never find a device entry. return ( # Do not disable if we won't activate our attach to device logic self.mac_address is None or self.device_info is not None # Disable if we automatically attach but there is no device or self.find_device_entry() is not None) @callback def add_to_platform_start( self, hass: HomeAssistant, platform: EntityPlatform, parallel_updates: asyncio.Semaphore | None, ) -> None: """Start adding an entity to a platform.""" super().add_to_platform_start(hass, platform, parallel_updates) if self.mac_address and self.unique_id: _async_register_mac(hass, platform.platform_name, self.mac_address, self.unique_id) @callback def find_device_entry(self) -> dr.DeviceEntry | None: """Return device entry.""" assert self.mac_address is not None return dr.async_get(self.hass).async_get_device( set(), {(dr.CONNECTION_NETWORK_MAC, self.mac_address)}) async def async_internal_added_to_hass(self) -> None: """Handle added to Home Assistant.""" # Entities without a unique ID don't have a device if (not self.registry_entry or not self.platform or not self.platform.config_entry or not self.mac_address or (device_entry := self.find_device_entry()) is None # Entities should not have a device info. We opt them out # of this logic if they do. or self.device_info): if self.device_info: LOGGER.debug("Entity %s unexpectedly has a device info", self.entity_id) await super().async_internal_added_to_hass() return # Attach entry to device if self.registry_entry.device_id != device_entry.id: self.registry_entry = er.async_get(self.hass).async_update_entity( self.entity_id, device_id=device_entry.id) # Attach device to config entry if self.platform.config_entry.entry_id not in device_entry.config_entries: dr.async_get(self.hass).async_update_device( device_entry.id, add_config_entry_id=self.platform.config_entry.entry_id, ) # Do this last or else the entity registry update listener has been installed await super().async_internal_added_to_hass()
async def test_switches( hass: HomeAssistant, mock_fully_kiosk: MagicMock, init_integration: MockConfigEntry ) -> None: """Test Fully Kiosk switches.""" entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) entity = hass.states.get("switch.amazon_fire_screensaver") assert entity assert entity.state == "off" entry = entity_registry.async_get("switch.amazon_fire_screensaver") assert entry assert entry.unique_id == "abcdef-123456-screensaver" await call_service(hass, "turn_on", "switch.amazon_fire_screensaver") assert len(mock_fully_kiosk.startScreensaver.mock_calls) == 1 await call_service(hass, "turn_off", "switch.amazon_fire_screensaver") assert len(mock_fully_kiosk.stopScreensaver.mock_calls) == 1 entity = hass.states.get("switch.amazon_fire_maintenance_mode") assert entity assert entity.state == "off" entry = entity_registry.async_get("switch.amazon_fire_maintenance_mode") assert entry assert entry.unique_id == "abcdef-123456-maintenance" await call_service(hass, "turn_on", "switch.amazon_fire_maintenance_mode") assert len(mock_fully_kiosk.enableLockedMode.mock_calls) == 1 await call_service(hass, "turn_off", "switch.amazon_fire_maintenance_mode") assert len(mock_fully_kiosk.disableLockedMode.mock_calls) == 1 entity = hass.states.get("switch.amazon_fire_kiosk_lock") assert entity assert entity.state == "on" entry = entity_registry.async_get("switch.amazon_fire_kiosk_lock") assert entry assert entry.unique_id == "abcdef-123456-kiosk" await call_service(hass, "turn_off", "switch.amazon_fire_kiosk_lock") assert len(mock_fully_kiosk.unlockKiosk.mock_calls) == 1 await call_service(hass, "turn_on", "switch.amazon_fire_kiosk_lock") assert len(mock_fully_kiosk.lockKiosk.mock_calls) == 1 entity = hass.states.get("switch.amazon_fire_motion_detection") assert entity assert entity.state == "off" entry = entity_registry.async_get("switch.amazon_fire_motion_detection") assert entry assert entry.unique_id == "abcdef-123456-motion-detection" await call_service(hass, "turn_on", "switch.amazon_fire_motion_detection") assert len(mock_fully_kiosk.enableMotionDetection.mock_calls) == 1 await call_service(hass, "turn_off", "switch.amazon_fire_motion_detection") assert len(mock_fully_kiosk.disableMotionDetection.mock_calls) == 1 entity = hass.states.get("switch.amazon_fire_screen") assert entity assert entity.state == "on" entry = entity_registry.async_get("switch.amazon_fire_screen") assert entry assert entry.unique_id == "abcdef-123456-screenOn" await call_service(hass, "turn_off", "switch.amazon_fire_screen") assert len(mock_fully_kiosk.screenOff.mock_calls) == 1 await call_service(hass, "turn_on", "switch.amazon_fire_screen") assert len(mock_fully_kiosk.screenOn.mock_calls) == 1 assert entry.device_id device_entry = device_registry.async_get(entry.device_id) assert device_entry assert device_entry.configuration_url == "http://192.168.1.234:2323" assert device_entry.entry_type is None assert device_entry.hw_version is None assert device_entry.identifiers == {(DOMAIN, "abcdef-123456")} assert device_entry.manufacturer == "amzn" assert device_entry.model == "KFDOWI" assert device_entry.name == "Amazon Fire" assert device_entry.sw_version == "1.42.5"
def find_device_entry(self) -> dr.DeviceEntry | None: """Return device entry.""" assert self.mac_address is not None return dr.async_get(self.hass).async_get_device( set(), {(dr.CONNECTION_NETWORK_MAC, self.mac_address)})
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up motionEye from a config entry.""" hass.data.setdefault(DOMAIN, {}) client = create_motioneye_client( entry.data[CONF_URL], admin_username=entry.data.get(CONF_ADMIN_USERNAME), admin_password=entry.data.get(CONF_ADMIN_PASSWORD), surveillance_username=entry.data.get(CONF_SURVEILLANCE_USERNAME), surveillance_password=entry.data.get(CONF_SURVEILLANCE_PASSWORD), session=async_get_clientsession(hass), ) try: await client.async_client_login() except MotionEyeClientInvalidAuthError as exc: await client.async_client_close() raise ConfigEntryAuthFailed from exc except MotionEyeClientError as exc: await client.async_client_close() raise ConfigEntryNotReady from exc # Ensure every loaded entry has a registered webhook id. if CONF_WEBHOOK_ID not in entry.data: hass.config_entries.async_update_entry( entry, data={ **entry.data, CONF_WEBHOOK_ID: async_generate_id() }) webhook_register(hass, DOMAIN, "motionEye", entry.data[CONF_WEBHOOK_ID], handle_webhook) @callback async def async_update_data() -> dict[str, Any] | None: try: return await client.async_get_cameras() except MotionEyeClientError as exc: raise UpdateFailed("Error communicating with API") from exc coordinator = DataUpdateCoordinator( hass, _LOGGER, name=DOMAIN, update_method=async_update_data, update_interval=DEFAULT_SCAN_INTERVAL, ) hass.data[DOMAIN][entry.entry_id] = { CONF_CLIENT: client, CONF_COORDINATOR: coordinator, } current_cameras: set[tuple[str, str]] = set() device_registry = dr.async_get(hass) @callback def _async_process_motioneye_cameras() -> None: """Process motionEye camera additions and removals.""" inbound_camera: set[tuple[str, str]] = set() if coordinator.data is None or KEY_CAMERAS not in coordinator.data: return for camera in coordinator.data[KEY_CAMERAS]: if not is_acceptable_camera(camera): return camera_id = camera[KEY_ID] device_identifier = get_motioneye_device_identifier( entry.entry_id, camera_id) inbound_camera.add(device_identifier) if device_identifier in current_cameras: continue current_cameras.add(device_identifier) _add_camera( hass, device_registry, client, entry, camera_id, camera, device_identifier, ) # Ensure every device associated with this config entry is still in the # list of motionEye cameras, otherwise remove the device (and thus # entities). for device_entry in dr.async_entries_for_config_entry( device_registry, entry.entry_id): for identifier in device_entry.identifiers: if identifier in inbound_camera: break else: device_registry.async_remove_device(device_entry.id) async def setup_then_listen() -> None: await asyncio.gather( *(hass.config_entries.async_forward_entry_setup(entry, platform) for platform in PLATFORMS)) entry.async_on_unload( coordinator.async_add_listener(_async_process_motioneye_cameras)) await coordinator.async_refresh() entry.async_on_unload(entry.add_update_listener(_async_entry_updated)) hass.async_create_task(setup_then_listen()) return True
def async_create_devices(self): """ Build device registry entries for all accessories paired with the bridge. This is done as well as by the entities for 2 reasons. First, the bridge might not have any entities attached to it. Secondly there are stateless entities like doorbells and remote controls. """ device_registry = dr.async_get(self.hass) devices = {} for accessory in self.entity_map.accessories: info = accessory.services.first( service_type=ServicesTypes.ACCESSORY_INFORMATION, ) serial_number = info.value(CharacteristicsTypes.SERIAL_NUMBER) if valid_serial_number(serial_number): identifiers = {(DOMAIN, IDENTIFIER_SERIAL_NUMBER, serial_number)} else: # Some accessories do not have a serial number identifiers = { ( DOMAIN, IDENTIFIER_ACCESSORY_ID, f"{self.unique_id}_{accessory.aid}", ) } if accessory.aid == 1: # Accessory 1 is the root device (sometimes the only device, sometimes a bridge) # Link the root device to the pairing id for the connection. identifiers.add((DOMAIN, IDENTIFIER_ACCESSORY_ID, self.unique_id)) device_info = DeviceInfo( identifiers=identifiers, name=info.value(CharacteristicsTypes.NAME), manufacturer=info.value(CharacteristicsTypes.MANUFACTURER, ""), model=info.value(CharacteristicsTypes.MODEL, ""), sw_version=info.value(CharacteristicsTypes.FIRMWARE_REVISION, ""), hw_version=info.value(CharacteristicsTypes.HARDWARE_REVISION, ""), ) if accessory.aid != 1: # Every pairing has an accessory 1 # It *doesn't* have a via_device, as it is the device we are connecting to # Every other accessory should use it as its via device. device_info[ATTR_VIA_DEVICE] = ( DOMAIN, IDENTIFIER_SERIAL_NUMBER, self.connection_info["serial-number"], ) device = device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, **device_info, ) devices[accessory.aid] = device.id self.devices = devices
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Somfy from a config entry.""" _LOGGER.warning( "The Somfy integration is deprecated and will be removed " "in Home Assistant Core 2022.7; due to the Somfy Open API deprecation." "The Somfy Open API will shutdown June 21st 2022, migrate to the " "Overkiz integration to control your Somfy devices" ) # Backwards compat if "auth_implementation" not in entry.data: hass.config_entries.async_update_entry( entry, data={**entry.data, "auth_implementation": DOMAIN} ) implementation = ( await config_entry_oauth2_flow.async_get_config_entry_implementation( hass, entry ) ) data = hass.data[DOMAIN] coordinator = SomfyDataUpdateCoordinator( hass, _LOGGER, name="somfy device update", client=api.ConfigEntrySomfyApi(hass, entry, implementation), update_interval=SCAN_INTERVAL, ) data[COORDINATOR] = coordinator await coordinator.async_config_entry_first_refresh() if all(not bool(device.states) for device in coordinator.data.values()): _LOGGER.debug( "All devices have assumed state. Update interval has been reduced to: %s", SCAN_INTERVAL_ALL_ASSUMED_STATE, ) coordinator.update_interval = SCAN_INTERVAL_ALL_ASSUMED_STATE device_registry = dr.async_get(hass) hubs = [ device for device in coordinator.data.values() if Category.HUB.value in device.categories ] for hub in hubs: device_registry.async_get_or_create( config_entry_id=entry.entry_id, identifiers={(DOMAIN, hub.id)}, manufacturer="Somfy", name=hub.name, model=hub.type, ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, ) -> bool: """Create a gateway.""" tradfri_data: dict[str, Any] = {} hass.data.setdefault(DOMAIN, {})[entry.entry_id] = tradfri_data factory = await APIFactory.init( entry.data[CONF_HOST], psk_id=entry.data[CONF_IDENTITY], psk=entry.data[CONF_KEY], ) tradfri_data[FACTORY] = factory # Used for async_unload_entry async def on_hass_stop(event: Event) -> None: """Close connection when hass stops.""" await factory.shutdown() # Setup listeners entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)) api = factory.request gateway = Gateway() try: gateway_info = await api(gateway.get_gateway_info(), timeout=TIMEOUT_API) devices_commands: Command = await api(gateway.get_devices(), timeout=TIMEOUT_API) devices: list[Device] = await api(devices_commands, timeout=TIMEOUT_API) except RequestError as exc: await factory.shutdown() raise ConfigEntryNotReady from exc dev_reg = dr.async_get(hass) dev_reg.async_get_or_create( config_entry_id=entry.entry_id, connections=set(), identifiers={(DOMAIN, entry.data[CONF_GATEWAY_ID])}, manufacturer="IKEA of Sweden", name="Gateway", # They just have 1 gateway model. Type is not exposed yet. model="E1526", sw_version=gateway_info.firmware_version, ) remove_stale_devices(hass, entry, devices) # Setup the device coordinators coordinator_data = { CONF_GATEWAY_ID: gateway, KEY_API: api, COORDINATOR_LIST: [], } for device in devices: coordinator = TradfriDeviceDataUpdateCoordinator(hass=hass, api=api, device=device) await coordinator.async_config_entry_first_refresh() entry.async_on_unload( async_dispatcher_connect(hass, SIGNAL_GW, coordinator.set_hub_available)) coordinator_data[COORDINATOR_LIST].append(coordinator) tradfri_data[COORDINATOR] = coordinator_data async def async_keep_alive(now: datetime) -> None: if hass.is_stopping: return gw_status = True try: await api(gateway.get_gateway_info()) except RequestError: LOGGER.error("Keep-alive failed") gw_status = False async_dispatcher_send(hass, SIGNAL_GW, gw_status) entry.async_on_unload( async_track_time_interval(hass, async_keep_alive, timedelta(seconds=60))) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True
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 async def async_setup_entry( # noqa: C901 hass: HomeAssistant, entry: ConfigEntry ) -> bool: """Set up Z-Wave JS from a config entry.""" if use_addon := entry.data.get(CONF_USE_ADDON): await async_ensure_addon_running(hass, entry) client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass)) dev_reg = device_registry.async_get(hass) ent_reg = entity_registry.async_get(hass) entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {}) entry_hass_data[DATA_CLIENT] = client entry_hass_data[DATA_PLATFORM_SETUP] = {} registered_unique_ids: dict[str, dict[str, set[str]]] = defaultdict(dict) discovered_value_ids: dict[str, set[str]] = defaultdict(set) @callback def remove_device(device: device_registry.DeviceEntry) -> None: """Remove device from registry.""" # note: removal of entity registry entry is handled by core dev_reg.async_remove_device(device.id) registered_unique_ids.pop(device.id, None)
async def test_migrate_zwave_dry_run( hass, zwave_integration, aeon_smart_switch_6, multisensor_6, integration, hass_ws_client, ): """Test the zwave to zwave_js migration websocket api dry run.""" entry = integration client = await hass_ws_client(hass) await client.send_json({ ID: 5, TYPE: "zwave_js/migrate_zwave", ENTRY_ID: entry.entry_id }) msg = await client.receive_json() result = msg["result"] migration_entity_map = { ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6", ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level", } assert result["zwave_entity_ids"] == [ ZWAVE_SWITCH_ENTITY, ZWAVE_POWER_ENTITY, ZWAVE_SOURCE_NODE_ENTITY, ZWAVE_BATTERY_ENTITY, ZWAVE_TAMPERING_ENTITY, ] expected_zwave_js_entities = [ "switch.smart_switch_6", "sensor.multisensor_6_air_temperature", "sensor.multisensor_6_illuminance", "sensor.multisensor_6_humidity", "sensor.multisensor_6_ultraviolet", "binary_sensor.multisensor_6_home_security_tampering_product_cover_removed", "binary_sensor.multisensor_6_home_security_motion_detection", "sensor.multisensor_6_battery_level", "binary_sensor.multisensor_6_low_battery_level", "light.smart_switch_6", "sensor.smart_switch_6_electric_consumed_kwh", "sensor.smart_switch_6_electric_consumed_w", "sensor.smart_switch_6_electric_consumed_v", "sensor.smart_switch_6_electric_consumed_a", ] # Assert that both lists have the same items without checking order assert not set( result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities) assert result["migration_entity_map"] == migration_entity_map dev_reg = dr.async_get(hass) multisensor_device_entry = dev_reg.async_get_device(identifiers={ ("zwave_js", "3245146787-52") }, connections=set()) assert multisensor_device_entry assert multisensor_device_entry.name_by_user is None assert multisensor_device_entry.area_id is None switch_device_entry = dev_reg.async_get_device(identifiers={ ("zwave_js", "3245146787-102") }, connections=set()) assert switch_device_entry assert switch_device_entry.name_by_user is None assert switch_device_entry.area_id is None migration_device_map = { ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id, ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_entry.id, } assert result["migration_device_map"] == migration_device_map assert result["migrated"] is False ent_reg = er.async_get(hass) # no real migration should have been done assert ent_reg.async_is_registered("switch.smart_switch_6") assert ent_reg.async_is_registered("sensor.multisensor_6_battery_level") assert ent_reg.async_is_registered( "sensor.smart_switch_6_electric_consumed_w") assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY) source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_ENTITY) assert source_entry assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY) battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY) assert battery_entry assert battery_entry.unique_id == ZWAVE_BATTERY_UNIQUE_ID assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY) power_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY) assert power_entry assert power_entry.unique_id == ZWAVE_POWER_UNIQUE_ID # check that the zwave config entry has not been removed assert hass.config_entries.async_entries("zwave") # Check that the zwave integration can be setup after dry run zwave_config_entry = zwave_integration with patch("openzwave.option.ZWaveOption"), patch( "openzwave.network.ZWaveNetwork"): assert await hass.config_entries.async_setup( zwave_config_entry.entry_id)
async def test_get_actions(hass, device_ias): """Test we get the expected actions from a zha device.""" ieee_address = str(device_ias[0].ieee) ha_device_registry = dr.async_get(hass) reg_device = ha_device_registry.async_get_device({(DOMAIN, ieee_address)}) actions = await async_get_device_automations(hass, DeviceAutomationType.ACTION, reg_device.id) expected_actions = [ { "domain": DOMAIN, "type": "squawk", "device_id": reg_device.id, "metadata": {}, }, { "domain": DOMAIN, "type": "warn", "device_id": reg_device.id, "metadata": {} }, { "domain": Platform.SELECT, "type": "select_option", "device_id": reg_device.id, "entity_id": "select.fakemanufacturer_fakemodel_e769900a_ias_wd_warningmode", "metadata": { "secondary": True }, }, { "domain": Platform.SELECT, "type": "select_option", "device_id": reg_device.id, "entity_id": "select.fakemanufacturer_fakemodel_e769900a_ias_wd_sirenlevel", "metadata": { "secondary": True }, }, { "domain": Platform.SELECT, "type": "select_option", "device_id": reg_device.id, "entity_id": "select.fakemanufacturer_fakemodel_e769900a_ias_wd_strobelevel", "metadata": { "secondary": True }, }, { "domain": Platform.SELECT, "type": "select_option", "device_id": reg_device.id, "entity_id": "select.fakemanufacturer_fakemodel_e769900a_ias_wd_strobe", "metadata": { "secondary": True }, }, ] assert actions == expected_actions
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Bond from a config entry.""" host = entry.data[CONF_HOST] token = entry.data[CONF_ACCESS_TOKEN] config_entry_id = entry.entry_id bond = Bond( host=host, token=token, timeout=ClientTimeout(total=_API_TIMEOUT), session=async_get_clientsession(hass), ) hub = BondHub(bond, host) try: await hub.setup() except ClientResponseError as ex: if ex.status == HTTPStatus.UNAUTHORIZED: _LOGGER.error("Bond token no longer valid: %s", ex) return False raise ConfigEntryNotReady from ex except (ClientError, AsyncIOTimeoutError, OSError) as error: raise ConfigEntryNotReady from error bpup_subs = BPUPSubscriptions() stop_bpup = await start_bpup(host, bpup_subs) @callback def _async_stop_event(*_: Any) -> None: stop_bpup() entry.async_on_unload(_async_stop_event) entry.async_on_unload( hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, _async_stop_event)) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { HUB: hub, BPUP_SUBS: bpup_subs, } if not entry.unique_id: hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id) assert hub.bond_id is not None hub_name = hub.name or hub.bond_id device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=config_entry_id, identifiers={(DOMAIN, hub.bond_id)}, manufacturer=BRIDGE_MAKE, name=hub_name, model=hub.target, sw_version=hub.fw_ver, hw_version=hub.mcu_ver, suggested_area=hub.location, configuration_url=f"http://{host}", ) _async_remove_old_device_identifiers(config_entry_id, device_registry, hub) hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def _device_connect(self, location: str) -> None: """Connect to the device now that it's available.""" _LOGGER.debug("Connecting to device at %s", location) async with self._device_lock: if self._device: _LOGGER.debug( "Trying to connect when device already connected") return domain_data = get_domain_data(self.hass) # Connect to the base UPNP device upnp_device = await domain_data.upnp_factory.async_create_device( location) # Create/get event handler that is reachable by the device, using # the connection's local IP to listen only on the relevant interface _, event_ip = await async_get_local_ip(location, self.hass.loop) self._event_addr = self._event_addr._replace(host=event_ip) event_handler = await domain_data.async_get_event_notifier( self._event_addr, self.hass) # Create profile wrapper self._device = DmrDevice(upnp_device, event_handler) self.location = location # Subscribe to event notifications try: self._device.on_event = self._on_event await self._device.async_subscribe_services( auto_resubscribe=True) except UpnpResponseError as err: # Device rejected subscription request. This is OK, variables # will be polled instead. _LOGGER.debug("Device rejected subscription: %r", err) except UpnpError as err: # Don't leave the device half-constructed self._device.on_event = None self._device = None await domain_data.async_release_event_notifier(self._event_addr ) _LOGGER.debug( "Error while subscribing during device connect: %r", err) raise if (not self.registry_entry or not self.registry_entry.config_entry_id or self.registry_entry.device_id): return # Create linked HA DeviceEntry now the information is known. dev_reg = device_registry.async_get(self.hass) device_entry = dev_reg.async_get_or_create( config_entry_id=self.registry_entry.config_entry_id, # Connections are based on the root device's UDN, and the DMR # embedded device's UDN. They may be the same, if the DMR is the # root device. connections={ ( device_registry.CONNECTION_UPNP, self._device.profile_device.root_device.udn, ), (device_registry.CONNECTION_UPNP, self._device.udn), }, identifiers={(DOMAIN, self.unique_id)}, default_manufacturer=self._device.manufacturer, default_model=self._device.model_name, default_name=self._device.name, ) # Update entity registry to link to the device ent_reg = entity_registry.async_get(self.hass) ent_reg.async_get_or_create( self.registry_entry.domain, self.registry_entry.platform, self.unique_id, device_id=device_entry.id, )
async def test_sensors( hass: HomeAssistant, init_integration: MockConfigEntry, ) -> None: """Test the Forecast.Solar sensors.""" entry_id = init_integration.entry_id entity_registry = er.async_get(hass) device_registry = dr.async_get(hass) state = hass.states.get("sensor.energy_production_today") entry = entity_registry.async_get("sensor.energy_production_today") assert entry assert state assert entry.unique_id == f"{entry_id}_energy_production_today" assert state.state == "100" assert (state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - Today") assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.energy_production_tomorrow") entry = entity_registry.async_get("sensor.energy_production_tomorrow") assert entry assert state assert entry.unique_id == f"{entry_id}_energy_production_tomorrow" assert state.state == "200" assert (state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - Tomorrow") assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.power_highest_peak_time_today") entry = entity_registry.async_get("sensor.power_highest_peak_time_today") assert entry assert state assert entry.unique_id == f"{entry_id}_power_highest_peak_time_today" assert state.state == "2021-06-27 13:00:00+00:00" assert state.attributes.get( ATTR_FRIENDLY_NAME) == "Highest Power Peak Time - Today" assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.power_highest_peak_time_tomorrow") entry = entity_registry.async_get( "sensor.power_highest_peak_time_tomorrow") assert entry assert state assert entry.unique_id == f"{entry_id}_power_highest_peak_time_tomorrow" assert state.state == "2021-06-27 14:00:00+00:00" assert (state.attributes.get(ATTR_FRIENDLY_NAME) == "Highest Power Peak Time - Tomorrow") assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.power_production_now") entry = entity_registry.async_get("sensor.power_production_now") assert entry assert state assert entry.unique_id == f"{entry_id}_power_production_now" assert state.state == "300" assert (state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Power Production - Now") assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == POWER_WATT assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_POWER assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.energy_current_hour") entry = entity_registry.async_get("sensor.energy_current_hour") assert entry assert state assert entry.unique_id == f"{entry_id}_energy_current_hour" assert state.state == "800" assert (state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - This Hour") assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.energy_next_hour") entry = entity_registry.async_get("sensor.energy_next_hour") assert entry assert state assert entry.unique_id == f"{entry_id}_energy_next_hour" assert state.state == "900" assert (state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - Next Hour") assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.attributes.get( ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY assert ATTR_ICON not in state.attributes assert entry.device_id device_entry = device_registry.async_get(entry.device_id) assert device_entry assert device_entry.identifiers == {(DOMAIN, f"{entry_id}")} assert device_entry.manufacturer == "Forecast.Solar" assert device_entry.name == "Solar Production Forecast" assert device_entry.entry_type == ENTRY_TYPE_SERVICE assert not device_entry.model assert not device_entry.sw_version
async def test_remove_orphaned_entries_service(hass, aioclient_mock): """Test service works and also don't remove more than expected.""" data = { "lights": { "1": { "name": "Light 1 name", "state": { "reachable": True }, "type": "Light", "uniqueid": "00:00:00:00:00:00:00:01-00", } }, "sensors": { "1": { "name": "Switch 1", "type": "ZHASwitch", "state": { "buttonevent": 1000, "gesture": 1 }, "config": { "battery": 100 }, "uniqueid": "00:00:00:00:00:00:00:03-00", }, }, } with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) device_registry = dr.async_get(hass) device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "123")}, ) assert (len([ entry for entry in device_registry.devices.values() if config_entry.entry_id in entry.config_entries ]) == 5 # Host, gateway, light, switch and orphan ) entity_registry = er.async_get(hass) entity_registry.async_get_or_create( SENSOR_DOMAIN, DECONZ_DOMAIN, "12345", suggested_object_id="Orphaned sensor", config_entry=config_entry, device_id=device.id, ) assert (len( async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 3 # Light, switch battery and orphan ) await hass.services.async_call( DECONZ_DOMAIN, SERVICE_REMOVE_ORPHANED_ENTRIES, service_data={CONF_BRIDGE_ID: BRIDGEID}, ) await hass.async_block_till_done() assert (len([ entry for entry in device_registry.devices.values() if config_entry.entry_id in entry.config_entries ]) == 4 # Host, gateway, light and switch ) assert (len( async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 2 # Light and switch battery )
if sub_type is None: if info[d_type.value] is None: return web.Response(status=HTTPStatus.NOT_FOUND) data = await info[d_type.value](hass, config_entry) filename = f"{d_type}-{filename}" return await _async_get_json_file_response( hass, data, filename, config_entry.domain, d_type.value, d_id ) # sub_type handling try: sub_type = DiagnosticsSubType(sub_type) except ValueError: return web.Response(status=HTTPStatus.BAD_REQUEST) dev_reg = async_get(hass) assert sub_id if (device := dev_reg.async_get(sub_id)) is None: return web.Response(status=HTTPStatus.NOT_FOUND) filename += f"-{device.name}-{device.id}" if info[sub_type.value] is None: return web.Response(status=HTTPStatus.NOT_FOUND) data = await info[sub_type.value](hass, config_entry, device) return await _async_get_json_file_response( hass, data, filename, config_entry.domain, d_type, d_id, sub_type, sub_id )
async def test_options_add_remove_device(hass): """Test we can add a device.""" await setup.async_setup_component(hass, "persistent_notification", {}) entry = MockConfigEntry( domain=DOMAIN, data={ "host": None, "port": None, "device": "/dev/tty123", "automatic_add": False, "devices": {}, }, unique_id=DOMAIN, ) entry.add_to_hass(hass) result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "automatic_add": True, "event_code": "0b1100cd0213c7f230010f71", }, ) assert result["type"] == "form" assert result["step_id"] == "set_device_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "fire_event": True, "signal_repetitions": 5, "off_delay": "4" }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() assert entry.data["automatic_add"] assert entry.data["devices"]["0b1100cd0213c7f230010f71"] assert entry.data["devices"]["0b1100cd0213c7f230010f71"]["fire_event"] assert entry.data["devices"]["0b1100cd0213c7f230010f71"][ "signal_repetitions"] == 5 assert entry.data["devices"]["0b1100cd0213c7f230010f71"]["off_delay"] == 4 state = hass.states.get("binary_sensor.ac_213c7f2_48") assert state assert state.state == "off" assert state.attributes.get("friendly_name") == "AC 213c7f2:48" device_registry = dr.async_get(hass) device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) assert device_entries[0].id result = await hass.config_entries.options.async_init(entry.entry_id) assert result["type"] == "form" assert result["step_id"] == "prompt_options" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={ "automatic_add": False, "remove_device": [device_entries[0].id], }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() assert not entry.data["automatic_add"] assert "0b1100cd0213c7f230010f71" not in entry.data["devices"] state = hass.states.get("binary_sensor.ac_213c7f2_48") assert not state