Switch to UpdateCoordinator for eight sleep (#52614)

* Switch to UpdateCoordinator for eight sleep

* use super call

* add self as codeowner

* Call API update method directly when creating coordinator

* Update homeassistant/components/eight_sleep/__init__.py

Co-authored-by: J. Nick Koston <nick@koston.org>

* Update homeassistant/components/eight_sleep/__init__.py

Co-authored-by: J. Nick Koston <nick@koston.org>

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Raman Gupta 2021-10-25 16:18:33 -04:00 committed by GitHub
parent 787de8ba66
commit 6bc5961f8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 105 deletions

View File

@ -138,7 +138,7 @@ homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/edl21/* @mtdcr
homeassistant/components/efergy/* @tkdrob
homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/eight_sleep/* @mezz64 @raman325
homeassistant/components/elgato/* @frenck
homeassistant/components/elkm1/* @gwww @bdraco
homeassistant/components/elv/* @majuss

View File

@ -12,23 +12,22 @@ from homeassistant.const import (
CONF_SENSORS,
CONF_USERNAME,
)
from homeassistant.core import callback
from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
_LOGGER = logging.getLogger(__name__)
CONF_PARTNER = "partner"
DATA_EIGHT = "eight_sleep"
DATA_HEAT = "heat"
DATA_USER = "user"
DATA_API = "api"
DOMAIN = "eight_sleep"
HEAT_ENTITY = "heat"
@ -115,7 +114,7 @@ async def async_setup(hass, config):
eight = EightSleep(user, password, timezone, async_get_clientsession(hass))
hass.data[DATA_EIGHT] = eight
hass.data.setdefault(DATA_EIGHT, {})[DATA_API] = eight
# Authenticate, build sensors
success = await eight.start()
@ -123,26 +122,14 @@ async def async_setup(hass, config):
# Authentication failed, cannot continue
return False
async def async_update_heat_data(now):
"""Update heat data from eight in HEAT_SCAN_INTERVAL."""
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
)
async def async_update_user_data(now):
"""Update user data from eight in USER_SCAN_INTERVAL."""
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
)
await async_update_heat_data(None)
await async_update_user_data(None)
heat_coordinator = hass.data[DOMAIN][DATA_HEAT] = EightSleepHeatDataCoordinator(
hass, eight
)
user_coordinator = hass.data[DOMAIN][DATA_USER] = EightSleepUserDataCoordinator(
hass, eight
)
await heat_coordinator.async_config_entry_first_refresh()
await user_coordinator.async_config_entry_first_refresh()
# Load sub components
sensors = []
@ -183,7 +170,7 @@ async def async_setup(hass, config):
usrobj = eight.users[userid]
await usrobj.set_heating_level(target, duration)
async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT)
await heat_coordinator.async_request_refresh()
# Register services
hass.services.async_register(
@ -193,55 +180,40 @@ async def async_setup(hass, config):
return True
class EightSleepUserEntity(Entity):
"""The Eight Sleep device entity."""
class EightSleepHeatDataCoordinator(DataUpdateCoordinator):
"""Class to retrieve heat data from Eight Sleep."""
def __init__(self, eight):
"""Initialize the data object."""
self._eight = eight
async def async_added_to_hass(self):
"""Register update dispatcher."""
@callback
def async_eight_user_update():
"""Update callback."""
self.async_schedule_update_ha_state(True)
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_USER, async_eight_user_update
)
def __init__(self, hass, api):
"""Initialize coordinator."""
self.api = api
super().__init__(
hass,
_LOGGER,
name=f"{DOMAIN}_heat",
update_interval=HEAT_SCAN_INTERVAL,
update_method=self.api.update_device_data,
)
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
class EightSleepUserDataCoordinator(DataUpdateCoordinator):
"""Class to retrieve user data from Eight Sleep."""
class EightSleepHeatEntity(Entity):
"""The Eight Sleep device entity."""
def __init__(self, eight):
"""Initialize the data object."""
self._eight = eight
async def async_added_to_hass(self):
"""Register update dispatcher."""
@callback
def async_eight_heat_update():
"""Update callback."""
self.async_schedule_update_ha_state(True)
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_HEAT, async_eight_heat_update
)
def __init__(self, hass, api):
"""Initialize coordinator."""
self.api = api
super().__init__(
hass,
_LOGGER,
name=f"{DOMAIN}_user",
update_interval=USER_SCAN_INTERVAL,
update_method=self.api.update_user_data,
)
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
class EightSleepEntity(CoordinatorEntity):
"""The Eight Sleep device entity."""
def __init__(self, coordinator, eight):
"""Initialize the data object."""
super().__init__(coordinator)
self._eight = eight

View File

@ -5,8 +5,16 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASS_OCCUPANCY,
BinarySensorEntity,
)
from homeassistant.core import callback
from . import CONF_BINARY_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity
from . import (
CONF_BINARY_SENSORS,
DATA_API,
DATA_EIGHT,
DATA_HEAT,
NAME_MAP,
EightSleepEntity,
)
_LOGGER = logging.getLogger(__name__)
@ -18,22 +26,23 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
name = "Eight"
sensors = discovery_info[CONF_BINARY_SENSORS]
eight = hass.data[DATA_EIGHT]
eight = hass.data[DATA_EIGHT][DATA_API]
heat_coordinator = hass.data[DATA_EIGHT][DATA_HEAT]
all_sensors = []
for sensor in sensors:
all_sensors.append(EightHeatSensor(name, eight, sensor))
all_sensors.append(EightHeatSensor(name, heat_coordinator, eight, sensor))
async_add_entities(all_sensors, True)
class EightHeatSensor(EightSleepHeatEntity, BinarySensorEntity):
class EightHeatSensor(EightSleepEntity, BinarySensorEntity):
"""Representation of a Eight Sleep heat-based sensor."""
def __init__(self, name, eight, sensor):
def __init__(self, name, coordinator, eight, sensor):
"""Initialize the sensor."""
super().__init__(eight)
super().__init__(coordinator, eight)
self._sensor = sensor
self._mapped_name = NAME_MAP.get(self._sensor, self._sensor)
@ -58,6 +67,8 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorEntity):
"""Return true if the binary sensor is on."""
return self._state
async def async_update(self):
"""Retrieve latest state."""
@callback
def _handle_coordinator_update(self):
"""Handle updated data from the coordinator."""
self._state = self._usrobj.bed_presence
super()._handle_coordinator_update()

View File

@ -3,6 +3,6 @@
"name": "Eight Sleep",
"documentation": "https://www.home-assistant.io/integrations/eight_sleep",
"requirements": ["pyeight==0.1.9"],
"codeowners": ["@mezz64"],
"codeowners": ["@mezz64", "@raman325"],
"iot_class": "cloud_polling"
}

View File

@ -8,13 +8,16 @@ from homeassistant.const import (
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.core import callback
from . import (
CONF_SENSORS,
DATA_API,
DATA_EIGHT,
DATA_HEAT,
DATA_USER,
NAME_MAP,
EightSleepHeatEntity,
EightSleepUserEntity,
EightSleepEntity,
)
ATTR_ROOM_TEMP = "Room Temperature"
@ -52,7 +55,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
name = "Eight"
sensors = discovery_info[CONF_SENSORS]
eight = hass.data[DATA_EIGHT]
eight = hass.data[DATA_EIGHT][DATA_API]
heat_coordinator = hass.data[DATA_EIGHT][DATA_HEAT]
user_coordinator = hass.data[DATA_EIGHT][DATA_USER]
if hass.config.units.is_metric:
units = "si"
@ -63,21 +68,25 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
for sensor in sensors:
if "bed_state" in sensor:
all_sensors.append(EightHeatSensor(name, eight, sensor))
all_sensors.append(EightHeatSensor(name, heat_coordinator, eight, sensor))
elif "room_temp" in sensor:
all_sensors.append(EightRoomSensor(name, eight, sensor, units))
all_sensors.append(
EightRoomSensor(name, user_coordinator, eight, sensor, units)
)
else:
all_sensors.append(EightUserSensor(name, eight, sensor, units))
all_sensors.append(
EightUserSensor(name, user_coordinator, eight, sensor, units)
)
async_add_entities(all_sensors, True)
class EightHeatSensor(EightSleepHeatEntity, SensorEntity):
class EightHeatSensor(EightSleepEntity, SensorEntity):
"""Representation of an eight sleep heat-based sensor."""
def __init__(self, name, eight, sensor):
def __init__(self, name, coordinator, eight, sensor):
"""Initialize the sensor."""
super().__init__(eight)
super().__init__(coordinator, eight)
self._sensor = sensor
self._mapped_name = NAME_MAP.get(self._sensor, self._sensor)
@ -110,10 +119,12 @@ class EightHeatSensor(EightSleepHeatEntity, SensorEntity):
"""Return the unit the value is expressed in."""
return PERCENTAGE
async def async_update(self):
"""Retrieve latest state."""
@callback
def _handle_coordinator_update(self):
"""Handle updated data from the coordinator."""
_LOGGER.debug("Updating Heat sensor: %s", self._sensor)
self._state = self._usrobj.heating_level
super()._handle_coordinator_update()
@property
def extra_state_attributes(self):
@ -125,12 +136,12 @@ class EightHeatSensor(EightSleepHeatEntity, SensorEntity):
}
class EightUserSensor(EightSleepUserEntity, SensorEntity):
class EightUserSensor(EightSleepEntity, SensorEntity):
"""Representation of an eight sleep user-based sensor."""
def __init__(self, name, eight, sensor, units):
def __init__(self, name, coordinator, eight, sensor, units):
"""Initialize the sensor."""
super().__init__(eight)
super().__init__(coordinator, eight)
self._sensor = sensor
self._sensor_root = self._sensor.split("_", 1)[1]
@ -183,8 +194,9 @@ class EightUserSensor(EightSleepUserEntity, SensorEntity):
return DEVICE_CLASS_TEMPERATURE
return None
async def async_update(self):
"""Retrieve latest state."""
@callback
def _handle_coordinator_update(self):
"""Handle updated data from the coordinator."""
_LOGGER.debug("Updating User sensor: %s", self._sensor)
if "current" in self._sensor:
if "fitness" in self._sensor:
@ -208,6 +220,8 @@ class EightUserSensor(EightSleepUserEntity, SensorEntity):
elif "sleep_stage" in self._sensor:
self._state = self._usrobj.current_values["stage"]
super()._handle_coordinator_update()
@property
def extra_state_attributes(self):
"""Return device state attributes."""
@ -296,12 +310,12 @@ class EightUserSensor(EightSleepUserEntity, SensorEntity):
return state_attr
class EightRoomSensor(EightSleepUserEntity, SensorEntity):
class EightRoomSensor(EightSleepEntity, SensorEntity):
"""Representation of an eight sleep room sensor."""
def __init__(self, name, eight, sensor, units):
def __init__(self, name, coordinator, eight, sensor, units):
"""Initialize the sensor."""
super().__init__(eight)
super().__init__(coordinator, eight)
self._sensor = sensor
self._mapped_name = NAME_MAP.get(self._sensor, self._sensor)
@ -320,8 +334,9 @@ class EightRoomSensor(EightSleepUserEntity, SensorEntity):
"""Return the state of the sensor."""
return self._state
async def async_update(self):
"""Retrieve latest state."""
@callback
def _handle_coordinator_update(self):
"""Handle updated data from the coordinator."""
_LOGGER.debug("Updating Room sensor: %s", self._sensor)
temp = self._eight.room_temperature()
try:
@ -331,6 +346,7 @@ class EightRoomSensor(EightSleepUserEntity, SensorEntity):
self._state = round((temp * 1.8) + 32, 2)
except TypeError:
self._state = None
super()._handle_coordinator_update()
@property
def native_unit_of_measurement(self):