Refactor zwave_js.sensor and add test coverage (#93259)
* Refactor zwave_js.sensor and add test coverage * use walrus * inherit config parameter class from list class * use walrus in more places * improve config parameter test
This commit is contained in:
parent
d6997d8656
commit
e1dd7118e0
2 changed files with 125 additions and 97 deletions
|
@ -38,10 +38,10 @@ from homeassistant.const import (
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
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_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_METER_TYPE,
|
ATTR_METER_TYPE,
|
||||||
|
@ -311,11 +311,7 @@ async def async_setup_entry(
|
||||||
|
|
||||||
entity_description = get_entity_description(data)
|
entity_description = get_entity_description(data)
|
||||||
|
|
||||||
if info.platform_hint == "string_sensor":
|
if info.platform_hint == "numeric_sensor":
|
||||||
entities.append(
|
|
||||||
ZWaveStringSensor(config_entry, driver, info, entity_description)
|
|
||||||
)
|
|
||||||
elif info.platform_hint == "numeric_sensor":
|
|
||||||
entities.append(
|
entities.append(
|
||||||
ZWaveNumericSensor(
|
ZWaveNumericSensor(
|
||||||
config_entry,
|
config_entry,
|
||||||
|
@ -340,12 +336,7 @@ async def async_setup_entry(
|
||||||
ZWaveMeterSensor(config_entry, driver, info, entity_description)
|
ZWaveMeterSensor(config_entry, driver, info, entity_description)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
LOGGER.warning(
|
entities.append(ZwaveSensor(config_entry, driver, info, entity_description))
|
||||||
"Sensor not implemented for %s/%s",
|
|
||||||
info.platform_hint,
|
|
||||||
info.primary_value.property_name,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
@ -383,7 +374,7 @@ async def async_setup_entry(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
|
class ZwaveSensor(ZWaveBaseEntity, SensorEntity):
|
||||||
"""Basic Representation of a Z-Wave sensor."""
|
"""Basic Representation of a Z-Wave sensor."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -403,26 +394,25 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
|
||||||
self._attr_force_update = True
|
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)
|
||||||
|
|
||||||
|
|
||||||
class ZWaveStringSensor(ZwaveSensorBase):
|
|
||||||
"""Representation of a Z-Wave String sensor."""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> str | None:
|
def native_value(self) -> StateType:
|
||||||
"""Return state of the sensor."""
|
"""Return state of the sensor."""
|
||||||
if self.info.primary_value.value is None:
|
key = str(self.info.primary_value.value)
|
||||||
return None
|
if key not in self.info.primary_value.metadata.states:
|
||||||
return str(self.info.primary_value.value)
|
return self.info.primary_value.value
|
||||||
|
return str(self.info.primary_value.metadata.states[key])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self) -> str | None:
|
def native_unit_of_measurement(self) -> str | None:
|
||||||
"""Return unit of measurement the value is expressed in."""
|
"""Return unit of measurement the value is expressed in."""
|
||||||
|
if (unit := super().native_unit_of_measurement) is not None:
|
||||||
|
return unit
|
||||||
if self.info.primary_value.metadata.unit is None:
|
if self.info.primary_value.metadata.unit is None:
|
||||||
return None
|
return None
|
||||||
return str(self.info.primary_value.metadata.unit)
|
return str(self.info.primary_value.metadata.unit)
|
||||||
|
|
||||||
|
|
||||||
class ZWaveNumericSensor(ZwaveSensorBase):
|
class ZWaveNumericSensor(ZwaveSensor):
|
||||||
"""Representation of a Z-Wave Numeric sensor."""
|
"""Representation of a Z-Wave Numeric sensor."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -439,18 +429,6 @@ class ZWaveNumericSensor(ZwaveSensorBase):
|
||||||
return 0
|
return 0
|
||||||
return round(float(self.info.primary_value.value), 2)
|
return round(float(self.info.primary_value.value), 2)
|
||||||
|
|
||||||
@property
|
|
||||||
def native_unit_of_measurement(self) -> str | None:
|
|
||||||
"""Return unit of measurement the value is expressed in."""
|
|
||||||
if self.entity_description.native_unit_of_measurement is not None:
|
|
||||||
return self.entity_description.native_unit_of_measurement
|
|
||||||
if self._attr_native_unit_of_measurement is not None:
|
|
||||||
return self._attr_native_unit_of_measurement
|
|
||||||
if self.info.primary_value.metadata.unit is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return str(self.info.primary_value.metadata.unit)
|
|
||||||
|
|
||||||
|
|
||||||
class ZWaveMeterSensor(ZWaveNumericSensor):
|
class ZWaveMeterSensor(ZWaveNumericSensor):
|
||||||
"""Representation of a Z-Wave Meter CC sensor."""
|
"""Representation of a Z-Wave Meter CC sensor."""
|
||||||
|
@ -458,21 +436,18 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> Mapping[str, int | str] | None:
|
def extra_state_attributes(self) -> Mapping[str, int | str] | None:
|
||||||
"""Return extra state attributes."""
|
"""Return extra state attributes."""
|
||||||
if meter_type := get_meter_type(self.info.primary_value):
|
meter_type = get_meter_type(self.info.primary_value)
|
||||||
return {
|
return {
|
||||||
ATTR_METER_TYPE: meter_type.value,
|
ATTR_METER_TYPE: meter_type.value,
|
||||||
ATTR_METER_TYPE_NAME: meter_type.name,
|
ATTR_METER_TYPE_NAME: meter_type.name,
|
||||||
}
|
}
|
||||||
return None
|
|
||||||
|
|
||||||
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
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Reset meter(s) on device."""
|
"""Reset meter(s) on device."""
|
||||||
node = self.info.node
|
node = self.info.node
|
||||||
primary_value = self.info.primary_value
|
endpoint = self.info.primary_value.endpoint or 0
|
||||||
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
|
||||||
|
@ -485,35 +460,19 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Meters on node %s endpoint %s reset with the following options: %s",
|
"Meters on node %s endpoint %s reset with the following options: %s",
|
||||||
node,
|
node,
|
||||||
primary_value.endpoint,
|
endpoint,
|
||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ZWaveListSensor(ZwaveSensorBase):
|
class ZWaveListSensor(ZwaveSensor):
|
||||||
"""Representation of a Z-Wave List sensor with multiple states."""
|
"""Representation of a Z-Wave Numeric sensor with multiple states."""
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config_entry: ConfigEntry,
|
|
||||||
driver: Driver,
|
|
||||||
info: ZwaveDiscoveryInfo,
|
|
||||||
entity_description: SensorEntityDescription,
|
|
||||||
unit_of_measurement: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a ZWaveListSensor entity."""
|
|
||||||
super().__init__(
|
|
||||||
config_entry, driver, info, entity_description, unit_of_measurement
|
|
||||||
)
|
|
||||||
|
|
||||||
# Entity class attributes
|
|
||||||
self._attr_name = self.generate_name(include_value_name=True)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self) -> SensorDeviceClass | None:
|
def device_class(self) -> SensorDeviceClass | None:
|
||||||
"""Return sensor device class."""
|
"""Return sensor device class."""
|
||||||
if super().device_class is not None:
|
if (device_class := super().device_class) is not None:
|
||||||
return super().device_class
|
return device_class
|
||||||
if self.info.primary_value.metadata.states:
|
if self.info.primary_value.metadata.states:
|
||||||
return SensorDeviceClass.ENUM
|
return SensorDeviceClass.ENUM
|
||||||
return None
|
return None
|
||||||
|
@ -525,16 +484,6 @@ class ZWaveListSensor(ZwaveSensorBase):
|
||||||
return list(self.info.primary_value.metadata.states.values())
|
return list(self.info.primary_value.metadata.states.values())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def native_value(self) -> str | None:
|
|
||||||
"""Return state of the sensor."""
|
|
||||||
if self.info.primary_value.value is None:
|
|
||||||
return None
|
|
||||||
key = str(self.info.primary_value.value)
|
|
||||||
if key not in self.info.primary_value.metadata.states:
|
|
||||||
return key
|
|
||||||
return str(self.info.primary_value.metadata.states[key])
|
|
||||||
|
|
||||||
@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."""
|
||||||
|
@ -544,7 +493,7 @@ class ZWaveListSensor(ZwaveSensorBase):
|
||||||
return {ATTR_VALUE: value}
|
return {ATTR_VALUE: value}
|
||||||
|
|
||||||
|
|
||||||
class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
class ZWaveConfigParameterSensor(ZWaveListSensor):
|
||||||
"""Representation of a Z-Wave config parameter sensor."""
|
"""Representation of a Z-Wave config parameter sensor."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -572,8 +521,8 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
||||||
@property
|
@property
|
||||||
def device_class(self) -> SensorDeviceClass | None:
|
def device_class(self) -> SensorDeviceClass | None:
|
||||||
"""Return sensor device class."""
|
"""Return sensor device class."""
|
||||||
if super().device_class is not None:
|
if (device_class := super(ZwaveSensor, self).device_class) is not None:
|
||||||
return super().device_class
|
return device_class
|
||||||
if (
|
if (
|
||||||
self._primary_value.configuration_value_type
|
self._primary_value.configuration_value_type
|
||||||
== ConfigurationValueType.ENUMERATED
|
== ConfigurationValueType.ENUMERATED
|
||||||
|
@ -581,26 +530,6 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
||||||
return SensorDeviceClass.ENUM
|
return SensorDeviceClass.ENUM
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def options(self) -> list[str] | None:
|
|
||||||
"""Return options for enum sensor."""
|
|
||||||
if self.device_class == SensorDeviceClass.ENUM:
|
|
||||||
return list(self.info.primary_value.metadata.states.values())
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_value(self) -> str | None:
|
|
||||||
"""Return state of the sensor."""
|
|
||||||
if self.info.primary_value.value is None:
|
|
||||||
return None
|
|
||||||
key = str(self.info.primary_value.value)
|
|
||||||
if (
|
|
||||||
self._primary_value.configuration_value_type == ConfigurationValueType.RANGE
|
|
||||||
or (key not in self.info.primary_value.metadata.states)
|
|
||||||
):
|
|
||||||
return key
|
|
||||||
return str(self.info.primary_value.metadata.states[key])
|
|
||||||
|
|
||||||
@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."""
|
||||||
|
|
|
@ -7,6 +7,7 @@ from zwave_js_server.event import Event
|
||||||
from zwave_js_server.model.node import Node
|
from zwave_js_server.model.node import Node
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
|
ATTR_OPTIONS,
|
||||||
ATTR_STATE_CLASS,
|
ATTR_STATE_CLASS,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
|
@ -27,6 +28,7 @@ from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
EntityCategory,
|
EntityCategory,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
|
@ -103,6 +105,30 @@ async def test_numeric_sensor(
|
||||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
|
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"value updated",
|
||||||
|
{
|
||||||
|
"source": "node",
|
||||||
|
"event": "value updated",
|
||||||
|
"nodeId": express_controls_ezmultipli.node_id,
|
||||||
|
"args": {
|
||||||
|
"commandClassName": "Multilevel Sensor",
|
||||||
|
"commandClass": 49,
|
||||||
|
"endpoint": 0,
|
||||||
|
"property": "Illuminance",
|
||||||
|
"propertyName": "Illuminance",
|
||||||
|
"newValue": None,
|
||||||
|
"prevValue": 61,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
express_controls_ezmultipli.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("sensor.hsm200_illuminance")
|
||||||
|
assert state
|
||||||
|
assert state.state == "0"
|
||||||
|
|
||||||
|
|
||||||
async def test_energy_sensors(
|
async def test_energy_sensors(
|
||||||
hass: HomeAssistant, hank_binary_switch, integration
|
hass: HomeAssistant, hank_binary_switch, integration
|
||||||
|
@ -165,6 +191,32 @@ async def test_disabled_notification_sensor(
|
||||||
assert state.state == "Motion detection"
|
assert state.state == "Motion detection"
|
||||||
assert state.attributes["value"] == 8
|
assert state.attributes["value"] == 8
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"value updated",
|
||||||
|
{
|
||||||
|
"source": "node",
|
||||||
|
"event": "value updated",
|
||||||
|
"nodeId": multisensor_6.node_id,
|
||||||
|
"args": {
|
||||||
|
"commandClassName": "Notification",
|
||||||
|
"commandClass": 113,
|
||||||
|
"endpoint": 0,
|
||||||
|
"property": "Home Security",
|
||||||
|
"propertyKey": "Motion sensor status",
|
||||||
|
"newValue": None,
|
||||||
|
"prevValue": 0,
|
||||||
|
"propertyName": "Home Security",
|
||||||
|
"propertyKeyName": "Motion sensor status",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
multisensor_6.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get(NOTIFICATION_MOTION_SENSOR)
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
async def test_disabled_indcator_sensor(
|
async def test_disabled_indcator_sensor(
|
||||||
hass: HomeAssistant, climate_radio_thermostat_ct100_plus, integration
|
hass: HomeAssistant, climate_radio_thermostat_ct100_plus, integration
|
||||||
|
@ -187,6 +239,53 @@ async def test_config_parameter_sensor(
|
||||||
assert entity_entry
|
assert entity_entry
|
||||||
assert entity_entry.disabled
|
assert entity_entry.disabled
|
||||||
|
|
||||||
|
updated_entry = ent_reg.async_update_entity(
|
||||||
|
entity_entry.entity_id, **{"disabled_by": None}
|
||||||
|
)
|
||||||
|
assert updated_entry != entity_entry
|
||||||
|
assert updated_entry.disabled is False
|
||||||
|
|
||||||
|
# reload integration and check if entity is correctly there
|
||||||
|
await hass.config_entries.async_reload(integration.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
|
||||||
|
assert state
|
||||||
|
assert state.state == "Disable Away Manual Lock"
|
||||||
|
assert state.attributes[ATTR_VALUE] == 0
|
||||||
|
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENUM
|
||||||
|
assert state.attributes[ATTR_OPTIONS] == [
|
||||||
|
"Disable Away Manual Lock",
|
||||||
|
"Disable Away Auto Lock",
|
||||||
|
"Enable Away Manual Lock",
|
||||||
|
"Enable Away Auto Lock",
|
||||||
|
]
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"value updated",
|
||||||
|
{
|
||||||
|
"source": "node",
|
||||||
|
"event": "value updated",
|
||||||
|
"nodeId": lock_id_lock_as_id150.node_id,
|
||||||
|
"args": {
|
||||||
|
"commandClassName": "Configuration",
|
||||||
|
"commandClass": 112,
|
||||||
|
"endpoint": 0,
|
||||||
|
"property": 1,
|
||||||
|
"newValue": None,
|
||||||
|
"prevValue": 0,
|
||||||
|
"propertyName": "Door lock mode",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
lock_id_lock_as_id150.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
assert ATTR_VALUE not in state.attributes
|
||||||
|
|
||||||
|
|
||||||
async def test_node_status_sensor(
|
async def test_node_status_sensor(
|
||||||
hass: HomeAssistant, client, lock_id_lock_as_id150, integration
|
hass: HomeAssistant, client, lock_id_lock_as_id150, integration
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue