Skip to content

Commit

Permalink
Using DataCoordinator instead of direct update
Browse files Browse the repository at this point in the history
Using DataCoordinator allows us to reduce number
of web calls being made. Some devices can have
multiple entities which increases the number of
web calls and hitting the daily limit.
DataCoordinator reduces that to just 1 call.
  • Loading branch information
iprak committed Dec 27, 2024
1 parent f8399b2 commit e806d5a
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 38 deletions.
22 changes: 22 additions & 0 deletions homeassistant/components/vesync/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""VeSync integration."""

from datetime import timedelta
import logging

from pyvesync import VeSync
Expand All @@ -8,11 +9,13 @@
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .common import async_process_devices
from .const import (
DOMAIN,
SERVICE_UPDATE_DEVS,
VS_COORDINATOR,
VS_DISCOVERY,
VS_FANS,
VS_LIGHTS,
Expand Down Expand Up @@ -48,6 +51,25 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
hass.data[DOMAIN] = {}
hass.data[DOMAIN][VS_MANAGER] = manager

# Create a DataUpdateCoordinator for the manager
async def async_update_data():
"""Fetch data from API endpoint."""
try:
await hass.async_add_executor_job(manager.update)
except Exception as err:
raise UpdateFailed(f"Update failed: {err}") from err

Check warning on line 60 in homeassistant/components/vesync/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/vesync/__init__.py#L59-L60

Added lines #L59 - L60 were not covered by tests

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=DOMAIN,
update_method=async_update_data,
update_interval=timedelta(seconds=30),
)

# Store coordinator at domain level since only single integration instance is permitted.
hass.data[DOMAIN][VS_COORDINATOR] = coordinator

switches = hass.data[DOMAIN][VS_SWITCHES] = []
fans = hass.data[DOMAIN][VS_FANS] = []
lights = hass.data[DOMAIN][VS_LIGHTS] = []
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/vesync/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
VS_FANS = "fans"
VS_LIGHTS = "lights"
VS_SENSORS = "sensors"
VS_COORDINATOR = "coordinator"
VS_MANAGER = "manager"

DEV_TYPE_TO_HA = {
Expand Down
20 changes: 15 additions & 5 deletions homeassistant/components/vesync/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@

from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity, ToggleEntity
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)

from .const import DOMAIN


class VeSyncBaseEntity(Entity):
class VeSyncBaseEntity(CoordinatorEntity, Entity):
"""Base class for VeSync Entity Representations."""

_attr_has_entity_name = True

def __init__(self, device: VeSyncBaseDevice) -> None:
def __init__(
self, device: VeSyncBaseDevice, coordinator: DataUpdateCoordinator
) -> None:
"""Initialize the VeSync device."""
super().__init__(coordinator)
self.device = device
self._attr_unique_id = self.base_unique_id

Expand Down Expand Up @@ -46,9 +53,12 @@ def device_info(self) -> DeviceInfo:
sw_version=self.device.current_firm_version,
)

def update(self) -> None:
"""Update vesync device."""
self.device.update()
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)


class VeSyncDevice(VeSyncBaseEntity, ToggleEntity):
Expand Down
32 changes: 24 additions & 8 deletions homeassistant/components/vesync/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,28 @@
import math
from typing import Any

from pyvesync.vesyncbasedevice import VeSyncBaseDevice

from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util.percentage import (
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .const import DEV_TYPE_TO_HA, DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_FANS
from .const import (
DEV_TYPE_TO_HA,
DOMAIN,
SKU_TO_BASE_DEVICE,
VS_COORDINATOR,
VS_DISCOVERY,
VS_FANS,
)
from .entity import VeSyncDevice

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -56,25 +66,31 @@ async def async_setup_entry(
) -> None:
"""Set up the VeSync fan platform."""

coordinator = hass.data[DOMAIN][VS_COORDINATOR]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

Check warning on line 74 in homeassistant/components/vesync/fan.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/vesync/fan.py#L74

Added line #L74 was not covered by tests

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_FANS), discover)
)

_setup_entities(hass.data[DOMAIN][VS_FANS], async_add_entities)
_setup_entities(hass.data[DOMAIN][VS_FANS], async_add_entities, coordinator)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(
devices: list[VeSyncBaseDevice],
async_add_entities,
coordinator: DataUpdateCoordinator,
):
"""Check if device is online and add entity."""
entities = []
for dev in devices:
if DEV_TYPE_TO_HA.get(SKU_TO_BASE_DEVICE.get(dev.device_type)) == "fan":
entities.append(VeSyncFanHA(dev))
if DEV_TYPE_TO_HA.get(SKU_TO_BASE_DEVICE.get(dev.device_type, "")) == "fan":
entities.append(VeSyncFanHA(dev, coordinator))
else:
_LOGGER.warning(
"%s - Unknown device type - %s", dev.device_name, dev.device_type
Expand All @@ -96,9 +112,9 @@ class VeSyncFanHA(VeSyncDevice, FanEntity):
_attr_name = None
_attr_translation_key = "vesync"

def __init__(self, fan) -> None:
def __init__(self, fan, coordinator: DataUpdateCoordinator) -> None:
"""Initialize the VeSync fan device."""
super().__init__(fan)
super().__init__(fan, coordinator)
self.smartfan = fan

@property
Expand Down
23 changes: 16 additions & 7 deletions homeassistant/components/vesync/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import logging
from typing import Any

from pyvesync.vesyncbasedevice import VeSyncBaseDevice

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
Expand All @@ -13,9 +15,10 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util import color as color_util

from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_LIGHTS
from .const import DEV_TYPE_TO_HA, DOMAIN, VS_COORDINATOR, VS_DISCOVERY, VS_LIGHTS
from .entity import VeSyncDevice

_LOGGER = logging.getLogger(__name__)
Expand All @@ -30,27 +33,33 @@ async def async_setup_entry(
) -> None:
"""Set up lights."""

coordinator = hass.data[DOMAIN][VS_COORDINATOR]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

Check warning on line 41 in homeassistant/components/vesync/light.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/vesync/light.py#L41

Added line #L41 was not covered by tests

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_LIGHTS), discover)
)

_setup_entities(hass.data[DOMAIN][VS_LIGHTS], async_add_entities)
_setup_entities(hass.data[DOMAIN][VS_LIGHTS], async_add_entities, coordinator)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(
devices: list[VeSyncBaseDevice],
async_add_entities,
coordinator: DataUpdateCoordinator,
):
"""Check if device is online and add entity."""
entities = []
entities: list[VeSyncBaseLight] = []
for dev in devices:
if DEV_TYPE_TO_HA.get(dev.device_type) in ("walldimmer", "bulb-dimmable"):
entities.append(VeSyncDimmableLightHA(dev))
entities.append(VeSyncDimmableLightHA(dev, coordinator))
elif DEV_TYPE_TO_HA.get(dev.device_type) in ("bulb-tunable-white",):
entities.append(VeSyncTunableWhiteLightHA(dev))
entities.append(VeSyncTunableWhiteLightHA(dev, coordinator))
else:
_LOGGER.debug(
"%s - Unknown device type - %s", dev.device_name, dev.device_type
Expand Down
31 changes: 24 additions & 7 deletions homeassistant/components/vesync/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dataclasses import dataclass
import logging

from pyvesync.vesyncbasedevice import VeSyncBaseDevice
from pyvesync.vesyncfan import VeSyncAirBypass
from pyvesync.vesyncoutlet import VeSyncOutlet
from pyvesync.vesyncswitch import VeSyncSwitch
Expand All @@ -29,8 +30,16 @@
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType

from .const import DEV_TYPE_TO_HA, DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_SENSORS
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import (
DEV_TYPE_TO_HA,
DOMAIN,
SKU_TO_BASE_DEVICE,
VS_COORDINATOR,
VS_DISCOVERY,
VS_SENSORS,
)
from .entity import VeSyncBaseEntity

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -187,24 +196,31 @@ async def async_setup_entry(
) -> None:
"""Set up switches."""

coordinator = hass.data[DOMAIN][VS_COORDINATOR]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

Check warning on line 204 in homeassistant/components/vesync/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/vesync/sensor.py#L204

Added line #L204 was not covered by tests

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_SENSORS), discover)
)

_setup_entities(hass.data[DOMAIN][VS_SENSORS], async_add_entities)
_setup_entities(hass.data[DOMAIN][VS_SENSORS], async_add_entities, coordinator)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(
devices: list[VeSyncBaseDevice],
async_add_entities,
coordinator: DataUpdateCoordinator,
):
"""Check if device is online and add entity."""

async_add_entities(
(
VeSyncSensorEntity(dev, description)
VeSyncSensorEntity(dev, description, coordinator)
for dev in devices
for description in SENSORS
if description.exists_fn(dev)
Expand All @@ -222,9 +238,10 @@ def __init__(
self,
device: VeSyncAirBypass | VeSyncOutlet | VeSyncSwitch,
description: VeSyncSensorEntityDescription,
coordinator,
) -> None:
"""Initialize the VeSync outlet device."""
super().__init__(device)
super().__init__(device, coordinator)
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}-{description.key}"

Expand Down
31 changes: 20 additions & 11 deletions homeassistant/components/vesync/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import logging
from typing import Any

from pyvesync.vesyncbasedevice import VeSyncBaseDevice

from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_SWITCHES
from .const import DEV_TYPE_TO_HA, DOMAIN, VS_COORDINATOR, VS_DISCOVERY, VS_SWITCHES
from .entity import VeSyncDevice

_LOGGER = logging.getLogger(__name__)
Expand All @@ -22,27 +25,33 @@ async def async_setup_entry(
) -> None:
"""Set up switches."""

coordinator = hass.data[DOMAIN][VS_COORDINATOR]

@callback
def discover(devices):
"""Add new devices to platform."""
_setup_entities(devices, async_add_entities)
_setup_entities(devices, async_add_entities, coordinator)

Check warning on line 33 in homeassistant/components/vesync/switch.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/vesync/switch.py#L33

Added line #L33 was not covered by tests

config_entry.async_on_unload(
async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_SWITCHES), discover)
)

_setup_entities(hass.data[DOMAIN][VS_SWITCHES], async_add_entities)
_setup_entities(hass.data[DOMAIN][VS_SWITCHES], async_add_entities, coordinator)


@callback
def _setup_entities(devices, async_add_entities):
def _setup_entities(
devices: list[VeSyncBaseDevice],
async_add_entities,
coordinator: DataUpdateCoordinator,
):
"""Check if device is online and add entity."""
entities = []
entities: list[VeSyncBaseSwitch] = []
for dev in devices:
if DEV_TYPE_TO_HA.get(dev.device_type) == "outlet":
entities.append(VeSyncSwitchHA(dev))
entities.append(VeSyncSwitchHA(dev, coordinator))
elif DEV_TYPE_TO_HA.get(dev.device_type) == "switch":
entities.append(VeSyncLightSwitch(dev))
entities.append(VeSyncLightSwitch(dev, coordinator))
else:
_LOGGER.warning(
"%s - Unknown device type - %s", dev.device_name, dev.device_type
Expand All @@ -65,9 +74,9 @@ def turn_on(self, **kwargs: Any) -> None:
class VeSyncSwitchHA(VeSyncBaseSwitch, SwitchEntity):
"""Representation of a VeSync switch."""

def __init__(self, plug):
def __init__(self, plug, coordinator: DataUpdateCoordinator) -> None:
"""Initialize the VeSync switch device."""
super().__init__(plug)
super().__init__(plug, coordinator)
self.smartplug = plug

def update(self) -> None:
Expand All @@ -79,7 +88,7 @@ def update(self) -> None:
class VeSyncLightSwitch(VeSyncBaseSwitch, SwitchEntity):
"""Handle representation of VeSync Light Switch."""

def __init__(self, switch):
def __init__(self, switch, coordinator: DataUpdateCoordinator) -> None:
"""Initialize Light Switch device class."""
super().__init__(switch)
super().__init__(switch, coordinator)
self.switch = switch

0 comments on commit e806d5a

Please sign in to comment.