Enhance platform discovery for zwave_js (#46355)

This commit is contained in:
Marcel van der Veldt 2021-02-12 13:29:11 +01:00 committed by GitHub
parent 74f5f8976f
commit b7dd9bf58f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -14,12 +14,14 @@ from homeassistant.core import callback
class ZwaveDiscoveryInfo: class ZwaveDiscoveryInfo:
"""Info discovered from (primary) ZWave Value to create entity.""" """Info discovered from (primary) ZWave Value to create entity."""
node: ZwaveNode # node to which the value(s) belongs # node to which the value(s) belongs
primary_value: ZwaveValue # the value object itself for primary value node: ZwaveNode
platform: str # the home assistant platform for which an entity should be created # the value object itself for primary value
platform_hint: Optional[ primary_value: ZwaveValue
str # the home assistant platform for which an entity should be created
] = "" # hint for the platform about this discovered entity platform: str
# hint for the platform about this discovered entity
platform_hint: Optional[str] = ""
@property @property
def value_id(self) -> str: def value_id(self) -> str:
@ -28,24 +30,14 @@ class ZwaveDiscoveryInfo:
@dataclass @dataclass
class ZWaveDiscoverySchema: class ZWaveValueDiscoverySchema:
"""Z-Wave discovery schema. """Z-Wave Value discovery schema.
The (primary) value for an entity must match these conditions. The Z-Wave Value must match these conditions.
Use the Z-Wave specifications to find out the values for these parameters: Use the Z-Wave specifications to find out the values for these parameters:
https://github.com/zwave-js/node-zwave-js/tree/master/specs https://github.com/zwave-js/node-zwave-js/tree/master/specs
""" """
# specify the hass platform for which this scheme applies (e.g. light, sensor)
platform: str
# [optional] hint for platform
hint: Optional[str] = None
# [optional] the node's basic device class must match ANY of these values
device_class_basic: Optional[Set[str]] = None
# [optional] the node's generic device class must match ANY of these values
device_class_generic: Optional[Set[str]] = None
# [optional] the node's specific device class must match ANY of these values
device_class_specific: Optional[Set[str]] = None
# [optional] the value's command class must match ANY of these values # [optional] the value's command class must match ANY of these values
command_class: Optional[Set[int]] = None command_class: Optional[Set[int]] = None
# [optional] the value's endpoint must match ANY of these values # [optional] the value's endpoint must match ANY of these values
@ -56,9 +48,121 @@ class ZWaveDiscoverySchema:
type: Optional[Set[str]] = None type: Optional[Set[str]] = None
@dataclass
class ZWaveDiscoverySchema:
"""Z-Wave discovery schema.
The Z-Wave node and it's (primary) value for an entity must match these conditions.
Use the Z-Wave specifications to find out the values for these parameters:
https://github.com/zwave-js/node-zwave-js/tree/master/specs
"""
# specify the hass platform for which this scheme applies (e.g. light, sensor)
platform: str
# primary value belonging to this discovery scheme
primary_value: ZWaveValueDiscoverySchema
# [optional] hint for platform
hint: Optional[str] = None
# [optional] the node's manufacturer_id must match ANY of these values
manufacturer_id: Optional[Set[int]] = None
# [optional] the node's product_id must match ANY of these values
product_id: Optional[Set[int]] = None
# [optional] the node's product_type must match ANY of these values
product_type: Optional[Set[int]] = None
# [optional] the node's firmware_version must match ANY of these values
firmware_version: Optional[Set[str]] = None
# [optional] the node's basic device class must match ANY of these values
device_class_basic: Optional[Set[str]] = None
# [optional] the node's generic device class must match ANY of these values
device_class_generic: Optional[Set[str]] = None
# [optional] the node's specific device class must match ANY of these values
device_class_specific: Optional[Set[str]] = None
# [optional] additional values that ALL need to be present on the node for this scheme to pass
required_values: Optional[Set[ZWaveValueDiscoverySchema]] = None
# [optional] bool to specify if this primary value may be discovered by multiple platforms
allow_multi: bool = False
# For device class mapping see: # For device class mapping see:
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json
DISCOVERY_SCHEMAS = [ DISCOVERY_SCHEMAS = [
# ====== START OF DEVICE SPECIFIC MAPPING SCHEMAS =======
# Honeywell 39358 In-Wall Fan Control using switch multilevel CC
ZWaveDiscoverySchema(
platform="fan",
manufacturer_id={0x0039},
product_id={0x3131},
product_type={0x4944},
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_MULTILEVEL},
property={"currentValue"},
type={"number"},
),
),
# GE/Jasco fan controllers using switch multilevel CC
ZWaveDiscoverySchema(
platform="fan",
manufacturer_id={0x0063},
product_id={0x3034, 0x3131, 0x3138},
product_type={0x4944},
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_MULTILEVEL},
property={"currentValue"},
type={"number"},
),
),
# Leviton ZW4SF fan controllers using switch multilevel CC
ZWaveDiscoverySchema(
platform="fan",
manufacturer_id={0x001D},
product_id={0x0002},
product_type={0x0038},
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_MULTILEVEL},
property={"currentValue"},
type={"number"},
),
),
# Fibaro Shutter Fibaro FGS222
ZWaveDiscoverySchema(
platform="cover",
hint="fibaro_fgs222",
manufacturer_id={0x010F},
product_id={0x1000},
product_type={0x0302},
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_MULTILEVEL},
property={"currentValue"},
type={"number"},
),
),
# Qubino flush shutter
ZWaveDiscoverySchema(
platform="cover",
hint="fibaro_fgs222",
manufacturer_id={0x0159},
product_id={0x0052},
product_type={0x0003},
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_MULTILEVEL},
property={"currentValue"},
type={"number"},
),
),
# Graber/Bali/Spring Fashion Covers
ZWaveDiscoverySchema(
platform="cover",
hint="fibaro_fgs222",
manufacturer_id={0x026E},
product_id={0x5A31},
product_type={0x4353},
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_MULTILEVEL},
property={"currentValue"},
type={"number"},
),
),
# ====== START OF GENERIC MAPPING SCHEMAS =======
# locks # locks
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="lock", platform="lock",
@ -69,12 +173,14 @@ DISCOVERY_SCHEMAS = [
"Secure Keypad Door Lock", "Secure Keypad Door Lock",
"Secure Lockbox", "Secure Lockbox",
}, },
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.LOCK, command_class={
CommandClass.DOOR_LOCK, CommandClass.LOCK,
}, CommandClass.DOOR_LOCK,
property={"currentMode", "locked"}, },
type={"number", "boolean"}, property={"currentMode", "locked"},
type={"number", "boolean"},
),
), ),
# door lock door status # door lock door status
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
@ -87,12 +193,14 @@ DISCOVERY_SCHEMAS = [
"Secure Keypad Door Lock", "Secure Keypad Door Lock",
"Secure Lockbox", "Secure Lockbox",
}, },
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.LOCK, command_class={
CommandClass.DOOR_LOCK, CommandClass.LOCK,
}, CommandClass.DOOR_LOCK,
property={"doorStatus"}, },
type={"any"}, property={"doorStatus"},
type={"any"},
),
), ),
# climate # climate
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
@ -102,10 +210,14 @@ DISCOVERY_SCHEMAS = [
"Setback Thermostat", "Setback Thermostat",
"Thermostat General", "Thermostat General",
"Thermostat General V2", "Thermostat General V2",
"General Thermostat",
"General Thermostat V2",
}, },
command_class={CommandClass.THERMOSTAT_MODE}, primary_value=ZWaveValueDiscoverySchema(
property={"mode"}, command_class={CommandClass.THERMOSTAT_MODE},
type={"number"}, property={"mode"},
type={"number"},
),
), ),
# climate # climate
# setpoint thermostats # setpoint thermostats
@ -115,9 +227,11 @@ DISCOVERY_SCHEMAS = [
device_class_specific={ device_class_specific={
"Setpoint Thermostat", "Setpoint Thermostat",
}, },
command_class={CommandClass.THERMOSTAT_SETPOINT}, primary_value=ZWaveValueDiscoverySchema(
property={"setpoint"}, command_class={CommandClass.THERMOSTAT_SETPOINT},
type={"number"}, property={"setpoint"},
type={"number"},
),
), ),
# lights # lights
# primary value is the currentValue (brightness) # primary value is the currentValue (brightness)
@ -132,85 +246,104 @@ DISCOVERY_SCHEMAS = [
"Multilevel Scene Switch", "Multilevel Scene Switch",
"Unused", "Unused",
}, },
command_class={CommandClass.SWITCH_MULTILEVEL}, primary_value=ZWaveValueDiscoverySchema(
property={"currentValue"}, command_class={CommandClass.SWITCH_MULTILEVEL},
type={"number"}, property={"currentValue"},
type={"number"},
),
), ),
# binary sensors # binary sensors
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="binary_sensor", platform="binary_sensor",
hint="boolean", hint="boolean",
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.SENSOR_BINARY, command_class={
CommandClass.BATTERY, CommandClass.SENSOR_BINARY,
CommandClass.SENSOR_ALARM, CommandClass.BATTERY,
}, CommandClass.SENSOR_ALARM,
type={"boolean"}, },
type={"boolean"},
),
), ),
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="binary_sensor", platform="binary_sensor",
hint="notification", hint="notification",
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.NOTIFICATION, command_class={
}, CommandClass.NOTIFICATION,
type={"number"}, },
type={"number"},
),
allow_multi=True,
), ),
# generic text sensors # generic text sensors
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="sensor", platform="sensor",
hint="string_sensor", hint="string_sensor",
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.SENSOR_ALARM, command_class={
CommandClass.INDICATOR, CommandClass.SENSOR_ALARM,
}, CommandClass.INDICATOR,
type={"string"}, },
type={"string"},
),
), ),
# generic numeric sensors # generic numeric sensors
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="sensor", platform="sensor",
hint="numeric_sensor", hint="numeric_sensor",
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.SENSOR_MULTILEVEL, command_class={
CommandClass.SENSOR_ALARM, CommandClass.SENSOR_MULTILEVEL,
CommandClass.INDICATOR, CommandClass.SENSOR_ALARM,
CommandClass.BATTERY, CommandClass.INDICATOR,
}, CommandClass.BATTERY,
type={"number"}, },
type={"number"},
),
), ),
# numeric sensors for Meter CC # numeric sensors for Meter CC
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="sensor", platform="sensor",
hint="numeric_sensor", hint="numeric_sensor",
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.METER, command_class={
}, CommandClass.METER,
type={"number"}, },
property={"value"}, type={"number"},
property={"value"},
),
), ),
# special list sensors (Notification CC) # special list sensors (Notification CC)
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="sensor", platform="sensor",
hint="list_sensor", hint="list_sensor",
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.NOTIFICATION, command_class={
}, CommandClass.NOTIFICATION,
type={"number"}, },
type={"number"},
),
allow_multi=True,
), ),
# sensor for basic CC # sensor for basic CC
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="sensor", platform="sensor",
hint="numeric_sensor", hint="numeric_sensor",
command_class={ primary_value=ZWaveValueDiscoverySchema(
CommandClass.BASIC, command_class={
}, CommandClass.BASIC,
type={"number"}, },
property={"currentValue"}, type={"number"},
property={"currentValue"},
),
), ),
# binary switches # binary switches
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="switch", platform="switch",
command_class={CommandClass.SWITCH_BINARY}, primary_value=ZWaveValueDiscoverySchema(
property={"currentValue"}, command_class={CommandClass.SWITCH_BINARY}, property={"currentValue"}
),
), ),
# cover # cover
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
@ -223,9 +356,11 @@ DISCOVERY_SCHEMAS = [
"Motor Control Class C", "Motor Control Class C",
"Multiposition Motor", "Multiposition Motor",
}, },
command_class={CommandClass.SWITCH_MULTILEVEL}, primary_value=ZWaveValueDiscoverySchema(
property={"currentValue"}, command_class={CommandClass.SWITCH_MULTILEVEL},
type={"number"}, property={"currentValue"},
type={"number"},
),
), ),
# fan # fan
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
@ -233,9 +368,11 @@ DISCOVERY_SCHEMAS = [
hint="fan", hint="fan",
device_class_generic={"Multilevel Switch"}, device_class_generic={"Multilevel Switch"},
device_class_specific={"Fan Switch"}, device_class_specific={"Fan Switch"},
command_class={CommandClass.SWITCH_MULTILEVEL}, primary_value=ZWaveValueDiscoverySchema(
property={"currentValue"}, command_class={CommandClass.SWITCH_MULTILEVEL},
type={"number"}, property={"currentValue"},
type={"number"},
),
), ),
] ]
@ -243,8 +380,33 @@ DISCOVERY_SCHEMAS = [
@callback @callback
def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]: def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]:
"""Run discovery on ZWave node and return matching (primary) values.""" """Run discovery on ZWave node and return matching (primary) values."""
# pylint: disable=too-many-nested-blocks
for value in node.values.values(): for value in node.values.values():
for schema in DISCOVERY_SCHEMAS: for schema in DISCOVERY_SCHEMAS:
# check manufacturer_id
if (
schema.manufacturer_id is not None
and value.node.manufacturer_id not in schema.manufacturer_id
):
continue
# check product_id
if (
schema.product_id is not None
and value.node.product_id not in schema.product_id
):
continue
# check product_type
if (
schema.product_type is not None
and value.node.product_type not in schema.product_type
):
continue
# check firmware_version
if (
schema.firmware_version is not None
and value.node.firmware_version not in schema.firmware_version
):
continue
# check device_class_basic # check device_class_basic
if ( if (
schema.device_class_basic is not None schema.device_class_basic is not None
@ -263,21 +425,19 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None
and value.node.device_class.specific not in schema.device_class_specific and value.node.device_class.specific not in schema.device_class_specific
): ):
continue continue
# check command_class # check primary value
if ( if not check_value(value, schema.primary_value):
schema.command_class is not None
and value.command_class not in schema.command_class
):
continue
# check endpoint
if schema.endpoint is not None and value.endpoint not in schema.endpoint:
continue
# check property
if schema.property is not None and value.property_ not in schema.property:
continue
# check metadata_type
if schema.type is not None and value.metadata.type not in schema.type:
continue continue
# check additional required values
if schema.required_values is not None:
required_values_present = True
for val_scheme in schema.required_values:
for val in node.values.values():
if not check_value(val, val_scheme):
required_values_present = False
break
if not required_values_present:
continue
# all checks passed, this value belongs to an entity # all checks passed, this value belongs to an entity
yield ZwaveDiscoveryInfo( yield ZwaveDiscoveryInfo(
node=value.node, node=value.node,
@ -285,3 +445,27 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None
platform=schema.platform, platform=schema.platform,
platform_hint=schema.hint, platform_hint=schema.hint,
) )
if not schema.allow_multi:
# break out of loop, this value may not be discovered by other schemas/platforms
break
@callback
def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool:
"""Check if value matches scheme."""
# check command_class
if (
schema.command_class is not None
and value.command_class not in schema.command_class
):
return False
# check endpoint
if schema.endpoint is not None and value.endpoint not in schema.endpoint:
return False
# check property
if schema.property is not None and value.property_ not in schema.property:
return False
# check metadata_type
if schema.type is not None and value.metadata.type not in schema.type:
return False
return True