Skip to content

Commit

Permalink
Fix tplink overloading power strips (#104208)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Jan 4, 2024
1 parent 0ff5ccb commit f5e7631
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 44 deletions.
19 changes: 17 additions & 2 deletions homeassistant/components/tplink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from .const import DOMAIN, PLATFORMS
from .coordinator import TPLinkDataUpdateCoordinator
from .models import TPLinkData

DISCOVERY_INTERVAL = timedelta(minutes=15)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
Expand Down Expand Up @@ -102,7 +103,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
f"Unexpected device found at {host}; expected {entry.unique_id}, found {found_mac}"
)

hass.data[DOMAIN][entry.entry_id] = TPLinkDataUpdateCoordinator(hass, device)
parent_coordinator = TPLinkDataUpdateCoordinator(hass, device, timedelta(seconds=5))
child_coordinators: list[TPLinkDataUpdateCoordinator] = []

if device.is_strip:
child_coordinators = [
# The child coordinators only update energy data so we can
# set a longer update interval to avoid flooding the device
TPLinkDataUpdateCoordinator(hass, child, timedelta(seconds=60))
for child in device.children
]

hass.data[DOMAIN][entry.entry_id] = TPLinkData(
parent_coordinator, child_coordinators
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True
Expand All @@ -111,7 +125,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
hass_data: dict[str, Any] = hass.data[DOMAIN]
device: SmartDevice = hass_data[entry.entry_id].device
data: TPLinkData = hass_data[entry.entry_id]
device = data.parent_coordinator.device
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass_data.pop(entry.entry_id)
await device.protocol.close()
Expand Down
15 changes: 2 additions & 13 deletions homeassistant/components/tplink/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ def __init__(
self,
hass: HomeAssistant,
device: SmartDevice,
update_interval: timedelta,
) -> None:
"""Initialize DataUpdateCoordinator to gather data for specific SmartPlug."""
self.device = device
self.update_children = True
update_interval = timedelta(seconds=5)
super().__init__(
hass,
_LOGGER,
Expand All @@ -39,19 +38,9 @@ def __init__(
),
)

async def async_request_refresh_without_children(self) -> None:
"""Request a refresh without the children."""
# If the children do get updated this is ok as this is an
# optimization to reduce the number of requests on the device
# when we do not need it.
self.update_children = False
await self.async_request_refresh()

async def _async_update_data(self) -> None:
"""Fetch all device and sensor data from api."""
try:
await self.device.update(update_children=self.update_children)
await self.device.update(update_children=False)
except SmartDeviceException as ex:
raise UpdateFailed(ex) from ex
finally:
self.update_children = True
5 changes: 3 additions & 2 deletions homeassistant/components/tplink/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from homeassistant.helpers.device_registry import format_mac

from .const import DOMAIN
from .coordinator import TPLinkDataUpdateCoordinator
from .models import TPLinkData

TO_REDACT = {
# Entry fields
Expand All @@ -36,7 +36,8 @@ async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
data: TPLinkData = hass.data[DOMAIN][entry.entry_id]
coordinator = data.parent_coordinator
oui = format_mac(coordinator.device.mac)[:8].upper()
return async_redact_data(
{"device_last_response": coordinator.device.internal_state, "oui": oui},
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/tplink/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def async_refresh_after(

async def _async_wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
await func(self, *args, **kwargs)
await self.coordinator.async_request_refresh_without_children()
await self.coordinator.async_request_refresh()

return _async_wrap

Expand Down
17 changes: 8 additions & 9 deletions homeassistant/components/tplink/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .const import DOMAIN
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import CoordinatedTPLinkEntity, async_refresh_after
from .models import TPLinkData

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -132,14 +133,12 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up switches."""
coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
if coordinator.device.is_light_strip:
data: TPLinkData = hass.data[DOMAIN][config_entry.entry_id]
parent_coordinator = data.parent_coordinator
device = parent_coordinator.device
if device.is_light_strip:
async_add_entities(
[
TPLinkSmartLightStrip(
cast(SmartLightStrip, coordinator.device), coordinator
)
]
[TPLinkSmartLightStrip(cast(SmartLightStrip, device), parent_coordinator)]
)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
Expand All @@ -152,9 +151,9 @@ async def async_setup_entry(
SEQUENCE_EFFECT_DICT,
"async_set_sequence_effect",
)
elif coordinator.device.is_bulb or coordinator.device.is_dimmer:
elif device.is_bulb or device.is_dimmer:
async_add_entities(
[TPLinkSmartBulb(cast(SmartBulb, coordinator.device), coordinator)]
[TPLinkSmartBulb(cast(SmartBulb, device), parent_coordinator)]
)


Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/tplink/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""The tplink integration models."""
from __future__ import annotations

from dataclasses import dataclass

from .coordinator import TPLinkDataUpdateCoordinator


@dataclass(slots=True)
class TPLinkData:
"""Data for the tplink integration."""

parent_coordinator: TPLinkDataUpdateCoordinator
children_coordinators: list[TPLinkDataUpdateCoordinator]
33 changes: 21 additions & 12 deletions homeassistant/components/tplink/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
)
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import CoordinatedTPLinkEntity
from .models import TPLinkData


@dataclass(frozen=True)
Expand Down Expand Up @@ -106,31 +107,39 @@ def async_emeter_from_device(
return None if device.is_bulb else 0.0


def _async_sensors_for_device(
device: SmartDevice, coordinator: TPLinkDataUpdateCoordinator
) -> list[SmartPlugSensor]:
"""Generate the sensors for the device."""
return [
SmartPlugSensor(device, coordinator, description)
for description in ENERGY_SENSORS
if async_emeter_from_device(device, description) is not None
]


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensors."""
coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
data: TPLinkData = hass.data[DOMAIN][config_entry.entry_id]
parent_coordinator = data.parent_coordinator
children_coordinators = data.children_coordinators
entities: list[SmartPlugSensor] = []
parent = coordinator.device
parent = parent_coordinator.device
if not parent.has_emeter:
return

def _async_sensors_for_device(device: SmartDevice) -> list[SmartPlugSensor]:
return [
SmartPlugSensor(device, coordinator, description)
for description in ENERGY_SENSORS
if async_emeter_from_device(device, description) is not None
]

if parent.is_strip:
# Historically we only add the children if the device is a strip
for child in parent.children:
entities.extend(_async_sensors_for_device(child))
for idx, child in enumerate(parent.children):
entities.extend(
_async_sensors_for_device(child, children_coordinators[idx])
)
else:
entities.extend(_async_sensors_for_device(parent))
entities.extend(_async_sensors_for_device(parent, parent_coordinator))

async_add_entities(entities)

Expand Down
12 changes: 7 additions & 5 deletions homeassistant/components/tplink/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .const import DOMAIN
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import CoordinatedTPLinkEntity, async_refresh_after
from .models import TPLinkData

_LOGGER = logging.getLogger(__name__)

Expand All @@ -26,20 +27,21 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up switches."""
coordinator: TPLinkDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
device = cast(SmartPlug, coordinator.device)
data: TPLinkData = hass.data[DOMAIN][config_entry.entry_id]
parent_coordinator = data.parent_coordinator
device = cast(SmartPlug, parent_coordinator.device)
if not device.is_plug and not device.is_strip and not device.is_dimmer:
return
entities: list = []
if device.is_strip:
# Historically we only add the children if the device is a strip
_LOGGER.debug("Initializing strip with %s sockets", len(device.children))
for child in device.children:
entities.append(SmartPlugSwitchChild(device, coordinator, child))
entities.append(SmartPlugSwitchChild(device, parent_coordinator, child))
elif device.is_plug:
entities.append(SmartPlugSwitch(device, coordinator))
entities.append(SmartPlugSwitch(device, parent_coordinator))

entities.append(SmartPlugLedSwitch(device, coordinator))
entities.append(SmartPlugLedSwitch(device, parent_coordinator))

async_add_entities(entities)

Expand Down

0 comments on commit f5e7631

Please sign in to comment.