mirror of
https://github.com/home-assistant/core.git
synced 2025-09-10 15:21:38 +02:00
Refactor zwave_js discovery schema foundation (#151146)
This commit is contained in:
@@ -115,11 +115,7 @@ from .const import (
|
|||||||
ZWAVE_JS_VALUE_NOTIFICATION_EVENT,
|
ZWAVE_JS_VALUE_NOTIFICATION_EVENT,
|
||||||
ZWAVE_JS_VALUE_UPDATED_EVENT,
|
ZWAVE_JS_VALUE_UPDATED_EVENT,
|
||||||
)
|
)
|
||||||
from .discovery import (
|
from .discovery import async_discover_node_values, async_discover_single_value
|
||||||
ZwaveDiscoveryInfo,
|
|
||||||
async_discover_node_values,
|
|
||||||
async_discover_single_value,
|
|
||||||
)
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
async_disable_server_logging_if_needed,
|
async_disable_server_logging_if_needed,
|
||||||
async_enable_server_logging_if_needed,
|
async_enable_server_logging_if_needed,
|
||||||
@@ -131,7 +127,7 @@ from .helpers import (
|
|||||||
get_valueless_base_unique_id,
|
get_valueless_base_unique_id,
|
||||||
)
|
)
|
||||||
from .migrate import async_migrate_discovered_value
|
from .migrate import async_migrate_discovered_value
|
||||||
from .models import ZwaveJSConfigEntry, ZwaveJSData
|
from .models import PlatformZwaveDiscoveryInfo, ZwaveJSConfigEntry, ZwaveJSData
|
||||||
from .services import async_setup_services
|
from .services import async_setup_services
|
||||||
|
|
||||||
CONNECT_TIMEOUT = 10
|
CONNECT_TIMEOUT = 10
|
||||||
@@ -776,7 +772,7 @@ class NodeEvents:
|
|||||||
# Remove any old value ids if this is a reinterview.
|
# Remove any old value ids if this is a reinterview.
|
||||||
self.controller_events.discovered_value_ids.pop(device.id, None)
|
self.controller_events.discovered_value_ids.pop(device.id, None)
|
||||||
|
|
||||||
value_updates_disc_info: dict[str, ZwaveDiscoveryInfo] = {}
|
value_updates_disc_info: dict[str, PlatformZwaveDiscoveryInfo] = {}
|
||||||
|
|
||||||
# run discovery on all node values and create/update entities
|
# run discovery on all node values and create/update entities
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
@@ -858,8 +854,8 @@ class NodeEvents:
|
|||||||
async def async_handle_discovery_info(
|
async def async_handle_discovery_info(
|
||||||
self,
|
self,
|
||||||
device: dr.DeviceEntry,
|
device: dr.DeviceEntry,
|
||||||
disc_info: ZwaveDiscoveryInfo,
|
disc_info: PlatformZwaveDiscoveryInfo,
|
||||||
value_updates_disc_info: dict[str, ZwaveDiscoveryInfo],
|
value_updates_disc_info: dict[str, PlatformZwaveDiscoveryInfo],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle discovery info and all dependent tasks."""
|
"""Handle discovery info and all dependent tasks."""
|
||||||
platform = disc_info.platform
|
platform = disc_info.platform
|
||||||
@@ -901,7 +897,9 @@ class NodeEvents:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def async_on_value_added(
|
async def async_on_value_added(
|
||||||
self, value_updates_disc_info: dict[str, ZwaveDiscoveryInfo], value: Value
|
self,
|
||||||
|
value_updates_disc_info: dict[str, PlatformZwaveDiscoveryInfo],
|
||||||
|
value: Value,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Fire value updated event."""
|
"""Fire value updated event."""
|
||||||
# If node isn't ready or a device for this node doesn't already exist, we can
|
# If node isn't ready or a device for this node doesn't already exist, we can
|
||||||
@@ -1036,7 +1034,9 @@ class NodeEvents:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_on_value_updated_fire_event(
|
def async_on_value_updated_fire_event(
|
||||||
self, value_updates_disc_info: dict[str, ZwaveDiscoveryInfo], value: Value
|
self,
|
||||||
|
value_updates_disc_info: dict[str, PlatformZwaveDiscoveryInfo],
|
||||||
|
value: Value,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Fire value updated event."""
|
"""Fire value updated event."""
|
||||||
# Get the discovery info for the value that was updated. If there is
|
# Get the discovery info for the value that was updated. If there is
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from zwave_js_server.const import CommandClass
|
from zwave_js_server.const import CommandClass
|
||||||
from zwave_js_server.const.command_class.lock import DOOR_STATUS_PROPERTY
|
from zwave_js_server.const.command_class.lock import DOOR_STATUS_PROPERTY
|
||||||
@@ -17,15 +17,21 @@ from homeassistant.components.binary_sensor import (
|
|||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
BinarySensorEntityDescription,
|
BinarySensorEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory, Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .discovery import ZwaveDiscoveryInfo
|
from .entity import NewZwaveDiscoveryInfo, ZWaveBaseEntity
|
||||||
from .entity import ZWaveBaseEntity
|
from .models import (
|
||||||
from .models import ZwaveJSConfigEntry
|
NewZWaveDiscoverySchema,
|
||||||
|
ValueType,
|
||||||
|
ZwaveDiscoveryInfo,
|
||||||
|
ZwaveJSConfigEntry,
|
||||||
|
ZWaveValueDiscoverySchema,
|
||||||
|
)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
@@ -50,11 +56,11 @@ NOTIFICATION_IRRIGATION = "17"
|
|||||||
NOTIFICATION_GAS = "18"
|
NOTIFICATION_GAS = "18"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class NotificationZWaveJSEntityDescription(BinarySensorEntityDescription):
|
class NotificationZWaveJSEntityDescription(BinarySensorEntityDescription):
|
||||||
"""Represent a Z-Wave JS binary sensor entity description."""
|
"""Represent a Z-Wave JS binary sensor entity description."""
|
||||||
|
|
||||||
off_state: str = "0"
|
not_states: set[str] = field(default_factory=lambda: {"0"})
|
||||||
states: tuple[str, ...] | None = None
|
states: tuple[str, ...] | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -65,6 +71,13 @@ class PropertyZWaveJSEntityDescription(BinarySensorEntityDescription):
|
|||||||
on_states: tuple[str, ...]
|
on_states: tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class NewNotificationZWaveJSEntityDescription(BinarySensorEntityDescription):
|
||||||
|
"""Represent a Z-Wave JS binary sensor entity description."""
|
||||||
|
|
||||||
|
state_key: str
|
||||||
|
|
||||||
|
|
||||||
# Mappings for Notification sensors
|
# Mappings for Notification sensors
|
||||||
# https://github.com/zwave-js/specs/blob/master/Registries/Notification%20Command%20Class%2C%20list%20of%20assigned%20Notifications.xlsx
|
# https://github.com/zwave-js/specs/blob/master/Registries/Notification%20Command%20Class%2C%20list%20of%20assigned%20Notifications.xlsx
|
||||||
#
|
#
|
||||||
@@ -106,24 +119,6 @@ class PropertyZWaveJSEntityDescription(BinarySensorEntityDescription):
|
|||||||
# - Sump pump failure
|
# - Sump pump failure
|
||||||
|
|
||||||
NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] = (
|
NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] = (
|
||||||
NotificationZWaveJSEntityDescription(
|
|
||||||
# NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected
|
|
||||||
key=NOTIFICATION_SMOKE_ALARM,
|
|
||||||
states=("1", "2"),
|
|
||||||
device_class=BinarySensorDeviceClass.SMOKE,
|
|
||||||
),
|
|
||||||
NotificationZWaveJSEntityDescription(
|
|
||||||
# NotificationType 1: Smoke Alarm - State Id's 4, 5, 7, 8
|
|
||||||
key=NOTIFICATION_SMOKE_ALARM,
|
|
||||||
states=("4", "5", "7", "8"),
|
|
||||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
|
||||||
),
|
|
||||||
NotificationZWaveJSEntityDescription(
|
|
||||||
# NotificationType 1: Smoke Alarm - All other State Id's
|
|
||||||
key=NOTIFICATION_SMOKE_ALARM,
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
|
||||||
),
|
|
||||||
NotificationZWaveJSEntityDescription(
|
NotificationZWaveJSEntityDescription(
|
||||||
# NotificationType 2: Carbon Monoxide - State Id's 1 and 2
|
# NotificationType 2: Carbon Monoxide - State Id's 1 and 2
|
||||||
key=NOTIFICATION_CARBON_MONOOXIDE,
|
key=NOTIFICATION_CARBON_MONOOXIDE,
|
||||||
@@ -212,8 +207,8 @@ NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] =
|
|||||||
NotificationZWaveJSEntityDescription(
|
NotificationZWaveJSEntityDescription(
|
||||||
# NotificationType 6: Access Control - State Id 22 (door/window open)
|
# NotificationType 6: Access Control - State Id 22 (door/window open)
|
||||||
key=NOTIFICATION_ACCESS_CONTROL,
|
key=NOTIFICATION_ACCESS_CONTROL,
|
||||||
off_state="23",
|
not_states={"23"},
|
||||||
states=("22", "23"),
|
states=("22",),
|
||||||
device_class=BinarySensorDeviceClass.DOOR,
|
device_class=BinarySensorDeviceClass.DOOR,
|
||||||
),
|
),
|
||||||
NotificationZWaveJSEntityDescription(
|
NotificationZWaveJSEntityDescription(
|
||||||
@@ -245,8 +240,8 @@ NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] =
|
|||||||
# NotificationType 8: Power Management -
|
# NotificationType 8: Power Management -
|
||||||
# State Id's 2, 3 (Mains status)
|
# State Id's 2, 3 (Mains status)
|
||||||
key=NOTIFICATION_POWER_MANAGEMENT,
|
key=NOTIFICATION_POWER_MANAGEMENT,
|
||||||
off_state="2",
|
not_states={"2"},
|
||||||
states=("2", "3"),
|
states=("3",),
|
||||||
device_class=BinarySensorDeviceClass.PLUG,
|
device_class=BinarySensorDeviceClass.PLUG,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
),
|
),
|
||||||
@@ -353,7 +348,7 @@ BOOLEAN_SENSOR_MAPPINGS: dict[tuple[int, int | str], BinarySensorEntityDescripti
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def is_valid_notification_binary_sensor(
|
def is_valid_notification_binary_sensor(
|
||||||
info: ZwaveDiscoveryInfo,
|
info: ZwaveDiscoveryInfo | NewZwaveDiscoveryInfo,
|
||||||
) -> bool | NotificationZWaveJSEntityDescription:
|
) -> bool | NotificationZWaveJSEntityDescription:
|
||||||
"""Return if the notification CC Value is valid as binary sensor."""
|
"""Return if the notification CC Value is valid as binary sensor."""
|
||||||
if not info.primary_value.metadata.states:
|
if not info.primary_value.metadata.states:
|
||||||
@@ -370,13 +365,36 @@ async def async_setup_entry(
|
|||||||
client = config_entry.runtime_data.client
|
client = config_entry.runtime_data.client
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None:
|
def async_add_binary_sensor(
|
||||||
|
info: ZwaveDiscoveryInfo | NewZwaveDiscoveryInfo,
|
||||||
|
) -> None:
|
||||||
"""Add Z-Wave Binary Sensor."""
|
"""Add Z-Wave Binary Sensor."""
|
||||||
driver = client.driver
|
driver = client.driver
|
||||||
assert driver is not None # Driver is ready before platforms are loaded.
|
assert driver is not None # Driver is ready before platforms are loaded.
|
||||||
entities: list[BinarySensorEntity] = []
|
entities: list[Entity] = []
|
||||||
|
|
||||||
if info.platform_hint == "notification":
|
if (
|
||||||
|
isinstance(info, NewZwaveDiscoveryInfo)
|
||||||
|
and info.entity_class is ZWaveNotificationBinarySensor
|
||||||
|
and isinstance(
|
||||||
|
info.entity_description, NotificationZWaveJSEntityDescription
|
||||||
|
)
|
||||||
|
and is_valid_notification_binary_sensor(info)
|
||||||
|
):
|
||||||
|
entities.extend(
|
||||||
|
ZWaveNotificationBinarySensor(
|
||||||
|
config_entry, driver, info, state_key, info.entity_description
|
||||||
|
)
|
||||||
|
for state_key in info.primary_value.metadata.states
|
||||||
|
if state_key not in info.entity_description.not_states
|
||||||
|
and (
|
||||||
|
not info.entity_description.states
|
||||||
|
or state_key in info.entity_description.states
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(info, NewZwaveDiscoveryInfo):
|
||||||
|
pass # other entity classes are not migrated yet
|
||||||
|
elif info.platform_hint == "notification":
|
||||||
# ensure the notification CC Value is valid as binary sensor
|
# ensure the notification CC Value is valid as binary sensor
|
||||||
if not is_valid_notification_binary_sensor(info):
|
if not is_valid_notification_binary_sensor(info):
|
||||||
return
|
return
|
||||||
@@ -401,7 +419,7 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
notification_description
|
notification_description
|
||||||
and notification_description.off_state == state_key
|
and state_key in notification_description.not_states
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
entities.append(
|
entities.append(
|
||||||
@@ -477,7 +495,7 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
|
|||||||
self,
|
self,
|
||||||
config_entry: ZwaveJSConfigEntry,
|
config_entry: ZwaveJSConfigEntry,
|
||||||
driver: Driver,
|
driver: Driver,
|
||||||
info: ZwaveDiscoveryInfo,
|
info: ZwaveDiscoveryInfo | NewZwaveDiscoveryInfo,
|
||||||
state_key: str,
|
state_key: str,
|
||||||
description: NotificationZWaveJSEntityDescription | None = None,
|
description: NotificationZWaveJSEntityDescription | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -543,3 +561,71 @@ class ZWaveConfigParameterBinarySensor(ZWaveBooleanBinarySensor):
|
|||||||
alternate_value_name=self.info.primary_value.property_name,
|
alternate_value_name=self.info.primary_value.property_name,
|
||||||
additional_info=[property_key_name] if property_key_name else None,
|
additional_info=[property_key_name] if property_key_name else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
DISCOVERY_SCHEMAS: list[NewZWaveDiscoverySchema] = [
|
||||||
|
NewZWaveDiscoverySchema(
|
||||||
|
platform=Platform.BINARY_SENSOR,
|
||||||
|
primary_value=ZWaveValueDiscoverySchema(
|
||||||
|
command_class={
|
||||||
|
CommandClass.NOTIFICATION,
|
||||||
|
},
|
||||||
|
type={ValueType.NUMBER},
|
||||||
|
any_available_states_keys={1, 2},
|
||||||
|
any_available_cc_specific={(CC_SPECIFIC_NOTIFICATION_TYPE, 1)},
|
||||||
|
),
|
||||||
|
allow_multi=True,
|
||||||
|
entity_description=NotificationZWaveJSEntityDescription(
|
||||||
|
# NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected
|
||||||
|
key=NOTIFICATION_SMOKE_ALARM,
|
||||||
|
states=("1", "2"),
|
||||||
|
device_class=BinarySensorDeviceClass.SMOKE,
|
||||||
|
),
|
||||||
|
entity_class=ZWaveNotificationBinarySensor,
|
||||||
|
),
|
||||||
|
NewZWaveDiscoverySchema(
|
||||||
|
platform=Platform.BINARY_SENSOR,
|
||||||
|
primary_value=ZWaveValueDiscoverySchema(
|
||||||
|
command_class={
|
||||||
|
CommandClass.NOTIFICATION,
|
||||||
|
},
|
||||||
|
type={ValueType.NUMBER},
|
||||||
|
any_available_states_keys={4, 5, 7, 8},
|
||||||
|
any_available_cc_specific={(CC_SPECIFIC_NOTIFICATION_TYPE, 1)},
|
||||||
|
),
|
||||||
|
allow_multi=True,
|
||||||
|
entity_description=NotificationZWaveJSEntityDescription(
|
||||||
|
# NotificationType 1: Smoke Alarm - State Id's 4, 5, 7, 8
|
||||||
|
key=NOTIFICATION_SMOKE_ALARM,
|
||||||
|
states=("4", "5", "7", "8"),
|
||||||
|
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
entity_class=ZWaveNotificationBinarySensor,
|
||||||
|
),
|
||||||
|
NewZWaveDiscoverySchema(
|
||||||
|
platform=Platform.BINARY_SENSOR,
|
||||||
|
primary_value=ZWaveValueDiscoverySchema(
|
||||||
|
command_class={
|
||||||
|
CommandClass.NOTIFICATION,
|
||||||
|
},
|
||||||
|
type={ValueType.NUMBER},
|
||||||
|
any_available_cc_specific={(CC_SPECIFIC_NOTIFICATION_TYPE, 1)},
|
||||||
|
),
|
||||||
|
allow_multi=True,
|
||||||
|
entity_description=NotificationZWaveJSEntityDescription(
|
||||||
|
# NotificationType 1: Smoke Alarm - All other State Id's
|
||||||
|
key=NOTIFICATION_SMOKE_ALARM,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
not_states={
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
entity_class=ZWaveNotificationBinarySensor,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
@@ -299,11 +299,23 @@ class ZWaveMultilevelSwitchCover(CoverPositionMixin):
|
|||||||
|
|
||||||
# Entity class attributes
|
# Entity class attributes
|
||||||
self._attr_device_class = CoverDeviceClass.WINDOW
|
self._attr_device_class = CoverDeviceClass.WINDOW
|
||||||
if self.info.platform_hint and self.info.platform_hint.startswith("shutter"):
|
if (
|
||||||
|
isinstance(self.info, ZwaveDiscoveryInfo)
|
||||||
|
and self.info.platform_hint
|
||||||
|
and self.info.platform_hint.startswith("shutter")
|
||||||
|
):
|
||||||
self._attr_device_class = CoverDeviceClass.SHUTTER
|
self._attr_device_class = CoverDeviceClass.SHUTTER
|
||||||
elif self.info.platform_hint and self.info.platform_hint.startswith("blind"):
|
elif (
|
||||||
|
isinstance(self.info, ZwaveDiscoveryInfo)
|
||||||
|
and self.info.platform_hint
|
||||||
|
and self.info.platform_hint.startswith("blind")
|
||||||
|
):
|
||||||
self._attr_device_class = CoverDeviceClass.BLIND
|
self._attr_device_class = CoverDeviceClass.BLIND
|
||||||
elif self.info.platform_hint and self.info.platform_hint.startswith("gate"):
|
elif (
|
||||||
|
isinstance(self.info, ZwaveDiscoveryInfo)
|
||||||
|
and self.info.platform_hint
|
||||||
|
and self.info.platform_hint.startswith("gate")
|
||||||
|
):
|
||||||
self._attr_device_class = CoverDeviceClass.GATE
|
self._attr_device_class = CoverDeviceClass.GATE
|
||||||
|
|
||||||
|
|
||||||
|
@@ -3,9 +3,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import dataclass
|
||||||
from enum import StrEnum
|
from typing import cast
|
||||||
from typing import TYPE_CHECKING, Any, cast
|
|
||||||
|
|
||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
from zwave_js_server.const import (
|
from zwave_js_server.const import (
|
||||||
@@ -55,6 +54,7 @@ from homeassistant.const import EntityCategory, Platform
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntry
|
from homeassistant.helpers.device_registry import DeviceEntry
|
||||||
|
|
||||||
|
from .binary_sensor import DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS
|
||||||
from .const import COVER_POSITION_PROPERTY_KEYS, COVER_TILT_PROPERTY_KEYS, LOGGER
|
from .const import COVER_POSITION_PROPERTY_KEYS, COVER_TILT_PROPERTY_KEYS, LOGGER
|
||||||
from .discovery_data_template import (
|
from .discovery_data_template import (
|
||||||
BaseDiscoverySchemaDataTemplate,
|
BaseDiscoverySchemaDataTemplate,
|
||||||
@@ -65,108 +65,20 @@ from .discovery_data_template import (
|
|||||||
FixedFanValueMappingDataTemplate,
|
FixedFanValueMappingDataTemplate,
|
||||||
NumericSensorDataTemplate,
|
NumericSensorDataTemplate,
|
||||||
)
|
)
|
||||||
from .helpers import ZwaveValueID
|
from .entity import NewZwaveDiscoveryInfo
|
||||||
|
from .models import (
|
||||||
|
FirmwareVersionRange,
|
||||||
|
NewZWaveDiscoverySchema,
|
||||||
|
ValueType,
|
||||||
|
ZwaveDiscoveryInfo,
|
||||||
|
ZWaveValueDiscoverySchema,
|
||||||
|
ZwaveValueID,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
NEW_DISCOVERY_SCHEMAS: dict[Platform, list[NewZWaveDiscoverySchema]] = {
|
||||||
from _typeshed import DataclassInstance
|
Platform.BINARY_SENSOR: BINARY_SENSOR_SCHEMAS,
|
||||||
|
}
|
||||||
|
SUPPORTED_PLATFORMS = tuple(NEW_DISCOVERY_SCHEMAS)
|
||||||
class ValueType(StrEnum):
|
|
||||||
"""Enum with all value types."""
|
|
||||||
|
|
||||||
ANY = "any"
|
|
||||||
BOOLEAN = "boolean"
|
|
||||||
NUMBER = "number"
|
|
||||||
STRING = "string"
|
|
||||||
|
|
||||||
|
|
||||||
class DataclassMustHaveAtLeastOne:
|
|
||||||
"""A dataclass that must have at least one input parameter that is not None."""
|
|
||||||
|
|
||||||
def __post_init__(self: DataclassInstance) -> None:
|
|
||||||
"""Post dataclass initialization."""
|
|
||||||
if all(val is None for val in asdict(self).values()):
|
|
||||||
raise ValueError("At least one input parameter must not be None")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class FirmwareVersionRange(DataclassMustHaveAtLeastOne):
|
|
||||||
"""Firmware version range dictionary."""
|
|
||||||
|
|
||||||
min: str | None = None
|
|
||||||
max: str | None = None
|
|
||||||
min_ver: AwesomeVersion | None = field(default=None, init=False)
|
|
||||||
max_ver: AwesomeVersion | None = field(default=None, init=False)
|
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
|
||||||
"""Post dataclass initialization."""
|
|
||||||
super().__post_init__()
|
|
||||||
if self.min:
|
|
||||||
self.min_ver = AwesomeVersion(self.min)
|
|
||||||
if self.max:
|
|
||||||
self.max_ver = AwesomeVersion(self.max)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ZwaveDiscoveryInfo:
|
|
||||||
"""Info discovered from (primary) ZWave Value to create entity."""
|
|
||||||
|
|
||||||
# node to which the value(s) belongs
|
|
||||||
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
|
|
||||||
assumed_state: bool
|
|
||||||
# the home assistant platform for which an entity should be created
|
|
||||||
platform: Platform
|
|
||||||
# helper data to use in platform setup
|
|
||||||
platform_data: Any
|
|
||||||
# additional values that need to be watched by entity
|
|
||||||
additional_value_ids_to_watch: set[str]
|
|
||||||
# hint for the platform about this discovered entity
|
|
||||||
platform_hint: str | None = ""
|
|
||||||
# data template to use in platform logic
|
|
||||||
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
|
|
||||||
class ZWaveValueDiscoverySchema(DataclassMustHaveAtLeastOne):
|
|
||||||
"""Z-Wave Value discovery schema.
|
|
||||||
|
|
||||||
The Z-Wave Value must match these conditions.
|
|
||||||
Use the Z-Wave specifications to find out the values for these parameters:
|
|
||||||
https://github.com/zwave-js/specs/tree/master
|
|
||||||
"""
|
|
||||||
|
|
||||||
# [optional] the value's command class must match ANY of these values
|
|
||||||
command_class: set[int] | None = None
|
|
||||||
# [optional] the value's endpoint must match ANY of these values
|
|
||||||
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 = 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
|
|
||||||
# [optional] the value's value must match this value
|
|
||||||
value: Any | None = None
|
|
||||||
# [optional] the value's metadata_stateful must match this value
|
|
||||||
stateful: bool | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1316,7 +1228,7 @@ DISCOVERY_SCHEMAS = [
|
|||||||
@callback
|
@callback
|
||||||
def async_discover_node_values(
|
def async_discover_node_values(
|
||||||
node: ZwaveNode, device: DeviceEntry, discovered_value_ids: dict[str, set[str]]
|
node: ZwaveNode, device: DeviceEntry, discovered_value_ids: dict[str, set[str]]
|
||||||
) -> Generator[ZwaveDiscoveryInfo]:
|
) -> Generator[ZwaveDiscoveryInfo | NewZwaveDiscoveryInfo]:
|
||||||
"""Run discovery on ZWave node and return matching (primary) values."""
|
"""Run discovery on ZWave node and return matching (primary) values."""
|
||||||
for value in node.values.values():
|
for value in node.values.values():
|
||||||
# We don't need to rediscover an already processed value_id
|
# We don't need to rediscover an already processed value_id
|
||||||
@@ -1327,9 +1239,19 @@ def async_discover_node_values(
|
|||||||
@callback
|
@callback
|
||||||
def async_discover_single_value(
|
def async_discover_single_value(
|
||||||
value: ZwaveValue, device: DeviceEntry, discovered_value_ids: dict[str, set[str]]
|
value: ZwaveValue, device: DeviceEntry, discovered_value_ids: dict[str, set[str]]
|
||||||
) -> Generator[ZwaveDiscoveryInfo]:
|
) -> Generator[ZwaveDiscoveryInfo | NewZwaveDiscoveryInfo]:
|
||||||
"""Run discovery on a single ZWave value and return matching schema info."""
|
"""Run discovery on a single ZWave value and return matching schema info."""
|
||||||
for schema in DISCOVERY_SCHEMAS:
|
# Temporary workaround for new schemas
|
||||||
|
schemas: tuple[ZWaveDiscoverySchema | NewZWaveDiscoverySchema, ...] = (
|
||||||
|
*(
|
||||||
|
new_schema
|
||||||
|
for _schemas in NEW_DISCOVERY_SCHEMAS.values()
|
||||||
|
for new_schema in _schemas
|
||||||
|
),
|
||||||
|
*DISCOVERY_SCHEMAS,
|
||||||
|
)
|
||||||
|
|
||||||
|
for schema in schemas:
|
||||||
# abort if attribute(s) already discovered
|
# abort if attribute(s) already discovered
|
||||||
if value.value_id in discovered_value_ids[device.id]:
|
if value.value_id in discovered_value_ids[device.id]:
|
||||||
continue
|
continue
|
||||||
@@ -1458,7 +1380,25 @@ def async_discover_single_value(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# all checks passed, this value belongs to an entity
|
# all checks passed, this value belongs to an entity
|
||||||
yield ZwaveDiscoveryInfo(
|
|
||||||
|
discovery_info: ZwaveDiscoveryInfo | NewZwaveDiscoveryInfo
|
||||||
|
|
||||||
|
# Temporary workaround for new schemas
|
||||||
|
if isinstance(schema, NewZWaveDiscoverySchema):
|
||||||
|
discovery_info = NewZwaveDiscoveryInfo(
|
||||||
|
node=value.node,
|
||||||
|
primary_value=value,
|
||||||
|
assumed_state=schema.assumed_state,
|
||||||
|
platform=schema.platform,
|
||||||
|
platform_data_template=schema.data_template,
|
||||||
|
platform_data=resolved_data,
|
||||||
|
additional_value_ids_to_watch=additional_value_ids_to_watch,
|
||||||
|
entity_class=schema.entity_class,
|
||||||
|
entity_description=schema.entity_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
discovery_info = ZwaveDiscoveryInfo(
|
||||||
node=value.node,
|
node=value.node,
|
||||||
primary_value=value,
|
primary_value=value,
|
||||||
assumed_state=schema.assumed_state,
|
assumed_state=schema.assumed_state,
|
||||||
@@ -1471,6 +1411,8 @@ def async_discover_single_value(
|
|||||||
entity_category=schema.entity_category,
|
entity_category=schema.entity_category,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield discovery_info
|
||||||
|
|
||||||
# prevent re-discovery of the (primary) value if not allowed
|
# prevent re-discovery of the (primary) value if not allowed
|
||||||
if not schema.allow_multi:
|
if not schema.allow_multi:
|
||||||
discovered_value_ids[device.id].add(value.value_id)
|
discovered_value_ids[device.id].add(value.value_id)
|
||||||
@@ -1615,6 +1557,25 @@ def check_value(
|
|||||||
)
|
)
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
if (
|
||||||
|
schema.any_available_states_keys is not None
|
||||||
|
and value.metadata.states is not None
|
||||||
|
and not any(
|
||||||
|
str(key) in value.metadata.states
|
||||||
|
for key in schema.any_available_states_keys
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
# check available cc specific
|
||||||
|
if (
|
||||||
|
schema.any_available_cc_specific is not None
|
||||||
|
and value.metadata.cc_specific is not None
|
||||||
|
and not any(
|
||||||
|
key in value.metadata.cc_specific and value.metadata.cc_specific[key] == val
|
||||||
|
for key, val in schema.any_available_cc_specific
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return False
|
||||||
# check value
|
# check value
|
||||||
if schema.value is not None and value.value not in schema.value:
|
if schema.value is not None and value.value not in schema.value:
|
||||||
return False
|
return False
|
||||||
|
@@ -90,11 +90,9 @@ from zwave_js_server.const.command_class.multilevel_sensor import (
|
|||||||
MultilevelSensorType,
|
MultilevelSensorType,
|
||||||
)
|
)
|
||||||
from zwave_js_server.exceptions import UnknownValueData
|
from zwave_js_server.exceptions import UnknownValueData
|
||||||
from zwave_js_server.model.node import Node as ZwaveNode
|
|
||||||
from zwave_js_server.model.value import (
|
from zwave_js_server.model.value import (
|
||||||
ConfigurationValue as ZwaveConfigurationValue,
|
ConfigurationValue as ZwaveConfigurationValue,
|
||||||
Value as ZwaveValue,
|
Value as ZwaveValue,
|
||||||
get_value_id_str,
|
|
||||||
)
|
)
|
||||||
from zwave_js_server.util.command_class.energy_production import (
|
from zwave_js_server.util.command_class.energy_production import (
|
||||||
get_energy_production_parameter,
|
get_energy_production_parameter,
|
||||||
@@ -159,7 +157,7 @@ from .const import (
|
|||||||
ENTITY_DESC_KEY_UV_INDEX,
|
ENTITY_DESC_KEY_UV_INDEX,
|
||||||
ENTITY_DESC_KEY_VOLTAGE,
|
ENTITY_DESC_KEY_VOLTAGE,
|
||||||
)
|
)
|
||||||
from .helpers import ZwaveValueID
|
from .models import BaseDiscoverySchemaDataTemplate, ZwaveValueID
|
||||||
|
|
||||||
ENERGY_PRODUCTION_DEVICE_CLASS_MAP: dict[str, list[EnergyProductionParameter]] = {
|
ENERGY_PRODUCTION_DEVICE_CLASS_MAP: dict[str, list[EnergyProductionParameter]] = {
|
||||||
ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME: [EnergyProductionParameter.TOTAL_TIME],
|
ENTITY_DESC_KEY_ENERGY_PRODUCTION_TIME: [EnergyProductionParameter.TOTAL_TIME],
|
||||||
@@ -264,49 +262,6 @@ MULTILEVEL_SENSOR_UNIT_MAP: dict[str, list[MultilevelSensorScaleType]] = {
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class BaseDiscoverySchemaDataTemplate:
|
|
||||||
"""Base class for discovery schema data templates."""
|
|
||||||
|
|
||||||
static_data: Any | None = None
|
|
||||||
|
|
||||||
def resolve_data(self, value: ZwaveValue) -> Any:
|
|
||||||
"""Resolve helper class data for a discovered value.
|
|
||||||
|
|
||||||
Can optionally be implemented by subclasses if input data needs to be
|
|
||||||
transformed once discovered Value is available.
|
|
||||||
"""
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def values_to_watch(self, resolved_data: Any) -> Iterable[ZwaveValue | None]:
|
|
||||||
"""Return list of all ZwaveValues resolved by helper that should be watched.
|
|
||||||
|
|
||||||
Should be implemented by subclasses only if there are values to watch.
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
def value_ids_to_watch(self, resolved_data: Any) -> set[str]:
|
|
||||||
"""Return list of all Value IDs resolved by helper that should be watched.
|
|
||||||
|
|
||||||
Not to be overwritten by subclasses.
|
|
||||||
"""
|
|
||||||
return {val.value_id for val in self.values_to_watch(resolved_data) if val}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_value_from_id(
|
|
||||||
node: ZwaveNode, value_id_obj: ZwaveValueID
|
|
||||||
) -> ZwaveValue | ZwaveConfigurationValue | None:
|
|
||||||
"""Get a ZwaveValue from a node using a ZwaveValueDict."""
|
|
||||||
value_id = get_value_id_str(
|
|
||||||
node,
|
|
||||||
value_id_obj.command_class,
|
|
||||||
value_id_obj.property_,
|
|
||||||
endpoint=value_id_obj.endpoint,
|
|
||||||
property_key=value_id_obj.property_key,
|
|
||||||
)
|
|
||||||
return node.values.get(value_id)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DynamicCurrentTempClimateDataTemplate(BaseDiscoverySchemaDataTemplate):
|
class DynamicCurrentTempClimateDataTemplate(BaseDiscoverySchemaDataTemplate):
|
||||||
"""Data template class for Z-Wave JS Climate entities with dynamic current temps."""
|
"""Data template class for Z-Wave JS Climate entities with dynamic current temps."""
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
||||||
@@ -18,16 +19,33 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||||
from homeassistant.helpers.typing import UNDEFINED
|
from homeassistant.helpers.typing import UNDEFINED
|
||||||
|
|
||||||
from .const import DOMAIN, EVENT_VALUE_UPDATED, LOGGER
|
from .const import DOMAIN, EVENT_VALUE_UPDATED, LOGGER
|
||||||
from .discovery import ZwaveDiscoveryInfo
|
from .discovery_data_template import BaseDiscoverySchemaDataTemplate
|
||||||
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
|
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
|
||||||
|
from .models import PlatformZwaveDiscoveryInfo, ZwaveDiscoveryInfo
|
||||||
|
|
||||||
EVENT_VALUE_REMOVED = "value removed"
|
EVENT_VALUE_REMOVED = "value removed"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class NewZwaveDiscoveryInfo(PlatformZwaveDiscoveryInfo):
|
||||||
|
"""Info discovered from (primary) ZWave Value to create entity.
|
||||||
|
|
||||||
|
This is the new discovery info that will replace ZwaveDiscoveryInfo.
|
||||||
|
"""
|
||||||
|
|
||||||
|
entity_class: type[ZWaveBaseEntity]
|
||||||
|
# the entity description to use
|
||||||
|
entity_description: EntityDescription
|
||||||
|
# helper data to use in platform setup
|
||||||
|
platform_data: Any = None
|
||||||
|
# data template to use in platform logic
|
||||||
|
platform_data_template: BaseDiscoverySchemaDataTemplate | None = None
|
||||||
|
|
||||||
|
|
||||||
class ZWaveBaseEntity(Entity):
|
class ZWaveBaseEntity(Entity):
|
||||||
"""Generic Entity Class for a Z-Wave Device."""
|
"""Generic Entity Class for a Z-Wave Device."""
|
||||||
|
|
||||||
@@ -35,7 +53,10 @@ class ZWaveBaseEntity(Entity):
|
|||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
|
self,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
driver: Driver,
|
||||||
|
info: ZwaveDiscoveryInfo | NewZwaveDiscoveryInfo,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a generic Z-Wave device entity."""
|
"""Initialize a generic Z-Wave device entity."""
|
||||||
self.config_entry = config_entry
|
self.config_entry = config_entry
|
||||||
@@ -52,12 +73,14 @@ class ZWaveBaseEntity(Entity):
|
|||||||
# Entity class attributes
|
# Entity class attributes
|
||||||
self._attr_name = self.generate_name()
|
self._attr_name = self.generate_name()
|
||||||
self._attr_unique_id = get_unique_id(driver, self.info.primary_value.value_id)
|
self._attr_unique_id = get_unique_id(driver, self.info.primary_value.value_id)
|
||||||
if self.info.entity_registry_enabled_default is False:
|
if isinstance(info, NewZwaveDiscoveryInfo):
|
||||||
self._attr_entity_registry_enabled_default = False
|
self.entity_description = info.entity_description
|
||||||
if self.info.entity_category is not None:
|
else:
|
||||||
self._attr_entity_category = self.info.entity_category
|
if (enabled_default := info.entity_registry_enabled_default) is False:
|
||||||
if self.info.assumed_state:
|
self._attr_entity_registry_enabled_default = enabled_default
|
||||||
self._attr_assumed_state = True
|
if (entity_category := info.entity_category) is not None:
|
||||||
|
self._attr_entity_category = entity_category
|
||||||
|
self._attr_assumed_state = self.info.assumed_state
|
||||||
# device is precreated in main handler
|
# device is precreated in main handler
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={get_device_id(driver, self.info.node)},
|
identifiers={get_device_id(driver, self.info.node)},
|
||||||
|
@@ -60,16 +60,6 @@ DRIVER_READY_EVENT_TIMEOUT = 60
|
|||||||
SERVER_VERSION_TIMEOUT = 10
|
SERVER_VERSION_TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ZwaveValueID:
|
|
||||||
"""Class to represent a value ID."""
|
|
||||||
|
|
||||||
property_: str | int
|
|
||||||
command_class: int
|
|
||||||
endpoint: int | None = None
|
|
||||||
property_key: str | int | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ZwaveValueMatcher:
|
class ZwaveValueMatcher:
|
||||||
"""Class to allow matching a Z-Wave Value."""
|
"""Class to allow matching a Z-Wave Value."""
|
||||||
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from zwave_js_server.const import CommandClass
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
from zwave_js_server.model.node import Node
|
from zwave_js_server.model.node import Node
|
||||||
from zwave_js_server.model.value import Value as ZwaveValue
|
from zwave_js_server.model.value import Value as ZwaveValue
|
||||||
@@ -14,8 +15,8 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .discovery import ZwaveDiscoveryInfo
|
|
||||||
from .helpers import get_unique_id, get_valueless_base_unique_id
|
from .helpers import get_unique_id, get_valueless_base_unique_id
|
||||||
|
from .models import PlatformZwaveDiscoveryInfo
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ def async_migrate_discovered_value(
|
|||||||
registered_unique_ids: set[str],
|
registered_unique_ids: set[str],
|
||||||
device: dr.DeviceEntry,
|
device: dr.DeviceEntry,
|
||||||
driver: Driver,
|
driver: Driver,
|
||||||
disc_info: ZwaveDiscoveryInfo,
|
disc_info: PlatformZwaveDiscoveryInfo,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Migrate unique ID for entity/entities tied to discovered value."""
|
"""Migrate unique ID for entity/entities tied to discovered value."""
|
||||||
|
|
||||||
@@ -162,7 +163,7 @@ def async_migrate_discovered_value(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
disc_info.platform == Platform.BINARY_SENSOR
|
disc_info.platform == Platform.BINARY_SENSOR
|
||||||
and disc_info.platform_hint == "notification"
|
and disc_info.primary_value.command_class == CommandClass.NOTIFICATION
|
||||||
):
|
):
|
||||||
for state_key in disc_info.primary_value.metadata.states:
|
for state_key in disc_info.primary_value.metadata.states:
|
||||||
# ignore idle key (0)
|
# ignore idle key (0)
|
||||||
|
@@ -1,15 +1,27 @@
|
|||||||
"""Type definitions for Z-Wave JS integration."""
|
"""Provide models for the Z-Wave integration."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from collections.abc import Iterable
|
||||||
from typing import TYPE_CHECKING
|
from dataclasses import asdict, dataclass, field
|
||||||
|
from enum import StrEnum
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
from awesomeversion import AwesomeVersion
|
||||||
from zwave_js_server.const import LogLevel
|
from zwave_js_server.const import LogLevel
|
||||||
|
from zwave_js_server.model.node import Node as ZwaveNode
|
||||||
|
from zwave_js_server.model.value import (
|
||||||
|
ConfigurationValue as ZwaveConfigurationValue,
|
||||||
|
Value as ZwaveValue,
|
||||||
|
get_value_id_str,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import EntityCategory, Platform
|
||||||
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from _typeshed import DataclassInstance
|
||||||
from zwave_js_server.client import Client as ZwaveClient
|
from zwave_js_server.client import Client as ZwaveClient
|
||||||
|
|
||||||
from . import DriverEvents
|
from . import DriverEvents
|
||||||
@@ -25,3 +37,213 @@ class ZwaveJSData:
|
|||||||
|
|
||||||
|
|
||||||
type ZwaveJSConfigEntry = ConfigEntry[ZwaveJSData]
|
type ZwaveJSConfigEntry = ConfigEntry[ZwaveJSData]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ZwaveValueID:
|
||||||
|
"""Class to represent a value ID."""
|
||||||
|
|
||||||
|
property_: str | int
|
||||||
|
command_class: int
|
||||||
|
endpoint: int | None = None
|
||||||
|
property_key: str | int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class ValueType(StrEnum):
|
||||||
|
"""Enum with all value types."""
|
||||||
|
|
||||||
|
ANY = "any"
|
||||||
|
BOOLEAN = "boolean"
|
||||||
|
NUMBER = "number"
|
||||||
|
STRING = "string"
|
||||||
|
|
||||||
|
|
||||||
|
class DataclassMustHaveAtLeastOne:
|
||||||
|
"""A dataclass that must have at least one input parameter that is not None."""
|
||||||
|
|
||||||
|
def __post_init__(self: DataclassInstance) -> None:
|
||||||
|
"""Post dataclass initialization."""
|
||||||
|
if all(val is None for val in asdict(self).values()):
|
||||||
|
raise ValueError("At least one input parameter must not be None")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FirmwareVersionRange(DataclassMustHaveAtLeastOne):
|
||||||
|
"""Firmware version range dictionary."""
|
||||||
|
|
||||||
|
min: str | None = None
|
||||||
|
max: str | None = None
|
||||||
|
min_ver: AwesomeVersion | None = field(default=None, init=False)
|
||||||
|
max_ver: AwesomeVersion | None = field(default=None, init=False)
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
"""Post dataclass initialization."""
|
||||||
|
super().__post_init__()
|
||||||
|
if self.min:
|
||||||
|
self.min_ver = AwesomeVersion(self.min)
|
||||||
|
if self.max:
|
||||||
|
self.max_ver = AwesomeVersion(self.max)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlatformZwaveDiscoveryInfo:
|
||||||
|
"""Info discovered from (primary) ZWave Value to create entity."""
|
||||||
|
|
||||||
|
# node to which the value(s) belongs
|
||||||
|
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
|
||||||
|
assumed_state: bool
|
||||||
|
# the home assistant platform for which an entity should be created
|
||||||
|
platform: Platform
|
||||||
|
# additional values that need to be watched by entity
|
||||||
|
additional_value_ids_to_watch: set[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ZwaveDiscoveryInfo(PlatformZwaveDiscoveryInfo):
|
||||||
|
"""Info discovered from (primary) ZWave Value to create entity."""
|
||||||
|
|
||||||
|
# helper data to use in platform setup
|
||||||
|
platform_data: Any = None
|
||||||
|
# data template to use in platform logic
|
||||||
|
platform_data_template: BaseDiscoverySchemaDataTemplate | None = None
|
||||||
|
# hint for the platform about this discovered entity
|
||||||
|
platform_hint: str | 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
|
||||||
|
class ZWaveValueDiscoverySchema(DataclassMustHaveAtLeastOne):
|
||||||
|
"""Z-Wave Value discovery schema.
|
||||||
|
|
||||||
|
The Z-Wave Value must match these conditions.
|
||||||
|
Use the Z-Wave specifications to find out the values for these parameters:
|
||||||
|
https://github.com/zwave-js/specs/tree/master
|
||||||
|
"""
|
||||||
|
|
||||||
|
# [optional] the value's command class must match ANY of these values
|
||||||
|
command_class: set[int] | None = None
|
||||||
|
# [optional] the value's endpoint must match ANY of these values
|
||||||
|
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 = 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
|
||||||
|
# [optional] the value's states map must include ANY of these keys
|
||||||
|
any_available_states_keys: set[int] | None = None
|
||||||
|
# [optional] the value's cc specific map must include ANY of these key/value pairs
|
||||||
|
any_available_cc_specific: set[tuple[Any, Any]] | None = None
|
||||||
|
# [optional] the value's value must match this value
|
||||||
|
value: Any | None = None
|
||||||
|
# [optional] the value's metadata_stateful must match this value
|
||||||
|
stateful: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NewZWaveDiscoverySchema:
|
||||||
|
"""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: Platform
|
||||||
|
# platform-specific entity description
|
||||||
|
entity_description: EntityDescription
|
||||||
|
# entity class to use to instantiate the entity
|
||||||
|
entity_class: type
|
||||||
|
# primary value belonging to this discovery scheme
|
||||||
|
primary_value: ZWaveValueDiscoverySchema
|
||||||
|
# [optional] template to generate platform specific data to use in setup
|
||||||
|
data_template: BaseDiscoverySchemaDataTemplate | None = None
|
||||||
|
# [optional] the node's manufacturer_id must match ANY of these values
|
||||||
|
manufacturer_id: set[int] | None = None
|
||||||
|
# [optional] the node's product_id must match ANY of these values
|
||||||
|
product_id: set[int] | None = None
|
||||||
|
# [optional] the node's product_type must match ANY of these values
|
||||||
|
product_type: set[int] | None = None
|
||||||
|
# [optional] the node's firmware_version must be within this range
|
||||||
|
firmware_version_range: FirmwareVersionRange | None = None
|
||||||
|
# [optional] the node's firmware_version must match ANY of these values
|
||||||
|
firmware_version: set[str] | None = None
|
||||||
|
# [optional] the node's basic device class must match ANY of these values
|
||||||
|
device_class_basic: set[str | int] | None = None
|
||||||
|
# [optional] the node's generic device class must match ANY of these values
|
||||||
|
device_class_generic: set[str | int] | None = None
|
||||||
|
# [optional] the node's specific device class must match ANY of these values
|
||||||
|
device_class_specific: set[str | int] | None = None
|
||||||
|
# [optional] additional values that ALL need to be present
|
||||||
|
# on the node for this scheme to pass
|
||||||
|
required_values: list[ZWaveValueDiscoverySchema] | None = None
|
||||||
|
# [optional] additional values that MAY NOT be present
|
||||||
|
# on the node for this scheme to pass
|
||||||
|
absent_values: list[ZWaveValueDiscoverySchema] | None = None
|
||||||
|
# [optional] bool to specify if this primary value may be discovered
|
||||||
|
# by multiple platforms
|
||||||
|
allow_multi: bool = False
|
||||||
|
# [optional] bool to specify whether state is assumed
|
||||||
|
# and events should be fired on value update
|
||||||
|
assumed_state: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BaseDiscoverySchemaDataTemplate:
|
||||||
|
"""Base class for discovery schema data templates."""
|
||||||
|
|
||||||
|
static_data: Any | None = None
|
||||||
|
|
||||||
|
def resolve_data(self, value: ZwaveValue) -> Any:
|
||||||
|
"""Resolve helper class data for a discovered value.
|
||||||
|
|
||||||
|
Can optionally be implemented by subclasses if input data needs to be
|
||||||
|
transformed once discovered Value is available.
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def values_to_watch(self, resolved_data: Any) -> Iterable[ZwaveValue | None]:
|
||||||
|
"""Return list of all ZwaveValues resolved by helper that should be watched.
|
||||||
|
|
||||||
|
Should be implemented by subclasses only if there are values to watch.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
def value_ids_to_watch(self, resolved_data: Any) -> set[str]:
|
||||||
|
"""Return list of all Value IDs resolved by helper that should be watched.
|
||||||
|
|
||||||
|
Not to be overwritten by subclasses.
|
||||||
|
"""
|
||||||
|
return {val.value_id for val in self.values_to_watch(resolved_data) if val}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_value_from_id(
|
||||||
|
node: ZwaveNode, value_id_obj: ZwaveValueID
|
||||||
|
) -> ZwaveValue | ZwaveConfigurationValue | None:
|
||||||
|
"""Get a ZwaveValue from a node using a ZwaveValueDict."""
|
||||||
|
value_id = get_value_id_str(
|
||||||
|
node,
|
||||||
|
value_id_obj.command_class,
|
||||||
|
value_id_obj.property_,
|
||||||
|
endpoint=value_id_obj.endpoint,
|
||||||
|
property_key=value_id_obj.property_key,
|
||||||
|
)
|
||||||
|
return node.values.get(value_id)
|
||||||
|
Reference in New Issue
Block a user