Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tplink overloading power strips #104208

Merged
merged 2 commits into from Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 17 additions & 2 deletions homeassistant/components/tplink/__init__.py
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))
bdraco marked this conversation as resolved.
Show resolved Hide resolved
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
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
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
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
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
@@ -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
Expand Up @@ -33,6 +33,7 @@
)
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import CoordinatedTPLinkEntity
from .models import TPLinkData


@dataclass
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
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