From ab1939f56f4dbcfc3c954059410f2f854c5fe9ab Mon Sep 17 00:00:00 2001
From: Paulus Schoutsen <balloob@gmail.com>
Date: Mon, 25 Jun 2018 16:04:17 -0400
Subject: [PATCH 1/7] Bump frontend to 20180625.0

---
 homeassistant/components/frontend/__init__.py | 2 +-
 requirements_all.txt                          | 2 +-
 requirements_test_all.txt                     | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py
index 3d2231ab43b..54a77af5cfb 100644
--- a/homeassistant/components/frontend/__init__.py
+++ b/homeassistant/components/frontend/__init__.py
@@ -26,7 +26,7 @@ from homeassistant.helpers.translation import async_get_translations
 from homeassistant.loader import bind_hass
 from homeassistant.util.yaml import load_yaml
 
-REQUIREMENTS = ['home-assistant-frontend==20180622.1']
+REQUIREMENTS = ['home-assistant-frontend==20180625.0']
 
 DOMAIN = 'frontend'
 DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
diff --git a/requirements_all.txt b/requirements_all.txt
index 52a5e052560..65f6485f176 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -404,7 +404,7 @@ hipnotify==1.0.8
 holidays==0.9.5
 
 # homeassistant.components.frontend
-home-assistant-frontend==20180622.1
+home-assistant-frontend==20180625.0
 
 # homeassistant.components.homekit_controller
 # homekit==0.6
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index a38c7f259b4..e1fdc2cb3d4 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -81,7 +81,7 @@ hbmqtt==0.9.2
 holidays==0.9.5
 
 # homeassistant.components.frontend
-home-assistant-frontend==20180622.1
+home-assistant-frontend==20180625.0
 
 # homeassistant.components.influxdb
 # homeassistant.components.sensor.influxdb

From 1c8b52f63073ca2f955e4acf1ebba40ee3193262 Mon Sep 17 00:00:00 2001
From: Jason Hu <awarecan@users.noreply.github.com>
Date: Mon, 25 Jun 2018 13:06:00 -0700
Subject: [PATCH 2/7] Prevent Nest component setup crash due insufficient
 permission. (#14966)

* Prevent Nest component setup crash due insufficient permission.

* Trigger CI

* Better error handle and address code review comments

* Lint

* Tiny wording adjust

* Notify user if async_setup_entry failed

* Return False if exception occurred in NestDevice.initialize
---
 homeassistant/components/nest/__init__.py | 85 +++++++++++++----------
 1 file changed, 48 insertions(+), 37 deletions(-)

diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py
index bd74897371a..bf99fadc1d7 100644
--- a/homeassistant/components/nest/__init__.py
+++ b/homeassistant/components/nest/__init__.py
@@ -122,7 +122,8 @@ async def async_setup_entry(hass, entry):
     _LOGGER.debug("proceeding with setup")
     conf = hass.data.get(DATA_NEST_CONFIG, {})
     hass.data[DATA_NEST] = NestDevice(hass, conf, nest)
-    await hass.async_add_job(hass.data[DATA_NEST].initialize)
+    if not await hass.async_add_job(hass.data[DATA_NEST].initialize):
+        return False
 
     for component in 'climate', 'camera', 'sensor', 'binary_sensor':
         hass.async_add_job(hass.config_entries.async_forward_entry_setup(
@@ -192,63 +193,73 @@ class NestDevice(object):
 
     def initialize(self):
         """Initialize Nest."""
-        if self.local_structure is None:
-            self.local_structure = [s.name for s in self.nest.structures]
+        from nest.nest import AuthorizationError, APIError
+        try:
+            # Do not optimize next statement, it is here for initialize
+            # persistence Nest API connection.
+            structure_names = [s.name for s in self.nest.structures]
+            if self.local_structure is None:
+                self.local_structure = structure_names
+
+        except (AuthorizationError, APIError, socket.error) as err:
+            _LOGGER.error(
+                "Connection error while access Nest web service: %s", err)
+            return False
+        return True
 
     def structures(self):
         """Generate a list of structures."""
+        from nest.nest import AuthorizationError, APIError
         try:
             for structure in self.nest.structures:
-                if structure.name in self.local_structure:
-                    yield structure
-                else:
+                if structure.name not in self.local_structure:
                     _LOGGER.debug("Ignoring structure %s, not in %s",
                                   structure.name, self.local_structure)
-        except socket.error:
+                    continue
+                yield structure
+
+        except (AuthorizationError, APIError, socket.error) as err:
             _LOGGER.error(
-                "Connection error logging into the nest web service.")
+                "Connection error while access Nest web service: %s", err)
 
     def thermostats(self):
-        """Generate a list of thermostats and their location."""
-        try:
-            for structure in self.nest.structures:
-                if structure.name in self.local_structure:
-                    for device in structure.thermostats:
-                        yield (structure, device)
-                else:
-                    _LOGGER.debug("Ignoring structure %s, not in %s",
-                                  structure.name, self.local_structure)
-        except socket.error:
-            _LOGGER.error(
-                "Connection error logging into the nest web service.")
+        """Generate a list of thermostats."""
+        return self._devices('thermostats')
 
     def smoke_co_alarms(self):
         """Generate a list of smoke co alarms."""
-        try:
-            for structure in self.nest.structures:
-                if structure.name in self.local_structure:
-                    for device in structure.smoke_co_alarms:
-                        yield (structure, device)
-                else:
-                    _LOGGER.debug("Ignoring structure %s, not in %s",
-                                  structure.name, self.local_structure)
-        except socket.error:
-            _LOGGER.error(
-                "Connection error logging into the nest web service.")
+        return self._devices('smoke_co_alarms')
 
     def cameras(self):
         """Generate a list of cameras."""
+        return self._devices('cameras')
+
+    def _devices(self, device_type):
+        """Generate a list of Nest devices."""
+        from nest.nest import AuthorizationError, APIError
         try:
             for structure in self.nest.structures:
-                if structure.name in self.local_structure:
-                    for device in structure.cameras:
-                        yield (structure, device)
-                else:
+                if structure.name not in self.local_structure:
                     _LOGGER.debug("Ignoring structure %s, not in %s",
                                   structure.name, self.local_structure)
-        except socket.error:
+                    continue
+
+                for device in getattr(structure, device_type, []):
+                    try:
+                        # Do not optimize next statement,
+                        # it is here for verify Nest API permission.
+                        device.name_long
+                    except KeyError:
+                        _LOGGER.warning("Cannot retrieve device name for [%s]"
+                                        ", please check your Nest developer "
+                                        "account permission settings.",
+                                        device.serial)
+                        continue
+                    yield (structure, device)
+
+        except (AuthorizationError, APIError, socket.error) as err:
             _LOGGER.error(
-                "Connection error logging into the nest web service.")
+                "Connection error while access Nest web service: %s", err)
 
 
 class NestSensorDevice(Entity):

From 893e0f8db630f2c6dbc35b6ec9daa6a4118ed526 Mon Sep 17 00:00:00 2001
From: Aaron Bach <bachya1208@gmail.com>
Date: Sat, 23 Jun 2018 13:22:48 -0600
Subject: [PATCH 3/7] Fix socket bug with Yi in 0.72 (#15109)

* Fixes BrokenPipeError exceptions with Yi (#15108)

* Make sure to close the socket
---
 homeassistant/components/camera/yi.py | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)

diff --git a/homeassistant/components/camera/yi.py b/homeassistant/components/camera/yi.py
index 868c5afb447..93f526c2b96 100644
--- a/homeassistant/components/camera/yi.py
+++ b/homeassistant/components/camera/yi.py
@@ -53,7 +53,6 @@ class YiCamera(Camera):
         """Initialize."""
         super().__init__()
         self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
-        self._ftp = None
         self._last_image = None
         self._last_url = None
         self._manager = hass.data[DATA_FFMPEG]
@@ -64,8 +63,6 @@ class YiCamera(Camera):
         self.user = config[CONF_USERNAME]
         self.passwd = config[CONF_PASSWORD]
 
-        hass.async_add_job(self._connect_to_client)
-
     @property
     def brand(self):
         """Camera brand."""
@@ -76,38 +73,35 @@ class YiCamera(Camera):
         """Return the name of this camera."""
         return self._name
 
-    async def _connect_to_client(self):
-        """Attempt to establish a connection via FTP."""
+    async def _get_latest_video_url(self):
+        """Retrieve the latest video file from the customized Yi FTP server."""
         from aioftp import Client, StatusCodeError
 
         ftp = Client()
         try:
             await ftp.connect(self.host)
             await ftp.login(self.user, self.passwd)
-            self._ftp = ftp
         except StatusCodeError as err:
             raise PlatformNotReady(err)
 
-    async def _get_latest_video_url(self):
-        """Retrieve the latest video file from the customized Yi FTP server."""
-        from aioftp import StatusCodeError
-
         try:
-            await self._ftp.change_directory(self.path)
+            await ftp.change_directory(self.path)
             dirs = []
-            for path, attrs in await self._ftp.list():
+            for path, attrs in await ftp.list():
                 if attrs['type'] == 'dir' and '.' not in str(path):
                     dirs.append(path)
             latest_dir = dirs[-1]
-            await self._ftp.change_directory(latest_dir)
+            await ftp.change_directory(latest_dir)
 
             videos = []
-            for path, _ in await self._ftp.list():
+            for path, _ in await ftp.list():
                 videos.append(path)
             if not videos:
                 _LOGGER.info('Video folder "%s" empty; delaying', latest_dir)
                 return None
 
+            await ftp.quit()
+
             return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
                 self.user, self.passwd, self.host, self.port, self.path,
                 latest_dir, videos[-1])

From 69502163bd298ed2eff13d76893e2129d477ec07 Mon Sep 17 00:00:00 2001
From: Jason Hu <awarecan@users.noreply.github.com>
Date: Mon, 25 Jun 2018 10:13:41 -0700
Subject: [PATCH 4/7] Skip nest security state sensor if no Nest Cam exists
 (#15112)

---
 homeassistant/components/sensor/nest.py | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py
index bf1b3f65c4a..75c25f25baa 100644
--- a/homeassistant/components/sensor/nest.py
+++ b/homeassistant/components/sensor/nest.py
@@ -24,10 +24,14 @@ PROTECT_SENSOR_TYPES = ['co_status',
                         # color_status: "gray", "green", "yellow", "red"
                         'color_status']
 
-STRUCTURE_SENSOR_TYPES = ['eta', 'security_state']
+STRUCTURE_SENSOR_TYPES = ['eta']
+
+# security_state is structure level sensor, but only meaningful when
+# Nest Cam exist
+STRUCTURE_CAMERA_SENSOR_TYPES = ['security_state']
 
 _VALID_SENSOR_TYPES = SENSOR_TYPES + TEMP_SENSOR_TYPES + PROTECT_SENSOR_TYPES \
-                      + STRUCTURE_SENSOR_TYPES
+                      + STRUCTURE_SENSOR_TYPES + STRUCTURE_CAMERA_SENSOR_TYPES
 
 SENSOR_UNITS = {'humidity': '%'}
 
@@ -105,6 +109,14 @@ async def async_setup_entry(hass, entry, async_add_devices):
                             for variable in conditions
                             if variable in PROTECT_SENSOR_TYPES]
 
+        structures_has_camera = {}
+        for structure, device in nest.cameras():
+            structures_has_camera[structure] = True
+        for structure in structures_has_camera:
+            all_sensors += [NestBasicSensor(structure, None, variable)
+                            for variable in conditions
+                            if variable in STRUCTURE_CAMERA_SENSOR_TYPES]
+
         return all_sensors
 
     async_add_devices(await hass.async_add_job(get_sensors), True)

From 3f21966ec92446571f095f158c848b56c9350e10 Mon Sep 17 00:00:00 2001
From: Paulus Schoutsen <paulus@paulusschoutsen.nl>
Date: Mon, 25 Jun 2018 15:59:05 -0400
Subject: [PATCH 5/7] Fix cast config (#15143)

---
 homeassistant/components/media_player/cast.py | 12 ++--
 tests/components/media_player/test_cast.py    | 55 +++++++++++++++++++
 2 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py
index eced0dbbe25..4e24d5f2f71 100644
--- a/homeassistant/components/media_player/cast.py
+++ b/homeassistant/components/media_player/cast.py
@@ -4,7 +4,7 @@ Provide functionality to interact with Cast devices on the network.
 For more details about this platform, please refer to the documentation at
 https://home-assistant.io/components/media_player.cast/
 """
-# pylint: disable=import-error
+import asyncio
 import logging
 import threading
 from typing import Optional, Tuple
@@ -200,9 +200,13 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
 
 async def async_setup_entry(hass, config_entry, async_add_devices):
     """Set up Cast from a config entry."""
-    await _async_setup_platform(
-        hass, hass.data[CAST_DOMAIN].get('media_player', {}),
-        async_add_devices, None)
+    config = hass.data[CAST_DOMAIN].get('media_player', {})
+    if not isinstance(config, list):
+        config = [config]
+
+    await asyncio.wait([
+        _async_setup_platform(hass, cfg, async_add_devices, None)
+        for cfg in config])
 
 
 async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType,
diff --git a/tests/components/media_player/test_cast.py b/tests/components/media_player/test_cast.py
index 41cf6749b71..47be39c68e5 100644
--- a/tests/components/media_player/test_cast.py
+++ b/tests/components/media_player/test_cast.py
@@ -17,6 +17,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect, \
 from homeassistant.components.media_player import cast
 from homeassistant.setup import async_setup_component
 
+from tests.common import MockConfigEntry, mock_coro
+
 
 @pytest.fixture(autouse=True)
 def cast_mock():
@@ -359,3 +361,56 @@ async def test_disconnect_on_stop(hass: HomeAssistantType):
     hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
     await hass.async_block_till_done()
     assert chromecast.disconnect.call_count == 1
+
+
+async def test_entry_setup_no_config(hass: HomeAssistantType):
+    """Test setting up entry with no config.."""
+    await async_setup_component(hass, 'cast', {})
+
+    with patch(
+        'homeassistant.components.media_player.cast._async_setup_platform',
+            return_value=mock_coro()) as mock_setup:
+        await cast.async_setup_entry(hass, MockConfigEntry(), None)
+
+    assert len(mock_setup.mock_calls) == 1
+    assert mock_setup.mock_calls[0][1][1] == {}
+
+
+async def test_entry_setup_single_config(hass: HomeAssistantType):
+    """Test setting up entry and having a single config option."""
+    await async_setup_component(hass, 'cast', {
+        'cast': {
+            'media_player': {
+                'host': 'bla'
+            }
+        }
+    })
+
+    with patch(
+        'homeassistant.components.media_player.cast._async_setup_platform',
+            return_value=mock_coro()) as mock_setup:
+        await cast.async_setup_entry(hass, MockConfigEntry(), None)
+
+    assert len(mock_setup.mock_calls) == 1
+    assert mock_setup.mock_calls[0][1][1] == {'host': 'bla'}
+
+
+async def test_entry_setup_list_config(hass: HomeAssistantType):
+    """Test setting up entry and having multiple config options."""
+    await async_setup_component(hass, 'cast', {
+        'cast': {
+            'media_player': [
+                {'host': 'bla'},
+                {'host': 'blu'},
+            ]
+        }
+    })
+
+    with patch(
+        'homeassistant.components.media_player.cast._async_setup_platform',
+            return_value=mock_coro()) as mock_setup:
+        await cast.async_setup_entry(hass, MockConfigEntry(), None)
+
+    assert len(mock_setup.mock_calls) == 2
+    assert mock_setup.mock_calls[0][1][1] == {'host': 'bla'}
+    assert mock_setup.mock_calls[1][1][1] == {'host': 'blu'}

From 2520fddbdf5ee8680eaa4fbb2d56075746c47c7e Mon Sep 17 00:00:00 2001
From: Jason Hu <awarecan@users.noreply.github.com>
Date: Mon, 25 Jun 2018 10:04:32 -0700
Subject: [PATCH 6/7] Bump python-nest to 4.0.3 (#15098)

Resolve network reconnect issue
---
 homeassistant/components/nest/__init__.py | 3 ++-
 homeassistant/components/sensor/nest.py   | 3 ++-
 requirements_all.txt                      | 2 +-
 requirements_test_all.txt                 | 2 +-
 4 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py
index bf99fadc1d7..58fa1953ef0 100644
--- a/homeassistant/components/nest/__init__.py
+++ b/homeassistant/components/nest/__init__.py
@@ -23,7 +23,7 @@ from homeassistant.helpers.entity import Entity
 from .const import DOMAIN
 from . import local_auth
 
-REQUIREMENTS = ['python-nest==4.0.2']
+REQUIREMENTS = ['python-nest==4.0.3']
 
 _CONFIGURING = {}
 _LOGGER = logging.getLogger(__name__)
@@ -86,6 +86,7 @@ async def async_nest_update_event_broker(hass, nest):
                 _LOGGER.debug("dispatching nest data update")
                 async_dispatcher_send(hass, SIGNAL_NEST_UPDATE)
             else:
+                _LOGGER.debug("stop listening nest.update_event")
                 return
 
 
diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py
index 75c25f25baa..d2e1501ad7e 100644
--- a/homeassistant/components/sensor/nest.py
+++ b/homeassistant/components/sensor/nest.py
@@ -145,7 +145,8 @@ class NestBasicSensor(NestSensorDevice):
         elif self.variable in PROTECT_SENSOR_TYPES \
                 and self.variable != 'color_status':
             # keep backward compatibility
-            self._state = getattr(self.device, self.variable).capitalize()
+            state = getattr(self.device, self.variable)
+            self._state = state.capitalize() if state is not None else None
         else:
             self._state = getattr(self.device, self.variable)
 
diff --git a/requirements_all.txt b/requirements_all.txt
index 65f6485f176..cd9dfb5194a 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1060,7 +1060,7 @@ python-mpd2==1.0.0
 python-mystrom==0.4.4
 
 # homeassistant.components.nest
-python-nest==4.0.2
+python-nest==4.0.3
 
 # homeassistant.components.device_tracker.nmap_tracker
 python-nmap==0.6.1
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index e1fdc2cb3d4..5f7967761de 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -156,7 +156,7 @@ pyqwikswitch==0.8
 python-forecastio==1.4.0
 
 # homeassistant.components.nest
-python-nest==4.0.2
+python-nest==4.0.3
 
 # homeassistant.components.sensor.whois
 pythonwhois==2.4.3

From 9b950f51928d66293eb942bdf6a7fd4a8eaad12b Mon Sep 17 00:00:00 2001
From: Paulus Schoutsen <balloob@gmail.com>
Date: Mon, 25 Jun 2018 16:59:14 -0400
Subject: [PATCH 7/7] Bumped version to 0.72.1

---
 homeassistant/const.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/homeassistant/const.py b/homeassistant/const.py
index a22605c37f4..f1a4e55d662 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -2,7 +2,7 @@
 """Constants used by Home Assistant components."""
 MAJOR_VERSION = 0
 MINOR_VERSION = 72
-PATCH_VERSION = '0'
+PATCH_VERSION = '1'
 __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
 __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
 REQUIRED_PYTHON_VER = (3, 5, 3)