diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 2514a49f23c..6a528a66ba6 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -37,7 +37,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): gateway.option_allow_clip_sensor or not sensor.type.startswith("CLIP") ) - and sensor.deconz_id not in gateway.deconz_ids.values() ): entities.append(DeconzBinarySensor(sensor, gateway)) diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 34cc0e0b832..7b0f44807ec 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -44,7 +44,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): gateway.option_allow_clip_sensor or not sensor.type.startswith("CLIP") ) - and sensor.deconz_id not in gateway.deconz_ids.values() ): entities.append(DeconzThermostat(sensor, gateway)) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index f62f9315c49..e836f1e4490 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] for group in groups: - if group.lights and group.deconz_id not in gateway.deconz_ids.values(): + if group.lights: entities.append(DeconzGroup(group, gateway)) async_add_entities(entities, True) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 6b88c414243..c32b26f299d 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -68,7 +68,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): gateway.option_allow_clip_sensor or not sensor.type.startswith("CLIP") ) - and sensor.deconz_id not in gateway.deconz_ids.values() ): entities.append(DeconzSensor(sensor, gateway)) diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 54c8e3e29b2..009b86e1bb8 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -91,7 +91,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= # Protocol version specific obis if dsmr_version in ("4", "5"): gas_obis = obis_ref.HOURLY_GAS_METER_READING - elif dsmr_version in ("5B"): + elif dsmr_version in ("5B",): gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING else: gas_obis = obis_ref.GAS_METER_READING @@ -238,7 +238,7 @@ class DSMREntity(Entity): """Convert 2/1 to normal/low depending on DSMR version.""" # DSMR V5B: Note: In Belgium values are swapped: # Rate code 2 is used for low rate and rate code 1 is used for normal rate. - if dsmr_version in ("5B"): + if dsmr_version in ("5B",): if value == "0001": value = "0002" elif value == "0002": diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 2c164e5769a..37089e54b00 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -1,6 +1,7 @@ """Code to handle a Hue bridge.""" import asyncio from functools import partial +import logging from aiohttp import client_exceptions import aiohue @@ -24,7 +25,8 @@ SCENE_SCHEMA = vol.Schema( {vol.Required(ATTR_GROUP_NAME): cv.string, vol.Required(ATTR_SCENE_NAME): cv.string} ) # How long should we sleep if the hub is busy -HUB_BUSY_SLEEP = 0.01 +HUB_BUSY_SLEEP = 0.5 +_LOGGER = logging.getLogger(__name__) class HueBridge: @@ -123,9 +125,14 @@ class HueBridge: except ( client_exceptions.ClientOSError, client_exceptions.ClientResponseError, + client_exceptions.ServerDisconnectedError, ) as err: - if tries == 3 or ( - # We only retry if it's a server error. So raise on all 4XX errors. + if tries == 3: + _LOGGER.error("Request failed %s times, giving up.", tries) + raise + + # We only retry if it's a server error. So raise on all 4XX errors. + if ( isinstance(err, client_exceptions.ClientResponseError) and err.status < 500 ): diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 1678dbbfc62..253c0a2069c 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -5,6 +5,7 @@ from functools import partial import logging import random +from aiohttp import client_exceptions import aiohue import async_timeout @@ -172,7 +173,11 @@ async def async_safe_fetch(bridge, fetch_method): except aiohue.Unauthorized: await bridge.handle_unauthorized_error() raise UpdateFailed - except (asyncio.TimeoutError, aiohue.AiohueException): + except ( + asyncio.TimeoutError, + aiohue.AiohueException, + client_exceptions.ClientError, + ): raise UpdateFailed diff --git a/homeassistant/components/hue/sensor_base.py b/homeassistant/components/hue/sensor_base.py index 0bc7cd53536..ed27cff8eab 100644 --- a/homeassistant/components/hue/sensor_base.py +++ b/homeassistant/components/hue/sensor_base.py @@ -3,6 +3,7 @@ import asyncio from datetime import timedelta import logging +from aiohttp import client_exceptions from aiohue import AiohueException, Unauthorized from aiohue.sensors import TYPE_ZLL_PRESENCE import async_timeout @@ -60,7 +61,7 @@ class SensorManager: except Unauthorized: await self.bridge.handle_unauthorized_error() raise UpdateFailed - except (asyncio.TimeoutError, AiohueException): + except (asyncio.TimeoutError, AiohueException, client_exceptions.ClientError): raise UpdateFailed async def async_register_component(self, binary, async_add_entities): diff --git a/homeassistant/components/ipma/manifest.json b/homeassistant/components/ipma/manifest.json index 02d4e459f72..1457ac24195 100644 --- a/homeassistant/components/ipma/manifest.json +++ b/homeassistant/components/ipma/manifest.json @@ -3,7 +3,7 @@ "name": "Instituto Português do Mar e Atmosfera (IPMA)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ipma", - "requirements": ["pyipma==2.0.3"], + "requirements": ["pyipma==2.0.4"], "dependencies": [], "codeowners": ["@dgomes", "@abmantis"] } diff --git a/homeassistant/components/light/strings.json b/homeassistant/components/light/strings.json index 77b842ba078..922a4957afd 100644 --- a/homeassistant/components/light/strings.json +++ b/homeassistant/components/light/strings.json @@ -1,6 +1,8 @@ { "device_automation": { "action_type": { + "brightness_decrease": "Decrease {entity_name} brightness", + "brightness_increase": "Increase {entity_name} brightness", "toggle": "Toggle {entity_name}", "turn_on": "Turn on {entity_name}", "turn_off": "Turn off {entity_name}" diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 09004189820..add37cb0f1e 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -154,6 +154,7 @@ CONFIG_SCHEMA = vol.Schema( @callback def _async_save_refresh_token(hass, config_entry, token): + """Save a refresh token to the config entry.""" hass.config_entries.async_update_entry( config_entry, data={**config_entry.data, CONF_TOKEN: token} ) @@ -547,12 +548,7 @@ class SimpliSafe: _LOGGER.error("Unknown error while updating: %s", result) return - if self._api.refresh_token_dirty: - # Reconnect the websocket: - await self._api.websocket.async_disconnect() - await self._api.websocket.async_connect() - - # Save the new refresh token: + if self._api.refresh_token != self._config_entry.data[CONF_TOKEN]: _async_save_refresh_token( self._hass, self._config_entry, self._api.refresh_token ) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index e44f39265cb..b5f89a65fea 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==8.1.1"], + "requirements": ["simplisafe-python==9.0.2"], "dependencies": [], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 5dd5f0c83ae..52370fb0e3d 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -200,6 +200,11 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): else: self.wired_bug = None + + # A client that has never been seen cannot be connected. + if self.client.last_seen is None: + return False + since_last_seen = dt_util.utcnow() - dt_util.utc_from_timestamp( float(self.client.last_seen) ) diff --git a/homeassistant/const.py b/homeassistant/const.py index ff8e6bf8a9e..f453e211897 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 106 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) diff --git a/requirements_all.txt b/requirements_all.txt index a340f99d7c6..252b6d9c6a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1305,7 +1305,7 @@ pyicloud==0.9.2 pyintesishome==1.6 # homeassistant.components.ipma -pyipma==2.0.3 +pyipma==2.0.4 # homeassistant.components.iqvia pyiqvia==0.2.1 @@ -1827,7 +1827,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==8.1.1 +simplisafe-python==9.0.2 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be073a4b929..3b0480ffe64 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -477,7 +477,7 @@ pyhomematic==0.1.64 pyicloud==0.9.2 # homeassistant.components.ipma -pyipma==2.0.3 +pyipma==2.0.4 # homeassistant.components.iqvia pyiqvia==0.2.1 @@ -626,7 +626,7 @@ sentry-sdk==0.13.5 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==8.1.1 +simplisafe-python==9.0.2 # homeassistant.components.sleepiq sleepyq==0.7 diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index c881f4b9168..426ba552136 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -187,6 +187,50 @@ async def test_v4_meter(hass, mock_connection_factory): assert gas_consumption.attributes.get("unit_of_measurement") == "m3" +async def test_v5_meter(hass, mock_connection_factory): + """Test if v5 meter is correctly parsed.""" + (connection_factory, transport, protocol) = mock_connection_factory + + from dsmr_parser.obis_references import ( + HOURLY_GAS_METER_READING, + ELECTRICITY_ACTIVE_TARIFF, + ) + from dsmr_parser.objects import CosemObject, MBusObject + + config = {"platform": "dsmr", "dsmr_version": "5"} + + telegram = { + HOURLY_GAS_METER_READING: MBusObject( + [ + {"value": datetime.datetime.fromtimestamp(1551642213)}, + {"value": Decimal(745.695), "unit": "m³"}, + ] + ), + ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]), + } + + with assert_setup_component(1): + await async_setup_component(hass, "sensor", {"sensor": config}) + + telegram_callback = connection_factory.call_args_list[0][0][2] + + # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser + telegram_callback(telegram) + + # after receiving telegram entities need to have the chance to update + await asyncio.sleep(0) + + # tariff should be translated in human readable and have no unit + power_tariff = hass.states.get("sensor.power_tariff") + assert power_tariff.state == "low" + assert power_tariff.attributes.get("unit_of_measurement") == "" + + # check if gas consumption is parsed correctly + gas_consumption = hass.states.get("sensor.gas_consumption") + assert gas_consumption.state == "745.695" + assert gas_consumption.attributes.get("unit_of_measurement") == "m³" + + async def test_belgian_meter(hass, mock_connection_factory): """Test if Belgian meter is correctly parsed.""" (connection_factory, transport, protocol) = mock_connection_factory diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 608e72b483a..cbef7c31922 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -54,6 +54,14 @@ CLIENT_4 = { "last_seen": 1562600145, "mac": "00:00:00:00:00:04", } +CLIENT_5 = { + "essid": "ssid", + "hostname": "client_5", + "ip": "10.0.0.5", + "is_wired": True, + "last_seen": None, + "mac": "00:00:00:00:00:05", +} DEVICE_1 = { "board_rev": 3, @@ -111,11 +119,11 @@ async def test_tracked_devices(hass): controller = await setup_unifi_integration( hass, options={CONF_SSID_FILTER: ["ssid"]}, - clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, client_4_copy], + clients_response=[CLIENT_1, CLIENT_2, CLIENT_3, CLIENT_5, client_4_copy], devices_response=[DEVICE_1, DEVICE_2], known_wireless_clients=(CLIENT_4["mac"],), ) - assert len(hass.states.async_all()) == 6 + assert len(hass.states.async_all()) == 7 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -134,6 +142,11 @@ async def test_tracked_devices(hass): assert client_4 is not None assert client_4.state == "not_home" + # A client that has never been seen should be marked away. + client_5 = hass.states.get("device_tracker.client_5") + assert client_5 is not None + assert client_5.state == "not_home" + device_1 = hass.states.get("device_tracker.device_1") assert device_1 is not None assert device_1.state == "not_home"