From 6bc5961f8aefad421856325fbb2363dcb08a8c85 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 25 Oct 2021 16:18:33 -0400 Subject: [PATCH] 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 * Update homeassistant/components/eight_sleep/__init__.py Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- CODEOWNERS | 2 +- .../components/eight_sleep/__init__.py | 120 +++++++----------- .../components/eight_sleep/binary_sensor.py | 27 ++-- .../components/eight_sleep/manifest.json | 2 +- .../components/eight_sleep/sensor.py | 58 ++++++--- 5 files changed, 104 insertions(+), 105 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index eeac3b6af78..e2494b8299a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/homeassistant/components/eight_sleep/__init__.py b/homeassistant/components/eight_sleep/__init__.py index f839b3fcc74..07474c44c62 100644 --- a/homeassistant/components/eight_sleep/__init__.py +++ b/homeassistant/components/eight_sleep/__init__.py @@ -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 diff --git a/homeassistant/components/eight_sleep/binary_sensor.py b/homeassistant/components/eight_sleep/binary_sensor.py index d8a763c2e54..ca8a10b0f93 100644 --- a/homeassistant/components/eight_sleep/binary_sensor.py +++ b/homeassistant/components/eight_sleep/binary_sensor.py @@ -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() diff --git a/homeassistant/components/eight_sleep/manifest.json b/homeassistant/components/eight_sleep/manifest.json index 1c3944a985e..e722f73c4e7 100644 --- a/homeassistant/components/eight_sleep/manifest.json +++ b/homeassistant/components/eight_sleep/manifest.json @@ -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" } diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index df0d7882491..0e84eea64f6 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -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):