From 2d131823ce3867de7df09c023182a804b8a29cb3 Mon Sep 17 00:00:00 2001
From: Diogo Gomes <diogogomes@gmail.com>
Date: Thu, 24 Dec 2020 01:24:11 +0000
Subject: [PATCH] Fix filter sensor None state (#44439)

Co-authored-by: Franck Nijhof <frenck@frenck.dev>
---
 homeassistant/components/filter/sensor.py | 18 +++++-
 tests/components/filter/test_sensor.py    | 73 ++++++++++++++++++++++-
 2 files changed, 87 insertions(+), 4 deletions(-)

diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py
index 72300c1621e..97d2b594cc3 100644
--- a/homeassistant/components/filter/sensor.py
+++ b/homeassistant/components/filter/sensor.py
@@ -197,7 +197,15 @@ class SensorFilter(Entity):
     @callback
     def _update_filter_sensor_state(self, new_state, update_ha=True):
         """Process device state changes."""
-        if new_state is None or new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
+        if new_state is None:
+            _LOGGER.warning(
+                "While updating filter %s, the new_state is None", self._name
+            )
+            self._state = None
+            self.async_write_ha_state()
+            return
+
+        if new_state.state in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
             self._state = new_state.state
             self.async_write_ha_state()
             return
@@ -218,7 +226,11 @@ class SensorFilter(Entity):
                     return
                 temp_state = filtered_state
         except ValueError:
-            _LOGGER.error("Could not convert state: %s to number", self._state)
+            _LOGGER.error(
+                "Could not convert state: %s (%s) to number",
+                new_state.state,
+                type(new_state.state),
+            )
             return
 
         self._state = temp_state.state
@@ -425,7 +437,7 @@ class Filter:
         """Implement a common interface for filters."""
         fstate = FilterState(new_state)
         if self._only_numbers and not isinstance(fstate.state, Number):
-            raise ValueError
+            raise ValueError(f"State <{fstate.state}> is not a Number")
 
         filtered = self._filter_state(fstate)
         filtered.set_precision(self.precision)
diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py
index 8b779696a70..1c3b4b0d672 100644
--- a/tests/components/filter/test_sensor.py
+++ b/tests/components/filter/test_sensor.py
@@ -15,7 +15,7 @@ from homeassistant.components.filter.sensor import (
     TimeThrottleFilter,
 )
 from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE
-from homeassistant.const import SERVICE_RELOAD, STATE_UNAVAILABLE
+from homeassistant.const import SERVICE_RELOAD, STATE_UNAVAILABLE, STATE_UNKNOWN
 import homeassistant.core as ha
 from homeassistant.setup import async_setup_component
 import homeassistant.util.dt as dt_util
@@ -136,6 +136,71 @@ async def test_chain_history(hass, values, missing=False):
                 assert "17.05" == state.state
 
 
+async def test_source_state_none(hass, values):
+    """Test is source sensor state is null and sets state to STATE_UNKNOWN."""
+    await async_init_recorder_component(hass)
+
+    config = {
+        "sensor": [
+            {
+                "platform": "template",
+                "sensors": {
+                    "template_test": {
+                        "value_template": "{{ states.sensor.test_state.state }}"
+                    }
+                },
+            },
+            {
+                "platform": "filter",
+                "name": "test",
+                "entity_id": "sensor.template_test",
+                "filters": [
+                    {
+                        "filter": "time_simple_moving_average",
+                        "window_size": "00:01",
+                        "precision": "2",
+                    }
+                ],
+            },
+        ]
+    }
+    await async_setup_component(hass, "sensor", config)
+    await hass.async_block_till_done()
+
+    hass.states.async_set("sensor.test_state", 0)
+
+    await hass.async_block_till_done()
+    state = hass.states.get("sensor.template_test")
+    assert state.state == "0"
+
+    await hass.async_block_till_done()
+    state = hass.states.get("sensor.test")
+    assert state.state == "0.0"
+
+    # Force Template Reload
+    yaml_path = path.join(
+        _get_fixtures_base_path(),
+        "fixtures",
+        "template/sensor_configuration.yaml",
+    )
+    with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
+        await hass.services.async_call(
+            "template",
+            SERVICE_RELOAD,
+            {},
+            blocking=True,
+        )
+        await hass.async_block_till_done()
+
+    # Template state gets to None
+    state = hass.states.get("sensor.template_test")
+    assert state is None
+
+    # Filter sensor ignores None state setting state to STATE_UNKNOWN
+    state = hass.states.get("sensor.test")
+    assert state.state == STATE_UNKNOWN
+
+
 async def test_chain_history_missing(hass, values):
     """Test if filter chaining works when recorder is enabled but the source is not recorded."""
     await test_chain_history(hass, values, missing=True)
@@ -239,6 +304,12 @@ async def test_invalid_state(hass):
         state = hass.states.get("sensor.test")
         assert state.state == STATE_UNAVAILABLE
 
+        hass.states.async_set("sensor.test_monitored", "invalid")
+        await hass.async_block_till_done()
+
+        state = hass.states.get("sensor.test")
+        assert state.state == STATE_UNAVAILABLE
+
 
 async def test_outlier(values):
     """Test if outlier filter works."""
-- 
GitLab