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,
|
||||
)
|
||||
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_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .const import (
|
||||
ATTR_METER_TYPE,
|
||||
|
@ -311,11 +311,7 @@ async def async_setup_entry(
|
|||
|
||||
entity_description = get_entity_description(data)
|
||||
|
||||
if info.platform_hint == "string_sensor":
|
||||
entities.append(
|
||||
ZWaveStringSensor(config_entry, driver, info, entity_description)
|
||||
)
|
||||
elif info.platform_hint == "numeric_sensor":
|
||||
if info.platform_hint == "numeric_sensor":
|
||||
entities.append(
|
||||
ZWaveNumericSensor(
|
||||
config_entry,
|
||||
|
@ -340,12 +336,7 @@ async def async_setup_entry(
|
|||
ZWaveMeterSensor(config_entry, driver, info, entity_description)
|
||||
)
|
||||
else:
|
||||
LOGGER.warning(
|
||||
"Sensor not implemented for %s/%s",
|
||||
info.platform_hint,
|
||||
info.primary_value.property_name,
|
||||
)
|
||||
return
|
||||
entities.append(ZwaveSensor(config_entry, driver, info, entity_description))
|
||||
|
||||
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."""
|
||||
|
||||
def __init__(
|
||||
|
@ -403,26 +394,25 @@ class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
|
|||
self._attr_force_update = True
|
||||
self._attr_name = self.generate_name(include_value_name=True)
|
||||
|
||||
|
||||
class ZWaveStringSensor(ZwaveSensorBase):
|
||||
"""Representation of a Z-Wave String sensor."""
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
def native_value(self) -> StateType:
|
||||
"""Return state of the sensor."""
|
||||
if self.info.primary_value.value is None:
|
||||
return None
|
||||
return str(self.info.primary_value.value)
|
||||
key = str(self.info.primary_value.value)
|
||||
if key not in self.info.primary_value.metadata.states:
|
||||
return self.info.primary_value.value
|
||||
return str(self.info.primary_value.metadata.states[key])
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""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:
|
||||
return None
|
||||
return str(self.info.primary_value.metadata.unit)
|
||||
|
||||
|
||||
class ZWaveNumericSensor(ZwaveSensorBase):
|
||||
class ZWaveNumericSensor(ZwaveSensor):
|
||||
"""Representation of a Z-Wave Numeric sensor."""
|
||||
|
||||
@callback
|
||||
|
@ -439,18 +429,6 @@ class ZWaveNumericSensor(ZwaveSensorBase):
|
|||
return 0
|
||||
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):
|
||||
"""Representation of a Z-Wave Meter CC sensor."""
|
||||
|
@ -458,21 +436,18 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
|
|||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, int | str] | None:
|
||||
"""Return extra state attributes."""
|
||||
if meter_type := get_meter_type(self.info.primary_value):
|
||||
meter_type = get_meter_type(self.info.primary_value)
|
||||
return {
|
||||
ATTR_METER_TYPE: meter_type.value,
|
||||
ATTR_METER_TYPE_NAME: meter_type.name,
|
||||
}
|
||||
return None
|
||||
|
||||
async def async_reset_meter(
|
||||
self, meter_type: int | None = None, value: int | None = None
|
||||
) -> None:
|
||||
"""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.")
|
||||
endpoint = self.info.primary_value.endpoint or 0
|
||||
options = {}
|
||||
if meter_type is not None:
|
||||
options[RESET_METER_OPTION_TYPE] = meter_type
|
||||
|
@ -485,35 +460,19 @@ class ZWaveMeterSensor(ZWaveNumericSensor):
|
|||
LOGGER.debug(
|
||||
"Meters on node %s endpoint %s reset with the following options: %s",
|
||||
node,
|
||||
primary_value.endpoint,
|
||||
endpoint,
|
||||
options,
|
||||
)
|
||||
|
||||
|
||||
class ZWaveListSensor(ZwaveSensorBase):
|
||||
"""Representation of a Z-Wave List 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)
|
||||
class ZWaveListSensor(ZwaveSensor):
|
||||
"""Representation of a Z-Wave Numeric sensor with multiple states."""
|
||||
|
||||
@property
|
||||
def device_class(self) -> SensorDeviceClass | None:
|
||||
"""Return sensor device class."""
|
||||
if super().device_class is not None:
|
||||
return super().device_class
|
||||
if (device_class := super().device_class) is not None:
|
||||
return device_class
|
||||
if self.info.primary_value.metadata.states:
|
||||
return SensorDeviceClass.ENUM
|
||||
return None
|
||||
|
@ -525,16 +484,6 @@ class ZWaveListSensor(ZwaveSensorBase):
|
|||
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 key not in self.info.primary_value.metadata.states:
|
||||
return key
|
||||
return str(self.info.primary_value.metadata.states[key])
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str] | None:
|
||||
"""Return the device specific state attributes."""
|
||||
|
@ -544,7 +493,7 @@ class ZWaveListSensor(ZwaveSensorBase):
|
|||
return {ATTR_VALUE: value}
|
||||
|
||||
|
||||
class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
||||
class ZWaveConfigParameterSensor(ZWaveListSensor):
|
||||
"""Representation of a Z-Wave config parameter sensor."""
|
||||
|
||||
def __init__(
|
||||
|
@ -572,8 +521,8 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
|||
@property
|
||||
def device_class(self) -> SensorDeviceClass | None:
|
||||
"""Return sensor device class."""
|
||||
if super().device_class is not None:
|
||||
return super().device_class
|
||||
if (device_class := super(ZwaveSensor, self).device_class) is not None:
|
||||
return device_class
|
||||
if (
|
||||
self._primary_value.configuration_value_type
|
||||
== ConfigurationValueType.ENUMERATED
|
||||
|
@ -581,26 +530,6 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
|||
return SensorDeviceClass.ENUM
|
||||
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
|
||||
def extra_state_attributes(self) -> dict[str, str] | None:
|
||||
"""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 homeassistant.components.sensor import (
|
||||
ATTR_OPTIONS,
|
||||
ATTR_STATE_CLASS,
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
|
@ -27,6 +28,7 @@ from homeassistant.const import (
|
|||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
PERCENTAGE,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
EntityCategory,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
|
@ -103,6 +105,30 @@ async def test_numeric_sensor(
|
|||
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||
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(
|
||||
hass: HomeAssistant, hank_binary_switch, integration
|
||||
|
@ -165,6 +191,32 @@ async def test_disabled_notification_sensor(
|
|||
assert state.state == "Motion detection"
|
||||
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(
|
||||
hass: HomeAssistant, climate_radio_thermostat_ct100_plus, integration
|
||||
|
@ -187,6 +239,53 @@ async def test_config_parameter_sensor(
|
|||
assert entity_entry
|
||||
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(
|
||||
hass: HomeAssistant, client, lock_id_lock_as_id150, integration
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue