Add zwave config parameter entities (#92223)

* Add zwave config parameter entities

* Remove unused entity const

* remove unusued imports

* review comments

* switch to reserved values

* fix test
This commit is contained in:
Raman Gupta 2023-05-30 11:49:55 -04:00 committed by GitHub
parent 55c2bb59c8
commit 65187c6f11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 431 additions and 106 deletions

View file

@ -315,6 +315,10 @@ async def async_setup_entry(
config_entry, driver, info, property_description
)
)
elif info.platform_hint == "config_parameter":
entities.append(
ZWaveConfigParameterBinarySensor(config_entry, driver, info)
)
else:
# boolean sensor
entities.append(ZWaveBooleanBinarySensor(config_entry, driver, info))
@ -411,3 +415,22 @@ class ZWavePropertyBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
if self.info.primary_value.value is None:
return None
return self.info.primary_value.value in self.entity_description.on_states
class ZWaveConfigParameterBinarySensor(ZWaveBooleanBinarySensor):
"""Representation of a Z-Wave config parameter binary sensor."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
) -> None:
"""Initialize a ZWaveConfigParameterBinarySensor entity."""
super().__init__(config_entry, driver, info)
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes
self._attr_name = self.generate_name(
alternate_value_name=self.info.primary_value.property_name,
additional_info=[property_key_name] if property_key_name else None,
)

View file

@ -36,6 +36,9 @@ EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry"
LOGGER = logging.getLogger(__package__)
# constants extra state attributes
ATTR_RESERVED_VALUES = "reserved_values" # ConfigurationValue number entities
# constants for events
ZWAVE_JS_VALUE_NOTIFICATION_EVENT = f"{DOMAIN}_value_notification"
ZWAVE_JS_NOTIFICATION_EVENT = f"{DOMAIN}_notification"

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from collections.abc import Generator
from dataclasses import asdict, dataclass, field
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast
from awesomeversion import AwesomeVersion
from zwave_js_server.const import (
@ -41,7 +41,11 @@ from zwave_js_server.const.command_class.thermostat import (
from zwave_js_server.exceptions import UnknownValueData
from zwave_js_server.model.device_class import DeviceClassItem
from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.value import Value as ZwaveValue
from zwave_js_server.model.value import (
ConfigurationValue,
ConfigurationValueType,
Value as ZwaveValue,
)
from homeassistant.backports.enum import StrEnum
from homeassistant.const import EntityCategory, Platform
@ -205,32 +209,6 @@ class ZWaveDiscoverySchema:
entity_category: EntityCategory | None = None
def get_config_parameter_discovery_schema(
property_: set[str | int] | None = None,
property_name: set[str] | None = None,
property_key: set[str | int | None] | None = None,
**kwargs: Any,
) -> ZWaveDiscoverySchema:
"""Return a discovery schema for a config parameter.
Supports all keyword arguments to ZWaveValueDiscoverySchema except platform, hint,
and primary_value.
"""
return ZWaveDiscoverySchema(
platform=Platform.SENSOR,
hint="config_parameter",
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.CONFIGURATION},
property=property_,
property_name=property_name,
property_key=property_key,
type={ValueType.NUMBER},
),
entity_registry_enabled_default=False,
**kwargs,
)
DOOR_LOCK_CURRENT_MODE_SCHEMA = ZWaveValueDiscoverySchema(
command_class={CommandClass.DOOR_LOCK},
property={CURRENT_MODE_PROPERTY},
@ -596,13 +574,6 @@ DISCOVERY_SCHEMAS = [
),
absent_values=[SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA],
),
# ====== START OF CONFIG PARAMETER SPECIFIC MAPPING SCHEMAS =======
# Door lock mode config parameter. Functionality equivalent to Notification CC
# list sensors.
get_config_parameter_discovery_schema(
property_name={"Door lock mode"},
device_class_generic={"Entry Control"},
),
# ====== START OF GENERIC MAPPING SCHEMAS =======
# locks
# Door Lock CC
@ -1112,6 +1083,85 @@ def async_discover_single_value(
# by other schemas/platforms
return
if value.command_class == CommandClass.CONFIGURATION:
yield from async_discover_single_configuration_value(
cast(ConfigurationValue, value)
)
@callback
def async_discover_single_configuration_value(
value: ConfigurationValue,
) -> Generator[ZwaveDiscoveryInfo, None, None]:
"""Run discovery on a single ZWave configuration value and return matching schema info."""
if value.metadata.writeable and value.metadata.readable:
if value.configuration_value_type == ConfigurationValueType.ENUMERATED:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.SELECT,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
elif value.configuration_value_type in (
ConfigurationValueType.RANGE,
ConfigurationValueType.MANUAL_ENTRY,
):
if value.metadata.type == ValueType.BOOLEAN or (
value.metadata.min == 0 and value.metadata.max == 1
):
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.SWITCH,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
else:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.NUMBER,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
elif not value.metadata.writeable and value.metadata.readable:
if value.metadata.type == ValueType.BOOLEAN or (
value.metadata.min == 0
and value.metadata.max == 1
and not value.metadata.states
):
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.BINARY_SENSOR,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
else:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.SENSOR,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
@callback
def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:

View file

@ -2,13 +2,16 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
from zwave_js_server.const import NodeStatus
from zwave_js_server.exceptions import BaseZwaveJSServerError
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo, Entity
@ -292,3 +295,19 @@ class ZWaveBaseEntity(Entity):
):
self.watched_value_ids.add(return_value.value_id)
return return_value
async def _async_set_value(
self,
value: ZwaveValue,
new_value: Any,
options: dict | None = None,
wait_for_result: bool | None = None,
) -> bool | None:
"""Set value on node."""
try:
return await self.info.node.async_set_value(
value, new_value, options=options, wait_for_result=wait_for_result
)
except BaseZwaveJSServerError as err:
LOGGER.error("Unable to set value %s: %s", value.value_id, err)
raise HomeAssistantError from err

View file

@ -1,7 +1,8 @@
"""Support for Z-Wave controls using the number platform."""
from __future__ import annotations
from typing import cast
from collections.abc import Mapping
from typing import Any, cast
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import TARGET_VALUE_PROPERTY
@ -10,12 +11,13 @@ from zwave_js_server.model.value import Value
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN, NumberEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_CLIENT, DOMAIN
from .const import ATTR_RESERVED_VALUES, DATA_CLIENT, DOMAIN
from .discovery import ZwaveDiscoveryInfo
from .entity import ZWaveBaseEntity
@ -38,6 +40,10 @@ async def async_setup_entry(
entities: list[ZWaveBaseEntity] = []
if info.platform_hint == "volume":
entities.append(ZwaveVolumeNumberEntity(config_entry, driver, info))
elif info.platform_hint == "config_parameter":
entities.append(
ZWaveConfigParameterNumberEntity(config_entry, driver, info)
)
else:
entities.append(ZwaveNumberEntity(config_entry, driver, info))
async_add_entities(entities)
@ -98,7 +104,37 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity):
"""Set new value."""
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, value)
await self._async_set_value(target_value, value)
class ZWaveConfigParameterNumberEntity(ZwaveNumberEntity):
"""Representation of a Z-Wave config parameter number."""
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
) -> None:
"""Initialize a ZWaveConfigParameterNumber entity."""
super().__init__(config_entry, driver, info)
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes
self._attr_name = self.generate_name(
alternate_value_name=self.info.primary_value.property_name,
additional_info=[property_key_name] if property_key_name else None,
)
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return extra state attributes for entity."""
if not self.info.primary_value.metadata.states:
return None
return {
ATTR_RESERVED_VALUES: {
int(k): v for k, v in self.info.primary_value.metadata.states.items()
}
}
class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity):

View file

@ -42,6 +42,10 @@ async def async_setup_entry(
entities.append(
ZwaveMultilevelSwitchSelectEntity(config_entry, driver, info)
)
elif info.platform_hint == "config_parameter":
entities.append(
ZWaveConfigParameterSelectEntity(config_entry, driver, info)
)
else:
entities.append(ZwaveSelectEntity(config_entry, driver, info))
async_add_entities(entities)
@ -91,6 +95,25 @@ class ZwaveSelectEntity(ZWaveBaseEntity, SelectEntity):
await self.info.node.async_set_value(self.info.primary_value, int(key))
class ZWaveConfigParameterSelectEntity(ZwaveSelectEntity):
"""Representation of a Z-Wave config parameter select."""
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
) -> None:
"""Initialize a ZWaveConfigParameterSelect entity."""
super().__init__(config_entry, driver, info)
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes
self._attr_name = self.generate_name(
alternate_value_name=self.info.primary_value.property_name,
additional_info=[property_key_name] if property_key_name else None,
)
class ZwaveDefaultToneSelectEntity(ZWaveBaseEntity, SelectEntity):
"""Representation of a Z-Wave default tone select entity."""

View file

@ -6,14 +6,14 @@ from typing import cast
import voluptuous as vol
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import CommandClass, ConfigurationValueType, NodeStatus
from zwave_js_server.const import CommandClass, NodeStatus
from zwave_js_server.const.command_class.meter import (
RESET_METER_OPTION_TARGET_VALUE,
RESET_METER_OPTION_TYPE,
)
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.value import ConfigurationValue
from zwave_js_server.model.value import ConfigurationValue, ConfigurationValueType
from zwave_js_server.util.command_class.meter import get_meter_type
from homeassistant.components.sensor import (
@ -490,6 +490,13 @@ class ZWaveListSensor(ZwaveSensor):
additional_info=[self.info.primary_value.property_key_name],
)
@property
def options(self) -> list[str] | None:
"""Return options for enum sensor."""
if self.device_class == SensorDeviceClass.ENUM:
return list(self.info.primary_value.metadata.states.values())
return None
@property
def device_class(self) -> SensorDeviceClass | None:
"""Return sensor device class."""
@ -499,13 +506,6 @@ class ZWaveListSensor(ZwaveSensor):
return SensorDeviceClass.ENUM
return None
@property
def options(self) -> list[str] | None:
"""Return options for enum sensor."""
if self.device_class == SensorDeviceClass.ENUM:
return list(self.info.primary_value.metadata.states.values())
return None
@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the device specific state attributes."""
@ -518,6 +518,8 @@ class ZWaveListSensor(ZwaveSensor):
class ZWaveConfigParameterSensor(ZWaveListSensor):
"""Representation of a Z-Wave config parameter sensor."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self,
config_entry: ConfigEntry,
@ -537,7 +539,6 @@ class ZWaveConfigParameterSensor(ZWaveListSensor):
self._attr_name = self.generate_name(
alternate_value_name=self.info.primary_value.property_name,
additional_info=[property_key_name] if property_key_name else None,
name_prefix="Config parameter",
)
@property
@ -555,10 +556,7 @@ class ZWaveConfigParameterSensor(ZWaveListSensor):
@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the device specific state attributes."""
if (
self._primary_value.configuration_value_type == ConfigurationValueType.RANGE
or (value := self.info.primary_value.value) is None
):
if (value := self.info.primary_value.value) is None:
return None
# add the value's int value as property for multi-value (list) items
return {ATTR_VALUE: value}

View file

@ -12,6 +12,7 @@ from zwave_js_server.model.driver import Driver
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -41,6 +42,8 @@ async def async_setup_entry(
entities.append(
ZWaveBarrierEventSignalingSwitch(config_entry, driver, info)
)
elif info.platform_hint == "config_parameter":
entities.append(ZWaveConfigParameterSwitch(config_entry, driver, info))
elif info.platform_hint == "indicator":
entities.append(ZWaveIndicatorSwitch(config_entry, driver, info))
else:
@ -79,12 +82,12 @@ class ZWaveSwitch(ZWaveBaseEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
if self._target_value is not None:
await self.info.node.async_set_value(self._target_value, True)
await self._async_set_value(self._target_value, True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
if self._target_value is not None:
await self.info.node.async_set_value(self._target_value, False)
await self._async_set_value(self._target_value, False)
class ZWaveIndicatorSwitch(ZWaveSwitch):
@ -129,7 +132,7 @@ class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.info.node.async_set_value(
await self._async_set_value(
self.info.primary_value, BarrierEventSignalingSubsystemState.ON
)
# this value is not refreshed, so assume success
@ -138,7 +141,7 @@ class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.info.node.async_set_value(
await self._async_set_value(
self.info.primary_value, BarrierEventSignalingSubsystemState.OFF
)
# this value is not refreshed, so assume success
@ -152,3 +155,30 @@ class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity):
self._state = (
self.info.primary_value.value == BarrierEventSignalingSubsystemState.ON
)
class ZWaveConfigParameterSwitch(ZWaveSwitch):
"""Representation of a Z-Wave config parameter switch."""
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
) -> None:
"""Initialize a ZWaveConfigParameterSwitch entity."""
super().__init__(config_entry, driver, info)
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes
self._attr_name = self.generate_name(
alternate_value_name=self.info.primary_value.property_name,
additional_info=[property_key_name] if property_key_name else None,
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self._async_set_value(self.info.primary_value, 1)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self._async_set_value(self.info.primary_value, 0)

View file

@ -39,9 +39,6 @@ BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color"
EATON_RF9640_ENTITY = "light.allloaddimmer"
AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6"
SCHLAGE_BE469_LOCK_ENTITY = "lock.touchscreen_deadbolt"
ID_LOCK_CONFIG_PARAMETER_SENSOR = (
"sensor.z_wave_module_for_id_lock_150_and_101_config_parameter_door_lock_mode"
)
ZEN_31_ENTITY = "light.kitchen_under_cabinet_lights"
METER_ENERGY_SENSOR = "sensor.smart_switch_6_electric_consumed_kwh"
METER_VOLTAGE_SENSOR = "sensor.smart_switch_6_electric_consumed_v"

View file

@ -259,3 +259,29 @@ async def test_property_sensor_door_status(
state = hass.states.get(PROPERTY_DOOR_STATUS_BINARY_SENSOR)
assert state
assert state.state == STATE_UNKNOWN
async def test_config_parameter_binary_sensor(
hass: HomeAssistant, climate_adc_t3000, integration
) -> None:
"""Test config parameter binary sensor is created."""
binary_sensor_entity_id = "binary_sensor.adc_t3000_system_configuration_override"
ent_reg = er.async_get(hass)
entity_entry = ent_reg.async_get(binary_sensor_entity_id)
assert entity_entry
assert entity_entry.disabled
assert entity_entry.entity_category == EntityCategory.DIAGNOSTIC
updated_entry = ent_reg.async_update_entity(
binary_sensor_entity_id, **{"disabled_by": None}
)
assert updated_entry != entity_entry
assert updated_entry.disabled is False
# reload integration and check if entity is correctly there
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
state = hass.states.get(binary_sensor_entity_id)
assert state
assert state.state == STATE_OFF

View file

@ -963,7 +963,7 @@ async def test_removed_device(
# Check how many entities there are
ent_reg = er.async_get(hass)
entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id)
assert len(entity_entries) == 36
assert len(entity_entries) == 62
# Remove a node and reload the entry
old_node = driver.controller.nodes.pop(13)
@ -975,7 +975,7 @@ async def test_removed_device(
device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id)
assert len(device_entries) == 2
entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id)
assert len(entity_entries) == 23
assert len(entity_entries) == 38
assert dev_reg.async_get_device({get_device_id(driver, old_node)}) is None

View file

@ -4,7 +4,7 @@ from unittest.mock import patch
import pytest
from zwave_js_server.event import Event
from homeassistant.const import STATE_UNKNOWN
from homeassistant.const import STATE_UNKNOWN, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
@ -229,3 +229,37 @@ async def test_disabled_basic_number(
assert entity_entry
assert entity_entry.disabled
assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
async def test_config_parameter_number(
hass: HomeAssistant, climate_adc_t3000, integration
) -> None:
"""Test config parameter number is created."""
number_entity_id = "number.adc_t3000_heat_staging_delay"
number_with_states_entity_id = "number.adc_t3000_calibration_temperature"
ent_reg = er.async_get(hass)
for entity_id in (number_entity_id, number_with_states_entity_id):
entity_entry = ent_reg.async_get(entity_id)
assert entity_entry
assert entity_entry.disabled
assert entity_entry.entity_category == EntityCategory.CONFIG
for entity_id in (number_entity_id, number_with_states_entity_id):
updated_entry = ent_reg.async_update_entity(entity_id, **{"disabled_by": None})
assert updated_entry != entity_entry
assert updated_entry.disabled is False
# reload integration and check if entity is correctly there
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
state = hass.states.get(number_entity_id)
assert state
assert state.state == "30.0"
assert "reserved_values" not in state.attributes
state = hass.states.get(number_with_states_entity_id)
assert state
assert state.state == "0.0"
assert "reserved_values" in state.attributes
assert state.attributes["reserved_values"] == {-1: "Disabled"}

View file

@ -294,3 +294,29 @@ async def test_multilevel_switch_select_no_value(
assert state
assert state.state == STATE_UNKNOWN
async def test_config_parameter_select(
hass: HomeAssistant, climate_adc_t3000, integration
) -> None:
"""Test config parameter select is created."""
select_entity_id = "select.adc_t3000_hvac_system_type"
ent_reg = er.async_get(hass)
entity_entry = ent_reg.async_get(select_entity_id)
assert entity_entry
assert entity_entry.disabled
assert entity_entry.entity_category == EntityCategory.CONFIG
updated_entry = ent_reg.async_update_entity(
select_entity_id, **{"disabled_by": None}
)
assert updated_entry != entity_entry
assert updated_entry.disabled is False
# reload integration and check if entity is correctly there
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
state = hass.states.get(select_entity_id)
assert state
assert state.state == "Normal"

View file

@ -45,7 +45,6 @@ from .common import (
CURRENT_SENSOR,
ENERGY_SENSOR,
HUMIDITY_SENSOR,
ID_LOCK_CONFIG_PARAMETER_SENSOR,
METER_ENERGY_SENSOR,
NOTIFICATION_MOTION_SENSOR,
POWER_SENSOR,
@ -188,7 +187,9 @@ async def test_disabled_notification_sensor(
state = hass.states.get(NOTIFICATION_MOTION_SENSOR)
assert state.state == "Motion detection"
assert state.attributes["value"] == 8
assert state.attributes[ATTR_VALUE] == 8
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENUM
assert state.attributes[ATTR_OPTIONS] == ["idle", "Motion detection"]
event = Event(
"value updated",
@ -218,13 +219,34 @@ async def test_disabled_notification_sensor(
async def test_config_parameter_sensor(
hass: HomeAssistant, lock_id_lock_as_id150, integration
hass: HomeAssistant, climate_adc_t3000, lock_id_lock_as_id150, integration
) -> None:
"""Test config parameter sensor is created."""
sensor_entity_id = "sensor.adc_t3000_system_configuration_cool_stages"
sensor_with_states_entity_id = "sensor.adc_t3000_power_source"
ent_reg = er.async_get(hass)
entity_entry = ent_reg.async_get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
for entity_id in (sensor_entity_id, sensor_with_states_entity_id):
entity_entry = ent_reg.async_get(entity_id)
assert entity_entry
assert entity_entry.disabled
assert entity_entry.entity_category == EntityCategory.DIAGNOSTIC
for entity_id in (sensor_entity_id, sensor_with_states_entity_id):
updated_entry = ent_reg.async_update_entity(entity_id, **{"disabled_by": None})
assert updated_entry != entity_entry
assert updated_entry.disabled is False
# reload integration and check if entity is correctly there
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
state = hass.states.get(sensor_entity_id)
assert state
assert state.state == "1"
state = hass.states.get(sensor_with_states_entity_id)
assert state
assert state.state == "C-Wire"
updated_entry = ent_reg.async_update_entity(
entity_entry.entity_id, **{"disabled_by": None}
@ -236,43 +258,6 @@ async def test_config_parameter_sensor(
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
state = hass.states.get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
assert state
assert state.state == "Disable Away Manual Lock"
assert state.attributes[ATTR_VALUE] == 0
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENUM
assert state.attributes[ATTR_OPTIONS] == [
"Disable Away Manual Lock",
"Disable Away Auto Lock",
"Enable Away Manual Lock",
"Enable Away Auto Lock",
]
event = Event(
"value updated",
{
"source": "node",
"event": "value updated",
"nodeId": lock_id_lock_as_id150.node_id,
"args": {
"commandClassName": "Configuration",
"commandClass": 112,
"endpoint": 0,
"property": 1,
"newValue": None,
"prevValue": 0,
"propertyName": "Door lock mode",
},
},
)
lock_id_lock_as_id150.receive_event(event)
await hass.async_block_till_done()
state = hass.states.get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
assert state
assert state.state == STATE_UNKNOWN
assert ATTR_VALUE not in state.attributes
async def test_node_status_sensor(
hass: HomeAssistant, client, lock_id_lock_as_id150, integration

View file

@ -1,12 +1,16 @@
"""Test the Z-Wave JS switch platform."""
import pytest
from zwave_js_server.const import CURRENT_VALUE_PROPERTY, CommandClass
from zwave_js_server.event import Event
from zwave_js_server.exceptions import FailedZWaveCommand
from zwave_js_server.model.node import Node
from homeassistant.components.switch import DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from .common import SWITCH_ENTITY, replace_value_of_zwave_value
@ -209,3 +213,74 @@ async def test_switch_no_value(
assert state
assert state.state == STATE_UNKNOWN
async def test_config_parameter_switch(
hass: HomeAssistant, hank_binary_switch, integration, client
) -> None:
"""Test config parameter switch is created."""
switch_entity_id = "switch.smart_plug_with_two_usb_ports_overload_protection"
ent_reg = er.async_get(hass)
entity_entry = ent_reg.async_get(switch_entity_id)
assert entity_entry
assert entity_entry.disabled
updated_entry = ent_reg.async_update_entity(
switch_entity_id, **{"disabled_by": None}
)
assert updated_entry != entity_entry
assert updated_entry.disabled is False
assert entity_entry.entity_category == EntityCategory.CONFIG
# reload integration and check if entity is correctly there
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
state = hass.states.get(switch_entity_id)
assert state
assert state.state == STATE_ON
client.async_send_command.reset_mock()
# Test turning on
await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, {"entity_id": switch_entity_id}, 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"] == hank_binary_switch.node_id
assert args["value"] == 1
assert args["valueId"] == {
"commandClass": 112,
"endpoint": 0,
"property": 20,
}
client.async_send_command.reset_mock()
# Test turning off
await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, {"entity_id": switch_entity_id}, 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"] == hank_binary_switch.node_id
assert args["value"] == 0
assert args["valueId"] == {
"commandClass": 112,
"endpoint": 0,
"property": 20,
}
client.async_send_command.reset_mock()
client.async_send_command.side_effect = FailedZWaveCommand("test", 1, "test")
# Test turning off error raises proper exception
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, {"entity_id": switch_entity_id}, blocking=True
)