From 9efa31ef9f392bdc9f8831b6602b245acab0a0de Mon Sep 17 00:00:00 2001
From: John Mihalic <mezz@johnmihalic.com>
Date: Fri, 15 Jun 2018 15:24:09 -0400
Subject: [PATCH] Eight Sleep add REM type, Update async syntax, Catch API
 quirks (#14937)

---
 .../components/binary_sensor/eight_sleep.py   |  8 +--
 homeassistant/components/eight_sleep.py       | 38 +++++------
 .../components/sensor/eight_sleep.py          | 66 ++++++++++++-------
 requirements_all.txt                          |  2 +-
 4 files changed, 60 insertions(+), 54 deletions(-)

diff --git a/homeassistant/components/binary_sensor/eight_sleep.py b/homeassistant/components/binary_sensor/eight_sleep.py
index a6d4476f047..40ca491e1f3 100644
--- a/homeassistant/components/binary_sensor/eight_sleep.py
+++ b/homeassistant/components/binary_sensor/eight_sleep.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
 https://home-assistant.io/components/binary_sensor.eight_sleep/
 """
 import logging
-import asyncio
 
 from homeassistant.components.binary_sensor import BinarySensorDevice
 from homeassistant.components.eight_sleep import (
@@ -16,8 +15,8 @@ _LOGGER = logging.getLogger(__name__)
 DEPENDENCIES = ['eight_sleep']
 
 
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_devices,
+                               discovery_info=None):
     """Set up the eight sleep binary sensor."""
     if discovery_info is None:
         return
@@ -63,7 +62,6 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice):
         """Return true if the binary sensor is on."""
         return self._state
 
-    @asyncio.coroutine
-    def async_update(self):
+    async def async_update(self):
         """Retrieve latest state."""
         self._state = self._usrobj.bed_presence
diff --git a/homeassistant/components/eight_sleep.py b/homeassistant/components/eight_sleep.py
index 3478d5cd08e..704eab1846b 100644
--- a/homeassistant/components/eight_sleep.py
+++ b/homeassistant/components/eight_sleep.py
@@ -4,7 +4,6 @@ Support for Eight smart mattress covers and mattresses.
 For more details about this component, please refer to the documentation at
 https://home-assistant.io/components/eight_sleep/
 """
-import asyncio
 import logging
 from datetime import timedelta
 
@@ -22,7 +21,7 @@ from homeassistant.helpers.entity import Entity
 from homeassistant.helpers.event import async_track_point_in_utc_time
 from homeassistant.util.dt import utcnow
 
-REQUIREMENTS = ['pyeight==0.0.8']
+REQUIREMENTS = ['pyeight==0.0.9']
 
 _LOGGER = logging.getLogger(__name__)
 
@@ -86,8 +85,7 @@ CONFIG_SCHEMA = vol.Schema({
 }, extra=vol.ALLOW_EXTRA)
 
 
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
     """Set up the Eight Sleep component."""
     from pyeight.eight import EightSleep
 
@@ -107,31 +105,29 @@ def async_setup(hass, config):
     hass.data[DATA_EIGHT] = eight
 
     # Authenticate, build sensors
-    success = yield from eight.start()
+    success = await eight.start()
     if not success:
         # Authentication failed, cannot continue
         return False
 
-    @asyncio.coroutine
-    def async_update_heat_data(now):
+    async def async_update_heat_data(now):
         """Update heat data from eight in HEAT_SCAN_INTERVAL."""
-        yield from eight.update_device_data()
+        await eight.update_device_data()
         async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT)
 
         async_track_point_in_utc_time(
             hass, async_update_heat_data, utcnow() + HEAT_SCAN_INTERVAL)
 
-    @asyncio.coroutine
-    def async_update_user_data(now):
+    async def async_update_user_data(now):
         """Update user data from eight in USER_SCAN_INTERVAL."""
-        yield from eight.update_user_data()
+        await eight.update_user_data()
         async_dispatcher_send(hass, SIGNAL_UPDATE_USER)
 
         async_track_point_in_utc_time(
             hass, async_update_user_data, utcnow() + USER_SCAN_INTERVAL)
 
-    yield from async_update_heat_data(None)
-    yield from async_update_user_data(None)
+    await async_update_heat_data(None)
+    await async_update_user_data(None)
 
     # Load sub components
     sensors = []
@@ -157,8 +153,7 @@ def async_setup(hass, config):
             CONF_BINARY_SENSORS: binary_sensors,
         }, config))
 
-    @asyncio.coroutine
-    def async_service_handler(service):
+    async def async_service_handler(service):
         """Handle eight sleep service calls."""
         params = service.data.copy()
 
@@ -170,7 +165,7 @@ def async_setup(hass, config):
             side = sens.split('_')[1]
             userid = eight.fetch_userid(side)
             usrobj = eight.users[userid]
-            yield from usrobj.set_heating_level(target, duration)
+            await usrobj.set_heating_level(target, duration)
 
         async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT)
 
@@ -179,10 +174,9 @@ def async_setup(hass, config):
         DOMAIN, SERVICE_HEAT_SET, async_service_handler,
         schema=SERVICE_EIGHT_SCHEMA)
 
-    @asyncio.coroutine
-    def stop_eight(event):
+    async def stop_eight(event):
         """Handle stopping eight api session."""
-        yield from eight.stop()
+        await eight.stop()
 
     hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_eight)
 
@@ -196,8 +190,7 @@ class EightSleepUserEntity(Entity):
         """Initialize the data object."""
         self._eight = eight
 
-    @asyncio.coroutine
-    def async_added_to_hass(self):
+    async def async_added_to_hass(self):
         """Register update dispatcher."""
         @callback
         def async_eight_user_update():
@@ -220,8 +213,7 @@ class EightSleepHeatEntity(Entity):
         """Initialize the data object."""
         self._eight = eight
 
-    @asyncio.coroutine
-    def async_added_to_hass(self):
+    async def async_added_to_hass(self):
         """Register update dispatcher."""
         @callback
         def async_eight_heat_update():
diff --git a/homeassistant/components/sensor/eight_sleep.py b/homeassistant/components/sensor/eight_sleep.py
index e0a42fdb6a8..fd7c1aee3ae 100644
--- a/homeassistant/components/sensor/eight_sleep.py
+++ b/homeassistant/components/sensor/eight_sleep.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
 https://home-assistant.io/components/sensor.eight_sleep/
 """
 import logging
-import asyncio
 
 from homeassistant.components.eight_sleep import (
     DATA_EIGHT, EightSleepHeatEntity, EightSleepUserEntity,
@@ -24,20 +23,20 @@ ATTR_AVG_HEART_RATE = 'Average Heart Rate'
 ATTR_SLEEP_DUR = 'Time Slept'
 ATTR_LIGHT_PERC = 'Light Sleep %'
 ATTR_DEEP_PERC = 'Deep Sleep %'
+ATTR_REM_PERC = 'REM Sleep %'
 ATTR_TNT = 'Tosses & Turns'
 ATTR_SLEEP_STAGE = 'Sleep Stage'
 ATTR_TARGET_HEAT = 'Target Heating Level'
 ATTR_ACTIVE_HEAT = 'Heating Active'
 ATTR_DURATION_HEAT = 'Heating Time Remaining'
-ATTR_LAST_SEEN = 'Last In Bed'
 ATTR_PROCESSING = 'Processing'
 ATTR_SESSION_START = 'Session Start'
 
 _LOGGER = logging.getLogger(__name__)
 
 
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_devices,
+                               discovery_info=None):
     """Set up the eight sleep sensors."""
     if discovery_info is None:
         return
@@ -98,8 +97,7 @@ class EightHeatSensor(EightSleepHeatEntity):
         """Return the unit the value is expressed in."""
         return '%'
 
-    @asyncio.coroutine
-    def async_update(self):
+    async def async_update(self):
         """Retrieve latest state."""
         _LOGGER.debug("Updating Heat sensor: %s", self._sensor)
         self._state = self._usrobj.heating_level
@@ -110,7 +108,6 @@ class EightHeatSensor(EightSleepHeatEntity):
         state_attr = {ATTR_TARGET_HEAT: self._usrobj.target_heating_level}
         state_attr[ATTR_ACTIVE_HEAT] = self._usrobj.now_heating
         state_attr[ATTR_DURATION_HEAT] = self._usrobj.heating_remaining
-        state_attr[ATTR_LAST_SEEN] = self._usrobj.last_seen
 
         return state_attr
 
@@ -164,8 +161,7 @@ class EightUserSensor(EightSleepUserEntity):
         if 'bed_temp' in self._sensor:
             return 'mdi:thermometer'
 
-    @asyncio.coroutine
-    def async_update(self):
+    async def async_update(self):
         """Retrieve latest state."""
         _LOGGER.debug("Updating User sensor: %s", self._sensor)
         if 'current' in self._sensor:
@@ -176,10 +172,13 @@ class EightUserSensor(EightSleepUserEntity):
             self._attr = self._usrobj.last_values
         elif 'bed_temp' in self._sensor:
             temp = self._usrobj.current_values['bed_temp']
-            if self._units == 'si':
-                self._state = round(temp, 2)
-            else:
-                self._state = round((temp*1.8)+32, 2)
+            try:
+                if self._units == 'si':
+                    self._state = round(temp, 2)
+                else:
+                    self._state = round((temp*1.8)+32, 2)
+            except TypeError:
+                self._state = None
         elif 'sleep_stage' in self._sensor:
             self._state = self._usrobj.current_values['stage']
 
@@ -208,12 +207,27 @@ class EightUserSensor(EightSleepUserEntity):
         except ZeroDivisionError:
             state_attr[ATTR_DEEP_PERC] = 0
 
-        if self._units == 'si':
-            room_temp = round(self._attr['room_temp'], 2)
-            bed_temp = round(self._attr['bed_temp'], 2)
-        else:
-            room_temp = round((self._attr['room_temp']*1.8)+32, 2)
-            bed_temp = round((self._attr['bed_temp']*1.8)+32, 2)
+        try:
+            state_attr[ATTR_REM_PERC] = round((
+                self._attr['breakdown']['rem'] / sleep_time) * 100, 2)
+        except ZeroDivisionError:
+            state_attr[ATTR_REM_PERC] = 0
+
+        try:
+            if self._units == 'si':
+                room_temp = round(self._attr['room_temp'], 2)
+            else:
+                room_temp = round((self._attr['room_temp']*1.8)+32, 2)
+        except TypeError:
+            room_temp = None
+
+        try:
+            if self._units == 'si':
+                bed_temp = round(self._attr['bed_temp'], 2)
+            else:
+                bed_temp = round((self._attr['bed_temp']*1.8)+32, 2)
+        except TypeError:
+            bed_temp = None
 
         if 'current' in self._sensor_root:
             state_attr[ATTR_RESP_RATE] = round(self._attr['resp_rate'], 2)
@@ -255,15 +269,17 @@ class EightRoomSensor(EightSleepUserEntity):
         """Return the state of the sensor."""
         return self._state
 
-    @asyncio.coroutine
-    def async_update(self):
+    async def async_update(self):
         """Retrieve latest state."""
         _LOGGER.debug("Updating Room sensor: %s", self._sensor)
         temp = self._eight.room_temperature()
-        if self._units == 'si':
-            self._state = round(temp, 2)
-        else:
-            self._state = round((temp*1.8)+32, 2)
+        try:
+            if self._units == 'si':
+                self._state = round(temp, 2)
+            else:
+                self._state = round((temp*1.8)+32, 2)
+        except TypeError:
+            self._state = None
 
     @property
     def unit_of_measurement(self):
diff --git a/requirements_all.txt b/requirements_all.txt
index 90cd28d61fc..921bbf8fd46 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -811,7 +811,7 @@ pyeconet==0.0.5
 pyedimax==0.1
 
 # homeassistant.components.eight_sleep
-pyeight==0.0.8
+pyeight==0.0.9
 
 # homeassistant.components.media_player.emby
 pyemby==1.5
-- 
GitLab