From 586471b5a9eaffa99b94c577a07bc7a0add8d046 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 28 Mar 2023 09:11:13 +0200 Subject: [PATCH] Improve threshold binary sensor (#88978) Improve threshold sensor --- .../components/threshold/binary_sensor.py | 58 ++++++++++------ .../threshold/test_binary_sensor.py | 68 +++++++++---------- 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/threshold/binary_sensor.py b/homeassistant/components/threshold/binary_sensor.py index 0badf7eb41f..538655ec0ce 100644 --- a/homeassistant/components/threshold/binary_sensor.py +++ b/homeassistant/components/threshold/binary_sensor.py @@ -114,6 +114,15 @@ async def async_setup_platform( ) +def _threshold_type(lower: float | None, upper: float | None) -> str: + """Return the type of threshold this sensor represents.""" + if lower is not None and upper is not None: + return TYPE_RANGE + if lower is not None: + return TYPE_LOWER + return TYPE_UPPER + + class ThresholdSensor(BinarySensorEntity): """Representation of a Threshold sensor.""" @@ -134,8 +143,11 @@ class ThresholdSensor(BinarySensorEntity): self._attr_unique_id = unique_id self._entity_id = entity_id self._name = name - self._threshold_lower = lower - self._threshold_upper = upper + if lower is not None: + self._threshold_lower = lower + if upper is not None: + self._threshold_upper = upper + self.threshold_type = _threshold_type(lower, upper) self._hysteresis: float = hysteresis self._device_class = device_class self._state_position = POSITION_UNKNOWN @@ -187,26 +199,17 @@ class ThresholdSensor(BinarySensorEntity): """Return the sensor class of the sensor.""" return self._device_class - @property - def threshold_type(self) -> str: - """Return the type of threshold this sensor represents.""" - if self._threshold_lower is not None and self._threshold_upper is not None: - return TYPE_RANGE - if self._threshold_lower is not None: - return TYPE_LOWER - return TYPE_UPPER - @property def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the sensor.""" return { ATTR_ENTITY_ID: self._entity_id, ATTR_HYSTERESIS: self._hysteresis, - ATTR_LOWER: self._threshold_lower, + ATTR_LOWER: getattr(self, "_threshold_lower", None), ATTR_POSITION: self._state_position, ATTR_SENSOR_VALUE: self.sensor_value, ATTR_TYPE: self.threshold_type, - ATTR_UPPER: self._threshold_upper, + ATTR_UPPER: getattr(self, "_threshold_upper", None), } @callback @@ -223,30 +226,42 @@ class ThresholdSensor(BinarySensorEntity): if self.sensor_value is None: self._state_position = POSITION_UNKNOWN - self._state = False + self._state = None return - if self.threshold_type == TYPE_LOWER and self._threshold_lower is not None: + if self.threshold_type == TYPE_LOWER: + if self._state is None: + self._state = False + self._state_position = POSITION_ABOVE + if below(self.sensor_value, self._threshold_lower): self._state_position = POSITION_BELOW self._state = True elif above(self.sensor_value, self._threshold_lower): self._state_position = POSITION_ABOVE self._state = False + return + + if self.threshold_type == TYPE_UPPER: + assert self._threshold_upper is not None + + if self._state is None: + self._state = False + self._state_position = POSITION_BELOW - if self.threshold_type == TYPE_UPPER and self._threshold_upper is not None: if above(self.sensor_value, self._threshold_upper): self._state_position = POSITION_ABOVE self._state = True elif below(self.sensor_value, self._threshold_upper): self._state_position = POSITION_BELOW self._state = False + return + + if self.threshold_type == TYPE_RANGE: + if self._state is None: + self._state = True + self._state_position = POSITION_IN_RANGE - if ( - self.threshold_type == TYPE_RANGE - and self._threshold_lower is not None - and self._threshold_upper is not None - ): if below(self.sensor_value, self._threshold_lower): self._state_position = POSITION_BELOW self._state = False @@ -258,3 +273,4 @@ class ThresholdSensor(BinarySensorEntity): ): self._state_position = POSITION_IN_RANGE self._state = True + return diff --git a/tests/components/threshold/test_binary_sensor.py b/tests/components/threshold/test_binary_sensor.py index eed3a8a40e0..9e11195d878 100644 --- a/tests/components/threshold/test_binary_sensor.py +++ b/tests/components/threshold/test_binary_sensor.py @@ -29,8 +29,8 @@ async def test_sensor_upper(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test_monitored", 15) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "below" + assert state.state == "off" hass.states.async_set( "sensor.test_monitored", @@ -63,12 +63,12 @@ async def test_sensor_upper(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" hass.states.async_set("sensor.test_monitored", 15) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" + assert state.attributes["position"] == "below" assert state.state == "off" @@ -89,8 +89,8 @@ async def test_sensor_lower(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test_monitored", 15) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "above" + assert state.state == "off" hass.states.async_set("sensor.test_monitored", 16) await hass.async_block_till_done() @@ -117,12 +117,12 @@ async def test_sensor_lower(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" hass.states.async_set("sensor.test_monitored", 15) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" + assert state.attributes["position"] == "above" assert state.state == "off" @@ -144,15 +144,15 @@ async def test_sensor_upper_hysteresis(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test_monitored", 17.5) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "below" + assert state.state == "off" # Set the monitored sensor's state to the threshold - hysteresis hass.states.async_set("sensor.test_monitored", 12.5) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "below" + assert state.state == "off" hass.states.async_set("sensor.test_monitored", 20) await hass.async_block_till_done() @@ -192,7 +192,7 @@ async def test_sensor_upper_hysteresis(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" hass.states.async_set("sensor.test_monitored", 18) await hass.async_block_till_done() @@ -219,15 +219,15 @@ async def test_sensor_lower_hysteresis(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test_monitored", 17.5) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "above" + assert state.state == "off" # Set the monitored sensor's state to the threshold - hysteresis hass.states.async_set("sensor.test_monitored", 12.5) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "above" + assert state.state == "off" hass.states.async_set("sensor.test_monitored", 20) await hass.async_block_till_done() @@ -267,7 +267,7 @@ async def test_sensor_lower_hysteresis(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" hass.states.async_set("sensor.test_monitored", 18) await hass.async_block_till_done() @@ -294,15 +294,15 @@ async def test_sensor_in_range_no_hysteresis(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test_monitored", 10) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "in_range" + assert state.state == "on" # Set the monitored sensor's state to the upper threshold hass.states.async_set("sensor.test_monitored", 20) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "in_range" + assert state.state == "on" hass.states.async_set( "sensor.test_monitored", @@ -336,7 +336,7 @@ async def test_sensor_in_range_no_hysteresis(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" hass.states.async_set("sensor.test_monitored", 21) await hass.async_block_till_done() @@ -364,29 +364,29 @@ async def test_sensor_in_range_with_hysteresis(hass: HomeAssistant) -> None: hass.states.async_set("sensor.test_monitored", 8) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "in_range" + assert state.state == "on" # Set the monitored sensor's state to the lower threshold + hysteresis hass.states.async_set("sensor.test_monitored", 12) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "in_range" + assert state.state == "on" # Set the monitored sensor's state to the upper threshold + hysteresis hass.states.async_set("sensor.test_monitored", 22) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "in_range" + assert state.state == "on" # Set the monitored sensor's state to the upper threshold - hysteresis hass.states.async_set("sensor.test_monitored", 18) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") - assert state.attributes["position"] == "unknown" - assert state.state == "unknown" + assert state.attributes["position"] == "in_range" + assert state.state == "on" hass.states.async_set( "sensor.test_monitored", @@ -460,7 +460,7 @@ async def test_sensor_in_range_with_hysteresis(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" hass.states.async_set("sensor.test_monitored", 17) await hass.async_block_till_done() @@ -507,13 +507,13 @@ async def test_sensor_in_range_unknown_state( await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" hass.states.async_set("sensor.test_monitored", STATE_UNAVAILABLE) await hass.async_block_till_done() state = hass.states.get("binary_sensor.threshold") assert state.attributes["position"] == "unknown" - assert state.state == "off" + assert state.state == "unknown" assert "State is not numerical" not in caplog.text