Add proper support for zwave_js Indicator CC (#90248)
* Add proper support for zwave_js Indicator CC * remove stale test * Make all indicators diagnostic * only set entity category if it is specified * Only set properties from discovery if specified * Conditionally set assumed state as well * fix const name * Don't create task * Disable property keys 3-5 by default * add additional dispatcher_connects so we catch all signals * be consistent about order * rename new discovery parameter * comment * exclude property keys 3-5 * fix remove logic * add comment so I don't forget * Switch entity category to config where necessary * cut line * less lines * Update homeassistant/components/zwave_js/switch.py Co-authored-by: kpine <keith.pine@gmail.com> * Move async_remove to respond to interview started event * Set up listener immediately so we don't wait for platform creation * remove dupe import * black * append --------- Co-authored-by: kpine <keith.pine@gmail.com>
This commit is contained in:
parent
66f7218b68
commit
872cd47e87
16 changed files with 1862 additions and 80 deletions
|
@ -321,6 +321,19 @@ class ControllerEvents:
|
|||
|
||||
async def async_on_node_added(self, node: ZwaveNode) -> None:
|
||||
"""Handle node added event."""
|
||||
# Remove stale entities that may exist from a previous interview when an
|
||||
# interview is started.
|
||||
base_unique_id = get_valueless_base_unique_id(self.driver_events.driver, node)
|
||||
self.config_entry.async_on_unload(
|
||||
node.on(
|
||||
"interview started",
|
||||
lambda _: async_dispatcher_send(
|
||||
self.hass,
|
||||
f"{DOMAIN}_{base_unique_id}_remove_entity_on_interview_started",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# No need for a ping button or node status sensor for controller nodes
|
||||
if not node.is_controller_node:
|
||||
# Create a node status sensor for each device
|
||||
|
@ -455,7 +468,6 @@ class NodeEvents:
|
|||
async def async_on_node_ready(self, node: ZwaveNode) -> None:
|
||||
"""Handle node ready event."""
|
||||
LOGGER.debug("Processing node %s", node)
|
||||
driver = self.controller_events.driver_events.driver
|
||||
# register (or update) node in device registry
|
||||
device = self.controller_events.register_node_in_dev_reg(node)
|
||||
# We only want to create the defaultdict once, even on reinterviews
|
||||
|
@ -464,15 +476,6 @@ class NodeEvents:
|
|||
|
||||
# Remove any old value ids if this is a reinterview.
|
||||
self.controller_events.discovered_value_ids.pop(device.id, None)
|
||||
# Remove stale entities that may exist from a previous interview.
|
||||
async_dispatcher_send(
|
||||
self.hass,
|
||||
(
|
||||
f"{DOMAIN}_"
|
||||
f"{get_valueless_base_unique_id(driver, node)}_"
|
||||
"remove_entity_on_ready_node"
|
||||
),
|
||||
)
|
||||
|
||||
value_updates_disc_info: dict[str, ZwaveDiscoveryInfo] = {}
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ async def async_setup_entry(
|
|||
entities: list[ZWaveBaseEntity] = []
|
||||
if info.platform_hint == "notification idle":
|
||||
entities.append(ZWaveNotificationIdleButton(config_entry, driver, info))
|
||||
else:
|
||||
entities.append(ZwaveBooleanNodeButton(config_entry, driver, info))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
@ -63,6 +65,21 @@ async def async_setup_entry(
|
|||
)
|
||||
|
||||
|
||||
class ZwaveBooleanNodeButton(ZWaveBaseEntity, ButtonEntity):
|
||||
"""Representation of a ZWave button entity for a boolean value."""
|
||||
|
||||
def __init__(
|
||||
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
|
||||
) -> None:
|
||||
"""Initialize entity."""
|
||||
super().__init__(config_entry, driver, info)
|
||||
self._attr_name = self.generate_name(include_value_name=True)
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
await self.info.node.async_set_value(self.info.primary_value, True)
|
||||
|
||||
|
||||
class ZWaveNodePingButton(ButtonEntity):
|
||||
"""Representation of a ping button entity."""
|
||||
|
||||
|
@ -98,6 +115,9 @@ class ZWaveNodePingButton(ButtonEntity):
|
|||
)
|
||||
)
|
||||
|
||||
# we don't listen for `remove_entity_on_ready_node` signal because this entity
|
||||
# is created when the node is added which occurs before ready. It only needs to
|
||||
# be removed if the node is removed from the network.
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
|
|
|
@ -44,7 +44,7 @@ from zwave_js_server.model.node import Node as ZwaveNode
|
|||
from zwave_js_server.model.value import Value as ZwaveValue
|
||||
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
|
@ -108,7 +108,8 @@ class ZwaveDiscoveryInfo:
|
|||
node: ZwaveNode
|
||||
# the value object itself for primary value
|
||||
primary_value: ZwaveValue
|
||||
# bool to specify whether state is assumed and events should be fired on value update
|
||||
# bool to specify whether state is assumed and events should be fired on value
|
||||
# update
|
||||
assumed_state: bool
|
||||
# the home assistant platform for which an entity should be created
|
||||
platform: Platform
|
||||
|
@ -122,6 +123,8 @@ class ZwaveDiscoveryInfo:
|
|||
platform_data_template: BaseDiscoverySchemaDataTemplate | None = None
|
||||
# bool to specify whether entity should be enabled by default
|
||||
entity_registry_enabled_default: bool = True
|
||||
# the entity category for the discovered entity
|
||||
entity_category: EntityCategory | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -143,8 +146,14 @@ class ZWaveValueDiscoverySchema(DataclassMustHaveAtLeastOne):
|
|||
property_name: set[str] | None = None
|
||||
# [optional] the value's property key must match ANY of these values
|
||||
property_key: set[str | int | None] | None = None
|
||||
# [optional] the value's property key must NOT match ANY of these values
|
||||
not_property_key: set[str | int | None] | None = None
|
||||
# [optional] the value's metadata_type must match ANY of these values
|
||||
type: set[str] | None = None
|
||||
# [optional] the value's metadata_readable must match this value
|
||||
readable: bool | None = None
|
||||
# [optional] the value's metadata_writeable must match this value
|
||||
writeable: bool | None = None
|
||||
# [optional] the value's states map must include ANY of these key/value pairs
|
||||
any_available_states: set[tuple[int, str]] | None = None
|
||||
|
||||
|
@ -192,6 +201,8 @@ class ZWaveDiscoverySchema:
|
|||
assumed_state: bool = False
|
||||
# [optional] bool to specify whether entity should be enabled by default
|
||||
entity_registry_enabled_default: bool = True
|
||||
# [optional] the entity category for the discovered entity
|
||||
entity_category: EntityCategory | None = None
|
||||
|
||||
|
||||
def get_config_parameter_discovery_schema(
|
||||
|
@ -695,6 +706,18 @@ DISCOVERY_SCHEMAS = [
|
|||
),
|
||||
allow_multi=True,
|
||||
),
|
||||
# binary sensor for Indicator CC
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
hint="boolean",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.INDICATOR},
|
||||
type={ValueType.BOOLEAN},
|
||||
readable=True,
|
||||
writeable=False,
|
||||
),
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
# generic text sensors
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
|
@ -704,15 +727,6 @@ DISCOVERY_SCHEMAS = [
|
|||
type={ValueType.STRING},
|
||||
),
|
||||
),
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
hint="string_sensor",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.INDICATOR},
|
||||
type={ValueType.STRING},
|
||||
),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
# generic numeric sensors
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
|
@ -733,9 +747,11 @@ DISCOVERY_SCHEMAS = [
|
|||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.INDICATOR},
|
||||
type={ValueType.NUMBER},
|
||||
readable=True,
|
||||
writeable=False,
|
||||
),
|
||||
data_template=NumericSensorDataTemplate(),
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
# Meter sensors for Meter CC
|
||||
ZWaveDiscoverySchema(
|
||||
|
@ -768,9 +784,7 @@ DISCOVERY_SCHEMAS = [
|
|||
platform=Platform.NUMBER,
|
||||
hint="Basic",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={
|
||||
CommandClass.BASIC,
|
||||
},
|
||||
command_class={CommandClass.BASIC},
|
||||
type={ValueType.NUMBER},
|
||||
property={CURRENT_VALUE_PROPERTY},
|
||||
),
|
||||
|
@ -783,14 +797,48 @@ DISCOVERY_SCHEMAS = [
|
|||
property={TARGET_VALUE_PROPERTY},
|
||||
)
|
||||
],
|
||||
data_template=NumericSensorDataTemplate(),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
# number for Indicator CC (exclude property keys 3-5)
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.NUMBER,
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.INDICATOR},
|
||||
type={ValueType.NUMBER},
|
||||
not_property_key={3, 4, 5},
|
||||
readable=True,
|
||||
writeable=True,
|
||||
),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
# button for Indicator CC
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.BUTTON,
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.INDICATOR},
|
||||
type={ValueType.BOOLEAN},
|
||||
readable=False,
|
||||
writeable=True,
|
||||
),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
# binary switches
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SWITCH,
|
||||
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
|
||||
),
|
||||
# switch for Indicator CC
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SWITCH,
|
||||
hint="indicator",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.INDICATOR},
|
||||
type={ValueType.BOOLEAN},
|
||||
readable=True,
|
||||
writeable=True,
|
||||
),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
# binary switch
|
||||
# barrier operator signaling states
|
||||
ZWaveDiscoverySchema(
|
||||
|
@ -1023,6 +1071,7 @@ def async_discover_single_value(
|
|||
platform_data=resolved_data,
|
||||
additional_value_ids_to_watch=additional_value_ids_to_watch,
|
||||
entity_registry_enabled_default=schema.entity_registry_enabled_default,
|
||||
entity_category=schema.entity_category,
|
||||
)
|
||||
|
||||
if not schema.allow_multi:
|
||||
|
@ -1058,9 +1107,21 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:
|
|||
and value.property_key not in schema.property_key
|
||||
):
|
||||
return False
|
||||
# check property_key against not_property_key set
|
||||
if (
|
||||
schema.not_property_key is not None
|
||||
and value.property_key in schema.not_property_key
|
||||
):
|
||||
return False
|
||||
# check metadata_type
|
||||
if schema.type is not None and value.metadata.type not in schema.type:
|
||||
return False
|
||||
# check metadata_readable
|
||||
if schema.readable is not None and value.metadata.readable != schema.readable:
|
||||
return False
|
||||
# check metadata_writeable
|
||||
if schema.writeable is not None and value.metadata.writeable != schema.writeable:
|
||||
return False
|
||||
# check available states
|
||||
if (
|
||||
schema.any_available_states is not None
|
||||
|
|
|
@ -44,10 +44,12 @@ class ZWaveBaseEntity(Entity):
|
|||
# Entity class attributes
|
||||
self._attr_name = self.generate_name()
|
||||
self._attr_unique_id = get_unique_id(driver, self.info.primary_value.value_id)
|
||||
self._attr_entity_registry_enabled_default = (
|
||||
self.info.entity_registry_enabled_default
|
||||
)
|
||||
self._attr_assumed_state = self.info.assumed_state
|
||||
if self.info.entity_registry_enabled_default is False:
|
||||
self._attr_entity_registry_enabled_default = False
|
||||
if self.info.entity_category is not None:
|
||||
self._attr_entity_category = self.info.entity_category
|
||||
if self.info.assumed_state:
|
||||
self._attr_assumed_state = True
|
||||
# device is precreated in main handler
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={get_device_id(driver, self.info.node)},
|
||||
|
@ -103,7 +105,18 @@ class ZWaveBaseEntity(Entity):
|
|||
(
|
||||
f"{DOMAIN}_"
|
||||
f"{get_valueless_base_unique_id(self.driver, self.info.node)}_"
|
||||
"remove_entity_on_ready_node"
|
||||
"remove_entity"
|
||||
),
|
||||
self.async_remove,
|
||||
)
|
||||
)
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
(
|
||||
f"{DOMAIN}_"
|
||||
f"{get_valueless_base_unique_id(self.driver, self.info.node)}_"
|
||||
"remove_entity_on_interview_started"
|
||||
),
|
||||
self.async_remove,
|
||||
)
|
||||
|
|
|
@ -66,7 +66,9 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity):
|
|||
self._target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
|
||||
|
||||
# Entity class attributes
|
||||
self._attr_name = self.generate_name(alternate_value_name=info.platform_hint)
|
||||
self._attr_name = self.generate_name(
|
||||
include_value_name=True, alternate_value_name=info.platform_hint
|
||||
)
|
||||
|
||||
@property
|
||||
def native_min_value(self) -> float:
|
||||
|
|
|
@ -615,6 +615,9 @@ class ZWaveNodeStatusSensor(SensorEntity):
|
|||
self.async_poll_value,
|
||||
)
|
||||
)
|
||||
# we don't listen for `remove_entity_on_ready_node` signal because this entity
|
||||
# is created when the node is added which occurs before ready. It only needs to
|
||||
# be removed if the node is removed from the network.
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
|
|
|
@ -41,6 +41,8 @@ async def async_setup_entry(
|
|||
entities.append(
|
||||
ZWaveBarrierEventSignalingSwitch(config_entry, driver, info)
|
||||
)
|
||||
elif info.platform_hint == "indicator":
|
||||
entities.append(ZWaveIndicatorSwitch(config_entry, driver, info))
|
||||
else:
|
||||
entities.append(ZWaveSwitch(config_entry, driver, info))
|
||||
|
||||
|
@ -85,6 +87,18 @@ class ZWaveSwitch(ZWaveBaseEntity, SwitchEntity):
|
|||
await self.info.node.async_set_value(self._target_value, False)
|
||||
|
||||
|
||||
class ZWaveIndicatorSwitch(ZWaveSwitch):
|
||||
"""Representation of a Z-Wave Indicator CC switch."""
|
||||
|
||||
def __init__(
|
||||
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
|
||||
) -> None:
|
||||
"""Initialize the switch."""
|
||||
super().__init__(config_entry, driver, info)
|
||||
self._target_value = self.info.primary_value
|
||||
self._attr_name = self.generate_name(include_value_name=True)
|
||||
|
||||
|
||||
class ZWaveBarrierEventSignalingSwitch(ZWaveBaseEntity, SwitchEntity):
|
||||
"""Switch is used to turn on/off a barrier device's event signaling subsystem."""
|
||||
|
||||
|
|
|
@ -317,7 +317,7 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
|
|||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{DOMAIN}_{self._base_unique_id}_remove_entity_on_ready_node",
|
||||
f"{DOMAIN}_{self._base_unique_id}_remove_entity_on_interview_started",
|
||||
self.async_remove,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -11,6 +11,8 @@ from zwave_js_server.model.driver import Driver
|
|||
from zwave_js_server.model.node import Node
|
||||
from zwave_js_server.version import VersionInfo
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
# Add-on fixtures
|
||||
|
@ -109,7 +111,7 @@ def mock_addon_options(addon_info):
|
|||
def set_addon_options_side_effect_fixture(addon_options):
|
||||
"""Return the set add-on options side effect."""
|
||||
|
||||
async def set_addon_options(hass, slug, options):
|
||||
async def set_addon_options(hass: HomeAssistant, slug, options):
|
||||
"""Mock set add-on options."""
|
||||
addon_options.update(options["options"])
|
||||
|
||||
|
@ -130,7 +132,7 @@ def mock_set_addon_options(set_addon_options_side_effect):
|
|||
def install_addon_side_effect_fixture(addon_store_info, addon_info):
|
||||
"""Return the install add-on side effect."""
|
||||
|
||||
async def install_addon(hass, slug):
|
||||
async def install_addon(hass: HomeAssistant, slug):
|
||||
"""Mock install add-on."""
|
||||
addon_store_info.return_value = {
|
||||
"available": True,
|
||||
|
@ -168,7 +170,7 @@ def mock_update_addon():
|
|||
def start_addon_side_effect_fixture(addon_store_info, addon_info):
|
||||
"""Return the start add-on options side effect."""
|
||||
|
||||
async def start_addon(hass, slug):
|
||||
async def start_addon(hass: HomeAssistant, slug):
|
||||
"""Mock start add-on."""
|
||||
addon_store_info.return_value = {
|
||||
"available": True,
|
||||
|
@ -365,7 +367,7 @@ def climate_adc_t3000_state_fixture():
|
|||
|
||||
@pytest.fixture(name="climate_danfoss_lc_13_state", scope="session")
|
||||
def climate_danfoss_lc_13_state_fixture():
|
||||
"""Load the climate Danfoss (LC-13) electronic radiator thermostat node state fixture data."""
|
||||
"""Load Danfoss (LC-13) electronic radiator thermostat node state fixture data."""
|
||||
return json.loads(load_fixture("zwave_js/climate_danfoss_lc_13_state.json"))
|
||||
|
||||
|
||||
|
@ -522,7 +524,7 @@ def climate_radio_thermostat_ct101_multiple_temp_units_state_fixture():
|
|||
scope="session",
|
||||
)
|
||||
def climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state_fixture():
|
||||
"""Load the climate device with mode and setpoint on different endpoints node state fixture data."""
|
||||
"""Load climate device w/ mode+setpoint on diff endpoints node state fixture data."""
|
||||
return json.loads(
|
||||
load_fixture(
|
||||
"zwave_js/climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_state.json"
|
||||
|
@ -604,6 +606,18 @@ def lock_home_connect_620_state_fixture():
|
|||
return json.loads(load_fixture("zwave_js/lock_home_connect_620_state.json"))
|
||||
|
||||
|
||||
@pytest.fixture(name="switch_zooz_zen72_state", scope="session")
|
||||
def switch_zooz_zen72_state_fixture():
|
||||
"""Load the Zooz Zen72 switch node state fixture data."""
|
||||
return json.loads(load_fixture("zwave_js/switch_zooz_zen72_state.json"))
|
||||
|
||||
|
||||
@pytest.fixture(name="indicator_test_state", scope="session")
|
||||
def indicator_test_state_fixture():
|
||||
"""Load the indicator CC test node state fixture data."""
|
||||
return json.loads(load_fixture("zwave_js/indicator_test_state.json"))
|
||||
|
||||
|
||||
# model fixtures
|
||||
|
||||
|
||||
|
@ -612,7 +626,6 @@ def mock_client_fixture(
|
|||
controller_state, controller_node_state, version_state, log_config_state
|
||||
):
|
||||
"""Mock a client."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.ZwaveClient", autospec=True
|
||||
) as client_class:
|
||||
|
@ -722,7 +735,7 @@ def climate_radio_thermostat_ct100_plus_fixture(
|
|||
def climate_radio_thermostat_ct100_plus_different_endpoints_fixture(
|
||||
client, climate_radio_thermostat_ct100_plus_different_endpoints_state
|
||||
):
|
||||
"""Mock a climate radio thermostat ct100 plus node with values on different endpoints."""
|
||||
"""Mock climate radio thermostat ct100 plus node w/ values on diff endpoints."""
|
||||
node = Node(
|
||||
client,
|
||||
copy.deepcopy(climate_radio_thermostat_ct100_plus_different_endpoints_state),
|
||||
|
@ -773,7 +786,7 @@ def climate_adc_t3000_missing_mode_fixture(client, climate_adc_t3000_state):
|
|||
|
||||
@pytest.fixture(name="climate_adc_t3000_missing_fan_mode_states")
|
||||
def climate_adc_t3000_missing_fan_mode_states_fixture(client, climate_adc_t3000_state):
|
||||
"""Mock a climate ADC-T3000 node with missing 'states' metadata on Thermostat Fan Mode."""
|
||||
"""Mock ADC-T3000 node w/ missing 'states' metadata on Thermostat Fan Mode."""
|
||||
data = copy.deepcopy(climate_adc_t3000_state)
|
||||
data["name"] = f"{data['name']} missing fan mode states"
|
||||
for value in data["values"]:
|
||||
|
@ -872,7 +885,7 @@ def nortek_thermostat_removed_event_fixture(client):
|
|||
|
||||
|
||||
@pytest.fixture(name="integration")
|
||||
async def integration_fixture(hass, client):
|
||||
async def integration_fixture(hass: HomeAssistant, client):
|
||||
"""Set up the zwave_js integration."""
|
||||
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
|
||||
entry.add_to_hass(hass)
|
||||
|
@ -1146,3 +1159,19 @@ def lock_home_connect_620_fixture(client, lock_home_connect_620_state):
|
|||
node = Node(client, copy.deepcopy(lock_home_connect_620_state))
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
||||
|
||||
@pytest.fixture(name="switch_zooz_zen72")
|
||||
def switch_zooz_zen72_fixture(client, switch_zooz_zen72_state):
|
||||
"""Mock a Zooz Zen72 switch node."""
|
||||
node = Node(client, copy.deepcopy(switch_zooz_zen72_state))
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
||||
|
||||
@pytest.fixture(name="indicator_test")
|
||||
def indicator_test_fixture(client, indicator_test_state):
|
||||
"""Mock a indicator CC test node."""
|
||||
node = Node(client, copy.deepcopy(indicator_test_state))
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
|
190
tests/components/zwave_js/fixtures/indicator_test_state.json
Normal file
190
tests/components/zwave_js/fixtures/indicator_test_state.json
Normal file
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
"nodeId": 43,
|
||||
"index": 0,
|
||||
"installerIcon": 1536,
|
||||
"userIcon": 1540,
|
||||
"status": 4,
|
||||
"ready": true,
|
||||
"isListening": true,
|
||||
"isRouting": true,
|
||||
"isSecure": true,
|
||||
"manufacturerId": 634,
|
||||
"productId": 40962,
|
||||
"productType": 28672,
|
||||
"firmwareVersion": "10.0.1",
|
||||
"zwavePlusVersion": 2,
|
||||
"location": "**REDACTED**",
|
||||
"deviceConfig": {
|
||||
"filename": "/usr/src/app/store/.config-db/devices/0x027a/zen72.json",
|
||||
"isEmbedded": true,
|
||||
"manufacturer": "Fake",
|
||||
"manufacturerId": 634,
|
||||
"label": "Device",
|
||||
"description": "This is a fake device",
|
||||
"devices": [
|
||||
{
|
||||
"productType": 28672,
|
||||
"productId": 40962
|
||||
}
|
||||
],
|
||||
"firmwareVersion": {
|
||||
"min": "0.0",
|
||||
"max": "255.255"
|
||||
},
|
||||
"associations": {},
|
||||
"paramInformation": {
|
||||
"_map": {}
|
||||
},
|
||||
"compat": {
|
||||
"skipConfigurationNameQuery": true,
|
||||
"skipConfigurationInfoQuery": true
|
||||
},
|
||||
"metadata": {
|
||||
"inclusion": "1. Initiate inclusion (pairing) in the app (or web interface).\n2. TAP UP 3 TIMES QUICKLY if using traditional Z-Wave inclusion.\n3. The LED indicator will blink blue to signal communication and turn green for 3 seconds if inclusion is successful or turn red for 3 seconds if the pairing attempt fails",
|
||||
"exclusion": "1. Bring your Z-Wave gateway (hub) close to the switch if possible\n2. Put the Z-Wave hub into exclusion mode (not sure how to do that? ask@getzooz.com) \n3. Tap the lower paddle on the switch 3 times quickly (the LED indicator will start blinking blue)\n4. Your hub will confirm exclusion, the LED indicator on the switch will turn green for 3 seconds, and the device will disappear from your controller's device list",
|
||||
"reset": "If your primary controller is missing or inoperable, you may need to reset the device to factory settings. To reset the switch, press and hold the lower paddle for 10 seconds until the LED indicator starts blinking. Release paddle, and immediately after, tap the lower paddle 5 times to complete the reset. The LED indicator will flash blue 3 times and turn red for 3 seconds to confirm successful reset",
|
||||
"manual": "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=product_documents/4108/zooz-700-series-z-wave-dimmer-zen72-manual.pdf"
|
||||
}
|
||||
},
|
||||
"label": "Device",
|
||||
"interviewAttempts": 0,
|
||||
"endpoints": [
|
||||
{
|
||||
"nodeId": 43,
|
||||
"index": 0,
|
||||
"installerIcon": 1536,
|
||||
"userIcon": 1540,
|
||||
"deviceClass": {
|
||||
"basic": {
|
||||
"key": 4,
|
||||
"label": "Routing Slave"
|
||||
},
|
||||
"generic": {
|
||||
"key": 17,
|
||||
"label": "Multilevel Switch"
|
||||
},
|
||||
"specific": {
|
||||
"key": 1,
|
||||
"label": "Multilevel Power Switch"
|
||||
},
|
||||
"mandatorySupportedCCs": [32, 38, 39],
|
||||
"mandatoryControlledCCs": []
|
||||
},
|
||||
"commandClasses": [
|
||||
{
|
||||
"id": 135,
|
||||
"name": "Indicator",
|
||||
"version": 3,
|
||||
"isSecure": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"values": [
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 135,
|
||||
"commandClassName": "Indicator",
|
||||
"property": "Test",
|
||||
"propertyKey": "Sensor",
|
||||
"propertyName": "Test",
|
||||
"propertyKeyName": "Sensor",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"description": "Tests non-writeable Indicator CC number gets created as a sensor entity",
|
||||
"label": "Sensor",
|
||||
"ccSpecific": {
|
||||
"indicatorId": 80,
|
||||
"propertyId": 4
|
||||
}
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 135,
|
||||
"commandClassName": "Indicator",
|
||||
"property": "Test",
|
||||
"propertyKey": "Switch",
|
||||
"propertyName": "Test",
|
||||
"propertyKeyName": "Switch",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "boolean",
|
||||
"readable": true,
|
||||
"writeable": true,
|
||||
"description": "Tests writeable Indicator CC boolean gets created as a switch entity",
|
||||
"label": "Switch",
|
||||
"ccSpecific": {
|
||||
"indicatorId": 80,
|
||||
"propertyId": 5
|
||||
}
|
||||
},
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"endpoint": 0,
|
||||
"commandClass": 135,
|
||||
"commandClassName": "Indicator",
|
||||
"property": "Test",
|
||||
"propertyKey": "Binary Sensor",
|
||||
"propertyName": "Test",
|
||||
"propertyKeyName": "Binary Sensor",
|
||||
"ccVersion": 3,
|
||||
"metadata": {
|
||||
"type": "boolean",
|
||||
"readable": true,
|
||||
"writeable": false,
|
||||
"description": "Tests non-writeable Indicator CC boolean gets created as a binary sensor entity",
|
||||
"label": "Binary Sensor",
|
||||
"ccSpecific": {
|
||||
"indicatorId": 80,
|
||||
"propertyId": 5
|
||||
}
|
||||
},
|
||||
"value": false
|
||||
}
|
||||
],
|
||||
"isFrequentListening": false,
|
||||
"maxDataRate": 100000,
|
||||
"supportedDataRates": [40000, 100000],
|
||||
"protocolVersion": 3,
|
||||
"supportsBeaming": true,
|
||||
"supportsSecurity": false,
|
||||
"nodeType": 1,
|
||||
"zwavePlusNodeType": 0,
|
||||
"zwavePlusRoleType": 5,
|
||||
"deviceClass": {
|
||||
"basic": {
|
||||
"key": 4,
|
||||
"label": "Routing Slave"
|
||||
},
|
||||
"generic": {
|
||||
"key": 17,
|
||||
"label": "Multilevel Switch"
|
||||
},
|
||||
"specific": {
|
||||
"key": 1,
|
||||
"label": "Multilevel Power Switch"
|
||||
},
|
||||
"mandatorySupportedCCs": [32, 38, 39],
|
||||
"mandatoryControlledCCs": []
|
||||
},
|
||||
"interviewStage": "Complete",
|
||||
"deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x027a:0x7000:0xa002:10.0.1",
|
||||
"statistics": {
|
||||
"commandsTX": 64,
|
||||
"commandsRX": 88,
|
||||
"commandsDroppedRX": 0,
|
||||
"commandsDroppedTX": 0,
|
||||
"timeoutResponse": 0,
|
||||
"rtt": 74.5,
|
||||
"rssi": -60
|
||||
},
|
||||
"highestSecurityClass": 1,
|
||||
"isControllerNode": false,
|
||||
"keepAwake": false
|
||||
}
|
1299
tests/components/zwave_js/fixtures/switch_zooz_zen72_state.json
Normal file
1299
tests/components/zwave_js/fixtures/switch_zooz_zen72_state.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -85,6 +85,8 @@ from homeassistant.helpers import device_registry as dr
|
|||
from tests.common import MockUser
|
||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||
|
||||
CONTROLLER_PATCH_PREFIX = "zwave_js_server.model.controller.Controller"
|
||||
|
||||
|
||||
def get_device(hass: HomeAssistant, node):
|
||||
"""Get device ID for a node."""
|
||||
|
@ -131,7 +133,7 @@ async def test_network_status(
|
|||
|
||||
# Try API call with entry ID
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_get_state",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_get_state",
|
||||
return_value=controller_state["controller"],
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -155,7 +157,7 @@ async def test_network_status(
|
|||
)
|
||||
assert device
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_get_state",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_get_state",
|
||||
return_value=controller_state["controller"],
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -410,7 +412,8 @@ async def test_node_metadata(
|
|||
"controller being inoperable or otherwise unavailable.)"
|
||||
)
|
||||
assert result["manual"] == (
|
||||
"https://products.z-wavealliance.org/ProductManual/File?folder=&filename=MarketCertificationFiles/2479/ZP3111-5_R2_20170316.pdf"
|
||||
"https://products.z-wavealliance.org/ProductManual/File?folder=&filename="
|
||||
"MarketCertificationFiles/2479/ZP3111-5_R2_20170316.pdf"
|
||||
)
|
||||
assert not result["wakeup"]
|
||||
assert (
|
||||
|
@ -885,7 +888,7 @@ async def test_add_node(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_begin_inclusion",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_begin_inclusion",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -1155,7 +1158,7 @@ async def test_provision_smart_start_node(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_provision_smart_start_node",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_provision_smart_start_node",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -1163,7 +1166,9 @@ async def test_provision_smart_start_node(
|
|||
ID: 7,
|
||||
TYPE: "zwave_js/provision_smart_start_node",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
|
||||
QR_CODE_STRING: (
|
||||
"90testtesttesttesttesttesttesttesttesttesttesttesttest"
|
||||
),
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
@ -1259,7 +1264,7 @@ async def test_unprovision_smart_start_node(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_unprovision_smart_start_node",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_unprovision_smart_start_node",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -1332,7 +1337,7 @@ async def test_get_provisioning_entries(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_get_provisioning_entries",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_get_provisioning_entries",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -1432,7 +1437,9 @@ async def test_parse_qr_code_string(
|
|||
ID: 6,
|
||||
TYPE: "zwave_js/parse_qr_code_string",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
|
||||
QR_CODE_STRING: (
|
||||
"90testtesttesttesttesttesttesttesttesttesttesttesttest"
|
||||
),
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
@ -1497,7 +1504,9 @@ async def test_try_parse_dsk_from_qr_code_string(
|
|||
ID: 6,
|
||||
TYPE: "zwave_js/try_parse_dsk_from_qr_code_string",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
|
||||
QR_CODE_STRING: (
|
||||
"90testtesttesttesttesttesttesttesttesttesttesttesttest"
|
||||
),
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
@ -1572,7 +1581,7 @@ async def test_cancel_inclusion_exclusion(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_stop_inclusion",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_stop_inclusion",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -1590,7 +1599,7 @@ async def test_cancel_inclusion_exclusion(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_stop_exclusion",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_stop_exclusion",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -1709,7 +1718,7 @@ async def test_remove_node(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_begin_exclusion",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_begin_exclusion",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -2054,7 +2063,7 @@ async def test_replace_failed_node(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_replace_failed_node",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_replace_failed_node",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -2105,7 +2114,7 @@ async def test_remove_failed_node(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_remove_failed_node",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_remove_failed_node",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -2200,7 +2209,7 @@ async def test_begin_healing_network(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_begin_healing_network",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_begin_healing_network",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -2342,7 +2351,7 @@ async def test_stop_healing_network(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_stop_healing_network",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_stop_healing_network",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -2403,7 +2412,7 @@ async def test_heal_node(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_heal_node",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_heal_node",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
@ -3279,7 +3288,7 @@ async def test_subscribe_log_updates(
|
|||
async def test_update_log_config(
|
||||
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test that the update_log_config WS API call works and that schema validation works."""
|
||||
"""Test that update_log_config WS API call and schema validation works."""
|
||||
entry = integration
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
|
@ -3841,7 +3850,7 @@ async def test_subscribe_firmware_update_status_initial_value(
|
|||
integration,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test subscribe_firmware_update_status websocket command with in progress update."""
|
||||
"""Test subscribe_firmware_update_status WS command with in progress update."""
|
||||
ws_client = await hass_ws_client(hass)
|
||||
device = get_device(hass, multisensor_6)
|
||||
|
||||
|
@ -4160,7 +4169,7 @@ async def test_is_any_ota_firmware_update_in_progress(
|
|||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.controller.Controller.async_is_any_ota_firmware_update_in_progress",
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_is_any_ota_firmware_update_in_progress",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
|
|
|
@ -122,7 +122,7 @@ async def test_device_diagnostics_missing_primary_value(
|
|||
integration,
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test that the device diagnostics handles an entity with a missing primary value."""
|
||||
"""Test that 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
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
"""Test discovery of entities for device-specific schemas for the Z-Wave JS integration."""
|
||||
"""Test entity discovery for device-specific schemas for the Z-Wave JS integration."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
from homeassistant.components.number import (
|
||||
ATTR_VALUE,
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
from homeassistant.components.zwave_js.discovery import (
|
||||
FirmwareVersionRange,
|
||||
ZWaveDiscoverySchema,
|
||||
|
@ -9,6 +22,7 @@ from homeassistant.components.zwave_js.discovery import (
|
|||
from homeassistant.components.zwave_js.discovery_data_template import (
|
||||
DynamicCurrentTempClimateDataTemplate,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_UNKNOWN, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
|
@ -141,3 +155,141 @@ async def test_merten_507801_disabled_enitites(
|
|||
)
|
||||
assert updated_entry != entry
|
||||
assert updated_entry.disabled is False
|
||||
|
||||
|
||||
async def test_zooz_zen72(
|
||||
hass: HomeAssistant, client, switch_zooz_zen72, integration
|
||||
) -> None:
|
||||
"""Test that Zooz ZEN72 Indicators are discovered as number entities."""
|
||||
ent_reg = er.async_get(hass)
|
||||
assert len(hass.states.async_entity_ids(NUMBER_DOMAIN)) == 1
|
||||
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 2 # includes ping
|
||||
entity_id = "number.z_wave_plus_700_series_dimmer_switch_indicator_value"
|
||||
entry = ent_reg.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.entity_category == EntityCategory.CONFIG
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: entity_id,
|
||||
ATTR_VALUE: 5,
|
||||
},
|
||||
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"] == switch_zooz_zen72.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 135,
|
||||
"endpoint": 0,
|
||||
"property": "value",
|
||||
}
|
||||
assert args["value"] == 5
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
entity_id = "button.z_wave_plus_700_series_dimmer_switch_identify"
|
||||
entry = ent_reg.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.entity_category == EntityCategory.CONFIG
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
SERVICE_PRESS,
|
||||
{ATTR_ENTITY_ID: 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"] == switch_zooz_zen72.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 135,
|
||||
"endpoint": 0,
|
||||
"property": "identify",
|
||||
}
|
||||
assert args["value"] is True
|
||||
|
||||
|
||||
async def test_indicator_test(
|
||||
hass: HomeAssistant, client, indicator_test, integration
|
||||
) -> None:
|
||||
"""Test that Indicators are discovered properly.
|
||||
|
||||
This test covers indicators that we don't already have device fixtures for.
|
||||
"""
|
||||
ent_reg = er.async_get(hass)
|
||||
assert len(hass.states.async_entity_ids(NUMBER_DOMAIN)) == 0
|
||||
assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 1 # only ping
|
||||
assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1
|
||||
assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 # include node status
|
||||
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1
|
||||
|
||||
entity_id = "binary_sensor.this_is_a_fake_device_binary_sensor"
|
||||
entry = ent_reg.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
entity_id = "sensor.this_is_a_fake_device_sensor"
|
||||
entry = ent_reg.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == "0.0"
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
entity_id = "switch.this_is_a_fake_device_switch"
|
||||
entry = ent_reg.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.entity_category == EntityCategory.CONFIG
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: 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"] == indicator_test.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 135,
|
||||
"endpoint": 0,
|
||||
"property": "Test",
|
||||
"propertyKey": "Switch",
|
||||
}
|
||||
assert args["value"] is True
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: 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"] == indicator_test.node_id
|
||||
assert args["valueId"] == {
|
||||
"commandClass": 135,
|
||||
"endpoint": 0,
|
||||
"property": "Test",
|
||||
"propertyKey": "Switch",
|
||||
}
|
||||
assert args["value"] is False
|
||||
|
|
|
@ -101,7 +101,7 @@ async def test_disabled_statistics(hass: HomeAssistant, client) -> None:
|
|||
|
||||
|
||||
async def test_noop_statistics(hass: HomeAssistant, client) -> None:
|
||||
"""Test that we don't make any statistics calls if user hasn't provided preference."""
|
||||
"""Test that we don't make statistics calls if user hasn't set preference."""
|
||||
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
|
@ -1332,7 +1332,7 @@ async def test_node_model_change(
|
|||
async def test_disabled_node_status_entity_on_node_replaced(
|
||||
hass: HomeAssistant, zp3111_state, zp3111, client, integration
|
||||
) -> None:
|
||||
"""Test that when a node replacement event is received the node status sensor is removed."""
|
||||
"""Test when node replacement event is received, node status sensor is removed."""
|
||||
node_status_entity = "sensor.4_in_1_sensor_node_status"
|
||||
state = hass.states.get(node_status_entity)
|
||||
assert state
|
||||
|
|
|
@ -46,7 +46,6 @@ from .common import (
|
|||
ENERGY_SENSOR,
|
||||
HUMIDITY_SENSOR,
|
||||
ID_LOCK_CONFIG_PARAMETER_SENSOR,
|
||||
INDICATOR_SENSOR,
|
||||
METER_ENERGY_SENSOR,
|
||||
NOTIFICATION_MOTION_SENSOR,
|
||||
POWER_SENSOR,
|
||||
|
@ -218,18 +217,6 @@ async def test_disabled_notification_sensor(
|
|||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_disabled_indcator_sensor(
|
||||
hass: HomeAssistant, climate_radio_thermostat_ct100_plus, integration
|
||||
) -> None:
|
||||
"""Test sensor is created from Indicator CC and is disabled."""
|
||||
ent_reg = er.async_get(hass)
|
||||
entity_entry = ent_reg.async_get(INDICATOR_SENSOR)
|
||||
|
||||
assert entity_entry
|
||||
assert entity_entry.disabled
|
||||
assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
|
||||
async def test_config_parameter_sensor(
|
||||
hass: HomeAssistant, lock_id_lock_as_id150, integration
|
||||
) -> None:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue