Add support for selective config parameter entity discovery (#48336)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
56abe25c1f
commit
fbc3f97097
7 changed files with 3065 additions and 3 deletions
|
@ -2,7 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator
|
||||
from typing import Any, Generator
|
||||
|
||||
from zwave_js_server.const import CommandClass
|
||||
from zwave_js_server.model.device_class import DeviceClassItem
|
||||
|
@ -41,6 +41,12 @@ class ZWaveValueDiscoverySchema:
|
|||
endpoint: set[int] | None = None
|
||||
# [optional] the value's property must match ANY of these values
|
||||
property: set[str | int] | None = None
|
||||
# [optional] the value's property name must match ANY of these values
|
||||
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
|
||||
# [optional] the value's property key name must match ANY of these values
|
||||
property_key_name: set[str] | None = None
|
||||
# [optional] the value's metadata_type must match ANY of these values
|
||||
type: set[str] | None = None
|
||||
|
||||
|
@ -82,6 +88,34 @@ class ZWaveDiscoverySchema:
|
|||
allow_multi: bool = False
|
||||
|
||||
|
||||
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,
|
||||
property_key_name: set[str] | 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="sensor",
|
||||
hint="config_parameter",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.CONFIGURATION},
|
||||
property=property_,
|
||||
property_name=property_name,
|
||||
property_key=property_key,
|
||||
property_key_name=property_key_name,
|
||||
type={"number"},
|
||||
),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
|
||||
command_class={CommandClass.SWITCH_MULTILEVEL},
|
||||
property={"currentValue"},
|
||||
|
@ -162,6 +196,19 @@ DISCOVERY_SCHEMAS = [
|
|||
product_type={0x0003},
|
||||
primary_value=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"},
|
||||
device_class_specific={
|
||||
"Door Lock",
|
||||
"Advanced Door Lock",
|
||||
"Secure Keypad Door Lock",
|
||||
"Secure Lockbox",
|
||||
},
|
||||
),
|
||||
# ====== START OF GENERIC MAPPING SCHEMAS =======
|
||||
# locks
|
||||
ZWaveDiscoverySchema(
|
||||
|
@ -489,6 +536,24 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:
|
|||
# check property
|
||||
if schema.property is not None and value.property_ not in schema.property:
|
||||
return False
|
||||
# check property_name
|
||||
if (
|
||||
schema.property_name is not None
|
||||
and value.property_name not in schema.property_name
|
||||
):
|
||||
return False
|
||||
# check property_key
|
||||
if (
|
||||
schema.property_key is not None
|
||||
and value.property_key not in schema.property_key
|
||||
):
|
||||
return False
|
||||
# check property_key_name
|
||||
if (
|
||||
schema.property_key_name is not None
|
||||
and value.property_key_name not in schema.property_key_name
|
||||
):
|
||||
return False
|
||||
# check metadata_type
|
||||
if schema.type is not None and value.metadata.type not in schema.type:
|
||||
return False
|
||||
|
|
|
@ -99,6 +99,7 @@ class ZWaveBaseEntity(Entity):
|
|||
include_value_name: bool = False,
|
||||
alternate_value_name: str | None = None,
|
||||
additional_info: list[str] | None = None,
|
||||
name_suffix: str | None = None,
|
||||
) -> str:
|
||||
"""Generate entity name."""
|
||||
if additional_info is None:
|
||||
|
@ -108,6 +109,8 @@ class ZWaveBaseEntity(Entity):
|
|||
or self.info.node.device_config.description
|
||||
or f"Node {self.info.node.node_id}"
|
||||
)
|
||||
if name_suffix:
|
||||
name = f"{name} {name_suffix}"
|
||||
if include_value_name:
|
||||
value_name = (
|
||||
alternate_value_name
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Callable
|
||||
from typing import Callable, cast
|
||||
|
||||
from zwave_js_server.client import Client as ZwaveClient
|
||||
from zwave_js_server.const import CommandClass
|
||||
from zwave_js_server.const import CommandClass, ConfigurationValueType
|
||||
from zwave_js_server.model.value import ConfigurationValue
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DEVICE_CLASS_BATTERY,
|
||||
|
@ -49,6 +50,8 @@ async def async_setup_entry(
|
|||
entities.append(ZWaveNumericSensor(config_entry, client, info))
|
||||
elif info.platform_hint == "list_sensor":
|
||||
entities.append(ZWaveListSensor(config_entry, client, info))
|
||||
elif info.platform_hint == "config_parameter":
|
||||
entities.append(ZWaveConfigParameterSensor(config_entry, client, info))
|
||||
else:
|
||||
LOGGER.warning(
|
||||
"Sensor not implemented for %s/%s",
|
||||
|
@ -118,6 +121,7 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
|
|||
# We hide some of the more advanced sensors by default to not overwhelm users
|
||||
if self.info.primary_value.command_class in [
|
||||
CommandClass.BASIC,
|
||||
CommandClass.CONFIGURATION,
|
||||
CommandClass.INDICATOR,
|
||||
CommandClass.NOTIFICATION,
|
||||
]:
|
||||
|
@ -221,3 +225,48 @@ class ZWaveListSensor(ZwaveSensorBase):
|
|||
"""Return the device specific state attributes."""
|
||||
# add the value's int value as property for multi-value (list) items
|
||||
return {"value": self.info.primary_value.value}
|
||||
|
||||
|
||||
class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
||||
"""Representation of a Z-Wave config parameter sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_entry: ConfigEntry,
|
||||
client: ZwaveClient,
|
||||
info: ZwaveDiscoveryInfo,
|
||||
) -> None:
|
||||
"""Initialize a ZWaveConfigParameterSensor entity."""
|
||||
super().__init__(config_entry, client, info)
|
||||
self._name = self.generate_name(
|
||||
include_value_name=True,
|
||||
alternate_value_name=self.info.primary_value.property_name,
|
||||
additional_info=[self.info.primary_value.property_key_name],
|
||||
name_suffix="Config Parameter",
|
||||
)
|
||||
self._primary_value = cast(ConfigurationValue, self.info.primary_value)
|
||||
|
||||
@property
|
||||
def state(self) -> str | None:
|
||||
"""Return state of the sensor."""
|
||||
if self.info.primary_value.value is None:
|
||||
return None
|
||||
if (
|
||||
self._primary_value.configuration_value_type == ConfigurationValueType.RANGE
|
||||
or (
|
||||
not str(self.info.primary_value.value)
|
||||
in self.info.primary_value.metadata.states
|
||||
)
|
||||
):
|
||||
return str(self.info.primary_value.value)
|
||||
return str(
|
||||
self.info.primary_value.metadata.states[str(self.info.primary_value.value)]
|
||||
)
|
||||
|
||||
@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:
|
||||
return None
|
||||
# add the value's int value as property for multi-value (list) items
|
||||
return {"value": self.info.primary_value.value}
|
||||
|
|
|
@ -22,3 +22,6 @@ CLIMATE_MAIN_HEAT_ACTIONNER = "climate.main_heat_actionner"
|
|||
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"
|
||||
ID_LOCK_CONFIG_PARAMETER_SENSOR = (
|
||||
"sensor.z_wave_module_for_id_lock_150_and_101_config_parameter_door_lock_mode"
|
||||
)
|
||||
|
|
|
@ -306,6 +306,12 @@ def null_name_check_state_fixture():
|
|||
return json.loads(load_fixture("zwave_js/null_name_check_state.json"))
|
||||
|
||||
|
||||
@pytest.fixture(name="lock_id_lock_as_id150_state", scope="session")
|
||||
def lock_id_lock_as_id150_state_fixture():
|
||||
"""Load the id lock id-150 lock node state fixture data."""
|
||||
return json.loads(load_fixture("zwave_js/lock_id_lock_as_id150_state.json"))
|
||||
|
||||
|
||||
@pytest.fixture(name="client")
|
||||
def mock_client_fixture(controller_state, version_state):
|
||||
"""Mock a client."""
|
||||
|
@ -568,3 +574,11 @@ def inovelli_lzw36_fixture(client, inovelli_lzw36_state):
|
|||
node = Node(client, copy.deepcopy(inovelli_lzw36_state))
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
||||
|
||||
@pytest.fixture(name="lock_id_lock_as_id150")
|
||||
def lock_id_lock_as_id150(client, lock_id_lock_as_id150_state):
|
||||
"""Mock an id lock id-150 lock node."""
|
||||
node = Node(client, copy.deepcopy(lock_id_lock_as_id150_state))
|
||||
client.driver.controller.nodes[node.node_id] = node
|
||||
return node
|
||||
|
|
|
@ -14,6 +14,7 @@ from .common import (
|
|||
AIR_TEMPERATURE_SENSOR,
|
||||
ENERGY_SENSOR,
|
||||
HUMIDITY_SENSOR,
|
||||
ID_LOCK_CONFIG_PARAMETER_SENSOR,
|
||||
NOTIFICATION_MOTION_SENSOR,
|
||||
POWER_SENSOR,
|
||||
)
|
||||
|
@ -76,3 +77,11 @@ async def test_disabled_notification_sensor(hass, multisensor_6, integration):
|
|||
state = hass.states.get(NOTIFICATION_MOTION_SENSOR)
|
||||
assert state.state == "Motion detection"
|
||||
assert state.attributes["value"] == 8
|
||||
|
||||
|
||||
async def test_config_parameter_sensor(hass, lock_id_lock_as_id150, integration):
|
||||
"""Test config parameter sensor is created."""
|
||||
ent_reg = er.async_get(hass)
|
||||
entity_entry = ent_reg.async_get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
|
||||
assert entity_entry
|
||||
assert entity_entry.disabled
|
||||
|
|
2919
tests/fixtures/zwave_js/lock_id_lock_as_id150_state.json
vendored
Normal file
2919
tests/fixtures/zwave_js/lock_id_lock_as_id150_state.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue