Compare commits

..

23 Commits

Author SHA1 Message Date
Paulus Schoutsen 3b147bcbf7 Merge pull request #32327 from home-assistant/rc
0.106.2
2020-02-28 12:40:20 -08:00
J. Nick Koston ca81c6e684 Ensure rest sensors are marked unavailable when http requests… (#32309) 2020-02-28 11:43:00 -08:00
Paulus Schoutsen e62ba49979 Bumped version to 0.106.2 2020-02-28 11:38:53 -08:00
Robert Svensson 6ea20090a4 UniFi - Temporary workaround to get device tracker to mark cli… (#32321) 2020-02-28 11:38:37 -08:00
Paulus Schoutsen c43b7d10d8 revent saving/deleting Lovelace config in safe mode (#32319) 2020-02-28 11:37:57 -08:00
Bram Kragten 430fa24acd Updated frontend to 20200220.5 (#32312) 2020-02-28 11:36:39 -08:00
Paulus Schoutsen d51b2ad675 Merge pull request #32282 from home-assistant/rc
0.106.1
2020-02-27 16:37:35 -08:00
Diogo Gomes b8fbe758d8 Bump pyipma dependency (fixes bug in 0.106) (#32286)
* Bump version

* Bump PyIPMA version
2020-02-27 14:44:22 -08:00
Paulus Schoutsen 61476f4f2c Fix dsmr test 2020-02-27 14:41:35 -08:00
Paulus Schoutsen cab60bcd0c Bumped version to 0.106.1 2020-02-27 14:03:07 -08:00
Paulus Schoutsen c0394232f3 Catch more Hue errors (#32275) 2020-02-27 14:03:00 -08:00
Robert Svensson a5d9e89d08 deCONZ - Race condition on slower systems (#32274)
When battery sensors gets created before other platforms loading deconz sensors gets created first the other platform would not create entities related to those battery sensors
2020-02-27 14:02:59 -08:00
Aaron Bach f43b26f250 Bump simplisafe-python to 9.0.2 (#32273) 2020-02-27 14:02:59 -08:00
Aaron Bach 58b32bbeff Bump simplisafe-python to 9.0.0 (#32215) 2020-02-27 14:02:58 -08:00
dupondje 6d0a465390 Fix DSMR 5 (#32233)
DSMR 5 was broken because some wrong if.
if dsmr_version in ("5B"):
-> this checks dsmr_version against 5 and B. Not if its 5B.
2020-02-27 14:00:51 -08:00
Jens Nistler a5d334bbf7 Mark clients away if they have never been seen. (#32222) 2020-02-27 14:00:51 -08:00
Erik Montnemery a77fd4892e Add missing translations for light actions (#32216) 2020-02-27 14:00:50 -08:00
Franck Nijhof 2d68b37dd5 Merge pull request #32211 from home-assistant/rc
0.106.0
2020-02-26 14:30:57 +01:00
Paulus Schoutsen 4de3871a78 Fix hue test 2020-02-26 12:54:19 +01:00
Franck Nijhof 9c755d8fd4 Remove deprecated Hue options (fixes CI) (#32027) 2020-02-26 12:53:44 +01:00
Franck Nijhof 1e5f0a5136 Bumped version to 0.106.0 2020-02-26 12:34:38 +01:00
Bram Kragten 897433ecba Updated frontend to 20200220.4 (#32205) 2020-02-26 12:31:50 +01:00
Aaron Bach 89625010e5 Fix error where SimpliSafe websocket would disconnect and not reconnect (#32199)
* Fix error where SimpliSafe websocket would disconnect and not reconnect

* Await
2020-02-26 12:31:44 +01:00
27 changed files with 183 additions and 75 deletions
@@ -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))
@@ -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))
+1 -1
View File
@@ -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)
@@ -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))
+2 -2
View File
@@ -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":
@@ -3,7 +3,7 @@
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": [
"home-assistant-frontend==20200220.3"
"home-assistant-frontend==20200220.5"
],
"dependencies": [
"api",
+1 -7
View File
@@ -46,13 +46,7 @@ CONFIG_SCHEMA = vol.Schema(
DOMAIN: vol.Schema(
{
vol.Optional(CONF_BRIDGES): vol.All(
cv.ensure_list,
[
vol.All(
cv.deprecated("filename", invalidation_version="0.106.0"),
BRIDGE_CONFIG_SCHEMA,
),
],
cv.ensure_list, [BRIDGE_CONFIG_SCHEMA],
)
}
)
+10 -3
View File
@@ -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
):
+6 -1
View File
@@ -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
+2 -1
View File
@@ -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):
+1 -1
View File
@@ -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"]
}
@@ -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}"
@@ -104,6 +104,9 @@ class LovelaceStorage:
async def async_save(self, config):
"""Save config."""
if self._hass.config.safe_mode:
raise HomeAssistantError("Deleting not supported in safe mode")
if self._data is None:
await self._load()
self._data["config"] = config
@@ -112,6 +115,9 @@ class LovelaceStorage:
async def async_delete(self):
"""Delete config."""
if self._hass.config.safe_mode:
raise HomeAssistantError("Deleting not supported in safe mode")
await self.async_save(None)
async def _load(self):
+12 -10
View File
@@ -202,17 +202,19 @@ class RestSensor(Entity):
self.rest.update()
value = self.rest.data
_LOGGER.debug("Data fetched from resource: %s", value)
content_type = self.rest.headers.get("content-type")
if self.rest.headers is not None:
# If the http request failed, headers will be None
content_type = self.rest.headers.get("content-type")
if content_type and content_type.startswith("text/xml"):
try:
value = json.dumps(xmltodict.parse(value))
_LOGGER.debug("JSON converted from XML: %s", value)
except ExpatError:
_LOGGER.warning(
"REST xml result could not be parsed and converted to JSON."
)
_LOGGER.debug("Erroneous XML: %s", value)
if content_type and content_type.startswith("text/xml"):
try:
value = json.dumps(xmltodict.parse(value))
_LOGGER.debug("JSON converted from XML: %s", value)
except ExpatError:
_LOGGER.warning(
"REST xml result could not be parsed and converted to JSON."
)
_LOGGER.debug("Erroneous XML: %s", value)
if self._json_attrs:
self._attributes = {}
+24 -30
View File
@@ -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}
)
@@ -520,41 +521,34 @@ class SimpliSafe:
tasks = [update_system(system) for system in self.systems.values()]
def cancel_tasks():
"""Cancel tasks and ensure their cancellation is processed."""
for task in tasks:
task.cancel()
results = await asyncio.gather(*tasks, return_exceptions=True)
for result in results:
if isinstance(result, InvalidCredentialsError):
if self._emergency_refresh_token_used:
_LOGGER.error(
"SimpliSafe authentication disconnected. Please restart HASS."
)
remove_listener = self._hass.data[DOMAIN][DATA_LISTENER].pop(
self._config_entry.entry_id
)
remove_listener()
return
try:
await asyncio.gather(*tasks)
except InvalidCredentialsError:
cancel_tasks()
_LOGGER.warning("SimpliSafe cloud error; trying stored refresh token")
self._emergency_refresh_token_used = True
return await self._api.refresh_access_token(
self._config_entry.data[CONF_TOKEN]
)
if self._emergency_refresh_token_used:
_LOGGER.error(
"SimpliSafe authentication disconnected. Please restart HASS."
)
remove_listener = self._hass.data[DOMAIN][DATA_LISTENER].pop(
self._config_entry.entry_id
)
remove_listener()
if isinstance(result, SimplipyError):
_LOGGER.error("SimpliSafe error while updating: %s", result)
return
_LOGGER.warning("SimpliSafe cloud error; trying stored refresh token")
self._emergency_refresh_token_used = True
return await self._api.refresh_access_token(
self._config_entry.data[CONF_TOKEN]
)
except SimplipyError as err:
cancel_tasks()
_LOGGER.error("SimpliSafe error while updating: %s", err)
return
except Exception as err: # pylint: disable=broad-except
cancel_tasks()
_LOGGER.error("Unknown error while updating: %s", err)
return
if isinstance(result, SimplipyError):
_LOGGER.error("Unknown error while updating: %s", result)
return
if self._api.refresh_token_dirty:
if self._api.refresh_token != self._config_entry.data[CONF_TOKEN]:
_async_save_refresh_token(
self._hass, self._config_entry, self._api.refresh_token
)
@@ -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"]
}
@@ -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)
)
@@ -333,4 +338,4 @@ class UniFiDeviceTracker(ScannerEntity):
@property
def should_poll(self):
"""No polling needed."""
return False
return True
@@ -62,4 +62,4 @@ class UniFiClient(Entity):
@property
def should_poll(self) -> bool:
"""No polling needed."""
return False
return True
+1 -1
View File
@@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 106
PATCH_VERSION = "0b5"
PATCH_VERSION = "2"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 7, 0)
+1 -1
View File
@@ -11,7 +11,7 @@ cryptography==2.8
defusedxml==0.6.0
distro==1.4.0
hass-nabucasa==0.31
home-assistant-frontend==20200220.3
home-assistant-frontend==20200220.5
importlib-metadata==1.5.0
jinja2>=2.10.3
netdisco==2.6.0
+3 -3
View File
@@ -683,7 +683,7 @@ hole==0.5.0
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200220.3
home-assistant-frontend==20200220.5
# homeassistant.components.zwave
homeassistant-pyozw==0.1.8
@@ -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
+3 -3
View File
@@ -254,7 +254,7 @@ hole==0.5.0
holidays==0.10.1
# homeassistant.components.frontend
home-assistant-frontend==20200220.3
home-assistant-frontend==20200220.5
# homeassistant.components.zwave
homeassistant-pyozw==0.1.8
@@ -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
+44
View File
@@ -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": ""},
]
),
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") == ""
async def test_belgian_meter(hass, mock_connection_factory):
"""Test if Belgian meter is correctly parsed."""
(connection_factory, transport, protocol) = mock_connection_factory
+1 -2
View File
@@ -37,7 +37,7 @@ async def test_setup_defined_hosts_known_auth(hass):
hue.CONF_ALLOW_HUE_GROUPS: False,
hue.CONF_ALLOW_UNREACHABLE: True,
},
{hue.CONF_HOST: "1.1.1.1", "filename": "bla"},
{hue.CONF_HOST: "1.1.1.1"},
]
}
},
@@ -59,7 +59,6 @@ async def test_setup_defined_hosts_known_auth(hass):
hue.CONF_HOST: "1.1.1.1",
hue.CONF_ALLOW_HUE_GROUPS: True,
hue.CONF_ALLOW_UNREACHABLE: False,
"filename": "bla",
},
}
+10
View File
@@ -45,6 +45,16 @@ async def test_lovelace_from_storage(hass, hass_ws_client, hass_storage):
assert not response["success"]
assert response["error"]["code"] == "config_not_found"
await client.send_json(
{"id": 9, "type": "lovelace/config/save", "config": {"yo": "hello"}}
)
response = await client.receive_json()
assert not response["success"]
await client.send_json({"id": 10, "type": "lovelace/config/delete"})
response = await client.receive_json()
assert not response["success"]
async def test_lovelace_from_storage_save_before_load(
hass, hass_ws_client, hass_storage
+29
View File
@@ -589,6 +589,35 @@ class TestRestSensor(unittest.TestCase):
assert mock_logger.warning.called
assert mock_logger.debug.called
@patch("homeassistant.components.rest.sensor._LOGGER")
def test_update_with_failed_get(self, mock_logger):
"""Test attributes get extracted from a XML result with bad xml."""
value_template = template("{{ value_json.toplevel.master_value }}")
value_template.hass = self.hass
self.rest.update = Mock(
"rest.RestData.update", side_effect=self.update_side_effect(None, None),
)
self.sensor = rest.RestSensor(
self.hass,
self.rest,
self.name,
self.unit_of_measurement,
self.device_class,
value_template,
["key"],
self.force_update,
self.resource_template,
self.json_attrs_path,
)
self.sensor.update()
assert {} == self.sensor.device_state_attributes
assert mock_logger.warning.called
assert mock_logger.debug.called
assert self.sensor.state is None
assert self.sensor.available is False
class TestRestData(unittest.TestCase):
"""Tests for RestData."""
+15 -2
View File
@@ -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"