def async_get_node_status_sensor_entity_id( hass: HomeAssistant, device_id: str, ent_reg: EntityRegistry | None = None, dev_reg: DeviceRegistry | None = None, ) -> str: """Get the node status sensor entity ID for a given Z-Wave JS device.""" if not ent_reg: ent_reg = async_get_ent_reg(hass) if not dev_reg: dev_reg = async_get_dev_reg(hass) device = dev_reg.async_get(device_id) if not device: raise HomeAssistantError("Invalid Device ID provided") entry_id = next(entry_id for entry_id in device.config_entries) client = hass.data[DOMAIN][entry_id][DATA_CLIENT] node = async_get_node_from_device_id(hass, device_id, dev_reg) entity_id = ent_reg.async_get_entity_id( SENSOR_DOMAIN, DOMAIN, f"{client.driver.controller.home_id}.{node.node_id}.node_status", ) if not entity_id: raise HomeAssistantError( "Node status sensor entity not found. Device may not be a zwave_js device" ) return entity_id
async def test_get_trigger_capabilities_node_status( hass, client, lock_schlage_be469, integration ): """Test we get the expected capabilities from a node_status trigger.""" dev_reg = async_get_dev_reg(hass) device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] ent_reg = async_get_ent_reg(hass) entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg ) ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() capabilities = await device_trigger.async_get_trigger_capabilities( hass, { "platform": "device", "domain": DOMAIN, "device_id": device.id, "entity_id": entity_id, "type": "state.node_status", }, ) assert capabilities and "extra_fields" in capabilities assert voluptuous_serialize.convert( capabilities["extra_fields"], custom_serializer=cv.custom_serializer ) == [ { "name": "from", "optional": True, "options": [ ("asleep", "asleep"), ("awake", "awake"), ("dead", "dead"), ("alive", "alive"), ], "type": "select", }, { "name": "to", "optional": True, "options": [ ("asleep", "asleep"), ("awake", "awake"), ("dead", "dead"), ("alive", "alive"), ], "type": "select", }, {"name": "for", "optional": True, "type": "positive_time_period_dict"}, ]
def async_get_node_from_entity_id(hass: HomeAssistant, entity_id: str) -> ZwaveNode: """ Get node from an entity ID. Raises ValueError if entity is invalid. """ entity_entry = async_get_ent_reg(hass).async_get(entity_id) if not entity_entry: raise ValueError("Entity ID is not valid") if entity_entry.platform != DOMAIN: raise ValueError("Entity is not from zwave_js integration") # Assert for mypy, safe because we know that zwave_js entities are always # tied to a device assert entity_entry.device_id return async_get_node_from_device_id(hass, entity_entry.device_id)
async def test_get_node_status_triggers(hass, client, lock_schlage_be469, integration): """Test we get the expected triggers from a device with node status sensor enabled.""" dev_reg = async_get_dev_reg(hass) device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] ent_reg = async_get_ent_reg(hass) entity_id = async_get_node_status_sensor_entity_id(hass, device.id, ent_reg, dev_reg) ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() expected_trigger = { "platform": "device", "domain": DOMAIN, "type": "state.node_status", "device_id": device.id, "entity_id": entity_id, } triggers = await async_get_device_automations(hass, "trigger", device.id) assert expected_trigger in triggers
def async_get_node_from_entity_id( hass: HomeAssistant, entity_id: str, ent_reg: EntityRegistry | None = None, dev_reg: DeviceRegistry | None = None, ) -> ZwaveNode: """ Get node from an entity ID. Raises ValueError if entity is invalid. """ if not ent_reg: ent_reg = async_get_ent_reg(hass) entity_entry = ent_reg.async_get(entity_id) if entity_entry is None or entity_entry.platform != DOMAIN: raise ValueError(f"Entity {entity_id} is not a valid {DOMAIN} entity.") # Assert for mypy, safe because we know that zwave_js entities are always # tied to a device assert entity_entry.device_id return async_get_node_from_device_id(hass, entity_entry.device_id, dev_reg)
async def test_set_config_parameter(hass, client, multisensor_6, integration): """Test the set_config_parameter service.""" dev_reg = async_get_dev_reg(hass) ent_reg = async_get_ent_reg(hass) entity_entry = ent_reg.async_get(AIR_TEMPERATURE_SENSOR) # Test setting config parameter by property and property_key await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, ATTR_CONFIG_PARAMETER: 102, ATTR_CONFIG_PARAMETER_BITMASK: 1, ATTR_CONFIG_VALUE: 1, }, blocking=True, ) assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { "commandClassName": "Configuration", "commandClass": 112, "endpoint": 0, "property": 102, "propertyName": "Group 2: Send battery reports", "propertyKey": 1, "metadata": { "type": "number", "readable": True, "writeable": True, "valueSize": 4, "min": 0, "max": 1, "default": 1, "format": 0, "allowManualEntry": True, "label": "Group 2: Send battery reports", "description": "Include battery information in periodic reports to Group 2", "isFromConfig": True, }, "value": 0, } assert args["value"] == 1 client.async_send_command_no_wait.reset_mock() # Test setting config parameter value in hex await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, ATTR_CONFIG_PARAMETER: 102, ATTR_CONFIG_PARAMETER_BITMASK: 1, ATTR_CONFIG_VALUE: "0x1", }, blocking=True, ) assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { "commandClassName": "Configuration", "commandClass": 112, "endpoint": 0, "property": 102, "propertyName": "Group 2: Send battery reports", "propertyKey": 1, "metadata": { "type": "number", "readable": True, "writeable": True, "valueSize": 4, "min": 0, "max": 1, "default": 1, "format": 0, "allowManualEntry": True, "label": "Group 2: Send battery reports", "description": "Include battery information in periodic reports to Group 2", "isFromConfig": True, }, "value": 0, } assert args["value"] == 1 client.async_send_command_no_wait.reset_mock() # Test setting parameter by property name await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, ATTR_CONFIG_PARAMETER: "Group 2: Send battery reports", ATTR_CONFIG_VALUE: 1, }, blocking=True, ) assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { "commandClassName": "Configuration", "commandClass": 112, "endpoint": 0, "property": 102, "propertyName": "Group 2: Send battery reports", "propertyKey": 1, "metadata": { "type": "number", "readable": True, "writeable": True, "valueSize": 4, "min": 0, "max": 1, "default": 1, "format": 0, "allowManualEntry": True, "label": "Group 2: Send battery reports", "description": "Include battery information in periodic reports to Group 2", "isFromConfig": True, }, "value": 0, } assert args["value"] == 1 client.async_send_command_no_wait.reset_mock() # Test setting parameter by property name and state label await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_DEVICE_ID: entity_entry.device_id, ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", ATTR_CONFIG_VALUE: "Fahrenheit", }, blocking=True, ) assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { "commandClassName": "Configuration", "commandClass": 112, "endpoint": 0, "property": 41, "propertyName": "Temperature Threshold (Unit)", "propertyKey": 15, "metadata": { "type": "number", "readable": True, "writeable": True, "valueSize": 3, "min": 1, "max": 2, "default": 1, "format": 0, "allowManualEntry": False, "states": {"1": "Celsius", "2": "Fahrenheit"}, "label": "Temperature Threshold (Unit)", "isFromConfig": True, }, "value": 0, } assert args["value"] == 2 client.async_send_command_no_wait.reset_mock() # Test setting parameter by property and bitmask await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, ATTR_CONFIG_PARAMETER: 102, ATTR_CONFIG_PARAMETER_BITMASK: "0x01", ATTR_CONFIG_VALUE: 1, }, blocking=True, ) assert len(client.async_send_command_no_wait.call_args_list) == 1 args = client.async_send_command_no_wait.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { "commandClassName": "Configuration", "commandClass": 112, "endpoint": 0, "property": 102, "propertyName": "Group 2: Send battery reports", "propertyKey": 1, "metadata": { "type": "number", "readable": True, "writeable": True, "valueSize": 4, "min": 0, "max": 1, "default": 1, "format": 0, "allowManualEntry": True, "label": "Group 2: Send battery reports", "description": "Include battery information in periodic reports to Group 2", "isFromConfig": True, }, "value": 0, } assert args["value"] == 1 # Test that an invalid entity ID raises a MultipleInvalid with pytest.raises(vol.MultipleInvalid): await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_ENTITY_ID: "sensor.fake_entity", ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", ATTR_CONFIG_VALUE: "Fahrenheit", }, blocking=True, ) # Test that an invalid device ID raises a MultipleInvalid with pytest.raises(vol.MultipleInvalid): await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_DEVICE_ID: "fake_device_id", ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", ATTR_CONFIG_VALUE: "Fahrenheit", }, blocking=True, ) # Test that we can't include a bitmask value if parameter is a string with pytest.raises(vol.Invalid): await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_DEVICE_ID: entity_entry.device_id, ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", ATTR_CONFIG_PARAMETER_BITMASK: 1, ATTR_CONFIG_VALUE: "Fahrenheit", }, blocking=True, ) non_zwave_js_config_entry = MockConfigEntry(entry_id="fake_entry_id") non_zwave_js_config_entry.add_to_hass(hass) non_zwave_js_device = dev_reg.async_get_or_create( config_entry_id=non_zwave_js_config_entry.entry_id, identifiers={("test", "test")}, ) # Test that a non Z-Wave JS device raises a MultipleInvalid with pytest.raises(vol.MultipleInvalid): await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_DEVICE_ID: non_zwave_js_device.id, ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", ATTR_CONFIG_VALUE: "Fahrenheit", }, blocking=True, ) zwave_js_device_with_invalid_node_id = dev_reg.async_get_or_create( config_entry_id=integration.entry_id, identifiers={(DOMAIN, "500-500")} ) # Test that a Z-Wave JS device with an invalid node ID raises a MultipleInvalid with pytest.raises(vol.MultipleInvalid): await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_DEVICE_ID: zwave_js_device_with_invalid_node_id.id, ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", ATTR_CONFIG_VALUE: "Fahrenheit", }, blocking=True, ) non_zwave_js_entity = ent_reg.async_get_or_create( "test", "sensor", "test_sensor", suggested_object_id="test_sensor", config_entry=non_zwave_js_config_entry, ) # Test that a non Z-Wave JS entity raises a MultipleInvalid with pytest.raises(vol.MultipleInvalid): await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_ENTITY_ID: non_zwave_js_entity.entity_id, ATTR_CONFIG_PARAMETER: "Temperature Threshold (Unit)", ATTR_CONFIG_VALUE: "Fahrenheit", }, blocking=True, ) # Test that when a device is awake, we call async_send_command instead of # async_send_command_no_wait multisensor_6.handle_wake_up(None) await hass.services.async_call( DOMAIN, SERVICE_SET_CONFIG_PARAMETER, { ATTR_ENTITY_ID: AIR_TEMPERATURE_SENSOR, ATTR_CONFIG_PARAMETER: 102, ATTR_CONFIG_PARAMETER_BITMASK: 1, ATTR_CONFIG_VALUE: 1, }, blocking=True, ) assert len(client.async_send_command.call_args_list) == 1 args = client.async_send_command.call_args[0][0] assert args["command"] == "node.set_value" assert args["nodeId"] == 52 assert args["valueId"] == { "commandClassName": "Configuration", "commandClass": 112, "endpoint": 0, "property": 102, "propertyName": "Group 2: Send battery reports", "propertyKey": 1, "metadata": { "type": "number", "readable": True, "writeable": True, "valueSize": 4, "min": 0, "max": 1, "default": 1, "format": 0, "allowManualEntry": True, "label": "Group 2: Send battery reports", "description": "Include battery information in periodic reports to Group 2", "isFromConfig": True, }, "value": 0, } assert args["value"] == 1 client.async_send_command.reset_mock()
async def test_if_node_status_change_fires( hass, client, lock_schlage_be469, integration, calls ): """Test for node_status trigger firing.""" node: Node = lock_schlage_be469 dev_reg = async_get_dev_reg(hass) device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0] ent_reg = async_get_ent_reg(hass) entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg ) ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() assert await async_setup_component( hass, automation.DOMAIN, { automation.DOMAIN: [ # from { "trigger": { "platform": "device", "domain": DOMAIN, "device_id": device.id, "entity_id": entity_id, "type": "state.node_status", "from": "alive", }, "action": { "service": "test.automation", "data_template": { "some": ( "state.node_status - " "{{ trigger.platform}} - " "{{ trigger.from_state.state }}" ) }, }, }, # no from or to { "trigger": { "platform": "device", "domain": DOMAIN, "device_id": device.id, "entity_id": entity_id, "type": "state.node_status", }, "action": { "service": "test.automation", "data_template": { "some": ( "state.node_status2 - " "{{ trigger.platform}} - " "{{ trigger.from_state.state }}" ) }, }, }, ] }, ) # Test status change event = Event( "dead", data={"source": "node", "event": "dead", "nodeId": node.node_id} ) node.receive_event(event) await hass.async_block_till_done() assert len(calls) == 2 assert calls[0].data["some"] == "state.node_status - device - alive" assert calls[1].data["some"] == "state.node_status2 - device - alive"
async def test_device_diagnostics_missing_primary_value( hass, client, multisensor_6, integration, hass_client, ): """Test that the device diagnostics handles an entity with a missing primary value.""" dev_reg = async_get_dev_reg(hass) device = dev_reg.async_get_device( {get_device_id(client.driver, multisensor_6)}) assert device entity_id = "sensor.multisensor_6_air_temperature" ent_reg = async_get_ent_reg(hass) entry = ent_reg.async_get(entity_id) # check that the primary value for the entity exists in the diagnostics diagnostics_data = await get_diagnostics_for_device( hass, hass_client, integration, device) value = multisensor_6.values.get( get_value_id_from_unique_id(entry.unique_id)) assert value air_entity = next(x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id) assert air_entity["primary_value"] == { "command_class": value.command_class, "command_class_name": value.command_class_name, "endpoint": value.endpoint, "property": value.property_, "property_name": value.property_name, "property_key": value.property_key, "property_key_name": value.property_key_name, } # make the entity's primary value go missing event = Event( type="value removed", data={ "source": "node", "event": "value removed", "nodeId": multisensor_6.node_id, "args": { "commandClassName": value.command_class_name, "commandClass": value.command_class, "endpoint": value.endpoint, "property": value.property_, "prevValue": 0, "propertyName": value.property_name, }, }, ) multisensor_6.receive_event(event) diagnostics_data = await get_diagnostics_for_device( hass, hass_client, integration, device) air_entity = next(x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id) assert air_entity["primary_value"] is None