Clean zwave_js platform typing (#72439)

* Fix binary sensor

* Fix climate

* Fix cover

* Fix fan

* Fix light

* Fix lock

* Fix number

* Fix select

* Fix sensor

* Add back type ignore until library bump
This commit is contained in:
Martin Hjelmare 2022-05-25 01:23:34 +02:00 committed by GitHub
parent 777c9c08ff
commit 6cac1dadeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 97 additions and 40 deletions

View file

@ -248,7 +248,7 @@ PROPERTY_SENSOR_MAPPINGS: dict[str, PropertyZWaveJSEntityDescription] = {
# Mappings for boolean sensors
BOOLEAN_SENSOR_MAPPINGS: dict[str, BinarySensorEntityDescription] = {
BOOLEAN_SENSOR_MAPPINGS: dict[int, BinarySensorEntityDescription] = {
CommandClass.BATTERY: BinarySensorEntityDescription(
key=str(CommandClass.BATTERY),
device_class=BinarySensorDeviceClass.BATTERY,
@ -304,9 +304,13 @@ async def async_setup_entry(
config_entry, driver, info, state_key, notification_description
)
)
elif info.platform_hint == "property" and (
property_description := PROPERTY_SENSOR_MAPPINGS.get(
info.primary_value.property_name
elif (
info.platform_hint == "property"
and info.primary_value.property_name
and (
property_description := PROPERTY_SENSOR_MAPPINGS.get(
info.primary_value.property_name
)
)
):
entities.append(

View file

@ -138,7 +138,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
self._current_mode = self.get_zwave_value(
THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE
)
self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue] = {}
self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue | None] = {}
for enum in ThermostatSetpointType:
self._setpoint_values[enum] = self.get_zwave_value(
THERMOSTAT_SETPOINT_PROPERTY,
@ -233,9 +233,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
self._hvac_presets = all_presets
@property
def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType | None]:
def _current_mode_setpoint_enums(self) -> list[ThermostatSetpointType]:
"""Return the list of enums that are relevant to the current thermostat mode."""
if self._current_mode is None:
if self._current_mode is None or self._current_mode.value is None:
# Thermostat(valve) with no support for setting a mode is considered heating-only
return [ThermostatSetpointType.HEATING]
return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), []) # type: ignore[no-any-return]
@ -329,12 +329,13 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., home, away, temp."""
if self._current_mode and self._current_mode.value is None:
if self._current_mode is None or self._current_mode.value is None:
# guard missing value
return None
if self._current_mode and int(self._current_mode.value) not in THERMOSTAT_MODES:
return_val: str = self._current_mode.metadata.states.get(
str(self._current_mode.value)
if int(self._current_mode.value) not in THERMOSTAT_MODES:
return_val: str = cast(
str,
self._current_mode.metadata.states.get(str(self._current_mode.value)),
)
return return_val
return PRESET_NONE
@ -468,6 +469,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode."""
if self._current_mode is None:
# Thermostat(valve) has no support for setting a mode, so we make it a no-op
return
if preset_mode == PRESET_NONE:
# try to restore to the (translated) main hvac mode
await self.async_set_hvac_mode(self.hvac_mode)

View file

@ -28,6 +28,7 @@ from homeassistant.components.cover import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -140,6 +141,8 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity):
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
if target_value is None:
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(
target_value, percent_to_zwave_position(kwargs[ATTR_POSITION])
)
@ -147,11 +150,15 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
if target_value is None:
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, 99)
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
if target_value is None:
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, 0)
async def async_stop_cover(self, **kwargs: Any) -> None:
@ -207,7 +214,9 @@ class ZWaveTiltCover(ZWaveCover):
None is unknown, 0 is closed, 100 is fully open.
"""
value = self.data_template.current_tilt_value(self.info.platform_data)
return zwave_tilt_to_percent(value.value) if value else None
if value is None or value.value is None:
return None
return zwave_tilt_to_percent(int(value.value))
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position."""
@ -241,8 +250,10 @@ class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity):
) -> None:
"""Initialize a ZwaveMotorizedBarrier entity."""
super().__init__(config_entry, driver, info)
self._target_state: ZwaveValue = self.get_zwave_value(
TARGET_STATE_PROPERTY, add_to_watched_value_ids=False
# TARGET_STATE_PROPERTY is required in the discovery schema.
self._target_state = cast(
ZwaveValue,
self.get_zwave_value(TARGET_STATE_PROPERTY, add_to_watched_value_ids=False),
)
@property

View file

@ -96,7 +96,9 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage)
)
await self.info.node.async_set_value(self._target_value, zwave_speed)
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, zwave_speed)
async def async_turn_on(
self,
@ -110,12 +112,16 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
elif preset_mode is not None:
await self.async_set_preset_mode(preset_mode)
else:
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
# Value 255 tells device to return to previous value
await self.info.node.async_set_value(self._target_value, 255)
await self.info.node.async_set_value(target_value, 255)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
await self.info.node.async_set_value(self._target_value, 0)
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, 0)
@property
def is_on(self) -> bool | None:
@ -160,14 +166,18 @@ class ValueMappingZwaveFan(ZwaveFan):
async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan."""
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
zwave_speed = self.percentage_to_zwave_speed(percentage)
await self.info.node.async_set_value(self._target_value, zwave_speed)
await self.info.node.async_set_value(target_value, zwave_speed)
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
for zwave_value, mapped_preset_mode in self.fan_value_mapping.presets.items():
if preset_mode == mapped_preset_mode:
await self.info.node.async_set_value(self._target_value, zwave_value)
await self.info.node.async_set_value(target_value, zwave_value)
return
raise NotValidPresetModeError(
@ -210,7 +220,9 @@ class ValueMappingZwaveFan(ZwaveFan):
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode."""
return self.fan_value_mapping.presets.get(self.info.primary_value.value)
if (value := self.info.primary_value.value) is None:
return None
return self.fan_value_mapping.presets.get(value)
@property
def has_fan_value_mapping(self) -> bool:

View file

@ -2,7 +2,7 @@
from __future__ import annotations
import logging
from typing import Any
from typing import Any, cast
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import (
@ -24,6 +24,7 @@ from zwave_js_server.const.command_class.color_switch import (
ColorComponent,
)
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import Value
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@ -301,10 +302,14 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
"""Set (multiple) defined colors to given value(s)."""
# prefer the (new) combined color property
# https://github.com/zwave-js/node-zwave-js/pull/1782
combined_color_val = self.get_zwave_value(
"targetColor",
CommandClass.SWITCH_COLOR,
value_property_key=None,
# Setting colors is only done if there's a target color value.
combined_color_val = cast(
Value,
self.get_zwave_value(
"targetColor",
CommandClass.SWITCH_COLOR,
value_property_key=None,
),
)
zwave_transition = None

View file

@ -14,7 +14,6 @@ from zwave_js_server.const.command_class.lock import (
LOCK_CMD_CLASS_TO_PROPERTY_MAP,
DoorLockMode,
)
from zwave_js_server.model.value import Value as ZwaveValue
from zwave_js_server.util.lock import clear_usercode, set_usercode
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity
@ -111,8 +110,10 @@ class ZWaveLock(ZWaveBaseEntity, LockEntity):
async def _set_lock_state(self, target_state: str, **kwargs: Any) -> None:
"""Set the lock state."""
target_value: ZwaveValue = self.get_zwave_value(
LOCK_CMD_CLASS_TO_PROPERTY_MAP[self.info.primary_value.command_class]
target_value = self.get_zwave_value(
LOCK_CMD_CLASS_TO_PROPERTY_MAP[
CommandClass(self.info.primary_value.command_class)
]
)
if target_value is not None:
await self.info.node.async_set_value(

View file

@ -1,13 +1,17 @@
"""Support for Z-Wave controls using the number platform."""
from __future__ import annotations
from typing import cast
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import TARGET_VALUE_PROPERTY
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import Value
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN, NumberEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -55,6 +59,7 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity):
) -> None:
"""Initialize a ZwaveNumberEntity entity."""
super().__init__(config_entry, driver, info)
self._target_value: Value | None
if self.info.primary_value.metadata.writeable:
self._target_value = self.info.primary_value
else:
@ -95,7 +100,9 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity):
async def async_set_value(self, value: float) -> None:
"""Set new value."""
await self.info.node.async_set_value(self._target_value, value)
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, value)
class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity):
@ -106,9 +113,9 @@ class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity):
) -> None:
"""Initialize a ZwaveVolumeNumberEntity entity."""
super().__init__(config_entry, driver, info)
self.correction_factor = int(
self.info.primary_value.metadata.max - self.info.primary_value.metadata.min
)
max_value = cast(int, self.info.primary_value.metadata.max)
min_value = cast(int, self.info.primary_value.metadata.min)
self.correction_factor = max_value - min_value
# Fallback in case we can't properly calculate correction factor
if self.correction_factor == 0:
self.correction_factor = 1

View file

@ -11,6 +11,7 @@ from zwave_js_server.model.driver import Driver
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN, SelectEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -174,5 +175,7 @@ class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
if (target_value := self._target_value) is None:
raise HomeAssistantError("Missing target value on device.")
key = next(key for key, val in self._lookup_map.items() if val == option)
await self.info.node.async_set_value(self._target_value, int(key))
await self.info.node.async_set_value(target_value, int(key))

View file

@ -26,6 +26,7 @@ from homeassistant.components.sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
@ -224,7 +225,7 @@ async def async_setup_entry(
LOGGER.warning(
"Sensor not implemented for %s/%s",
info.platform_hint,
info.primary_value.propertyname,
info.primary_value.property_name,
)
return
@ -352,13 +353,15 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
"""Reset meter(s) on device."""
node = self.info.node
primary_value = self.info.primary_value
if (endpoint := primary_value.endpoint) is None:
raise HomeAssistantError("Missing endpoint on device.")
options = {}
if meter_type is not None:
options[RESET_METER_OPTION_TYPE] = meter_type
if value is not None:
options[RESET_METER_OPTION_TARGET_VALUE] = value
args = [options] if options else []
await node.endpoints[primary_value.endpoint].async_invoke_cc_api(
await node.endpoints[endpoint].async_invoke_cc_api(
CommandClass.METER, "reset", *args, wait_for_result=False
)
LOGGER.debug(
@ -385,11 +388,12 @@ class ZWaveListSensor(ZwaveSensorBase):
config_entry, driver, info, entity_description, unit_of_measurement
)
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes
self._attr_name = self.generate_name(
include_value_name=True,
alternate_value_name=self.info.primary_value.property_name,
additional_info=[self.info.primary_value.property_key_name],
additional_info=[property_key_name] if property_key_name else None,
)
@property
@ -409,8 +413,10 @@ class ZWaveListSensor(ZwaveSensorBase):
@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the device specific state attributes."""
if (value := self.info.primary_value.value) is None:
return None
# add the value's int value as property for multi-value (list) items
return {ATTR_VALUE: self.info.primary_value.value}
return {ATTR_VALUE: value}
class ZWaveConfigParameterSensor(ZwaveSensorBase):
@ -430,11 +436,12 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
)
self._primary_value = cast(ConfigurationValue, self.info.primary_value)
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes
self._attr_name = self.generate_name(
include_value_name=True,
alternate_value_name=self.info.primary_value.property_name,
additional_info=[self.info.primary_value.property_key_name],
additional_info=[property_key_name] if property_key_name else None,
name_suffix="Config Parameter",
)
@ -458,10 +465,13 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
@property
def extra_state_attributes(self) -> dict[str, str] | None:
"""Return the device specific state attributes."""
if self._primary_value.configuration_value_type == ConfigurationValueType.RANGE:
if (
self._primary_value.configuration_value_type == ConfigurationValueType.RANGE
or (value := self.info.primary_value.value) is None
):
return None
# add the value's int value as property for multi-value (list) items
return {ATTR_VALUE: self.info.primary_value.value}
return {ATTR_VALUE: value}
class ZWaveNodeStatusSensor(SensorEntity):