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

View File

@ -138,7 +138,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
self._current_mode = self.get_zwave_value( self._current_mode = self.get_zwave_value(
THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE 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: for enum in ThermostatSetpointType:
self._setpoint_values[enum] = self.get_zwave_value( self._setpoint_values[enum] = self.get_zwave_value(
THERMOSTAT_SETPOINT_PROPERTY, THERMOSTAT_SETPOINT_PROPERTY,
@ -233,9 +233,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
self._hvac_presets = all_presets self._hvac_presets = all_presets
@property @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.""" """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 # Thermostat(valve) with no support for setting a mode is considered heating-only
return [ThermostatSetpointType.HEATING] return [ThermostatSetpointType.HEATING]
return THERMOSTAT_MODE_SETPOINT_MAP.get(int(self._current_mode.value), []) # type: ignore[no-any-return] 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 @property
def preset_mode(self) -> str | None: def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., home, away, temp.""" """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 # guard missing value
return None return None
if self._current_mode and int(self._current_mode.value) not in THERMOSTAT_MODES: if int(self._current_mode.value) not in THERMOSTAT_MODES:
return_val: str = self._current_mode.metadata.states.get( return_val: str = cast(
str(self._current_mode.value) str,
self._current_mode.metadata.states.get(str(self._current_mode.value)),
) )
return return_val return return_val
return PRESET_NONE return PRESET_NONE
@ -468,6 +469,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode.""" """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: if preset_mode == PRESET_NONE:
# try to restore to the (translated) main hvac mode # try to restore to the (translated) main hvac mode
await self.async_set_hvac_mode(self.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.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
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
@ -140,6 +141,8 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity):
async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) 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( await self.info.node.async_set_value(
target_value, percent_to_zwave_position(kwargs[ATTR_POSITION]) 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: async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover.""" """Open the cover."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) 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) await self.info.node.async_set_value(target_value, 99)
async def async_close_cover(self, **kwargs: Any) -> None: async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover.""" """Close cover."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) 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) await self.info.node.async_set_value(target_value, 0)
async def async_stop_cover(self, **kwargs: Any) -> None: 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. None is unknown, 0 is closed, 100 is fully open.
""" """
value = self.data_template.current_tilt_value(self.info.platform_data) 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: async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position.""" """Move the cover tilt to a specific position."""
@ -241,8 +250,10 @@ class ZwaveMotorizedBarrier(ZWaveBaseEntity, CoverEntity):
) -> None: ) -> None:
"""Initialize a ZwaveMotorizedBarrier entity.""" """Initialize a ZwaveMotorizedBarrier entity."""
super().__init__(config_entry, driver, info) super().__init__(config_entry, driver, info)
self._target_state: ZwaveValue = self.get_zwave_value( # TARGET_STATE_PROPERTY is required in the discovery schema.
TARGET_STATE_PROPERTY, add_to_watched_value_ids=False self._target_state = cast(
ZwaveValue,
self.get_zwave_value(TARGET_STATE_PROPERTY, add_to_watched_value_ids=False),
) )
@property @property

View File

@ -96,7 +96,9 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
percentage_to_ranged_value(DEFAULT_SPEED_RANGE, percentage) 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( async def async_turn_on(
self, self,
@ -110,12 +112,16 @@ class ZwaveFan(ZWaveBaseEntity, FanEntity):
elif preset_mode is not None: elif preset_mode is not None:
await self.async_set_preset_mode(preset_mode) await self.async_set_preset_mode(preset_mode)
else: 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 # 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: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off.""" """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 @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:
@ -160,14 +166,18 @@ class ValueMappingZwaveFan(ZwaveFan):
async def async_set_percentage(self, percentage: int) -> None: async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan.""" """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) 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: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode.""" """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(): for zwave_value, mapped_preset_mode in self.fan_value_mapping.presets.items():
if preset_mode == mapped_preset_mode: 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 return
raise NotValidPresetModeError( raise NotValidPresetModeError(
@ -210,7 +220,9 @@ class ValueMappingZwaveFan(ZwaveFan):
@property @property
def preset_mode(self) -> str | None: def preset_mode(self) -> str | None:
"""Return the current preset mode.""" """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 @property
def has_fan_value_mapping(self) -> bool: def has_fan_value_mapping(self) -> bool:

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any from typing import Any, cast
from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import ( from zwave_js_server.const import (
@ -24,6 +24,7 @@ from zwave_js_server.const.command_class.color_switch import (
ColorComponent, ColorComponent,
) )
from zwave_js_server.model.driver import Driver from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import Value
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
@ -301,10 +302,14 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity):
"""Set (multiple) defined colors to given value(s).""" """Set (multiple) defined colors to given value(s)."""
# prefer the (new) combined color property # prefer the (new) combined color property
# https://github.com/zwave-js/node-zwave-js/pull/1782 # https://github.com/zwave-js/node-zwave-js/pull/1782
combined_color_val = self.get_zwave_value( # Setting colors is only done if there's a target color value.
"targetColor", combined_color_val = cast(
CommandClass.SWITCH_COLOR, Value,
value_property_key=None, self.get_zwave_value(
"targetColor",
CommandClass.SWITCH_COLOR,
value_property_key=None,
),
) )
zwave_transition = 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, LOCK_CMD_CLASS_TO_PROPERTY_MAP,
DoorLockMode, DoorLockMode,
) )
from zwave_js_server.model.value import Value as ZwaveValue
from zwave_js_server.util.lock import clear_usercode, set_usercode from zwave_js_server.util.lock import clear_usercode, set_usercode
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity 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: async def _set_lock_state(self, target_state: str, **kwargs: Any) -> None:
"""Set the lock state.""" """Set the lock state."""
target_value: ZwaveValue = self.get_zwave_value( target_value = self.get_zwave_value(
LOCK_CMD_CLASS_TO_PROPERTY_MAP[self.info.primary_value.command_class] LOCK_CMD_CLASS_TO_PROPERTY_MAP[
CommandClass(self.info.primary_value.command_class)
]
) )
if target_value is not None: if target_value is not None:
await self.info.node.async_set_value( await self.info.node.async_set_value(

View File

@ -1,13 +1,17 @@
"""Support for Z-Wave controls using the number platform.""" """Support for Z-Wave controls using the number platform."""
from __future__ import annotations from __future__ import annotations
from typing import cast
from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import TARGET_VALUE_PROPERTY from zwave_js_server.const import TARGET_VALUE_PROPERTY
from zwave_js_server.model.driver import Driver 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.components.number import DOMAIN as NUMBER_DOMAIN, NumberEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
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
@ -55,6 +59,7 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity):
) -> None: ) -> None:
"""Initialize a ZwaveNumberEntity entity.""" """Initialize a ZwaveNumberEntity entity."""
super().__init__(config_entry, driver, info) super().__init__(config_entry, driver, info)
self._target_value: Value | None
if self.info.primary_value.metadata.writeable: if self.info.primary_value.metadata.writeable:
self._target_value = self.info.primary_value self._target_value = self.info.primary_value
else: else:
@ -95,7 +100,9 @@ class ZwaveNumberEntity(ZWaveBaseEntity, NumberEntity):
async def async_set_value(self, value: float) -> None: async def async_set_value(self, value: float) -> None:
"""Set new value.""" """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): class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity):
@ -106,9 +113,9 @@ class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity):
) -> None: ) -> None:
"""Initialize a ZwaveVolumeNumberEntity entity.""" """Initialize a ZwaveVolumeNumberEntity entity."""
super().__init__(config_entry, driver, info) super().__init__(config_entry, driver, info)
self.correction_factor = int( max_value = cast(int, self.info.primary_value.metadata.max)
self.info.primary_value.metadata.max - self.info.primary_value.metadata.min 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 # Fallback in case we can't properly calculate correction factor
if self.correction_factor == 0: if self.correction_factor == 0:
self.correction_factor = 1 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.components.select import DOMAIN as SELECT_DOMAIN, SelectEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -174,5 +175,7 @@ class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity):
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Change the selected option.""" """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) 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.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform 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 import DeviceInfo, EntityCategory from homeassistant.helpers.entity import DeviceInfo, EntityCategory
@ -224,7 +225,7 @@ async def async_setup_entry(
LOGGER.warning( LOGGER.warning(
"Sensor not implemented for %s/%s", "Sensor not implemented for %s/%s",
info.platform_hint, info.platform_hint,
info.primary_value.propertyname, info.primary_value.property_name,
) )
return return
@ -352,13 +353,15 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
"""Reset meter(s) on device.""" """Reset meter(s) on device."""
node = self.info.node node = self.info.node
primary_value = self.info.primary_value primary_value = self.info.primary_value
if (endpoint := primary_value.endpoint) is None:
raise HomeAssistantError("Missing endpoint on device.")
options = {} options = {}
if meter_type is not None: if meter_type is not None:
options[RESET_METER_OPTION_TYPE] = meter_type options[RESET_METER_OPTION_TYPE] = meter_type
if value is not None: if value is not None:
options[RESET_METER_OPTION_TARGET_VALUE] = 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[endpoint].async_invoke_cc_api(
CommandClass.METER, "reset", *args, wait_for_result=False CommandClass.METER, "reset", *args, wait_for_result=False
) )
LOGGER.debug( LOGGER.debug(
@ -385,11 +388,12 @@ class ZWaveListSensor(ZwaveSensorBase):
config_entry, driver, info, entity_description, unit_of_measurement config_entry, driver, info, entity_description, unit_of_measurement
) )
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes # Entity class attributes
self._attr_name = self.generate_name( self._attr_name = self.generate_name(
include_value_name=True, include_value_name=True,
alternate_value_name=self.info.primary_value.property_name, 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 @property
@ -409,8 +413,10 @@ class ZWaveListSensor(ZwaveSensorBase):
@property @property
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."""
if (value := self.info.primary_value.value) is 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 {ATTR_VALUE: self.info.primary_value.value} return {ATTR_VALUE: value}
class ZWaveConfigParameterSensor(ZwaveSensorBase): class ZWaveConfigParameterSensor(ZwaveSensorBase):
@ -430,11 +436,12 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
) )
self._primary_value = cast(ConfigurationValue, self.info.primary_value) self._primary_value = cast(ConfigurationValue, self.info.primary_value)
property_key_name = self.info.primary_value.property_key_name
# Entity class attributes # Entity class attributes
self._attr_name = self.generate_name( self._attr_name = self.generate_name(
include_value_name=True, include_value_name=True,
alternate_value_name=self.info.primary_value.property_name, 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", name_suffix="Config Parameter",
) )
@ -458,10 +465,13 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
@property @property
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."""
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 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 {ATTR_VALUE: self.info.primary_value.value} return {ATTR_VALUE: value}
class ZWaveNodeStatusSensor(SensorEntity): class ZWaveNodeStatusSensor(SensorEntity):