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/edl21/* @mtdcr
homeassistant/components/efergy/* @tkdrob homeassistant/components/efergy/* @tkdrob
homeassistant/components/egardia/* @jeroenterheerdt homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64 homeassistant/components/eight_sleep/* @mezz64 @raman325
homeassistant/components/elgato/* @frenck homeassistant/components/elgato/* @frenck
homeassistant/components/elkm1/* @gwww @bdraco homeassistant/components/elkm1/* @gwww @bdraco
homeassistant/components/elv/* @majuss homeassistant/components/elv/* @majuss

View File

@ -12,23 +12,22 @@ from homeassistant.const import (
CONF_SENSORS, CONF_SENSORS,
CONF_USERNAME, CONF_USERNAME,
) )
from homeassistant.core import callback
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.update_coordinator import (
async_dispatcher_connect, CoordinatorEntity,
async_dispatcher_send, 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__) _LOGGER = logging.getLogger(__name__)
CONF_PARTNER = "partner" CONF_PARTNER = "partner"
DATA_EIGHT = "eight_sleep" DATA_EIGHT = "eight_sleep"
DATA_HEAT = "heat"
DATA_USER = "user"
DATA_API = "api"
DOMAIN = "eight_sleep" DOMAIN = "eight_sleep"
HEAT_ENTITY = "heat" HEAT_ENTITY = "heat"
@ -115,7 +114,7 @@ async def async_setup(hass, config):
eight = EightSleep(user, password, timezone, async_get_clientsession(hass)) 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 # Authenticate, build sensors
success = await eight.start() success = await eight.start()
@ -123,26 +122,14 @@ async def async_setup(hass, config):
# Authentication failed, cannot continue # Authentication failed, cannot continue
return False return False
async def async_update_heat_data(now): heat_coordinator = hass.data[DOMAIN][DATA_HEAT] = EightSleepHeatDataCoordinator(
"""Update heat data from eight in HEAT_SCAN_INTERVAL.""" hass, eight
await eight.update_device_data() )
async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT) user_coordinator = hass.data[DOMAIN][DATA_USER] = EightSleepUserDataCoordinator(
hass, eight
async_track_point_in_utc_time( )
hass, async_update_heat_data, utcnow() + HEAT_SCAN_INTERVAL await heat_coordinator.async_config_entry_first_refresh()
) await user_coordinator.async_config_entry_first_refresh()
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)
# Load sub components # Load sub components
sensors = [] sensors = []
@ -183,7 +170,7 @@ async def async_setup(hass, config):
usrobj = eight.users[userid] usrobj = eight.users[userid]
await usrobj.set_heating_level(target, duration) await usrobj.set_heating_level(target, duration)
async_dispatcher_send(hass, SIGNAL_UPDATE_HEAT) await heat_coordinator.async_request_refresh()
# Register services # Register services
hass.services.async_register( hass.services.async_register(
@ -193,55 +180,40 @@ async def async_setup(hass, config):
return True return True
class EightSleepUserEntity(Entity): class EightSleepHeatDataCoordinator(DataUpdateCoordinator):
"""The Eight Sleep device entity.""" """Class to retrieve heat data from Eight Sleep."""
def __init__(self, eight): def __init__(self, hass, api):
"""Initialize the data object.""" """Initialize coordinator."""
self._eight = eight self.api = api
super().__init__(
async def async_added_to_hass(self): hass,
"""Register update dispatcher.""" _LOGGER,
name=f"{DOMAIN}_heat",
@callback update_interval=HEAT_SCAN_INTERVAL,
def async_eight_user_update(): update_method=self.api.update_device_data,
"""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
)
) )
@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): def __init__(self, hass, api):
"""The Eight Sleep device entity.""" """Initialize coordinator."""
self.api = api
def __init__(self, eight): super().__init__(
"""Initialize the data object.""" hass,
self._eight = eight _LOGGER,
name=f"{DOMAIN}_user",
async def async_added_to_hass(self): update_interval=USER_SCAN_INTERVAL,
"""Register update dispatcher.""" update_method=self.api.update_user_data,
@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
)
) )
@property
def should_poll(self): class EightSleepEntity(CoordinatorEntity):
"""Return True if entity has to be polled for state.""" """The Eight Sleep device entity."""
return False
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, DEVICE_CLASS_OCCUPANCY,
BinarySensorEntity, 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__) _LOGGER = logging.getLogger(__name__)
@ -18,22 +26,23 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
name = "Eight" name = "Eight"
sensors = discovery_info[CONF_BINARY_SENSORS] 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 = [] all_sensors = []
for sensor in 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) async_add_entities(all_sensors, True)
class EightHeatSensor(EightSleepHeatEntity, BinarySensorEntity): class EightHeatSensor(EightSleepEntity, BinarySensorEntity):
"""Representation of a Eight Sleep heat-based sensor.""" """Representation of a Eight Sleep heat-based sensor."""
def __init__(self, name, eight, sensor): def __init__(self, name, coordinator, eight, sensor):
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(eight) super().__init__(coordinator, eight)
self._sensor = sensor self._sensor = sensor
self._mapped_name = NAME_MAP.get(self._sensor, self._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 true if the binary sensor is on."""
return self._state return self._state
async def async_update(self): @callback
"""Retrieve latest state.""" def _handle_coordinator_update(self):
"""Handle updated data from the coordinator."""
self._state = self._usrobj.bed_presence self._state = self._usrobj.bed_presence
super()._handle_coordinator_update()

View File

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

View File

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