Compare commits

...

19 Commits

Author SHA1 Message Date
Paulus Schoutsen
937eba3dbe Merge pull request #22216 from home-assistant/rc
0.90.0
2019-03-20 10:16:24 -07:00
Paulus Schoutsen
9d8054e6e2 Bumped version to 0.90.0 2019-03-20 07:51:23 -07:00
Penny Wood
d4cd39e43e Fixed typing errors (#22207) 2019-03-20 07:51:14 -07:00
Paulus Schoutsen
1bf49ce5a3 Updated frontend to 20190320.0 2019-03-20 07:50:24 -07:00
Paulus Schoutsen
7cf1f4f9fe Bumped version to 0.90.0b7 2019-03-19 16:48:31 -07:00
Paulus Schoutsen
268d129ea9 Updated frontend to 20190319.1 2019-03-19 16:36:11 -07:00
Paulus Schoutsen
b8f246356a Bumped version to 0.90.0b6 2019-03-19 11:41:08 -07:00
Paulus Schoutsen
e6ffc790f2 Always load Hass.io component on Hass.io (#22185)
* Always load Hass.io component on Hass.io

* Lint

* Lint
2019-03-19 11:40:49 -07:00
Pascal Vizeli
b85189e699 Update Hass-NabuCasa 0.8 (#22177) 2019-03-19 11:40:48 -07:00
uchagani
f202114ead bump total_connect_client to 0.24 (#22166) 2019-03-19 11:40:06 -07:00
Paulus Schoutsen
fff6927f9c Updated frontend to 20190319.0 2019-03-19 11:38:16 -07:00
Paulus Schoutsen
ad0ec66353 Bumped version to 0.90.0b5 2019-03-18 17:04:49 -07:00
Franck Nijhof
592edd10ef Upgrade toonapilib to 3.2.2 + lower interval (#22160) 2019-03-18 17:04:43 -07:00
Pascal Vizeli
d75d75e49f Remove config check over supervisor (#22156)
* Remove config check over supervisor

* Fix lint

* Fix tests
2019-03-18 17:04:42 -07:00
Jason Hunter
1c9b750e36 Fix resetting access token on streams with keepalive (#22148) 2019-03-18 17:04:41 -07:00
WebSpider
33a7075883 Bump tado version (#22145)
* Bump python-tado, new API endpoint

* Change references of old API endpoint to new

* Update REQUIREMENTS
2019-03-18 17:04:40 -07:00
Paulus Schoutsen
cc00f3cd2e Allow non-admins to listen to certain events (#22137) 2019-03-18 17:04:39 -07:00
Jason Hu
22624715a9 Remove hass.config from aws_lambda notify payload (#22125) 2019-03-18 17:04:38 -07:00
Paulus Schoutsen
c37dcacf54 Updated frontend to 20190318.0 2019-03-18 16:54:31 -07:00
26 changed files with 213 additions and 131 deletions

View File

@@ -5,7 +5,7 @@ import os
import sys
from time import time
from collections import OrderedDict
from typing import Any, Optional, Dict
from typing import Any, Optional, Dict, Set
import voluptuous as vol
@@ -127,10 +127,7 @@ async def async_from_config_dict(config: Dict[str, Any],
hass.config_entries = config_entries.ConfigEntries(hass, config)
await hass.config_entries.async_initialize()
# Filter out the repeating and common config section [homeassistant]
components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN)
components.update(hass.config_entries.async_domains())
components = _get_components(hass, config)
# Resolve all dependencies of all components.
for component in list(components):
@@ -391,3 +388,21 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir
@core.callback
def _get_components(hass: core.HomeAssistant,
config: Dict[str, Any]) -> Set[str]:
"""Get components to set up."""
# Filter out the repeating and common config section [homeassistant]
components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN)
# Add config entry domains
components.update(hass.config_entries.async_domains()) # type: ignore
# Make sure the Hass.io component is loaded
if 'HASSIO' in os.environ:
components.add('hassio')
return components

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_CUSTOM_BYPASS)
REQUIREMENTS = ['total_connect_client==0.22']
REQUIREMENTS = ['total_connect_client==0.24']
_LOGGER = logging.getLogger(__name__)

View File

@@ -24,7 +24,7 @@ from .const import (
CONF_USER_POOL_ID, DOMAIN, MODE_DEV, MODE_PROD)
from .prefs import CloudPreferences
REQUIREMENTS = ['hass-nabucasa==0.7']
REQUIREMENTS = ['hass-nabucasa==0.8']
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)

View File

@@ -9,7 +9,6 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
import json
from datetime import timedelta
import logging
import os
import voluptuous as vol
@@ -199,10 +198,6 @@ async def async_setup(hass, config):
"""Schedule the first discovery when Home Assistant starts up."""
async_track_point_in_utc_time(hass, scan_devices, dt_util.utcnow())
# Discovery for local services
if 'HASSIO' in os.environ:
hass.async_create_task(new_service_found(SERVICE_HASSIO, {}))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, schedule_first)
return True

View File

@@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass
from .storage import async_setup_frontend_storage
REQUIREMENTS = ['home-assistant-frontend==20190316.0']
REQUIREMENTS = ['home-assistant-frontend==20190320.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',

View File

@@ -7,6 +7,7 @@ import voluptuous as vol
from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.components import SERVICE_CHECK_CONFIG
import homeassistant.config as conf_util
from homeassistant.const import (
ATTR_NAME, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP)
from homeassistant.core import DOMAIN as HASS_DOMAIN, callback
@@ -130,23 +131,6 @@ def is_hassio(hass):
return DOMAIN in hass.config.components
@bind_hass
async def async_check_config(hass):
"""Check configuration over Hass.io API."""
hassio = hass.data[DOMAIN]
try:
result = await hassio.check_homeassistant_config()
except HassioAPIError as err:
_LOGGER.error("Error on Hass.io API: %s", err)
raise HomeAssistantError() from None
else:
if result['result'] == "error":
return result['message']
return None
async def async_setup(hass, config):
"""Set up the Hass.io component."""
# Check local setup
@@ -259,9 +243,13 @@ async def async_setup(hass, config):
await hassio.stop_homeassistant()
return
error = await async_check_config(hass)
if error:
_LOGGER.error(error)
try:
errors = await conf_util.async_check_ha_config_file(hass)
except HomeAssistantError:
return
if errors:
_LOGGER.error(errors)
hass.components.persistent_notification.async_create(
"Config error. See dev-info panel for details.",
"Config validating", "{0}.check_config".format(HASS_DOMAIN))

View File

@@ -97,13 +97,6 @@ class HassIO:
"""
return self.send_command("/homeassistant/stop")
def check_homeassistant_config(self):
"""Check Home-Assistant config with Hass.io API.
This method return a coroutine.
"""
return self.send_command("/homeassistant/check", timeout=600)
@_api_data
def retrieve_discovery_messages(self):
"""Return all discovery data from Hass.io API.

View File

@@ -39,8 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_service(hass, config, discovery_info=None):
"""Get the AWS Lambda notification service."""
context_str = json.dumps({'hass': hass.config.as_dict(),
'custom': config[CONF_CONTEXT]}, cls=JSONEncoder)
context_str = json.dumps({'custom': config[CONF_CONTEXT]}, cls=JSONEncoder)
context_b64 = base64.b64encode(context_str.encode('utf-8'))
context = context_b64.decode('utf-8')

View File

@@ -120,10 +120,16 @@ class Stream:
"""Remove provider output stream."""
if provider.format in self._outputs:
del self._outputs[provider.format]
self.check_idle()
if not self._outputs:
self.stop()
def check_idle(self):
"""Reset access token if all providers are idle."""
if all([p.idle for p in self._outputs.values()]):
self.access_token = None
def start(self):
"""Start a stream."""
if self._thread is None or not self._thread.isAlive():

View File

@@ -43,6 +43,7 @@ class StreamOutput:
def __init__(self, stream) -> None:
"""Initialize a stream output."""
self.idle = False
self._stream = stream
self._cursor = None
self._event = asyncio.Event()
@@ -77,10 +78,11 @@ class StreamOutput:
def get_segment(self, sequence: int = None) -> Any:
"""Retrieve a specific segment, or the whole list."""
self.idle = False
# Reset idle timeout
if self._unsub is not None:
self._unsub()
self._unsub = async_call_later(self._stream.hass, 300, self._cleanup)
self._unsub = async_call_later(self._stream.hass, 300, self._timeout)
if not sequence:
return self._segments
@@ -109,7 +111,7 @@ class StreamOutput:
# Start idle timeout when we start recieving data
if self._unsub is None:
self._unsub = async_call_later(
self._stream.hass, 300, self._cleanup)
self._stream.hass, 300, self._timeout)
if segment is None:
self._event.set()
@@ -124,7 +126,15 @@ class StreamOutput:
self._event.clear()
@callback
def _cleanup(self, _now=None):
def _timeout(self, _now=None):
"""Handle stream timeout."""
if self._stream.keepalive:
self.idle = True
self._stream.check_idle()
else:
self._cleanup()
def _cleanup(self):
"""Remove provider."""
self._segments = []
self._stream.remove_provider(self)

View File

@@ -10,7 +10,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.util import Throttle
REQUIREMENTS = ['python-tado==0.2.3']
REQUIREMENTS = ['python-tado==0.2.8']
_LOGGER = logging.getLogger(__name__)

View File

@@ -52,9 +52,9 @@ class TadoDeviceScanner(DeviceScanner):
# If there's a home_id, we need a different API URL
if self.home_id is None:
self.tadoapiurl = 'https://my.tado.com/api/v2/me'
self.tadoapiurl = 'https://auth.tado.com/api/v2/me'
else:
self.tadoapiurl = 'https://my.tado.com/api/v2' \
self.tadoapiurl = 'https://auth.tado.com/api/v2' \
'/homes/{home_id}/mobileDevices'
# The API URL always needs a username and password

View File

@@ -16,7 +16,7 @@ from .const import (
CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DISPLAY, CONF_TENANT,
DATA_TOON_CLIENT, DATA_TOON_CONFIG, DOMAIN)
REQUIREMENTS = ['toonapilib==3.2.1']
REQUIREMENTS = ['toonapilib==3.2.2']
_LOGGER = logging.getLogger(__name__)

View File

@@ -17,7 +17,7 @@ DEPENDENCIES = ['toon']
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL = timedelta(seconds=300)
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry,

View File

@@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL = timedelta(seconds=300)
HA_TOON = {
STATE_AUTO: 'Comfort',

View File

@@ -16,7 +16,7 @@ DEPENDENCIES = ['toon']
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL = timedelta(seconds=300)
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry,

View File

@@ -1,7 +1,9 @@
"""Commands part of Websocket API."""
import voluptuous as vol
from homeassistant.const import MATCH_ALL, EVENT_TIME_CHANGED
from homeassistant.auth.permissions.const import POLICY_READ
from homeassistant.const import (
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED)
from homeassistant.core import callback, DOMAIN as HASS_DOMAIN
from homeassistant.exceptions import Unauthorized, ServiceNotFound, \
HomeAssistantError
@@ -42,20 +44,37 @@ def handle_subscribe_events(hass, connection, msg):
Async friendly.
"""
if not connection.user.is_admin:
from .permissions import SUBSCRIBE_WHITELIST
event_type = msg['event_type']
if (event_type not in SUBSCRIBE_WHITELIST and
not connection.user.is_admin):
raise Unauthorized
async def forward_events(event):
"""Forward events to websocket."""
if event.event_type == EVENT_TIME_CHANGED:
return
if event_type == EVENT_STATE_CHANGED:
@callback
def forward_events(event):
"""Forward state changed events to websocket."""
if not connection.user.permissions.check_entity(
event.data['entity_id'], POLICY_READ):
return
connection.send_message(messages.event_message(
msg['id'], event.as_dict()
))
connection.send_message(messages.event_message(msg['id'], event))
else:
@callback
def forward_events(event):
"""Forward events to websocket."""
if event.event_type == EVENT_TIME_CHANGED:
return
connection.send_message(messages.event_message(
msg['id'], event.as_dict()
))
connection.subscriptions[msg['id']] = hass.bus.async_listen(
msg['event_type'], forward_events)
event_type, forward_events)
connection.send_message(messages.result_message(msg['id']))

View File

@@ -0,0 +1,23 @@
"""Permission constants for the websocket API.
Separate file to avoid circular imports.
"""
from homeassistant.const import (
EVENT_COMPONENT_LOADED,
EVENT_SERVICE_REGISTERED,
EVENT_SERVICE_REMOVED,
EVENT_STATE_CHANGED,
EVENT_THEMES_UPDATED)
from homeassistant.components.persistent_notification import (
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED)
# These are events that do not contain any sensitive data
# Except for state_changed, which is handled accordingly.
SUBSCRIBE_WHITELIST = {
EVENT_COMPONENT_LOADED,
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED,
EVENT_SERVICE_REGISTERED,
EVENT_SERVICE_REMOVED,
EVENT_STATE_CHANGED,
EVENT_THEMES_UPDATED,
}

View File

@@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 90
PATCH_VERSION = '0b4'
PATCH_VERSION = '0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)

View File

@@ -524,7 +524,7 @@ habitipy==0.2.0
hangups==0.4.6
# homeassistant.components.cloud
hass-nabucasa==0.7
hass-nabucasa==0.8
# homeassistant.components.mqtt.server
hbmqtt==0.9.4
@@ -554,7 +554,7 @@ hole==0.3.0
holidays==0.9.9
# homeassistant.components.frontend
home-assistant-frontend==20190316.0
home-assistant-frontend==20190320.0
# homeassistant.components.zwave
homeassistant-pyozw==0.1.2
@@ -1394,7 +1394,7 @@ python-songpal==0.0.9.1
python-synology==0.2.0
# homeassistant.components.tado
python-tado==0.2.3
python-tado==0.2.8
# homeassistant.components.telegram_bot
python-telegram-bot==11.1.0
@@ -1707,10 +1707,10 @@ tikteck==0.4
todoist-python==7.0.17
# homeassistant.components.toon
toonapilib==3.2.1
toonapilib==3.2.2
# homeassistant.components.alarm_control_panel.totalconnect
total_connect_client==0.22
total_connect_client==0.24
# homeassistant.components.tplink_lte
tp-connected==0.0.4

View File

@@ -114,7 +114,7 @@ ha-ffmpeg==1.11
hangups==0.4.6
# homeassistant.components.cloud
hass-nabucasa==0.7
hass-nabucasa==0.8
# homeassistant.components.mqtt.server
hbmqtt==0.9.4
@@ -126,7 +126,7 @@ hdate==0.8.7
holidays==0.9.9
# homeassistant.components.frontend
home-assistant-frontend==20190316.0
home-assistant-frontend==20190320.0
# homeassistant.components.homekit_controller
homekit[IP]==0.13.0
@@ -298,7 +298,7 @@ srpenergy==1.0.5
statsd==3.2.1
# homeassistant.components.toon
toonapilib==3.2.1
toonapilib==3.2.2
# homeassistant.components.camera.uvc
uvcclient==0.11.0

View File

@@ -1,6 +1,5 @@
"""The tests for the discovery component."""
import asyncio
import os
from unittest.mock import patch, MagicMock
import pytest
@@ -142,21 +141,6 @@ def test_discover_duplicates(hass):
SERVICE_NO_PLATFORM_COMPONENT, BASE_CONFIG)
@asyncio.coroutine
def test_load_component_hassio(hass):
"""Test load hassio component."""
def discover(netdisco):
"""Fake discovery."""
return []
with patch.dict(os.environ, {'HASSIO': "FAKE_HASSIO"}), \
patch('homeassistant.components.hassio.async_setup',
return_value=mock_coro(return_value=True)) as mock_hassio:
yield from mock_discovery(hass, discover)
assert mock_hassio.called
async def test_discover_config_flow(hass):
"""Test discovery triggering a config flow."""
discovery_info = {

View File

@@ -74,17 +74,6 @@ async def test_api_homeassistant_restart(hassio_handler, aioclient_mock):
assert aioclient_mock.call_count == 1
async def test_api_homeassistant_config(hassio_handler, aioclient_mock):
"""Test setup with API HomeAssistant config."""
aioclient_mock.post(
"http://127.0.0.1/homeassistant/check", json={
'result': 'ok', 'data': {'test': 'bla'}})
data = await hassio_handler.check_homeassistant_config()
assert data['data']['test'] == 'bla'
assert aioclient_mock.call_count == 1
async def test_api_addon_info(hassio_handler, aioclient_mock):
"""Test setup with API Add-on info."""
aioclient_mock.get(

View File

@@ -7,8 +7,7 @@ import pytest
from homeassistant.auth.const import GROUP_ID_ADMIN
from homeassistant.setup import async_setup_component
from homeassistant.components.hassio import (
STORAGE_KEY, async_check_config)
from homeassistant.components.hassio import STORAGE_KEY
from tests.common import mock_coro
@@ -311,8 +310,6 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
"http://127.0.0.1/homeassistant/restart", json={'result': 'ok'})
aioclient_mock.post(
"http://127.0.0.1/homeassistant/stop", json={'result': 'ok'})
aioclient_mock.post(
"http://127.0.0.1/homeassistant/check", json={'result': 'ok'})
yield from hass.services.async_call('homeassistant', 'stop')
yield from hass.async_block_till_done()
@@ -322,32 +319,14 @@ def test_service_calls_core(hassio_env, hass, aioclient_mock):
yield from hass.services.async_call('homeassistant', 'check_config')
yield from hass.async_block_till_done()
assert aioclient_mock.call_count == 2
with patch(
'homeassistant.config.async_check_ha_config_file',
return_value=mock_coro()
) as mock_check_config:
yield from hass.services.async_call('homeassistant', 'restart')
yield from hass.async_block_till_done()
assert mock_check_config.called
assert aioclient_mock.call_count == 3
yield from hass.services.async_call('homeassistant', 'restart')
yield from hass.async_block_till_done()
assert aioclient_mock.call_count == 5
@asyncio.coroutine
def test_check_config_ok(hassio_env, hass, aioclient_mock):
"""Check Config that is okay."""
assert (yield from async_setup_component(hass, 'hassio', {}))
aioclient_mock.post(
"http://127.0.0.1/homeassistant/check", json={'result': 'ok'})
assert (yield from async_check_config(hass)) is None
@asyncio.coroutine
def test_check_config_fail(hassio_env, hass, aioclient_mock):
"""Check Config that is wrong."""
assert (yield from async_setup_component(hass, 'hassio', {}))
aioclient_mock.post(
"http://127.0.0.1/homeassistant/check", json={
'result': 'error', 'message': "Error"})
assert (yield from async_check_config(hass)) == "Error"

View File

@@ -333,3 +333,76 @@ async def test_get_states_not_allows_nan(hass, websocket_client):
msg = await websocket_client.receive_json()
assert not msg['success']
assert msg['error']['code'] == const.ERR_UNKNOWN_ERROR
async def test_subscribe_unsubscribe_events_whitelist(
hass, websocket_client, hass_admin_user):
"""Test subscribe/unsubscribe events on whitelist."""
hass_admin_user.groups = []
await websocket_client.send_json({
'id': 5,
'type': 'subscribe_events',
'event_type': 'not-in-whitelist'
})
msg = await websocket_client.receive_json()
assert msg['id'] == 5
assert msg['type'] == const.TYPE_RESULT
assert not msg['success']
assert msg['error']['code'] == 'unauthorized'
await websocket_client.send_json({
'id': 6,
'type': 'subscribe_events',
'event_type': 'themes_updated'
})
msg = await websocket_client.receive_json()
assert msg['id'] == 6
assert msg['type'] == const.TYPE_RESULT
assert msg['success']
hass.bus.async_fire('themes_updated')
with timeout(3, loop=hass.loop):
msg = await websocket_client.receive_json()
assert msg['id'] == 6
assert msg['type'] == 'event'
event = msg['event']
assert event['event_type'] == 'themes_updated'
assert event['origin'] == 'LOCAL'
async def test_subscribe_unsubscribe_events_state_changed(
hass, websocket_client, hass_admin_user):
"""Test subscribe/unsubscribe state_changed events."""
hass_admin_user.groups = []
hass_admin_user.mock_policy({
'entities': {
'entity_ids': {
'light.permitted': True
}
}
})
await websocket_client.send_json({
'id': 7,
'type': 'subscribe_events',
'event_type': 'state_changed'
})
msg = await websocket_client.receive_json()
assert msg['id'] == 7
assert msg['type'] == const.TYPE_RESULT
assert msg['success']
hass.states.async_set('light.not_permitted', 'on')
hass.states.async_set('light.permitted', 'on')
msg = await websocket_client.receive_json()
assert msg['id'] == 7
assert msg['type'] == 'event'
assert msg['event']['event_type'] == 'state_changed'
assert msg['event']['data']['entity_id'] == 'light.permitted'

View File

@@ -34,7 +34,7 @@ def test_from_config_file(hass):
}
with patch_yaml_files(files, True):
yield from bootstrap.async_from_config_file('config.yaml')
yield from bootstrap.async_from_config_file('config.yaml', hass)
assert components == hass.config.components
@@ -103,3 +103,12 @@ async def test_async_from_config_file_not_mount_deps_folder(loop):
await bootstrap.async_from_config_file('mock-path', hass)
assert len(mock_mount.mock_calls) == 0
async def test_load_hassio(hass):
"""Test that we load Hass.io component."""
with patch.dict(os.environ, {}, clear=True):
assert bootstrap._get_components(hass, {}) == set()
with patch.dict(os.environ, {'HASSIO': '1'}):
assert bootstrap._get_components(hass, {}) == {'hassio'}