Use entity descriptions for zwave_js sensors (#53744)

* Use entity descriptions for zwave_js sensors

* reorder

* use new type

* revert typing changes

* switch to using maps

* Get device and state class from discovery instead

* ues constants for keys

* Add meter type attribute and simplify platform data access

* comments

* second refactor

* Add None lookup value

* readability

* Switch base data template to type Any for more flexibility

* Additional changes based on feedback

* rewrite based on new upstream util functions

* Use new combo type

* Handle UnknownValueData in discovery

* bug fixes

* remove redundant comment

* re-add force_update

* fixes and tweaks

* pylint and feedback
This commit is contained in:
Raman Gupta
2021-08-20 16:25:39 -04:00
committed by GitHub
parent debc6d632c
commit 1f2134a31a
4 changed files with 310 additions and 94 deletions

View File

@@ -75,5 +75,24 @@ ATTR_REFRESH_ALL_VALUES = "refresh_all_values"
ATTR_BROADCAST = "broadcast" ATTR_BROADCAST = "broadcast"
# meter reset # meter reset
ATTR_METER_TYPE = "meter_type" ATTR_METER_TYPE = "meter_type"
ATTR_METER_TYPE_NAME = "meter_type_name"
ADDON_SLUG = "core_zwave_js" ADDON_SLUG = "core_zwave_js"
# Sensor entity description constants
ENTITY_DESC_KEY_BATTERY = "battery"
ENTITY_DESC_KEY_CURRENT = "current"
ENTITY_DESC_KEY_VOLTAGE = "voltage"
ENTITY_DESC_KEY_ENERGY_MEASUREMENT = "energy_measurement"
ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING = "energy_total_increasing"
ENTITY_DESC_KEY_POWER = "power"
ENTITY_DESC_KEY_POWER_FACTOR = "power_factor"
ENTITY_DESC_KEY_CO = "co"
ENTITY_DESC_KEY_CO2 = "co2"
ENTITY_DESC_KEY_HUMIDITY = "humidity"
ENTITY_DESC_KEY_ILLUMINANCE = "illuminance"
ENTITY_DESC_KEY_PRESSURE = "pressure"
ENTITY_DESC_KEY_SIGNAL_STRENGTH = "signal_strength"
ENTITY_DESC_KEY_TEMPERATURE = "temperature"
ENTITY_DESC_KEY_TARGET_TEMPERATURE = "target_temperature"
ENTITY_DESC_KEY_TIMESTAMP = "timestamp"

View File

@@ -7,15 +7,18 @@ from typing import Any
from awesomeversion import AwesomeVersion from awesomeversion import AwesomeVersion
from zwave_js_server.const import THERMOSTAT_CURRENT_TEMP_PROPERTY, CommandClass from zwave_js_server.const import THERMOSTAT_CURRENT_TEMP_PROPERTY, CommandClass
from zwave_js_server.exceptions import UnknownValueData
from zwave_js_server.model.device_class import DeviceClassItem from zwave_js_server.model.device_class import DeviceClassItem
from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.value import Value as ZwaveValue from zwave_js_server.model.value import Value as ZwaveValue
from homeassistant.core import callback from homeassistant.core import callback
from .const import LOGGER
from .discovery_data_template import ( from .discovery_data_template import (
BaseDiscoverySchemaDataTemplate, BaseDiscoverySchemaDataTemplate,
DynamicCurrentTempClimateDataTemplate, DynamicCurrentTempClimateDataTemplate,
NumericSensorDataTemplate,
ZwaveValueID, ZwaveValueID,
) )
@@ -59,14 +62,14 @@ class ZwaveDiscoveryInfo:
assumed_state: bool assumed_state: bool
# the home assistant platform for which an entity should be created # the home assistant platform for which an entity should be created
platform: str platform: str
# 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 # hint for the platform about this discovered entity
platform_hint: str | None = "" platform_hint: str | None = ""
# data template to use in platform logic # data template to use in platform logic
platform_data_template: BaseDiscoverySchemaDataTemplate | None = None platform_data_template: BaseDiscoverySchemaDataTemplate | None = None
# helper data to use in platform setup
platform_data: dict[str, Any] | None = None
# additional values that need to be watched by entity
additional_value_ids_to_watch: set[str] | None = None
# bool to specify whether entity should be enabled by default # bool to specify whether entity should be enabled by default
entity_registry_enabled_default: bool = True entity_registry_enabled_default: bool = True
@@ -487,6 +490,7 @@ DISCOVERY_SCHEMAS = [
}, },
type={"number"}, type={"number"},
), ),
data_template=NumericSensorDataTemplate(),
), ),
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
platform="sensor", platform="sensor",
@@ -495,6 +499,7 @@ DISCOVERY_SCHEMAS = [
command_class={CommandClass.INDICATOR}, command_class={CommandClass.INDICATOR},
type={"number"}, type={"number"},
), ),
data_template=NumericSensorDataTemplate(),
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
# Meter sensors for Meter CC # Meter sensors for Meter CC
@@ -508,6 +513,7 @@ DISCOVERY_SCHEMAS = [
type={"number"}, type={"number"},
property={"value"}, property={"value"},
), ),
data_template=NumericSensorDataTemplate(),
), ),
# special list sensors (Notification CC) # special list sensors (Notification CC)
ZWaveDiscoverySchema( ZWaveDiscoverySchema(
@@ -542,6 +548,7 @@ DISCOVERY_SCHEMAS = [
property={"targetValue"}, property={"targetValue"},
) )
], ],
data_template=NumericSensorDataTemplate(),
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
# binary switches # binary switches
@@ -745,9 +752,15 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None
# resolve helper data from template # resolve helper data from template
resolved_data = None resolved_data = None
additional_value_ids_to_watch = None additional_value_ids_to_watch = set()
if schema.data_template: if schema.data_template:
resolved_data = schema.data_template.resolve_data(value) try:
resolved_data = schema.data_template.resolve_data(value)
except UnknownValueData as err:
LOGGER.error(
"Discovery for value %s will be skipped: %s", value, err
)
continue
additional_value_ids_to_watch = schema.data_template.value_ids_to_watch( additional_value_ids_to_watch = schema.data_template.value_ids_to_watch(
resolved_data resolved_data
) )

View File

@@ -1,12 +1,80 @@
"""Data template classes for discovery used to generate device specific data for setup.""" """Data template classes for discovery used to generate additional data for setup."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Iterable from collections.abc import Iterable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from zwave_js_server.const import (
CO2_SENSORS,
CO_SENSORS,
CURRENT_METER_TYPES,
CURRENT_SENSORS,
ENERGY_METER_TYPES,
ENERGY_SENSORS,
HUMIDITY_SENSORS,
ILLUMINANCE_SENSORS,
POWER_FACTOR_METER_TYPES,
POWER_METER_TYPES,
POWER_SENSORS,
PRESSURE_SENSORS,
SIGNAL_STRENGTH_SENSORS,
TEMPERATURE_SENSORS,
TIMESTAMP_SENSORS,
VOLTAGE_METER_TYPES,
VOLTAGE_SENSORS,
CommandClass,
MeterScaleType,
MultilevelSensorType,
)
from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from zwave_js_server.model.value import Value as ZwaveValue, get_value_id
from zwave_js_server.util.command_class import (
get_meter_scale_type,
get_multilevel_sensor_type,
)
from .const import (
ENTITY_DESC_KEY_BATTERY,
ENTITY_DESC_KEY_CO,
ENTITY_DESC_KEY_CO2,
ENTITY_DESC_KEY_CURRENT,
ENTITY_DESC_KEY_ENERGY_MEASUREMENT,
ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
ENTITY_DESC_KEY_HUMIDITY,
ENTITY_DESC_KEY_ILLUMINANCE,
ENTITY_DESC_KEY_POWER,
ENTITY_DESC_KEY_POWER_FACTOR,
ENTITY_DESC_KEY_PRESSURE,
ENTITY_DESC_KEY_SIGNAL_STRENGTH,
ENTITY_DESC_KEY_TARGET_TEMPERATURE,
ENTITY_DESC_KEY_TEMPERATURE,
ENTITY_DESC_KEY_TIMESTAMP,
ENTITY_DESC_KEY_VOLTAGE,
)
METER_DEVICE_CLASS_MAP: dict[str, set[MeterScaleType]] = {
ENTITY_DESC_KEY_CURRENT: CURRENT_METER_TYPES,
ENTITY_DESC_KEY_VOLTAGE: VOLTAGE_METER_TYPES,
ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING: ENERGY_METER_TYPES,
ENTITY_DESC_KEY_POWER: POWER_METER_TYPES,
ENTITY_DESC_KEY_POWER_FACTOR: POWER_FACTOR_METER_TYPES,
}
MULTILEVEL_SENSOR_DEVICE_CLASS_MAP: dict[str, set[MultilevelSensorType]] = {
ENTITY_DESC_KEY_CO: CO_SENSORS,
ENTITY_DESC_KEY_CO2: CO2_SENSORS,
ENTITY_DESC_KEY_CURRENT: CURRENT_SENSORS,
ENTITY_DESC_KEY_ENERGY_MEASUREMENT: ENERGY_SENSORS,
ENTITY_DESC_KEY_HUMIDITY: HUMIDITY_SENSORS,
ENTITY_DESC_KEY_ILLUMINANCE: ILLUMINANCE_SENSORS,
ENTITY_DESC_KEY_POWER: POWER_SENSORS,
ENTITY_DESC_KEY_PRESSURE: PRESSURE_SENSORS,
ENTITY_DESC_KEY_SIGNAL_STRENGTH: SIGNAL_STRENGTH_SENSORS,
ENTITY_DESC_KEY_TEMPERATURE: TEMPERATURE_SENSORS,
ENTITY_DESC_KEY_TIMESTAMP: TIMESTAMP_SENSORS,
ENTITY_DESC_KEY_VOLTAGE: VOLTAGE_SENSORS,
}
@dataclass @dataclass
@@ -19,11 +87,10 @@ class ZwaveValueID:
property_key: str | int | None = None property_key: str | int | None = None
@dataclass
class BaseDiscoverySchemaDataTemplate: class BaseDiscoverySchemaDataTemplate:
"""Base class for discovery schema data templates.""" """Base class for discovery schema data templates."""
def resolve_data(self, value: ZwaveValue) -> dict[str, Any]: def resolve_data(self, value: ZwaveValue) -> Any:
""" """
Resolve helper class data for a discovered value. Resolve helper class data for a discovered value.
@@ -33,7 +100,7 @@ class BaseDiscoverySchemaDataTemplate:
# pylint: disable=no-self-use # pylint: disable=no-self-use
return {} return {}
def values_to_watch(self, resolved_data: dict[str, Any]) -> Iterable[ZwaveValue]: def values_to_watch(self, resolved_data: Any) -> Iterable[ZwaveValue]:
""" """
Return list of all ZwaveValues resolved by helper that should be watched. Return list of all ZwaveValues resolved by helper that should be watched.
@@ -42,7 +109,7 @@ class BaseDiscoverySchemaDataTemplate:
# pylint: disable=no-self-use # pylint: disable=no-self-use
return [] return []
def value_ids_to_watch(self, resolved_data: dict[str, Any]) -> set[str]: def value_ids_to_watch(self, resolved_data: Any) -> set[str]:
""" """
Return list of all Value IDs resolved by helper that should be watched. Return list of all Value IDs resolved by helper that should be watched.
@@ -107,3 +174,32 @@ class DynamicCurrentTempClimateDataTemplate(BaseDiscoverySchemaDataTemplate):
return lookup_table.get(lookup_key) return lookup_table.get(lookup_key)
return None return None
class NumericSensorDataTemplate(BaseDiscoverySchemaDataTemplate):
"""Data template class for Z-Wave Sensor entities."""
def resolve_data(self, value: ZwaveValue) -> str | None:
"""Resolve helper class data for a discovered value."""
if value.command_class == CommandClass.BATTERY:
return ENTITY_DESC_KEY_BATTERY
if value.command_class == CommandClass.METER:
scale_type = get_meter_scale_type(value)
for key, scale_type_set in METER_DEVICE_CLASS_MAP.items():
if scale_type in scale_type_set:
return key
if value.command_class == CommandClass.SENSOR_MULTILEVEL:
sensor_type = get_multilevel_sensor_type(value)
if sensor_type == MultilevelSensorType.TARGET_TEMPERATURE:
return ENTITY_DESC_KEY_TARGET_TEMPERATURE
for (
key,
sensor_type_set,
) in MULTILEVEL_SENSOR_DEVICE_CLASS_MAP.items():
if sensor_type in sensor_type_set:
return key
return None

View File

@@ -1,30 +1,45 @@
"""Representation of Z-Wave sensors.""" """Representation of Z-Wave sensors."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
import logging import logging
from typing import cast from typing import cast
import voluptuous as vol import voluptuous as vol
from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import CommandClass, ConfigurationValueType from zwave_js_server.const import (
RESET_METER_OPTION_TARGET_VALUE,
RESET_METER_OPTION_TYPE,
CommandClass,
ConfigurationValueType,
)
from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.value import ConfigurationValue from zwave_js_server.model.value import ConfigurationValue
from zwave_js_server.util.command_class import get_meter_type
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_POWER,
DOMAIN as SENSOR_DOMAIN, DOMAIN as SENSOR_DOMAIN,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
SensorEntity, SensorEntity,
SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO,
DEVICE_CLASS_CO2,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
TEMP_CELSIUS, TEMP_CELSIUS,
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
@@ -34,7 +49,30 @@ from homeassistant.helpers import entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_METER_TYPE, ATTR_VALUE, DATA_CLIENT, DOMAIN, SERVICE_RESET_METER from .const import (
ATTR_METER_TYPE,
ATTR_METER_TYPE_NAME,
ATTR_VALUE,
DATA_CLIENT,
DOMAIN,
ENTITY_DESC_KEY_BATTERY,
ENTITY_DESC_KEY_CO,
ENTITY_DESC_KEY_CO2,
ENTITY_DESC_KEY_CURRENT,
ENTITY_DESC_KEY_ENERGY_MEASUREMENT,
ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
ENTITY_DESC_KEY_HUMIDITY,
ENTITY_DESC_KEY_ILLUMINANCE,
ENTITY_DESC_KEY_POWER,
ENTITY_DESC_KEY_POWER_FACTOR,
ENTITY_DESC_KEY_PRESSURE,
ENTITY_DESC_KEY_SIGNAL_STRENGTH,
ENTITY_DESC_KEY_TARGET_TEMPERATURE,
ENTITY_DESC_KEY_TEMPERATURE,
ENTITY_DESC_KEY_TIMESTAMP,
ENTITY_DESC_KEY_VOLTAGE,
SERVICE_RESET_METER,
)
from .discovery import ZwaveDiscoveryInfo from .discovery import ZwaveDiscoveryInfo
from .entity import ZWaveBaseEntity from .entity import ZWaveBaseEntity
from .helpers import get_device_id from .helpers import get_device_id
@@ -42,6 +80,97 @@ from .helpers import get_device_id
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@dataclass
class ZwaveSensorEntityDescription(SensorEntityDescription):
"""Base description of a Zwave Sensor entity."""
info: ZwaveDiscoveryInfo | None = None
ENTITY_DESCRIPTION_KEY_MAP: dict[str, ZwaveSensorEntityDescription] = {
ENTITY_DESC_KEY_BATTERY: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_BATTERY,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_CURRENT: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_CURRENT,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_VOLTAGE: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_VOLTAGE,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_ENERGY_MEASUREMENT: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_ENERGY_TOTAL_INCREASING,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
ENTITY_DESC_KEY_POWER: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_POWER,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_POWER_FACTOR: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_POWER_FACTOR,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_CO: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_CO,
device_class=DEVICE_CLASS_CO,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_CO2: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_CO2,
device_class=DEVICE_CLASS_CO2,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_HUMIDITY: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_ILLUMINANCE: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_ILLUMINANCE,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_PRESSURE: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_SIGNAL_STRENGTH: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_SIGNAL_STRENGTH,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_TEMPERATURE: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_TIMESTAMP: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_TIMESTAMP,
device_class=DEVICE_CLASS_TIMESTAMP,
state_class=STATE_CLASS_MEASUREMENT,
),
ENTITY_DESC_KEY_TARGET_TEMPERATURE: ZwaveSensorEntityDescription(
ENTITY_DESC_KEY_TARGET_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=None,
),
}
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
@@ -55,16 +184,25 @@ async def async_setup_entry(
"""Add Z-Wave Sensor.""" """Add Z-Wave Sensor."""
entities: list[ZWaveBaseEntity] = [] entities: list[ZWaveBaseEntity] = []
entity_description = ENTITY_DESCRIPTION_KEY_MAP.get(
info.platform_data
) or ZwaveSensorEntityDescription("base_sensor")
entity_description.info = info
if info.platform_hint == "string_sensor": if info.platform_hint == "string_sensor":
entities.append(ZWaveStringSensor(config_entry, client, info)) entities.append(ZWaveStringSensor(config_entry, client, entity_description))
elif info.platform_hint == "numeric_sensor": elif info.platform_hint == "numeric_sensor":
entities.append(ZWaveNumericSensor(config_entry, client, info)) entities.append(
ZWaveNumericSensor(config_entry, client, entity_description)
)
elif info.platform_hint == "list_sensor": elif info.platform_hint == "list_sensor":
entities.append(ZWaveListSensor(config_entry, client, info)) entities.append(ZWaveListSensor(config_entry, client, entity_description))
elif info.platform_hint == "config_parameter": elif info.platform_hint == "config_parameter":
entities.append(ZWaveConfigParameterSensor(config_entry, client, info)) entities.append(
ZWaveConfigParameterSensor(config_entry, client, entity_description)
)
elif info.platform_hint == "meter": elif info.platform_hint == "meter":
entities.append(ZWaveMeterSensor(config_entry, client, info)) entities.append(ZWaveMeterSensor(config_entry, client, entity_description))
else: else:
LOGGER.warning( LOGGER.warning(
"Sensor not implemented for %s/%s", "Sensor not implemented for %s/%s",
@@ -114,62 +252,16 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
self, self,
config_entry: ConfigEntry, config_entry: ConfigEntry,
client: ZwaveClient, client: ZwaveClient,
info: ZwaveDiscoveryInfo, entity_description: ZwaveSensorEntityDescription,
) -> None: ) -> None:
"""Initialize a ZWaveSensorBase entity.""" """Initialize a ZWaveSensorBase entity."""
super().__init__(config_entry, client, info) assert entity_description.info
super().__init__(config_entry, client, entity_description.info)
self.entity_description = entity_description
# Entity class attributes # Entity class attributes
self._attr_force_update = True
self._attr_name = self.generate_name(include_value_name=True) self._attr_name = self.generate_name(include_value_name=True)
self._attr_device_class = self._get_device_class()
self._attr_state_class = self._get_state_class()
def _get_device_class(self) -> str | None:
"""
Get the device class of the sensor.
This should be run once during initialization so we don't have to calculate
this value on every state update.
"""
if self.info.primary_value.command_class == CommandClass.BATTERY:
return DEVICE_CLASS_BATTERY
if isinstance(self.info.primary_value.property_, str):
property_lower = self.info.primary_value.property_.lower()
if "humidity" in property_lower:
return DEVICE_CLASS_HUMIDITY
if "temperature" in property_lower:
return DEVICE_CLASS_TEMPERATURE
if self.info.primary_value.metadata.unit == "A":
return DEVICE_CLASS_CURRENT
if self.info.primary_value.metadata.unit == "W":
return DEVICE_CLASS_POWER
if self.info.primary_value.metadata.unit == "kWh":
return DEVICE_CLASS_ENERGY
if self.info.primary_value.metadata.unit == "V":
return DEVICE_CLASS_VOLTAGE
if self.info.primary_value.metadata.unit == "Lux":
return DEVICE_CLASS_ILLUMINANCE
return None
def _get_state_class(self) -> str | None:
"""
Get the state class of the sensor.
This should be run once during initialization so we don't have to calculate
this value on every state update.
"""
if self.info.primary_value.command_class == CommandClass.BATTERY:
return STATE_CLASS_MEASUREMENT
if isinstance(self.info.primary_value.property_, str):
property_lower = self.info.primary_value.property_.lower()
if "humidity" in property_lower or "temperature" in property_lower:
return STATE_CLASS_MEASUREMENT
return None
@property
def force_update(self) -> bool:
"""Force updates."""
return True
class ZWaveStringSensor(ZwaveSensorBase): class ZWaveStringSensor(ZwaveSensorBase):
@@ -216,20 +308,16 @@ class ZWaveNumericSensor(ZwaveSensorBase):
class ZWaveMeterSensor(ZWaveNumericSensor): class ZWaveMeterSensor(ZWaveNumericSensor):
"""Representation of a Z-Wave Meter CC sensor.""" """Representation of a Z-Wave Meter CC sensor."""
def __init__( @property
self, def extra_state_attributes(self) -> Mapping[str, int | str] | None:
config_entry: ConfigEntry, """Return extra state attributes."""
client: ZwaveClient, meter_type = get_meter_type(self.info.primary_value)
info: ZwaveDiscoveryInfo, if meter_type:
) -> None: return {
"""Initialize a ZWaveNumericSensor entity.""" ATTR_METER_TYPE: meter_type.value,
super().__init__(config_entry, client, info) ATTR_METER_TYPE_NAME: meter_type.name,
}
# Entity class attributes return None
if self.device_class == DEVICE_CLASS_ENERGY:
self._attr_state_class = STATE_CLASS_TOTAL_INCREASING
else:
self._attr_state_class = STATE_CLASS_MEASUREMENT
async def async_reset_meter( async def async_reset_meter(
self, meter_type: int | None = None, value: int | None = None self, meter_type: int | None = None, value: int | None = None
@@ -239,9 +327,9 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
primary_value = self.info.primary_value primary_value = self.info.primary_value
options = {} options = {}
if meter_type is not None: if meter_type is not None:
options["type"] = meter_type options[RESET_METER_OPTION_TYPE] = meter_type
if value is not None: if value is not None:
options["targetValue"] = value options[RESET_METER_OPTION_TARGET_VALUE] = value
args = [options] if options else [] args = [options] if options else []
await node.endpoints[primary_value.endpoint].async_invoke_cc_api( await node.endpoints[primary_value.endpoint].async_invoke_cc_api(
CommandClass.METER, "reset", *args, wait_for_result=False CommandClass.METER, "reset", *args, wait_for_result=False
@@ -261,10 +349,10 @@ class ZWaveListSensor(ZwaveSensorBase):
self, self,
config_entry: ConfigEntry, config_entry: ConfigEntry,
client: ZwaveClient, client: ZwaveClient,
info: ZwaveDiscoveryInfo, entity_description: ZwaveSensorEntityDescription,
) -> None: ) -> None:
"""Initialize a ZWaveListSensor entity.""" """Initialize a ZWaveListSensor entity."""
super().__init__(config_entry, client, info) super().__init__(config_entry, client, entity_description)
# Entity class attributes # Entity class attributes
self._attr_name = self.generate_name( self._attr_name = self.generate_name(
@@ -291,7 +379,7 @@ class ZWaveListSensor(ZwaveSensorBase):
def extra_state_attributes(self) -> dict[str, str] | None: def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the device specific state attributes.""" """Return the device specific state attributes."""
# add the value's int value as property for multi-value (list) items # add the value's int value as property for multi-value (list) items
return {"value": self.info.primary_value.value} return {ATTR_VALUE: self.info.primary_value.value}
class ZWaveConfigParameterSensor(ZwaveSensorBase): class ZWaveConfigParameterSensor(ZwaveSensorBase):
@@ -301,10 +389,10 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
self, self,
config_entry: ConfigEntry, config_entry: ConfigEntry,
client: ZwaveClient, client: ZwaveClient,
info: ZwaveDiscoveryInfo, entity_description: ZwaveSensorEntityDescription,
) -> None: ) -> None:
"""Initialize a ZWaveConfigParameterSensor entity.""" """Initialize a ZWaveConfigParameterSensor entity."""
super().__init__(config_entry, client, info) super().__init__(config_entry, client, entity_description)
self._primary_value = cast(ConfigurationValue, self.info.primary_value) self._primary_value = cast(ConfigurationValue, self.info.primary_value)
# Entity class attributes # Entity class attributes
@@ -338,7 +426,7 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
if self._primary_value.configuration_value_type == ConfigurationValueType.RANGE: if self._primary_value.configuration_value_type == ConfigurationValueType.RANGE:
return None return None
# add the value's int value as property for multi-value (list) items # add the value's int value as property for multi-value (list) items
return {"value": self.info.primary_value.value} return {ATTR_VALUE: self.info.primary_value.value}
class ZWaveNodeStatusSensor(SensorEntity): class ZWaveNodeStatusSensor(SensorEntity):