Only store and pass around coordinator in Rituals Perfume Genie (#92298)

This commit is contained in:
Franck Nijhof 2023-04-30 21:32:14 +02:00 committed by GitHub
parent bb4c03ce3c
commit 5cbf08f89a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 102 additions and 127 deletions

View file

@ -1,4 +1,6 @@
"""The Rituals Perfume Genie integration."""
import asyncio
import aiohttp
from pyrituals import Account
@ -8,7 +10,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ACCOUNT_HASH, COORDINATORS, DEVICES, DOMAIN
from .const import ACCOUNT_HASH, DOMAIN
from .coordinator import RitualsDataUpdateCoordinator
PLATFORMS = [
@ -30,20 +32,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except aiohttp.ClientError as err:
raise ConfigEntryNotReady from err
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
COORDINATORS: {},
DEVICES: {},
# Create a coordinator for each diffuser
coordinators = {
diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser)
for diffuser in account_devices
}
for device in account_devices:
hublot = device.hublot
coordinator = RitualsDataUpdateCoordinator(hass, device)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id][DEVICES][hublot] = device
hass.data[DOMAIN][entry.entry_id][COORDINATORS][hublot] = coordinator
# Refresh all coordinators
await asyncio.gather(
*[
coordinator.async_config_entry_first_refresh()
for coordinator in coordinators.values()
]
)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinators
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

View file

@ -1,8 +1,6 @@
"""Support for Rituals Perfume Genie binary sensors."""
from __future__ import annotations
from pyrituals import Diffuser
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
@ -12,7 +10,7 @@ from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import COORDINATORS, DEVICES, DOMAIN
from .const import DOMAIN
from .coordinator import RitualsDataUpdateCoordinator
from .entity import DiffuserEntity
@ -25,13 +23,14 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the diffuser binary sensors."""
diffusers = hass.data[DOMAIN][config_entry.entry_id][DEVICES]
coordinators = hass.data[DOMAIN][config_entry.entry_id][COORDINATORS]
coordinators: dict[str, RitualsDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities(
DiffuserBatteryChargingBinarySensor(diffuser, coordinators[hublot])
for hublot, diffuser in diffusers.items()
if diffuser.has_battery
DiffuserBatteryChargingBinarySensor(coordinator)
for coordinator in coordinators.values()
if coordinator.diffuser.has_battery
)
@ -41,13 +40,11 @@ class DiffuserBatteryChargingBinarySensor(DiffuserEntity, BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the battery charging binary sensor."""
super().__init__(diffuser, coordinator, CHARGING_SUFFIX)
super().__init__(coordinator, CHARGING_SUFFIX)
@property
def is_on(self) -> bool:
"""Return the state of the battery charging binary sensor."""
return self._diffuser.charging
return self.coordinator.diffuser.charging

View file

@ -6,7 +6,4 @@ DOMAIN = "rituals_perfume_genie"
ACCOUNT_HASH = "account_hash"
COORDINATORS = "coordinators"
DEVICES = "devices"
UPDATE_INTERVAL = timedelta(minutes=2)

View file

@ -14,16 +14,16 @@ _LOGGER = logging.getLogger(__name__)
class RitualsDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Class to manage fetching Rituals Perfume Genie device data from single endpoint."""
def __init__(self, hass: HomeAssistant, device: Diffuser) -> None:
def __init__(self, hass: HomeAssistant, diffuser: Diffuser) -> None:
"""Initialize global Rituals Perfume Genie data updater."""
self._device = device
self.diffuser = diffuser
super().__init__(
hass,
_LOGGER,
name=f"{DOMAIN}-{device.hublot}",
name=f"{DOMAIN}-{diffuser.hublot}",
update_interval=UPDATE_INTERVAL,
)
async def _async_update_data(self) -> None:
"""Fetch data from Rituals."""
await self._device.update_data()
await self.diffuser.update_data()

View file

@ -1,8 +1,6 @@
"""Base class for Rituals Perfume Genie diffuser entity."""
from __future__ import annotations
from pyrituals import Diffuser
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -19,28 +17,25 @@ class DiffuserEntity(CoordinatorEntity[RitualsDataUpdateCoordinator]):
def __init__(
self,
diffuser: Diffuser,
coordinator: RitualsDataUpdateCoordinator,
entity_suffix: str,
) -> None:
"""Init from config, hookup diffuser and coordinator."""
super().__init__(coordinator)
self._diffuser = diffuser
hublot = self._diffuser.hublot
hubname = self._diffuser.name
hublot = coordinator.diffuser.hublot
hubname = coordinator.diffuser.name
self._attr_name = f"{hubname}{entity_suffix}"
self._attr_unique_id = f"{hublot}{entity_suffix}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, hublot)},
manufacturer=MANUFACTURER,
model=MODEL if diffuser.has_battery else MODEL2,
model=MODEL if coordinator.diffuser.has_battery else MODEL2,
name=hubname,
sw_version=diffuser.version,
sw_version=coordinator.diffuser.version,
)
@property
def available(self) -> bool:
"""Return if the entity is available."""
return super().available and self._diffuser.is_online
return super().available and self.coordinator.diffuser.is_online

View file

@ -1,14 +1,12 @@
"""Support for Rituals Perfume Genie numbers."""
from __future__ import annotations
from pyrituals import Diffuser
from homeassistant.components.number import NumberEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import COORDINATORS, DEVICES, DOMAIN
from .const import DOMAIN
from .coordinator import RitualsDataUpdateCoordinator
from .entity import DiffuserEntity
@ -24,14 +22,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the diffuser numbers."""
diffusers = hass.data[DOMAIN][config_entry.entry_id][DEVICES]
coordinators = hass.data[DOMAIN][config_entry.entry_id][COORDINATORS]
entities: list[DiffuserEntity] = []
for hublot, diffuser in diffusers.items():
coordinator = coordinators[hublot]
entities.append(DiffuserPerfumeAmount(diffuser, coordinator))
async_add_entities(entities)
coordinators: dict[str, RitualsDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities(
DiffuserPerfumeAmount(coordinator) for coordinator in coordinators.values()
)
class DiffuserPerfumeAmount(DiffuserEntity, NumberEntity):
@ -41,16 +37,14 @@ class DiffuserPerfumeAmount(DiffuserEntity, NumberEntity):
_attr_native_max_value = MAX_PERFUME_AMOUNT
_attr_native_min_value = MIN_PERFUME_AMOUNT
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the diffuser perfume amount number."""
super().__init__(diffuser, coordinator, PERFUME_AMOUNT_SUFFIX)
super().__init__(coordinator, PERFUME_AMOUNT_SUFFIX)
@property
def native_value(self) -> int:
"""Return the current perfume amount."""
return self._diffuser.perfume_amount
return self.coordinator.diffuser.perfume_amount
async def async_set_native_value(self, value: float) -> None:
"""Set the perfume amount."""
@ -59,4 +53,4 @@ class DiffuserPerfumeAmount(DiffuserEntity, NumberEntity):
f"Can't set the perfume amount to {value}. Perfume amount must be an"
" integer."
)
await self._diffuser.set_perfume_amount(int(value))
await self.coordinator.diffuser.set_perfume_amount(int(value))

View file

@ -1,15 +1,13 @@
"""Support for Rituals Perfume Genie numbers."""
from __future__ import annotations
from pyrituals import Diffuser
from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import AREA_SQUARE_METERS, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import COORDINATORS, DEVICES, DOMAIN
from .const import DOMAIN
from .coordinator import RitualsDataUpdateCoordinator
from .entity import DiffuserEntity
@ -22,11 +20,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the diffuser select entities."""
diffusers = hass.data[DOMAIN][config_entry.entry_id][DEVICES]
coordinators = hass.data[DOMAIN][config_entry.entry_id][COORDINATORS]
coordinators: dict[str, RitualsDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities(
DiffuserRoomSize(diffuser, coordinators[hublot])
for hublot, diffuser in diffusers.items()
DiffuserRoomSize(coordinator) for coordinator in coordinators.values()
)
@ -38,18 +37,18 @@ class DiffuserRoomSize(DiffuserEntity, SelectEntity):
_attr_options = ["15", "30", "60", "100"]
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the diffuser room size select entity."""
super().__init__(diffuser, coordinator, ROOM_SIZE_SUFFIX)
self._attr_entity_registry_enabled_default = diffuser.has_battery
super().__init__(coordinator, ROOM_SIZE_SUFFIX)
self._attr_entity_registry_enabled_default = (
self.coordinator.diffuser.has_battery
)
@property
def current_option(self) -> str:
"""Return the diffuser room size."""
return str(self._diffuser.room_size_square_meter)
return str(self.coordinator.diffuser.room_size_square_meter)
async def async_select_option(self, option: str) -> None:
"""Change the diffuser room size."""
await self._diffuser.set_room_size_square_meter(int(option))
await self.coordinator.diffuser.set_room_size_square_meter(int(option))

View file

@ -1,15 +1,13 @@
"""Support for Rituals Perfume Genie sensors."""
from __future__ import annotations
from pyrituals import Diffuser
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import COORDINATORS, DEVICES, DOMAIN
from .const import DOMAIN
from .coordinator import RitualsDataUpdateCoordinator
from .entity import DiffuserEntity
@ -25,16 +23,21 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the diffuser sensors."""
diffusers = hass.data[DOMAIN][config_entry.entry_id][DEVICES]
coordinators = hass.data[DOMAIN][config_entry.entry_id][COORDINATORS]
coordinators: dict[str, RitualsDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]
entities: list[DiffuserEntity] = []
for hublot, diffuser in diffusers.items():
coordinator = coordinators[hublot]
entities.append(DiffuserPerfumeSensor(diffuser, coordinator))
entities.append(DiffuserFillSensor(diffuser, coordinator))
entities.append(DiffuserWifiSensor(diffuser, coordinator))
if diffuser.has_battery:
entities.append(DiffuserBatterySensor(diffuser, coordinator))
for coordinator in coordinators.values():
entities.extend(
[
DiffuserPerfumeSensor(coordinator),
DiffuserFillSensor(coordinator),
DiffuserWifiSensor(coordinator),
]
)
if coordinator.diffuser.has_battery:
entities.append(DiffuserBatterySensor(coordinator))
async_add_entities(entities)
@ -42,45 +45,41 @@ async def async_setup_entry(
class DiffuserPerfumeSensor(DiffuserEntity, SensorEntity):
"""Representation of a diffuser perfume sensor."""
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the perfume sensor."""
super().__init__(diffuser, coordinator, PERFUME_SUFFIX)
super().__init__(coordinator, PERFUME_SUFFIX)
@property
def icon(self) -> str:
"""Return the perfume sensor icon."""
if self._diffuser.has_cartridge:
if self.coordinator.diffuser.has_cartridge:
return "mdi:tag-text"
return "mdi:tag-remove"
@property
def native_value(self) -> str:
"""Return the state of the perfume sensor."""
return self._diffuser.perfume
return self.coordinator.diffuser.perfume
class DiffuserFillSensor(DiffuserEntity, SensorEntity):
"""Representation of a diffuser fill sensor."""
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the fill sensor."""
super().__init__(diffuser, coordinator, FILL_SUFFIX)
super().__init__(coordinator, FILL_SUFFIX)
@property
def icon(self) -> str:
"""Return the fill sensor icon."""
if self._diffuser.has_cartridge:
if self.coordinator.diffuser.has_cartridge:
return "mdi:beaker"
return "mdi:beaker-question"
@property
def native_value(self) -> str:
"""Return the state of the fill sensor."""
return self._diffuser.fill
return self.coordinator.diffuser.fill
class DiffuserBatterySensor(DiffuserEntity, SensorEntity):
@ -90,16 +89,14 @@ class DiffuserBatterySensor(DiffuserEntity, SensorEntity):
_attr_native_unit_of_measurement = PERCENTAGE
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the battery sensor."""
super().__init__(diffuser, coordinator, BATTERY_SUFFIX)
super().__init__(coordinator, BATTERY_SUFFIX)
@property
def native_value(self) -> int:
"""Return the state of the battery sensor."""
return self._diffuser.battery_percentage
return self.coordinator.diffuser.battery_percentage
class DiffuserWifiSensor(DiffuserEntity, SensorEntity):
@ -108,13 +105,11 @@ class DiffuserWifiSensor(DiffuserEntity, SensorEntity):
_attr_native_unit_of_measurement = PERCENTAGE
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the wifi sensor."""
super().__init__(diffuser, coordinator, WIFI_SUFFIX)
super().__init__(coordinator, WIFI_SUFFIX)
@property
def native_value(self) -> int:
"""Return the state of the wifi sensor."""
return self._diffuser.wifi_percentage
return self.coordinator.diffuser.wifi_percentage

View file

@ -3,14 +3,12 @@ from __future__ import annotations
from typing import Any
from pyrituals import Diffuser
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import COORDINATORS, DEVICES, DOMAIN
from .const import DOMAIN
from .coordinator import RitualsDataUpdateCoordinator
from .entity import DiffuserEntity
@ -21,14 +19,13 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the diffuser switch."""
diffusers = hass.data[DOMAIN][config_entry.entry_id][DEVICES]
coordinators = hass.data[DOMAIN][config_entry.entry_id][COORDINATORS]
entities = []
for hublot, diffuser in diffusers.items():
coordinator = coordinators[hublot]
entities.append(DiffuserSwitch(diffuser, coordinator))
coordinators: dict[str, RitualsDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities(entities)
async_add_entities(
DiffuserSwitch(coordinator) for coordinator in coordinators.values()
)
class DiffuserSwitch(DiffuserEntity, SwitchEntity):
@ -36,27 +33,25 @@ class DiffuserSwitch(DiffuserEntity, SwitchEntity):
_attr_icon = "mdi:fan"
def __init__(
self, diffuser: Diffuser, coordinator: RitualsDataUpdateCoordinator
) -> None:
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the diffuser switch."""
super().__init__(diffuser, coordinator, "")
self._attr_is_on = self._diffuser.is_on
super().__init__(coordinator, "")
self._attr_is_on = self.coordinator.diffuser.is_on
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
await self._diffuser.turn_on()
await self.coordinator.diffuser.turn_on()
self._attr_is_on = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
await self._diffuser.turn_off()
await self.coordinator.diffuser.turn_off()
self._attr_is_on = False
self.async_write_ha_state()
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_is_on = self._diffuser.is_on
self._attr_is_on = self.coordinator.diffuser.is_on
self.async_write_ha_state()

View file

@ -2,7 +2,7 @@
from __future__ import annotations
from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY
from homeassistant.components.rituals_perfume_genie.const import COORDINATORS, DOMAIN
from homeassistant.components.rituals_perfume_genie.const import DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
@ -47,7 +47,7 @@ async def test_switch_handle_coordinator_update(hass: HomeAssistant) -> None:
diffuser = mock_diffuser_v1_battery_cartridge()
await init_integration(hass, config_entry, [diffuser])
await async_setup_component(hass, "homeassistant", {})
coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATORS]["lot123v1"]
coordinator = hass.data[DOMAIN][config_entry.entry_id]["lot123v1"]
diffuser.is_on = False
state = hass.states.get("switch.genie")