564 lines
19 KiB
Python
564 lines
19 KiB
Python
"""Data template classes for discovery used to generate additional data for setup."""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable
|
|
from dataclasses import dataclass, field
|
|
import logging
|
|
from typing import Any
|
|
|
|
from zwave_js_server.const import CommandClass
|
|
from zwave_js_server.const.command_class.meter import (
|
|
CURRENT_METER_TYPES,
|
|
ENERGY_TOTAL_INCREASING_METER_TYPES,
|
|
POWER_FACTOR_METER_TYPES,
|
|
POWER_METER_TYPES,
|
|
UNIT_AMPERE as METER_UNIT_AMPERE,
|
|
UNIT_CUBIC_FEET,
|
|
UNIT_CUBIC_METER as METER_UNIT_CUBIC_METER,
|
|
UNIT_KILOWATT_HOUR,
|
|
UNIT_US_GALLON,
|
|
UNIT_VOLT as METER_UNIT_VOLT,
|
|
UNIT_WATT as METER_UNIT_WATT,
|
|
VOLTAGE_METER_TYPES,
|
|
ElectricScale,
|
|
MeterScaleType,
|
|
)
|
|
from zwave_js_server.const.command_class.multilevel_sensor import (
|
|
CO2_SENSORS,
|
|
CO_SENSORS,
|
|
CURRENT_SENSORS,
|
|
ENERGY_MEASUREMENT_SENSORS,
|
|
HUMIDITY_SENSORS,
|
|
ILLUMINANCE_SENSORS,
|
|
POWER_SENSORS,
|
|
PRESSURE_SENSORS,
|
|
SIGNAL_STRENGTH_SENSORS,
|
|
TEMPERATURE_SENSORS,
|
|
UNIT_AMPERE as SENSOR_UNIT_AMPERE,
|
|
UNIT_BTU_H,
|
|
UNIT_CELSIUS,
|
|
UNIT_CENTIMETER,
|
|
UNIT_CUBIC_FEET_PER_MINUTE,
|
|
UNIT_CUBIC_METER as SENSOR_UNIT_CUBIC_METER,
|
|
UNIT_CUBIC_METER_PER_HOUR,
|
|
UNIT_DECIBEL,
|
|
UNIT_DEGREES,
|
|
UNIT_DENSITY,
|
|
UNIT_FAHRENHEIT,
|
|
UNIT_FEET,
|
|
UNIT_GALLONS,
|
|
UNIT_HERTZ,
|
|
UNIT_INCHES_OF_MERCURY,
|
|
UNIT_INCHES_PER_HOUR,
|
|
UNIT_KILOGRAM,
|
|
UNIT_KILOHERTZ,
|
|
UNIT_LITER,
|
|
UNIT_LUX,
|
|
UNIT_M_S,
|
|
UNIT_METER,
|
|
UNIT_MICROGRAM_PER_CUBIC_METER,
|
|
UNIT_MILLIAMPERE,
|
|
UNIT_MILLIMETER_HOUR,
|
|
UNIT_MILLIVOLT,
|
|
UNIT_MPH,
|
|
UNIT_PARTS_MILLION,
|
|
UNIT_PERCENTAGE_VALUE,
|
|
UNIT_POUND_PER_SQUARE_INCH,
|
|
UNIT_POUNDS,
|
|
UNIT_POWER_LEVEL,
|
|
UNIT_RSSI,
|
|
UNIT_SECOND,
|
|
UNIT_SYSTOLIC,
|
|
UNIT_VOLT as SENSOR_UNIT_VOLT,
|
|
UNIT_WATT as SENSOR_UNIT_WATT,
|
|
UNIT_WATT_PER_SQUARE_METER,
|
|
VOLTAGE_SENSORS,
|
|
MultilevelSensorScaleType,
|
|
MultilevelSensorType,
|
|
)
|
|
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,
|
|
)
|
|
from zwave_js_server.util.command_class.meter import get_meter_scale_type
|
|
from zwave_js_server.util.command_class.multilevel_sensor import (
|
|
get_multilevel_sensor_scale_type,
|
|
get_multilevel_sensor_type,
|
|
)
|
|
|
|
from homeassistant.const import (
|
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
|
CONCENTRATION_PARTS_PER_MILLION,
|
|
DEGREE,
|
|
ELECTRIC_CURRENT_AMPERE,
|
|
ELECTRIC_CURRENT_MILLIAMPERE,
|
|
ELECTRIC_POTENTIAL_MILLIVOLT,
|
|
ELECTRIC_POTENTIAL_VOLT,
|
|
ENERGY_KILO_WATT_HOUR,
|
|
FREQUENCY_HERTZ,
|
|
FREQUENCY_KILOHERTZ,
|
|
IRRADIATION_WATTS_PER_SQUARE_METER,
|
|
LENGTH_CENTIMETERS,
|
|
LENGTH_FEET,
|
|
LENGTH_METERS,
|
|
LIGHT_LUX,
|
|
MASS_KILOGRAMS,
|
|
MASS_POUNDS,
|
|
PERCENTAGE,
|
|
POWER_BTU_PER_HOUR,
|
|
POWER_WATT,
|
|
PRECIPITATION_INCHES_PER_HOUR,
|
|
PRECIPITATION_MILLIMETERS_PER_HOUR,
|
|
PRESSURE_INHG,
|
|
PRESSURE_MMHG,
|
|
PRESSURE_PSI,
|
|
SIGNAL_STRENGTH_DECIBELS,
|
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
|
SPEED_METERS_PER_SECOND,
|
|
SPEED_MILES_PER_HOUR,
|
|
TEMP_CELSIUS,
|
|
TEMP_FAHRENHEIT,
|
|
TIME_SECONDS,
|
|
VOLUME_CUBIC_FEET,
|
|
VOLUME_CUBIC_METERS,
|
|
VOLUME_FLOW_RATE_CUBIC_FEET_PER_MINUTE,
|
|
VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR,
|
|
VOLUME_GALLONS,
|
|
VOLUME_LITERS,
|
|
)
|
|
|
|
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_MEASUREMENT,
|
|
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_TOTAL_INCREASING,
|
|
ENTITY_DESC_KEY_VOLTAGE,
|
|
)
|
|
from .helpers import ZwaveValueID
|
|
|
|
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_TOTAL_INCREASING_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_MEASUREMENT_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_VOLTAGE: VOLTAGE_SENSORS,
|
|
}
|
|
|
|
METER_UNIT_MAP: dict[str, set[MeterScaleType]] = {
|
|
ELECTRIC_CURRENT_AMPERE: METER_UNIT_AMPERE,
|
|
VOLUME_CUBIC_FEET: UNIT_CUBIC_FEET,
|
|
VOLUME_CUBIC_METERS: METER_UNIT_CUBIC_METER,
|
|
VOLUME_GALLONS: UNIT_US_GALLON,
|
|
ENERGY_KILO_WATT_HOUR: UNIT_KILOWATT_HOUR,
|
|
ELECTRIC_POTENTIAL_VOLT: METER_UNIT_VOLT,
|
|
POWER_WATT: METER_UNIT_WATT,
|
|
}
|
|
|
|
MULTILEVEL_SENSOR_UNIT_MAP: dict[str, set[MultilevelSensorScaleType]] = {
|
|
ELECTRIC_CURRENT_AMPERE: SENSOR_UNIT_AMPERE,
|
|
POWER_BTU_PER_HOUR: UNIT_BTU_H,
|
|
TEMP_CELSIUS: UNIT_CELSIUS,
|
|
LENGTH_CENTIMETERS: UNIT_CENTIMETER,
|
|
VOLUME_FLOW_RATE_CUBIC_FEET_PER_MINUTE: UNIT_CUBIC_FEET_PER_MINUTE,
|
|
VOLUME_CUBIC_METERS: SENSOR_UNIT_CUBIC_METER,
|
|
VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR: UNIT_CUBIC_METER_PER_HOUR,
|
|
SIGNAL_STRENGTH_DECIBELS: UNIT_DECIBEL,
|
|
DEGREE: UNIT_DEGREES,
|
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER: {
|
|
*UNIT_DENSITY,
|
|
*UNIT_MICROGRAM_PER_CUBIC_METER,
|
|
},
|
|
TEMP_FAHRENHEIT: UNIT_FAHRENHEIT,
|
|
LENGTH_FEET: UNIT_FEET,
|
|
VOLUME_GALLONS: UNIT_GALLONS,
|
|
FREQUENCY_HERTZ: UNIT_HERTZ,
|
|
PRESSURE_INHG: UNIT_INCHES_OF_MERCURY,
|
|
PRECIPITATION_INCHES_PER_HOUR: UNIT_INCHES_PER_HOUR,
|
|
MASS_KILOGRAMS: UNIT_KILOGRAM,
|
|
FREQUENCY_KILOHERTZ: UNIT_KILOHERTZ,
|
|
VOLUME_LITERS: UNIT_LITER,
|
|
LIGHT_LUX: UNIT_LUX,
|
|
LENGTH_METERS: UNIT_METER,
|
|
ELECTRIC_CURRENT_MILLIAMPERE: UNIT_MILLIAMPERE,
|
|
PRECIPITATION_MILLIMETERS_PER_HOUR: UNIT_MILLIMETER_HOUR,
|
|
ELECTRIC_POTENTIAL_MILLIVOLT: UNIT_MILLIVOLT,
|
|
SPEED_MILES_PER_HOUR: UNIT_MPH,
|
|
SPEED_METERS_PER_SECOND: UNIT_M_S,
|
|
CONCENTRATION_PARTS_PER_MILLION: UNIT_PARTS_MILLION,
|
|
PERCENTAGE: {*UNIT_PERCENTAGE_VALUE, *UNIT_RSSI},
|
|
MASS_POUNDS: UNIT_POUNDS,
|
|
PRESSURE_PSI: UNIT_POUND_PER_SQUARE_INCH,
|
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT: UNIT_POWER_LEVEL,
|
|
TIME_SECONDS: UNIT_SECOND,
|
|
PRESSURE_MMHG: UNIT_SYSTOLIC,
|
|
ELECTRIC_POTENTIAL_VOLT: SENSOR_UNIT_VOLT,
|
|
POWER_WATT: SENSOR_UNIT_WATT,
|
|
IRRADIATION_WATTS_PER_SQUARE_METER: UNIT_WATT_PER_SQUARE_METER,
|
|
}
|
|
|
|
_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]:
|
|
"""
|
|
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 | None:
|
|
"""Get a ZwaveValue from a node using a ZwaveValueDict."""
|
|
value_id = get_value_id(
|
|
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
|
|
class DynamicCurrentTempClimateDataTemplate(BaseDiscoverySchemaDataTemplate):
|
|
"""Data template class for Z-Wave JS Climate entities with dynamic current temps."""
|
|
|
|
lookup_table: dict[str | int, ZwaveValueID] = field(default_factory=dict)
|
|
dependent_value: ZwaveValueID | None = None
|
|
|
|
def resolve_data(self, value: ZwaveValue) -> dict[str, Any]:
|
|
"""Resolve helper class data for a discovered value."""
|
|
if not self.lookup_table or not self.dependent_value:
|
|
raise ValueError("Invalid discovery data template")
|
|
data: dict[str, Any] = {
|
|
"lookup_table": {},
|
|
"dependent_value": self._get_value_from_id(
|
|
value.node, self.dependent_value
|
|
),
|
|
}
|
|
for key, value_id in self.lookup_table.items():
|
|
data["lookup_table"][key] = self._get_value_from_id(value.node, value_id)
|
|
|
|
return data
|
|
|
|
def values_to_watch(self, resolved_data: dict[str, Any]) -> Iterable[ZwaveValue]:
|
|
"""Return list of all ZwaveValues resolved by helper that should be watched."""
|
|
return [
|
|
*resolved_data["lookup_table"].values(),
|
|
resolved_data["dependent_value"],
|
|
]
|
|
|
|
@staticmethod
|
|
def current_temperature_value(resolved_data: dict[str, Any]) -> ZwaveValue | None:
|
|
"""Get current temperature ZwaveValue from resolved data."""
|
|
lookup_table: dict[str | int, ZwaveValue | None] = resolved_data["lookup_table"]
|
|
dependent_value: ZwaveValue | None = resolved_data["dependent_value"]
|
|
|
|
if dependent_value and dependent_value.value is not None:
|
|
lookup_key = dependent_value.metadata.states[
|
|
str(dependent_value.value)
|
|
].split("-")[0]
|
|
return lookup_table.get(lookup_key)
|
|
|
|
return None
|
|
|
|
|
|
@dataclass
|
|
class NumericSensorDataTemplateData:
|
|
"""Class to represent returned data from NumericSensorDataTemplate."""
|
|
|
|
entity_description_key: str | None = None
|
|
unit_of_measurement: str | None = None
|
|
|
|
|
|
class NumericSensorDataTemplate(BaseDiscoverySchemaDataTemplate):
|
|
"""Data template class for Z-Wave Sensor entities."""
|
|
|
|
@staticmethod
|
|
def find_key_from_matching_set(
|
|
enum_value: MultilevelSensorType | MultilevelSensorScaleType | MeterScaleType,
|
|
set_map: dict[
|
|
str, set[MultilevelSensorType | MultilevelSensorScaleType | MeterScaleType]
|
|
],
|
|
) -> str | None:
|
|
"""Find a key in a set map that matches a given enum value."""
|
|
for key, value_set in set_map.items():
|
|
for value_in_set in value_set:
|
|
# Since these are IntEnums and the different classes reuse the same
|
|
# values, we need to match the class as well
|
|
if (
|
|
value_in_set.__class__ == enum_value.__class__
|
|
and value_in_set == enum_value
|
|
):
|
|
return key
|
|
return None
|
|
|
|
def resolve_data(self, value: ZwaveValue) -> NumericSensorDataTemplateData:
|
|
"""Resolve helper class data for a discovered value."""
|
|
|
|
if value.command_class == CommandClass.BATTERY:
|
|
return NumericSensorDataTemplateData(ENTITY_DESC_KEY_BATTERY, PERCENTAGE)
|
|
|
|
if value.command_class == CommandClass.METER:
|
|
scale_type = get_meter_scale_type(value)
|
|
unit = self.find_key_from_matching_set(scale_type, METER_UNIT_MAP)
|
|
# We do this because even though these are energy scales, they don't meet
|
|
# the unit requirements for the energy device class.
|
|
if scale_type in (
|
|
ElectricScale.PULSE_COUNT,
|
|
ElectricScale.KILOVOLT_AMPERE_HOUR,
|
|
ElectricScale.KILOVOLT_AMPERE_REACTIVE_HOUR,
|
|
):
|
|
return NumericSensorDataTemplateData(
|
|
ENTITY_DESC_KEY_TOTAL_INCREASING, unit
|
|
)
|
|
# We do this because even though these are power scales, they don't meet
|
|
# the unit requirements for the power device class.
|
|
if scale_type == ElectricScale.KILOVOLT_AMPERE_REACTIVE:
|
|
return NumericSensorDataTemplateData(ENTITY_DESC_KEY_MEASUREMENT, unit)
|
|
|
|
return NumericSensorDataTemplateData(
|
|
self.find_key_from_matching_set(scale_type, METER_DEVICE_CLASS_MAP),
|
|
unit,
|
|
)
|
|
|
|
if value.command_class == CommandClass.SENSOR_MULTILEVEL:
|
|
sensor_type = get_multilevel_sensor_type(value)
|
|
scale_type = get_multilevel_sensor_scale_type(value)
|
|
unit = self.find_key_from_matching_set(
|
|
scale_type, MULTILEVEL_SENSOR_UNIT_MAP
|
|
)
|
|
if sensor_type == MultilevelSensorType.TARGET_TEMPERATURE:
|
|
return NumericSensorDataTemplateData(
|
|
ENTITY_DESC_KEY_TARGET_TEMPERATURE, unit
|
|
)
|
|
key = self.find_key_from_matching_set(
|
|
sensor_type, MULTILEVEL_SENSOR_DEVICE_CLASS_MAP
|
|
)
|
|
if key:
|
|
return NumericSensorDataTemplateData(key, unit)
|
|
|
|
return NumericSensorDataTemplateData()
|
|
|
|
|
|
@dataclass
|
|
class TiltValueMix:
|
|
"""Mixin data class for the tilt_value."""
|
|
|
|
tilt_value_id: ZwaveValueID
|
|
|
|
|
|
@dataclass
|
|
class CoverTiltDataTemplate(BaseDiscoverySchemaDataTemplate, TiltValueMix):
|
|
"""Tilt data template class for Z-Wave Cover entities."""
|
|
|
|
def resolve_data(self, value: ZwaveValue) -> dict[str, Any]:
|
|
"""Resolve helper class data for a discovered value."""
|
|
return {"tilt_value": self._get_value_from_id(value.node, self.tilt_value_id)}
|
|
|
|
def values_to_watch(self, resolved_data: dict[str, Any]) -> Iterable[ZwaveValue]:
|
|
"""Return list of all ZwaveValues resolved by helper that should be watched."""
|
|
return [resolved_data["tilt_value"]]
|
|
|
|
@staticmethod
|
|
def current_tilt_value(resolved_data: dict[str, Any]) -> ZwaveValue | None:
|
|
"""Get current tilt ZwaveValue from resolved data."""
|
|
return resolved_data["tilt_value"]
|
|
|
|
|
|
@dataclass
|
|
class FanValueMapping:
|
|
"""Data class to represent how a fan's values map to features."""
|
|
|
|
presets: dict[int, str] = field(default_factory=dict)
|
|
speeds: list[tuple[int, int]] = field(default_factory=list)
|
|
|
|
def __post_init__(self) -> None:
|
|
"""
|
|
Validate inputs.
|
|
|
|
These inputs are hardcoded in `discovery.py`, so these checks should
|
|
only fail due to developer error.
|
|
"""
|
|
assert len(self.speeds) > 0, "At least one speed must be specified"
|
|
for speed_range in self.speeds:
|
|
(low, high) = speed_range
|
|
assert high >= low, "Speed range values must be ordered"
|
|
|
|
|
|
@dataclass
|
|
class FanValueMappingDataTemplate:
|
|
"""Mixin to define `get_fan_value_mapping`."""
|
|
|
|
def get_fan_value_mapping(
|
|
self, resolved_data: dict[str, Any]
|
|
) -> FanValueMapping | None:
|
|
"""Get the value mappings for this device."""
|
|
raise NotImplementedError
|
|
|
|
|
|
@dataclass
|
|
class ConfigurableFanValueMappingValueMix:
|
|
"""Mixin data class for defining fan properties that change based on a device configuration option."""
|
|
|
|
configuration_option: ZwaveValueID
|
|
configuration_value_to_fan_value_mapping: dict[int, FanValueMapping]
|
|
|
|
|
|
@dataclass
|
|
class ConfigurableFanValueMappingDataTemplate(
|
|
BaseDiscoverySchemaDataTemplate,
|
|
FanValueMappingDataTemplate,
|
|
ConfigurableFanValueMappingValueMix,
|
|
):
|
|
"""
|
|
Gets fan speeds based on a configuration value.
|
|
|
|
Example:
|
|
ZWaveDiscoverySchema(
|
|
platform="fan",
|
|
hint="has_fan_value_mapping",
|
|
...
|
|
data_template=ConfigurableFanValueMappingDataTemplate(
|
|
configuration_option=ZwaveValueID(
|
|
property_=5, command_class=CommandClass.CONFIGURATION, endpoint=0
|
|
),
|
|
configuration_value_to_fan_value_mapping={
|
|
0: FanValueMapping(speeds=[(1,33), (34,66), (67,99)]),
|
|
1: FanValueMapping(speeds=[(1,24), (25,49), (50,74), (75,99)]),
|
|
},
|
|
),
|
|
|
|
`configuration_option` is a reference to the setting that determines which
|
|
value mapping to use (e.g., 3 speeds or 4 speeds).
|
|
|
|
`configuration_value_to_fan_value_mapping` maps the values from
|
|
`configuration_option` to the value mapping object.
|
|
"""
|
|
|
|
def resolve_data(self, value: ZwaveValue) -> dict[str, ZwaveConfigurationValue]:
|
|
"""Resolve helper class data for a discovered value."""
|
|
zwave_value: ZwaveValue = self._get_value_from_id(
|
|
value.node, self.configuration_option
|
|
)
|
|
return {"configuration_value": zwave_value}
|
|
|
|
def values_to_watch(self, resolved_data: dict[str, Any]) -> Iterable[ZwaveValue]:
|
|
"""Return list of all ZwaveValues that should be watched."""
|
|
return [
|
|
resolved_data["configuration_value"],
|
|
]
|
|
|
|
def get_fan_value_mapping(
|
|
self, resolved_data: dict[str, ZwaveConfigurationValue]
|
|
) -> FanValueMapping | None:
|
|
"""Get current fan properties from resolved data."""
|
|
zwave_value: ZwaveValue = resolved_data["configuration_value"]
|
|
|
|
if zwave_value is None:
|
|
_LOGGER.warning("Unable to read device configuration value")
|
|
return None
|
|
|
|
if zwave_value.value is None:
|
|
_LOGGER.warning("Fan configuration value is missing")
|
|
return None
|
|
|
|
fan_value_mapping = self.configuration_value_to_fan_value_mapping.get(
|
|
zwave_value.value
|
|
)
|
|
if fan_value_mapping is None:
|
|
_LOGGER.warning("Unrecognized fan configuration value")
|
|
return None
|
|
|
|
return fan_value_mapping
|
|
|
|
|
|
@dataclass
|
|
class FixedFanValueMappingValueMix:
|
|
"""Mixin data class for defining supported fan speeds."""
|
|
|
|
fan_value_mapping: FanValueMapping
|
|
|
|
|
|
@dataclass
|
|
class FixedFanValueMappingDataTemplate(
|
|
BaseDiscoverySchemaDataTemplate,
|
|
FanValueMappingDataTemplate,
|
|
FixedFanValueMappingValueMix,
|
|
):
|
|
"""
|
|
Specifies a fixed set of properties for a fan.
|
|
|
|
Example:
|
|
ZWaveDiscoverySchema(
|
|
platform="fan",
|
|
hint="has_fan_value_mapping",
|
|
...
|
|
data_template=FixedFanValueMappingDataTemplate(
|
|
config=FanValueMapping(
|
|
speeds=[(1, 32), (33, 65), (66, 99)]
|
|
)
|
|
),
|
|
),
|
|
"""
|
|
|
|
def get_fan_value_mapping(
|
|
self, resolved_data: dict[str, ZwaveConfigurationValue]
|
|
) -> FanValueMapping:
|
|
"""Get the fan properties for this device."""
|
|
return self.fan_value_mapping
|