From a37c3af2b40f9b6ee7bdbf3b8a7011d53a01765c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 4 Sep 2021 00:23:35 -0700 Subject: [PATCH 1/6] better detect legacy eagly devices (#55706) --- homeassistant/components/rainforest_eagle/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/rainforest_eagle/data.py b/homeassistant/components/rainforest_eagle/data.py index 76ddb2d25d7..70c2bddb4b3 100644 --- a/homeassistant/components/rainforest_eagle/data.py +++ b/homeassistant/components/rainforest_eagle/data.py @@ -54,7 +54,7 @@ async def async_get_type(hass, cloud_id, install_code, host): meters = await hub.get_device_list() except aioeagle.BadAuth as err: raise InvalidAuth from err - except aiohttp.ClientError: + except (KeyError, aiohttp.ClientError): # This can happen if it's an eagle-100 meters = None From a4f2c5583da896f3272f6a2565b7e3cb0cc461e7 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 4 Sep 2021 10:47:42 +0200 Subject: [PATCH 2/6] Handle negative numbers in sensor long term statistics (#55708) * Handle negative numbers in sensor long term statistics * Use negative states in tests --- homeassistant/components/sensor/recorder.py | 44 +++++++------- tests/components/sensor/test_recorder.py | 64 ++++++++++----------- 2 files changed, 52 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index 0054b01abd2..8bf251ffb18 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -129,13 +129,6 @@ def _get_entities(hass: HomeAssistant) -> list[tuple[str, str, str | None]]: return entity_ids -# Faster than try/except -# From https://stackoverflow.com/a/23639915 -def _is_number(s: str) -> bool: # pylint: disable=invalid-name - """Return True if string is a number.""" - return s.replace(".", "", 1).isdigit() - - def _time_weighted_average( fstates: list[tuple[float, State]], start: datetime.datetime, end: datetime.datetime ) -> float: @@ -190,9 +183,13 @@ def _normalize_states( if device_class not in UNIT_CONVERSIONS: # We're not normalizing this device class, return the state as they are - fstates = [ - (float(el.state), el) for el in entity_history if _is_number(el.state) - ] + fstates = [] + for state in entity_history: + try: + fstates.append((float(state.state), state)) + except ValueError: + continue + if fstates: all_units = _get_units(fstates) if len(all_units) > 1: @@ -220,23 +217,22 @@ def _normalize_states( fstates = [] for state in entity_history: - # Exclude non numerical states from statistics - if not _is_number(state.state): - continue + try: + fstate = float(state.state) + unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + # Exclude unsupported units from statistics + if unit not in UNIT_CONVERSIONS[device_class]: + if WARN_UNSUPPORTED_UNIT not in hass.data: + hass.data[WARN_UNSUPPORTED_UNIT] = set() + if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]: + hass.data[WARN_UNSUPPORTED_UNIT].add(entity_id) + _LOGGER.warning("%s has unknown unit %s", entity_id, unit) + continue - fstate = float(state.state) - unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - # Exclude unsupported units from statistics - if unit not in UNIT_CONVERSIONS[device_class]: - if WARN_UNSUPPORTED_UNIT not in hass.data: - hass.data[WARN_UNSUPPORTED_UNIT] = set() - if entity_id not in hass.data[WARN_UNSUPPORTED_UNIT]: - hass.data[WARN_UNSUPPORTED_UNIT].add(entity_id) - _LOGGER.warning("%s has unknown unit %s", entity_id, unit) + fstates.append((UNIT_CONVERSIONS[device_class][unit](fstate), state)) + except ValueError: continue - fstates.append((UNIT_CONVERSIONS[device_class][unit](fstate), state)) - return DEVICE_CLASS_UNITS[device_class], fstates diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index 115473c23de..aeeab317eb1 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -50,18 +50,18 @@ GAS_SENSOR_ATTRIBUTES = { @pytest.mark.parametrize( "device_class,unit,native_unit,mean,min,max", [ - (None, "%", "%", 16.440677, 10, 30), - ("battery", "%", "%", 16.440677, 10, 30), - ("battery", None, None, 16.440677, 10, 30), - ("humidity", "%", "%", 16.440677, 10, 30), - ("humidity", None, None, 16.440677, 10, 30), - ("pressure", "Pa", "Pa", 16.440677, 10, 30), - ("pressure", "hPa", "Pa", 1644.0677, 1000, 3000), - ("pressure", "mbar", "Pa", 1644.0677, 1000, 3000), - ("pressure", "inHg", "Pa", 55674.53, 33863.89, 101591.67), - ("pressure", "psi", "Pa", 113354.48, 68947.57, 206842.71), - ("temperature", "°C", "°C", 16.440677, 10, 30), - ("temperature", "°F", "°C", -8.644068, -12.22222, -1.111111), + (None, "%", "%", 13.050847, -10, 30), + ("battery", "%", "%", 13.050847, -10, 30), + ("battery", None, None, 13.050847, -10, 30), + ("humidity", "%", "%", 13.050847, -10, 30), + ("humidity", None, None, 13.050847, -10, 30), + ("pressure", "Pa", "Pa", 13.050847, -10, 30), + ("pressure", "hPa", "Pa", 1305.0847, -1000, 3000), + ("pressure", "mbar", "Pa", 1305.0847, -1000, 3000), + ("pressure", "inHg", "Pa", 44195.25, -33863.89, 101591.67), + ("pressure", "psi", "Pa", 89982.42, -68947.57, 206842.71), + ("temperature", "°C", "°C", 13.050847, -10, 30), + ("temperature", "°F", "°C", -10.52731, -23.33333, -1.111111), ], ) def test_compile_hourly_statistics( @@ -155,8 +155,8 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes { "statistic_id": "sensor.test1", "start": process_timestamp_to_utc_isoformat(zero), - "mean": approx(16.440677966101696), - "min": approx(10.0), + "mean": approx(13.050847), + "min": approx(-10.0), "max": approx(30.0), "last_reset": None, "state": None, @@ -167,8 +167,8 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes { "statistic_id": "sensor.test6", "start": process_timestamp_to_utc_isoformat(zero), - "mean": approx(16.440677966101696), - "min": approx(10.0), + "mean": approx(13.050847), + "min": approx(-10.0), "max": approx(30.0), "last_reset": None, "state": None, @@ -179,8 +179,8 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes { "statistic_id": "sensor.test7", "start": process_timestamp_to_utc_isoformat(zero), - "mean": approx(16.440677966101696), - "min": approx(10.0), + "mean": approx(13.050847), + "min": approx(-10.0), "max": approx(30.0), "last_reset": None, "state": None, @@ -988,10 +988,10 @@ def test_list_statistic_ids_unsupported(hass_recorder, caplog, _attributes): @pytest.mark.parametrize( "device_class,unit,native_unit,mean,min,max", [ - (None, None, None, 16.440677, 10, 30), - (None, "%", "%", 16.440677, 10, 30), - ("battery", "%", "%", 16.440677, 10, 30), - ("battery", None, None, 16.440677, 10, 30), + (None, None, None, 13.050847, -10, 30), + (None, "%", "%", 13.050847, -10, 30), + ("battery", "%", "%", 13.050847, -10, 30), + ("battery", None, None, 13.050847, -10, 30), ], ) def test_compile_hourly_statistics_changing_units_1( @@ -1074,10 +1074,10 @@ def test_compile_hourly_statistics_changing_units_1( @pytest.mark.parametrize( "device_class,unit,native_unit,mean,min,max", [ - (None, None, None, 16.440677, 10, 30), - (None, "%", "%", 16.440677, 10, 30), - ("battery", "%", "%", 16.440677, 10, 30), - ("battery", None, None, 16.440677, 10, 30), + (None, None, None, 13.050847, -10, 30), + (None, "%", "%", 13.050847, -10, 30), + ("battery", "%", "%", 13.050847, -10, 30), + ("battery", None, None, 13.050847, -10, 30), ], ) def test_compile_hourly_statistics_changing_units_2( @@ -1119,10 +1119,10 @@ def test_compile_hourly_statistics_changing_units_2( @pytest.mark.parametrize( "device_class,unit,native_unit,mean,min,max", [ - (None, None, None, 16.440677, 10, 30), - (None, "%", "%", 16.440677, 10, 30), - ("battery", "%", "%", 16.440677, 10, 30), - ("battery", None, None, 16.440677, 10, 30), + (None, None, None, 13.050847, -10, 30), + (None, "%", "%", 13.050847, -10, 30), + ("battery", "%", "%", 13.050847, -10, 30), + ("battery", None, None, 13.050847, -10, 30), ], ) def test_compile_hourly_statistics_changing_units_3( @@ -1203,7 +1203,7 @@ def test_compile_hourly_statistics_changing_units_3( @pytest.mark.parametrize( "device_class,unit,native_unit,mean,min,max", [ - (None, None, None, 16.440677, 10, 30), + (None, None, None, 13.050847, -10, 30), ], ) def test_compile_hourly_statistics_changing_statistics( @@ -1309,7 +1309,7 @@ def record_states(hass, zero, entity_id, attributes): states = {entity_id: []} with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=one): - states[entity_id].append(set_state(entity_id, "10", attributes=attributes)) + states[entity_id].append(set_state(entity_id, "-10", attributes=attributes)) with patch("homeassistant.components.recorder.dt_util.utcnow", return_value=two): states[entity_id].append(set_state(entity_id, "15", attributes=attributes)) From 9d5431fba13e524f51f18fb0dcd45d2cbbdaf7cf Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sat, 4 Sep 2021 22:56:59 +0200 Subject: [PATCH 3/6] Handle Fritz InternalError (#55711) --- homeassistant/components/fritz/sensor.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index bc579b1125e..53efc7a83f3 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -9,6 +9,7 @@ from fritzconnection.core.exceptions import ( FritzActionError, FritzActionFailedError, FritzConnectionException, + FritzInternalError, FritzServiceError, ) from fritzconnection.lib.fritzstatus import FritzStatus @@ -273,7 +274,12 @@ async def async_setup_entry( "GetInfo", ) dsl = dslinterface["NewEnable"] - except (FritzActionError, FritzActionFailedError, FritzServiceError): + except ( + FritzInternalError, + FritzActionError, + FritzActionFailedError, + FritzServiceError, + ): pass for sensor_type, sensor_data in SENSOR_DATA.items(): From eb48e75fc561d07d2c5b3493cc5846e9e009593d Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 4 Sep 2021 13:18:23 +0200 Subject: [PATCH 4/6] Fix LIFX firmware version information (#55713) --- homeassistant/components/lifx/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lifx/manifest.json b/homeassistant/components/lifx/manifest.json index 847c75b4fa5..2dc46615f3a 100644 --- a/homeassistant/components/lifx/manifest.json +++ b/homeassistant/components/lifx/manifest.json @@ -3,7 +3,7 @@ "name": "LIFX", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/lifx", - "requirements": ["aiolifx==0.6.10", "aiolifx_effects==0.2.2"], + "requirements": ["aiolifx==0.7.0", "aiolifx_effects==0.2.2"], "homekit": { "models": ["LIFX"] }, diff --git a/requirements_all.txt b/requirements_all.txt index c09604740ab..11f70be6c1c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -201,7 +201,7 @@ aiokafka==0.6.0 aiokef==0.2.16 # homeassistant.components.lifx -aiolifx==0.6.10 +aiolifx==0.7.0 # homeassistant.components.lifx aiolifx_effects==0.2.2 From 04816fe26d93bc8c878319b13fc931f9c2dd3967 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sat, 4 Sep 2021 22:58:34 +0200 Subject: [PATCH 5/6] Fix SamsungTV sendkey when not connected (#55723) --- homeassistant/components/samsungtv/bridge.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 095d3339428..0d00a0cb94f 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -240,7 +240,8 @@ class SamsungTVLegacyBridge(SamsungTVBridge): def _send_key(self, key): """Send the key using legacy protocol.""" - self._get_remote().control(key) + if remote := self._get_remote(): + remote.control(key) def stop(self): """Stop Bridge.""" @@ -315,7 +316,8 @@ class SamsungTVWSBridge(SamsungTVBridge): """Send the key using websocket protocol.""" if key == "KEY_POWEROFF": key = "KEY_POWER" - self._get_remote().send_key(key) + if remote := self._get_remote(): + remote.send_key(key) def _get_remote(self, avoid_open: bool = False): """Create or return a remote control instance.""" From 37cf295e20892d6ba5e0f64f5ac15d5b30268ddd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 4 Sep 2021 14:13:37 -0700 Subject: [PATCH 6/6] Bumped version to 2021.9.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ae0e3255beb..c7bac6e5d1d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 9 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)